webpack构建流程
-
初始化参数: 从配置文件和shell语句中读取合并参数,得到最终的配置 -
开始编译: 利用上一步得到的配置参数初始化compiler对象,它扩展自Tapable类,注册配置的所有插件,并执行compiler的run方法开始执行编译 -
确定入口: 根据配置文件中的entry字段查找所有的入口文件 -
编译模块: 从入口文件出发递归查找所有的模块,并利用配置的loader规则对相应的模块进行解析,直到所有的模块都被解析完成。得到每个模块被翻译后的内容和他们之间的依赖关系,这个过程是利用@babel/parser解析代码为ast,并用@babel/traverse识别模块依赖,构建模块依赖图 -
输出资源: 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转化为一个单独的文件加入到输出列表中,此时是插件可修改输出内容的最后时机 -
输出完成: 确定好输出内容后,根据配置的输出路径和文件名,把文件内容输出到文件系统。
如何提高webpack的构建速度
-
多进程构建:
thread-loader对耗时的loader使用多进程转化 -
压缩代码
uglifyjs-webpack-plugin、terser-webpack-plugin开启parallel参数,开启多进程压缩 -
缓存
- 使用
cache-loader、hard-source-webpack-plugin开启构建缓存 babel-loader开启缓存
- 使用
-
提取页面公共资源 将页面中的基础包通过CDN的方式引入,避免其打包进bundle中
-
缩小打包范围
- exclude/include ,确定 loader 规则范围
- 合理使用alias,减少不必要的查找
-
Tree shaking 利用tree-shaking减小打包范围
-
动态polyfill
如何利用webpack优化前端性能
- 路由懒加载
- 压缩图片
- 拆分大文件,合并相同模块代码
- tree shaking
- cdn引入基础库
- gzip压缩文件
compression-webpack-plugin
loader
loader用于将各种各样的模块转化为标准的js模块输出,供webpack处理,例如:
vue-loader:将vue文件转化为js和cssurl-loader:将小于limit大小的文件转化为内敛Data Url的形式raw-loader:将文件以字符串的形式输出
loader按照配置的顺序从前往后pitch,再从后往前执行
loader的编写思路
loader导出一个可执行的函数,函数上还挂载又pitch函数,例如
const loader = function (source){
console.log('后执行')
return source;
}
loader.pitch = function(requestString) {
console.log('先执行')
}
module.exports = loader
plugin
通过插件可以扩展webpack的能力,插件可以参与到webpack构建的各个时机,在webpack向外暴露的构建进程的事件上执行所需操作,从而改变构建的输出。
Webpack 会在启动后按照注册的顺序逐次调用插件对象的 apply 函数,同时传入编译器对象 compiler ,插件开发者可以以此为起点触达到 webpack 内部定义的任意钩子。
plugin的编写思路
插件是一个构造函数或是类,拥有apply方法,例如
class SomePlugin {
apply(compiler) {
compiler.hooks.thisCompilation.tap('SomePlugin', (compilation) => { })
}
}
function SomePlugin () {}
SomePlugin.prototype.apply = function (compiler) {
compiler.hooks.thisCompilation.tap('SomePlugin', (compilation) => { })
}
webpack热更新原理
webpack-dev-server启动一个web服务器,同时通过websocket与客户端建立一个长连接,当webpack监听到文件变化时,会重新对文件进行编译打包,保存到内存中,webpack-dev-server监听到webpack的done事件,会通过socket将打包后的新模块的hash值发送到客户端,客户端代码通过对比前后hash值,确定是否存在修改的代码,如果存在,则去请求最新模块代码,通过jsonp的方式插入页面,实现热更新。
webpak-dev-server:创建了web服务器,实现了websocket长连接webpack-dev-middleware:本地文件的监听、编译、输出### HotModuleReplacementPlugin:部分代码插入到客户端代码中,实现客户端的websocket处理,主要对比前后hash值,从而去请求hot-updata.json和hot-update.js文件,实现热更新
如何配置多入口应用
- 在entry中配置多入口文件路径
- 配置
CommonsChunkPlugin实现各自引入所需的chunk
tree-shaking原理
- ESM 输出的是值得引用,CommonJs输出的是值得拷贝
- ESM 是编译时执行,CommonJs是运行时加载
ESM 最大的特点就是静态化,在编译时就可以确定模块之间的依赖关系,和运行时的状态无关,可以进行可靠的静态分析,从而确定哪些代码是不可到达的,从而有实现Tree-shaking的可能性。
Tree-shaking是依赖ESM的。
在webpack中开启tree-shaking后,webpack会为那些没有用到的代码打上unused的标志,在代码压缩工作压缩时,会删除带有该标志的代码,从而实现Tree-shaking
source map 原理
source map是将编译、打包、压缩后的代码映射回源代码的文件
source map只有在打开开发者工具时才会加载
开发环境配置如下:
-
eval-source-map- 每个模块使用eval()执行,并且 source map 转换为 DataUrl 后添加到eval()中。初始化 source map 时比较慢,但是会在重新构建时提供比较快的速度,并且生成实际的文件。行数能够正确映射,因为会映射到原始代码中。它会生成用于开发环境的最佳品质的 source map。 -
eval-cheap-module-source-map- 类似eval-cheap-source-map,并且,在这种情况下,源自 loader 的 source map 会得到更好的处理结果。然而,loader source map 会被简化为每行一个映射(mapping)。
生产环境配置如下:
-
(none)(省略devtool选项) - 不生成 source map。这是一个不错的选择。 -
source-map- 整个 source map 作为一个单独的文件生成。它为 bundle 添加了一个引用注释,以便开发工具知道在哪里可以找到它
webpack如何实现懒加载
本质其实就是利用jsonp去加载对应的异步模块,然后执行该模块,从而实现按需加载
webpack本地开发如何解决跨域问题
使用webpack-dev-server提供的proxy配置项,原理是在后端实现一个代理服务器转发跨域请求,因为跨域是浏览器的限制,服务直接请求服务不受跨域限制。
webpack和rollup的区别
webpack支持度很广,适合打包项目中包含众多静态资源、commonjs规范
rollup适合打包纯js的项目,打包类库的体积更小,不需要各种webpack的辅助函数
模块化种类和区别
ESM:es6的模块规范
CommonJS:
ESM与CommonJS区别
- ESM模块输出的是值的引用,CommomJS模块输出的是值的拷贝
- ESM模块是编译时输出接口,CommomJS模块是运行时加载
- CommonJS 模块的
require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
得益于ESM的静态属性,才使得构建工具可以在编译阶段实现tree-shaking优化。 得益于ESM的异步加载性,vite才能利用原生ESM模块实现拦截浏览器的异步加载模块请求
新一代构建工具--vite
vite:
- 基于
esbuild实现的极速开发体验 - 多框架支持,vue,react,angular
- 兼容
Rollup的插件机制 - SSR
- HMR
vite启动时只做了两件事:
- 启动一个可以承载资源的server
- 使用
esbuild预构建npm依赖包
之后vite就一直等着,等待浏览器已http方式发来ESM规范的模块请求,vite便开始按需编译被请求的模块。