前端进阶高薪必看-Webpack 篇
# 前端进阶高薪必看-Webpack 篇
# 1 webpack 是什么
webpack 是自动化打包解决方案,也可以理解为是一个模块打包机。它帮助我们分析项目结构,找到 JavaScript 模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript 等),并将其打包为合适的格式以供浏览器使用。
- 代码转换:TypeScript 编译成 JavaScript,SCSS、LESS 编译成 CSS
- 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片
- 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。
- 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件
- 自动刷新:监听本地源代码变化,自动重新构建,刷新浏览器
# 2 webpack 常见有哪些配置
- Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
- Output:输出结果,在 Webpack 经过一系列处理并得出最终想要的代码后输出结果。
- mode:提供 mode 配置选项,告知 webpack 使用相应模式的内置优化
- Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。
- Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
- Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
- Plugin:扩展插件,在 Webpack 构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要的事情。
# 3 webpack 工作流程
- 参数解析:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
- 找到入口文件:从 Entry 里配置的 Module 开始递归解析 Entry 依赖的所有 Module
- 调用 Loader 编译文件:每找到一个 Module, 就会根据配置的 Loader 去找出对应的转换规则
- 遍历 AST,收集依赖:对 Module 进行转换后,再解析出当前 Module 依赖的 Module生成
- Chunk:这些模块会以 Entry 为单位进行分组,一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个 Chunk
- 输出文件:最后 Webpack 会把所有 Chunk 转换成文件输出
想看具体的 webpack 编译原理源码的同学 传送门 (opens new window)
# 4 常见 Loader 配置以及工作流程
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{ test: /\.js$/, use: 'babel-loader' },
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{ loader: 'postcss-loader' },
]
}
]
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
「Loader 工作流程」
- webpack.config.js 里配置了一个 模块 的 Loader;
- 遇到 相应模块 文件时,触发了 该模块的 loader;
- loader 接受了一个表示该 模块 文件内容的 source;
- loader 使用 webapck 提供的一系列 api 对 source 进行转换,得到一个 result;
- 将 result 返回或者传递给下一个 Loader,直到处理完毕。
看看 less-loader 的例子(具体的 loader 实现可以看官网例子 (opens new window))
let less = require('less');
module.exports = function (source) {
const callback = this.async();
//this.async() 返回一个回调函数,用于异步执行
less.render(source, (err, result) => {
//使用less处理对应的less文件的source
callback(err, result.css);
});
}
2
3
4
5
6
7
8
9
# 5 常见 plugin 配置以及简易原理
「项目常用插件介绍」
extract-text-webpack-plugin webpack 默认会将 css 当做一个模块打包到一个 chunk 中,extract-text-webpack-plugin 的作用就是将 css 提取成独立的 css 文件
const ExtractTextPlugin = require('extract-text-webpack-plugin'); new ExtractTextPlugin({ filename: 'css/[name].css', }) { test: /\.css$/, use: ExtractTextPlugin.extract({ use: ['css-loader','postcss-loader','less-loader'], fallback: 'vue-style-loader', #使用vue时要用这个配置 }) },1
2
3
4
5
6
7
8
9
10
11html-webpack-plugin 这个插件很重要,作用一是创建 HTML 页面文件到你的输出目录,作用二是将 webpack 打包后的 chunk 自动引入到这个 HTML 中
const HtmlPlugin = require('html-webpack-plugin') new HtmlPlugin({ filename: 'index.html', template: 'pages/index.html' }1
2
3
4
5DefinePlugin 定义全局常量
new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify(process.env.NODE_ENV) }, PRODUCTION: JSON.stringify(PRODUCTION), APP_CONFIG: JSON.stringify(appConfig[process.env.NODE_ENV]), }),1
2
3
4
5
6
7UglifyJsPlugin js 压缩
new webpack.optimize.UglifyJsPlugin()1提示
注意: webpack4 已经移除了该插件,用 optimization.minimize 替代
CommonsChunkPlugin CommonsChunkPlugin 主要是用来提取第三方库(如 jQuery)和公共模块(公共 js,css 都可以),常用于多页面应用程序,生成公共 chunk,避免重复引用。
{ entry: { vendor: 'index.js' }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: ['vendor','runtime'], filename: '[name].js' }), ] }1
2
3
4
5
6
7
8
9
10
11提示
注意: webpack4 已经移除了该插件,用 optimization.SplitChunks 替代
「简易原理」 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。webpack 通过 Tapable 来组织这条复杂的生产线。 webpack 在编译过代码程中,会触发一系列 Tapable 钩子事件,插件所做的,就是找到相应的钩子,往上面挂上自己的任务,也就是注册事件,这样,当 webpack 构建的时候,插件注册的事件就会随着钩子的触发而执行了。
webpack 插件由以下组成:
- 一个 JavaScript 命名函数。
- 在插件函数的 prototype 上定义一个 apply 方法。
- 指定一个绑定到 webpack 自身的事件钩子。
- 处理 webpack 内部实例的特定数据。
- 功能完成后调用 webpack 提供的回调
「自定义插件例子」(更多知识请看官网创建插件 (opens new window))
// 一个 JavaScript 命名函数。
function MyExampleWebpackPlugin() {
};
// 在插件函数的 prototype 上定义一个 apply 方法。
MyExampleWebpackPlugin.prototype.apply = function(compiler) {
// 指定一个挂载到 webpack 自身的事件钩子。
compiler.plugin('webpacksEventHook', function(compilation /* 处理 webpack 内部实例的特定数据。*/, callback) {
console.log("This is an example plugin!!!");
// 功能完成后调用 webpack 提供的回调。
callback();
});
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 6 webpack 打包速度太慢怎么办
缩小编译范围,减少不必要的编译工作,即 modules、mainFields、noParse、includes、exclude、alias 全部用起来。
const resolve = dir => path.join(__dirname, '..', dir); resolve: { modules: [ // 指定以下目录寻找第三方模块,避免webpack往父级目录递归搜索 resolve('src'), resolve('node_modules'), resolve(config.common.layoutPath) ], mainFields: ['main'], // 只采用main字段作为入口文件描述字段,减少搜索步骤 alias: { vue$: "vue/dist/vue.common", "@": resolve("src") // 缓存src目录为@符号,避免重复寻址 } }, module: { noParse: /jquery|lodash/, // 忽略未采用模块化的文件,因此jquery或lodash将不会被下面的loaders解析 // noParse: function(content) { // return /jquery|lodash/.test(content) // }, rules: [ { test: /\.js$/, include: [ // 表示只解析以下目录,减少loader处理范围 resolve("src"), resolve(config.common.layoutPath) ], exclude: file => /test/.test(file), // 排除test目录文件 loader: "happypack/loader?id=happy-babel" // 后面会介绍 }, ] }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30webpack-parallel-uglify-plugin 插件(优化 js 压缩过程)
webpack-parallel-uglify-plugin 能够把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程,从而实现并发编译,进而大幅提升 js 压缩速度
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); // ... optimization: { minimizer: [ new ParallelUglifyPlugin({ // 多进程压缩 cacheDir: '.cache/', uglifyJS: { output: { comments: false, beautify: false }, compress: { warnings: false, drop_console: true, collapse_vars: true, reduce_vars: true } } }), ] }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21HappyPack 在 webpack 运行在 node 中打包的时候是单线程去一件一件事情的做,HappyPack 可以开启多个子进程去并发执行,子进程处理完后把结果交给主进程
const HappyPack = require('happypack'); module.exports = { entry: './src/index.js', output: { path: path.join(__dirname, './dist'), filename: 'main.js', }, module: { rules: [ { test: /\.jsx?$/, exclude: /node_modules/, use: 'happypack/loader?id=babel', }, ] }, plugins: [ new HappyPack({ id: 'babel', //id值,与loader配置项对应 threads: 4, //配置多少个子进程 loaders: ['babel-loader'] //用什么loader处理 }), ] }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24DLL 动态链接 第三方库不是经常更新,打包的时候希望分开打包,来提升打包速度。打包 dll 需要新建一个 webpack 配置文件(webpack.dll.config.js),在打包 dll 的时候,webpack 做一个索引,写在 manifest 文件中。然后打包项目文件时只需要读取 manifest 文件。
const webpack = require("webpack"); const path = require('path'); const CleanWebpackPlugin = require("clean-webpack-plugin"); const dllPath = path.resolve(__dirname, "../src/assets/dll"); // dll文件存放的目录 module.exports = { entry: { // 把 vue 相关模块的放到一个单独的动态链接库 vue: ["babel-polyfill", "fastclick", "vue", "vue-router", "vuex", "axios", "element-ui"] }, output: { filename: "[name]-[hash].dll.js", // 生成vue.dll.js path: dllPath, library: "dll[name]" }, plugins: [ new CleanWebpackPlugin(["*.js"], { // 清除之前的dll文件 root: dllPath, }), new webpack.DllPlugin({ name: "dll[name]", // manifest.json 描述动态链接库包含了哪些内容 path: path.join(__dirname, "./", "[name].dll.manifest.json") }), ], };1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25接着, 需要在 package.json 中新增 dll 命令。
"scripts": { "dll": "webpack --mode production --config build/webpack.dll.config.js" }1
2
3运行 npm run dll 后,会生成 ./src/assets/dll/vue.dll-[hash].js 公共 js 和 ./build/vue.dll.manifest.json 资源说明文件,至此 dll 准备工作完成,接下来在 webpack 中引用即可。
externals: { 'vue': 'Vue', 'vue-router': 'VueRouter', 'vuex': 'vuex', 'elemenct-ui': 'ELEMENT', 'axios': 'axios', 'fastclick': 'FastClick' }, plugins: [ ...(config.common.needDll ? [ new webpack.DllReferencePlugin({ manifest: require("./vue.dll.manifest.json") }) ] : []) ]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 7 webpack 如何优化前端性能
- 第三方库按需加载、路由懒加载
//第三方ui库element,vant等库都提供来按需加载的方式,避免全部引入,加大项目体积 import { Button, Select } from 'element-ui'; //路由懒加载 const showImage = () => import('@/components/common/showImage');1
2
3
4
5 - 代码分割
- 提取第三方库 「vendor」
module.exports = { entry: { main: './src/index.js', vendor: ['react', 'react-dom'], }, }1
2
3
4
5
6 - 依赖库分离 「splitChunks」
optimization: { splitChunks: { chunks: "async", // 必须三选一: "initial" | "all"(推荐) | "async" (默认就是async) minSize: 30000, // 最小尺寸,30000 minChunks: 1, // 最小 chunk ,默认1 maxAsyncRequests: 5, // 最大异步请求数, 默认5 maxInitialRequests : 3, // 最大初始化请求书,默认3 automaticNameDelimiter: '~',// 打包分隔符 name: function(){}, // 打包后的名称,此选项可接收 function cacheGroups:{ // 这里开始设置缓存的 chunks priority: 0, // 缓存组优先级 vendor: { // key 为entry中定义的 入口名称 chunks: "initial", // 必须三选一: "initial" | "all" | "async"(默认就是async) test: /react|lodash/, // 正则规则验证,如果符合就提取 chunk name: "vendor", // 要缓存的 分隔出来的 chunk 名称 minSize: 30000, minChunks: 1, enforce: true, maxAsyncRequests: 5, // 最大异步请求数, 默认1 maxInitialRequests : 3, // 最大初始化请求书,默认1 reuseExistingChunk: true // 可设置是否重用该chunk } } } },1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 提取第三方库 「vendor」
- 删除冗余代码
「Tree-Shaking」
// 在webpack之前的版本中,这样使用,显示配置UglifyjsWebpackPlugin即可 const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); plugins:[ new UglifyJSPlugin() ] // 但是在webpack4中,只需要配置mode为"production"即可1
2
3
4
5
6