webpack-面试题

528 阅读9分钟

weboack究竟解决了什么问题

如何在前端项目中更高效地管理和维护项目中的每个资源

模块化的演进过程

stage1-文件划分方式
缺点:

  • 模块直接在全局工作,大量模块成员污染全局作用域
  • 没有私有空间,素有模块内的成员都可以在模块外部被访问或者修改
  • 一旦模块增多,容易产生命名冲突
  • 无法管理模块与模块之间的依赖关系
  • 维护的过程中很难分辨每个成员所属的模块

stage2-命名空间方式
优点:解决了命名冲突的问题
缺点:stage1的其他缺点依旧存在

stage3-IIFE 立即执行函数
优点:

  • 解决了作用域污染
  • 解决了命名冲突的问题
  • 可利用参数声明模块依赖的模块,使使依赖关系更加明确

上面三阶段后,仍存在两点重要的需求:

  • 一个同意的模块化标准规范
  • 一个可以自动加载模块的基础库

CommonJS规范
是node.js中遵循的模块规范。同步模块加载
该规范约定一个文件就是一个模块,每个模块都有单独的作用域。 通过module.exports导出成员,再通过require函数载入模块

AMD
Asynchronous Module Definition 异步模块定义规范

define(['jquery', './module2.js'], function($, modle2) {
    return {
        start: function() {
            $('body').animate({margin: '200px'});
            module2();
        }
    }
})

require只能用来加载模块,define可用来定义和加载模块。
当我们使用require的时候,程序会自动创建一个script标签,去请求执行js文件

require(['./modules/module1.js'], function(module1){
    module1.start();
})

ES Module
ES6中菜定义的模块系统,存在环境兼容的问题。ES Module已发展为现今最主流的前端模块规范。

// module.js
var test = 'es module';
export { foo }

// app.js
import {foo} from './module.js';
console.log(foo);

weboack构建流程

这个过程完成了内容转换+资源合并两种功能,实现上包含了是哪个阶段:

  1. 初始化阶段:
  • 初始化参数:从配置文件、配置对象、sehll参数重读取,与默认配置结合得到最终的参数
  • 创建编译器对象:用上一步得到的参数创建Compiler对象
  • 初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化RuleSet集合、加载配置中的插件等
  • 开始编译:执行Compiler对象中的run方法
  • 确定入口:根据配置中的entry找出所有的入口文件,调用compilition.addEntry将入口文件转换为dependence对象
  1. 构建阶段
  • 编译模块(make):根据entry对应的dependence创建module对象,调用loader将模块转译为标砖的js内容,调用js解释器将内容转换为AST对象,从中找出该模块依赖的模块,再递归执行本步骤,直到所有入口依赖文件都经过了本步骤的处理
  • 完成模块编译:上一步低轨处理所有能触达到的模块后,得到了每个模块被编译后的内容以及他们之间的依赖关系图
  1. 生成阶段
  • 输出资源(seal):根据入口和模块的依赖关系,组装成一个个包含多个模块的chunk,再把每个chunk转换成一个个单独的文件加入到输出劣币哦啊种,这步数可以修改输出内容的最后机会
  • 写入系统文件(emitAssets):在确定输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

weback和babel

webpack编译过程会将源码解析为AST吗?webpack和bable分别实现了什么?

  • 构建阶段会读取源码,解析为AST集合
  • webpack读出AST之后仅编译AST节课;babel则对源码做等价转换

webpack编译过程中,如何识别资源对其他资源的依赖?
webpack遍历AST集合过程中,识别require/import之类的导入语句,明确导入模块对其他资源的依赖关系

什么是HRM(模块热替换)

是当我们对代码修改并保存后,webpack将会对代码进行重新大包,并将新的模块发送到浏览器端,浏览器用新的模块替换掉就的模块,以实现在不刷新浏览器的前提下更新页面。最明显的优势就是相对窜痛的 live reload而言,HMR并不会丢失应用状态,提升开发效率.

核心流程:

  • 使用wenpack-dev-server(后简称WDS)托管静态资源,同时以Runtime方式注入HMR客户端代码
  • 浏览器加载页面后,与WDS简历webSocket连接
  • webpack监听到文件变化后,增量构建已发生的变更的模块,并通过websocket发送hash事件
  • 浏览器接收到hash事件后,请求manifes资源文件,确认增量变更范围
  • 浏览器加载发生变更后的增量模块
  • webpack运行时触发变更模块的module.hot.accept回调,执行代码变更逻辑
  • done

tree shaking

它是一种基于ES Module规范的哦Dead Code Elimination技术,它会在运行过程中静态分析模块之间的导入导出,确定ESM模块中哪些到处值未曾被其他模块使用,并将其删除,以此实现大包产物的优化。

