模块打包工具
模块打包工具主要工作方式主要分成两种:
- 讲存在依赖关系的模块按照特定规则合并为单个JS 文件,一次全部加载进页面。
- 在页面初始的时候加载一个入口模块,其他模块异步进行加载。
Webpack-server
webpack能够实现热更背后的原理是webpack-server。它可以看做一个服务器,每次都接受浏览器的请求,然后返回需要的数据。但是需要注意的是webpack-sever在运行的时候不是真的将打包结果写入bundle.js,而是写入内存中,每次收到请求的时候就把内存中的数据返回给webpack。
webpack打包流程
打包过程其实就是对依赖关系的一种解析过程。
在一切流程最开始,我们需要指定一个或者多个入口(entry),也就是告诉webpack具体从源码的哪个位置开始打包,如果把工程的各个模块的依赖关系当做一棵树,那么入口就是这颗依赖树的树根。
chunk被称为代码块,里面包含着很多依赖关系。根据配置不同,一个工程在打包的时候可能会产生一个或多个chunk。由这个chunk打包之后的最终产物,我们称之为bundle。
入口和出口
const path = require('path');
const htmlPlugin = require('html-webpack-plugin');
module.exports = {
entry: ['babel-polyfill', './src/index.js'],
output: { filename: 'bundle.js' },
mode: 'development',
plugins: [new htmlPlugin({ title: path.basename(__dirname) })],
devServer: {
publicPath: '/dist/',
port: 3000,
},
};
entry传入一个数组的作用是将多个资源预先合并。我们也可以在entry处传入一个函数便于动态控制。对于多页应用,我们也可以设置多个入口。
const path = require('path');
const htmlPlugin = require('html-webpack-plugin');
module.exports = {
entry: () => ({
partA: './src/partA.js',
partB: './src/partB.js',
index: ['babel-polyfill', './src/index.js'],
}),
// filename指输出资源名称
output: { filename: '[name].js' },
mode: 'development',
plugins: [new htmlPlugin({ title: path.basename(__dirname) })],
devServer: {
publicPath: '/dist/',
port: 3000,
},
};
loader
loader在webpack中称之为预处理器。我们一般写页面的时候有对应的js文件和css文件,但是导入的时候却只用导入js文件不用导入css文件,这是为什么呢,背后原理是loader帮我们做了一些数据的预处理。
预处理器有一种函数式编程的思想,就是采用链式的管道思维。output = loader(input),可以多个loader组合然后工作。
const path = require('path');
const htmlPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].js',
},
mode: 'development',
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
}
],
},
plugins: [new htmlPlugin({ title: path.basename(__dirname) })],
devServer: {
publicPath: '/dist/',
port: 3000,
},
};
代码分片
通过代码分片可以将代码进行拆分,实现按需加载,而不必一次全部加载。
通过入口划分代码
最容易想到的代码分片就是通过entry来划分代码。另外还可以将多个页面都需要使用到的依赖比如react划分到一个单独的entry,这样就可以利用客户端缓存,不需要每次请求页面的时候都重新加载。
其他分片方案
其他分片方案就是使用插件,一般思路就是将常用依赖打包到common.js中,利用缓存来提高加载速度。
Plugin
插件相比于loader,具有更强大的处理能力。plugin用于接受一个插件数组。一般都是和loader结合起来一起使用。
缓存问题
代码分片利用缓存但是也可能会带来缓存的问题。如果开发者想对某处代码进行bug fix,并希望立即更新到所有的浏览器中,而不希望他们使用旧的缓存。此时最好的办法是改变资源url,这样可以迫使客户端下载最新的资源。其中一种做法是可以对资环的hash做更改。
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle@[chunkhash].js',
},
mode: 'production',
devServer: {
publicPath: '/dist/',
port: 3000,
},
};
这样通过每次打包都会对资源内容做一次hash,并作为版本号放在文件名中,当代码发生变化的时候hash值也会发生变化。
其他插件可以解决缓存中的其他问题。
Tree Shaking
tree shaking就是将写入但是没有实际运行的代码在打包的时候剔除掉。但是tree shaking只能是在ES 6有效而对CommonJS无效。
原因在于CommonJS对模块依赖解析是动态的,而ES6是静态的。动态是指,模块依赖关系的建立是发生在代码运行阶段。静态是指,模块依赖关系发生在代码编译阶段。
webpack会将那些没有被执行到的代码进行标记,并在资源压缩时将他们从最终bundle中去除掉。
Happy Pack
打包过程的主要流程分为如下4步:
- 从配置中获取打包入口
- 匹配loader规则,并对入口模块进行转译
- 对转义后的模块进行依赖查找(如a.js中加载b.js)
- 对新找到的模块重复步骤2和3,直到没有新模块依赖
不难看出步骤2到4是一个递归的过程。webpack是单线程找,happypack可以实现多线程找。
热更
热更称之为Hot Module Replacement(HMR)。HMR的核心就是客户端从服务端拉取更新后的资源(不是整个资源,而是chunk diff)。
参考资料
《Webpack实战 入门、进阶与调优》