- 能详细说一说webpack的构建流程吗?
整个过程大致上分为三个主流程:- 初始化: 此阶段, webpack会将cli参数, 配置文件, 默认配置融合到一起, 形成一个最终的配置文件, 方便webpack在编译时读取该配置文件
- 编译: 编译的流程相对来说比较复杂, 我们可以大致理解为下面几步
- 创建chunk: chunk是webpack内部的一个概念, 他表示通过某个入口找到的所有模块的统称(比如A依赖了B, B依赖了C, A也依赖了C, 那么ABC统称为一个chunk), 每个chunk都拥有以下两个属性:
- name: 该chunk的名称, 默认为main
- id: 该chunk的唯一编号, 开发环境中和name相同, 生产环境是一个编号
- 创建chunk: chunk是webpack内部的一个概念, 他表示通过某个入口找到的所有模块的统称(比如A依赖了B, B依赖了C, A也依赖了C, 那么ABC统称为一个chunk), 每个chunk都拥有以下两个属性:
2. 构建所有的依赖模块(我们以main.chunk为例):<br />1 读取入口文件, 然后检查该入口文件是否已经有加载过(去chunk的模块记录中查询), 如果读取过则直接返回读取结果<br />2. 读取文件内容根据配置对象中的loader进行源码处理<br />3. 将处理结果交给AST进行抽象语法树的解析<br />4. 通过AST可以解析出该模块依赖的其他模块, 然后保存到一个dependencies的依赖数组中<br />5. 将源码中的依赖函数进行替换(比如require, import, 统一替换成webpack_require)<br />6. 保存替换后的模块代码进chunk的模块记录<br />7. 递归加载刚刚的dependencies模块数组, 进入第一步的流程2. 产生chunk assets: 在第二布完成以后, chunk中会生成一个模块列表, 里面包含了模块id和模块转换后的代码, 接下来, webpack会根据配置为chunk生成一个最终的资源列表(chunk assets), 这个资源列表其实已经就是我们最终要输出文件的文件名和文件内容了, 同时这个chunk assets会生成一个chunk hash2. 然后将所有chunk的assets放到一起, 并产生一个总的hash
- 输出: 利用fs模块根据上面的assets, 生成文件到出口目录
在以上过程中, webpack会在特点的节点广播出事件, plugin在监听到对应的事件以后会执行特定的回调逻辑
webpack的编译结果有关注过吗?
webpack的编译结果主要体现在两块, 一个是他对chunk的结果呈现如下:
var modules = {"./src/index.js": (module, exports, require) => {const { default as foo } = require("./src/moduleA.js")// ... 你写的模块化代码},"./src/moduleA.js": (module, exports, require) => {exports.default = () =>{// 你写的模块化代码}}}
当webpack构建好了模块化依赖表格以后, 他就会去将这个整个模块对象放入一个IIFE中, 如下: ```javascript (function() { // 并在IIFE中书写一个require函数 function __webpack_require(moduleId) { var module = { exports: {
} }
modulesmoduleId;
return module.exports; }
// 直接执行入口模块的导入 require(“./src/index.js”)
}(modules))
3. 如何利用webpack来优化前端性能?- 压缩代码: 以前我们需要自己压缩, 现在其实webpack默认有一个压缩策略- cdn加速: 在构建过程中, 将引用的静态资源路径修改为cdn上对应的路径, 可以利用webpack对于output参数和个loader的publicPath来修改资源路径- tree-shaking: 这个现在也是webpack自动会帮我进行- 使用分包策略提取公共代码: `optimization`下的`splitChunks`4. 如果利用webpack来提高构建速度?- 因为我们知道, 模块解析的步骤是包括: 抽象语法树分析, 依赖分析, 模块语法分析这些步骤的, 如果我们对一些没有必要解析的模块(比如人家dist/vue.js, 人家已经打包好的vue压缩文件)也去进行模块解析的话, 就得不偿失了```javascriptmodule.exports = {module: {noParse: /vue/, // 我们直接控制不解析vue}}
优化loader性能: 优化loader性能大致分为以下几个方面:
进一步限制loader的处理范围: 例如我们知道babel-loader就是用来处理语法降级的, 然后当我们模块中有引入lodash这种库的话, lodash本身就是es5写的不需要降级, 这里我们不能使用noParse, 因为我们可能依然需要去压缩lodash这个库, 所以我们只能在loader这里做文章, 我们在babel-loader的配置中告诉babel-loader: 我们不需要对lodash进行语法降级
module.exports = {module: {rules: [{test: /.js/,exclude: /lodash/, // 直接排除掉lodashuse: {loader: "babel-loader",options: {presets: "babel-presets-env"}}}]}}
使用cache-loader缓存模块结果: 我们可能希望只要我们的源文件没有太多的变化, loader就别去重新解析了, 耗费时间也耗费性能(默认情况下: 每一次打包, loader都会重新去解析文件)
cache-loader的原理也很简单: 利用的就是loader.pitch函数
热替换: 在聊热替换之前, 我们或许应该知道, 当我们使用了
webpack-dev-server这个插进帮助我们开启localhost本地服务以后, 从代码变更到页面呈现都大概走了哪些步骤:
使用了热替换以后, 热更新提供给了我们类似于ajax的能力, 在不刷新页面的情况下, 使用js更新页面的内容
开启热更新的方式也非常简单, 我们只需要在配置中写如下代码: 其实我们更应该探究的是: 当设置了hot为true以后发生了什么:module.exports = {devServer: {hot: true, // 将hot设置为true, 系统会自动帮我们开启hot module replace}}
- webpack会向plugins中注入一个热更新插件
webpack.HotModuleReplacementPlugin - 然后我们不是之前说我们的打包结果中是有webpack帮我们构建了一个module对象然后传递给各个模块吗, 开启热更新以后, module对象上会被自动注入一个hot属性为true
- 然后会在入口文件中注入一些代码如下:
if (module.hot) {hot.accept(); // 当调用这个方法以后, 浏览器就不会刷新页面, 而是允许我们使用ajax局部更新页面}
- 分包: 分包主要用于多入口时的性能优化, 他帮助我们将多个入口引入的相同模块进行单独抽离打包, 以达到节约模块解析的成本, 这个也适用于线上的性能优化, 因为模块代码少了自然传输速率也上去了
- 手动分包: 手动分包是由我们自己处理分包, 具体流程如下:
- 先将确定的公共模块单独打包(这意味着我们需要新开一个单独的webpack配置文件)
- 在单独打包的过程中, 我们需要让每一个单独打包的模块都提供一个对外暴露的全局变量, 并生成一个manifest清单, 该清单主要用于告诉webpack哪些已经被打包过了(需要用到插件webpack.DllPlugin)
- 将打包结果在
index.html中引入 - 然后开始根据真正的webpack.config.js来打包我们的代码, 当在模块依赖解析的时候, 只要解析依赖都会先去manifest清单中找(需要用到webpack.webpack.DllReferencePlugin)插件, 如果找到了同名的变量, 就直接不解析了, 没有就走后续流程
- 手动分包: 手动分包是由我们自己处理分包, 具体流程如下:

- 自动分包: 自动分包是我们给webpack提供一个宏观分包策略. 让它来协助我们进行分包- 通过webpack的optimization字段来配置优化信息, optimization中有一个splitChunks字段就是用来配置分包策略的
module.exports = {optimization: {splitChunks: {chunks: "all", // async -> 只对异步模块使用分包策略, all -> 对所有模块都使用分包策略, initail -> 只对普通模块使用分包策略cacheGroups: [ // 具体分包策略书写的地方, 一个分包策略为一个对象// webpack默认有两个分包策略defaultVendors: {test: /[\\/]node_modules[\\/]/,priority: -10, // 代表分包策略优先级, 优先级越高, 该策略越优先处理},default: {minChunks: 2, // 最少要被两个chunk用到才会进行分包priority: -20,}]}}}
- loader和plugin的原理有了解过吗?
- loader就是一个纯函数, 你给他一个源码字符串, 他给你返回一个新的字符串
- plugin需要监听webpack的生命周期, 当不同的生命周期被触发的时候, 执行不同的plugin回调