Tree shaking的实现一是先标记出模块导出值中安歇没有被用过,而是使用Terser删除这些没被用到的导出语句。标记过程大致可划分为三个步骤:

  • make阶段,收集模块导出变量并记录到模块依赖关系图中ModuleGraph中
  • seal阶段,遍历ModuleGraph标记模块导出变量有没有被使用
  • 生成产物是,变量没有被其他模块使用则删除对应的导出语句

标记功能需要配置optimization.usedExports=true开启

webpack中实现tree shaking的实现分为以下步骤:

  • FlagDependencyExportsPlugin插件中根据模块的dependences列表手机模块导出值,并记录到MudleGraph体系的exportsInfo
  • FlagDependencyUsagePlugin差劲中手机模块的导出值的使用阶段,并记录到exportInfo._usedInRuntime集合中
  • HarmonyExportXXXDependency.Template.apply方法中根据导出值的使用情况生成不同的导出语句
  • 使用DCE工具删除Dead Code,完成完整的摇树效果

有哪些常见的loader?用过哪些

  • raw-loader: 加载文件原始内容(utf-8)
  • file-loader:把文件输出到一个文件夹中,在代码中通过相对URL去饮用输出文件(处理图片和字体
  • url-loader:与file-loader类似,区别是用户可以设置一个阀值,大于阀值会交给file-loader处理,小于阀值返回文件base64形式编码(图片和字体)
  • source-map-loader:加载额外的source map文件,以便断点调试
  • svg-inline-loader:将压缩后的SVG内容注入代码中
  • image-loader:加载并且压缩图片文件
  • json-loader:加载JSON文件
  • ts-loader:将ts转换成js
  • style-loader:将css住处到js中,通过dom操作去加载css
  • css-loader:加载css,支持模块化、压缩、文件导入等特性

更多 Loader 请参考官网

常见的Plugin,用过哪些

  • html-webpack-plugin:简化HTML文件创建
  • serviceworker-webpack-plugin:为网页应用增加离线缓存功能
  • clean-webpack-plugin:目录清理

更多 Plugin 请参考官网

plugin和loader的区别

loader本质就是一个函数,在该函数中对接受到的内容进行转换,返回转换后的结果。因为webpack只认识js,所以laoder就成了翻译官,对其他类型的资源进行转译的预处理工作。
loader在module.rules中配置,作为模块的解析规则,类型为数组,每一项都是一Object,内部包含了test、loader、options等属性。
plugin插件,基于事件流框架Tapable,插件可以扩展wepack的功能,在webpack运行的生命周期播出许多事件,Plugin可以监听这些事件,在合适的时机通过webpack提供的API改变输出结果。
Plugin在plugins中单独配置,类型为数组,每一项是一个Plugin实例,参数都是通过构造函数传入。

用过哪些可以提高效率的插件

  • size-plugin监控源体积变化,尽早发现问题
  • webpack-merge:提取公共配置,减少重复配置的代码

source-map是什么,生产环境怎么用?

source map是将编译、打包、压缩后的代码映射回源代码的过程。打包压缩后的代码不具备良好的可读性,想要调试源码就需要source map。
map文件只要不打开开发者工具,浏览器是不会加载的。
线上环境一般有三种处理方案:

  • hiddle-source-map:借助地方错误监控平台sentry使用
  • nosources-source-map只会显示具体行数以及查看源码的错误栈。安全性比sourcemap高
  • sourcemap:通过nginx设置将.map文件支队白名单开放

避免在生产中使用inline-和eval-,因为他们回增加bundle体积大小,并境地整体的性能。

文件指纹是什么?怎么用?

文件指纹是大包后输出的文件名的后缀(hash)。

  • hash:和整个项目的构建相关,值要项目文件有修改,整个项目构建的hash值就会更改
  • chunkhash:和webpack打包的chunk有关,不同的entry回生出不同的chunkhash
  • contenthash:根据文件内容来定义hash,文件内容不变,则contenthash不变

文件指纹配置

module.exports = {
    entry: {
        app: './src/app.js',
        main: './src/main.js',
    },
    output: {
        filename: '[name][chunkhash:8].js',
        path: __dirname + '/dist'
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name][contenthash:8].css'
        })
    ]
}

Babel原理

大多数js Parser遵循estree规范,Bable最初机遇acorn项目(轻量级现代js解析器)Babel大概分为三大部分

  • 解析:将代码转换成AST
    • 词法分析:将代码(字符串)分割为tolen流,即语法单元组成的数组
    • 语法分析:分析token流并生成AST
  • 转换:访问AST的节点进行变换操作生成新的AST
  • 生成:以新的AST为基础生成代码

更多详细内容: 120 行代码帮你了解 Webpack 下的 HMR 机制
Webpack 原理系列二:插件架构原理与应用
「吐血整理」再来一打Webpack面试题