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构建流程
这个过程完成了内容转换+资源合并两种功能,实现上包含了是哪个阶段:
- 初始化阶段:
- 初始化参数:从配置文件、配置对象、sehll参数重读取,与默认配置结合得到最终的参数
- 创建编译器对象:用上一步得到的参数创建
Compiler
对象 - 初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化RuleSet集合、加载配置中的插件等
- 开始编译:执行
Compiler
对象中的run方法 - 确定入口:根据配置中的entry找出所有的入口文件,调用
compilition.addEntry
将入口文件转换为dependence
对象
- 构建阶段
- 编译模块(make):根据entry对应的dependence创建module对象,调用loader将模块转译为标砖的js内容,调用js解释器将内容转换为AST对象,从中找出该模块依赖的模块,再递归执行本步骤,直到所有入口依赖文件都经过了本步骤的处理
- 完成模块编译:上一步低轨处理所有能触达到的模块后,得到了每个模块被编译后的内容以及他们之间的依赖关系图
- 生成阶段
- 输出资源(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面试题