🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # plugin plugin是用于扩展 webpack 的功能,各种各样的 plugin 几乎可以让 webpack 做任何与构建相关的事情。 plugin 的配置很简单,plugins 配置项接收一个数组,数组里的每一项都是一个要使用的plugin的实例,plugin 需要的参数通过构造函数传入。 举个栗子 ``` const HtmlWebpackPlugin = require('html-webpack-plugin') plugins: [ new HtmlWebpackPlugin({ // 打包输出HTML title: 'Hello World app', minify: { // 压缩HTML文件 removeComments: true, // 移除HTML中的注释 collapseWhitespace: true, // 删除空白符与换行符 minifyCSS: true// 压缩内联css }, filename: 'index.html', template: 'index.html' }), ] ``` 使用plugin 的难点在于 plugin 本身的配置项,而不是如何在 webpack 中引入 plugin,几乎所有webpack 无法直接实现的功能,都能找到开源的 plugin 去解决,我们要做的就是去找更据自己的需要找出相应 的plugin。 # 编写 plugin # html-webpack-plugin 这个plugin曝光率很高,他主要有两个作用 * 为html文件中引入的外部资源如script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题 * 可以生成创建html入口文件,比如单页面可以生成一个html文件入口,配置N个html-webpack-plugin可以生成N个页面入口 github上有些关于 [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin) 的属性介绍 ## title 生成html文件的标题 ## filename 输出的html的文件名称 ## template html模板所在的文件路径 根据自己的指定的模板文件来生成特定的 html 文件。这里的模板类型可以是任意你喜欢的模板,可以是 html, jade, ejs, hbs, 等等,但是要注意的是,使用自定义的模板文件时,需要提前安装对应的 loader, 否则webpack不能正确解析。 如果你设置的 title 和 filename于模板中发生了冲突,那么以你的 title 和 filename 的配置值为准。 ## inject 注入选项。有四个选项值 true, body, head, false. 1. true:默认值,script标签位于html文件的 body 底部 2. body:script标签位于html文件的 body 底部(同 true) 3. head:script 标签位于 head 标签内 4. false:不插入生成的 js 文件,只是单纯的生成一个 html 文件 ## favicon 给生成的 html 文件生成一个 favicon。属性值为 favicon 文件所在的路径名 ## minify minify 的作用是对 html 文件进行压缩,minify 的属性值是一个压缩选项或者 false 。默认值为false, 不对生成的 html 文件进行压缩。 下面罗列了一些常用的配置: ~~~javascript plugins:[ new HtmlWebpackPlugin({ //部分省略,具体看minify的配置 minify: { //是否对大小写敏感,默认false caseSensitive: true, //是否简写boolean格式的属性如:disabled="disabled" 简写为disabled 默认false collapseBooleanAttributes: true, //是否去除空格,默认false collapseWhitespace: true, //是否压缩html里的css(使用clean-css进行的压缩) 默认值false; minifyCSS: true, //是否压缩html里的js(使用uglify-js进行的压缩) minifyJS: true, //Prevents the escaping of the values of attributes preventAttributesEscaping: true, //是否移除属性的引号 默认false removeAttributeQuotes: true, //是否移除注释 默认false removeComments: true, //从脚本和样式删除的注释 默认false removeCommentsFromCDATA: true, //是否删除空属性,默认false removeEmptyAttributes: true, // 若开启此项,生成的html中没有 body 和 head,html也未闭合 removeOptionalTags: false, //删除多余的属性 removeRedundantAttributes: true, //删除script的类型属性,在h5下面script的type默认值:text/javascript 默认值false removeScriptTypeAttributes: true, //删除style的类型属性, type="text/css" 同上 removeStyleLinkTypeAttributes: true, //使用短的文档类型,默认false useShortDoctype: true, } }), ] ~~~ ## hash hash选项的作用是 给生成的 js 文件一个独特的 hash 值,该 hash 值是该次 webpack 编译的 hash 值。默认值为 false 。同样看一个例子。 ~~~javascript plugins: [ new HtmlWebpackPlugin({ hash: true }) ] ~~~ 编译打包后 ~~~html <script type=text/javascript src=bundle.js?22b9692e22e7be37b57e></script> ~~~ 执行 webpack 命令后,你会看到你的生成的 html 文件的 script 标签内引用的 js 文件,是不是有点变化了。 bundle.js 文件后跟的一串 hash 值就是此次 webpack 编译对应的 hash 值。 ## cache 默认是true的,表示内容变化的时候生成一个新的文件。 ## showErrors 这个我们自运行项目的时候经常会用到,showErrors 的作用是,如果 webpack 编译出现错误,webpack会将错误信息包裹在一个 pre 标签内,属性的默认值为 true ,也就是显示错误信息。 开启这个,方便定位错误 ## chunks 主要是针对多入口(entry)文件。当你有多个入口文件的时候,对应就会生成多个编译后的 js 文件。 那么 chunks 选项就可以决定是否都使用这些生成的 js 文件。 chunks 默认会在生成的 html 文件中引用所有的 js 文件,当然你也可以指定引入哪些特定的文件。 ~~~ entry: { index: path.resolve(__dirname, './src/index.js'), devor: path.resolve(__dirname, './src/devor.js'), main: path.resolve(__dirname, './src/main.js') } plugins: [ new httpWebpackPlugin({ chunks: ['index','main'] }) ] ~~~ 那么编译后: ~~~ <script type=text/javascript src="index.js"></script> <script type=text/javascript src="main.js"></script> ~~~ 而如果没有指定 chunks 选项,默认会全部引用。 ## excludeChunks 排除掉一些js, ~~~ entry: { index: path.resolve(__dirname, './src/index.js'), devor: path.resolve(__dirname, './src/devor.js'), main: path.resolve(__dirname, './src/main.js') } plugins: [ new httpWebpackPlugin({ excludeChunks: ['devor.js']//和的等等效 }) ] ~~~ 那么编译后: ~~~ <script type=text/javascript src="index.js"></script> <script type=text/javascript src="main.js"></script> ~~~ ## webpack4 与 html-webpack-plugin 用最新版本的的 html-webpack-plugin你可能还会遇到如下的错误: ``` throw new Error('Cyclic dependency' + nodeRep) ``` 产生这个 bug 的原因是循环引用依赖,如果你没有这个问题可以忽略。 目前解决方案可以使用 Alpha 版本,n`pm i --save-dev html-webpack-plugin@next ` 或者加入 `chunksSortMode: 'none' ` 就可以了。 但仔细查看文档发现设置成 `chunksSortMode: 'none'`这样是会有问题的。 这属性会决定你 chunks 的加载顺序,如果设置为 none,你的 chunk 加载在页面中加载的顺序就不能够保证了,可能会出现样式被覆盖的情况。比如我在 `app.css` 里面修改了一个第三方库 element-ui的样式,通过加载顺序的先后来覆盖它,但由于设置为了 `none`,打包出来的结果变成了这样: ~~~ <link href="/app.8945fbfc.css" rel="stylesheet"> <link href="/chunk-elementUI.2db88087.css" rel="stylesheet"> ~~~ app.css 被先加载了,之前写的样式覆盖就失效了,除非你使用important或者其它 css 权重的方式覆盖它,但这明显是不太合理的。 vue-cli 正好也有这个相关 issue,尤雨溪也在不使用@next版本的基础上 hack 了它,有兴趣的可以自己研究一下, 其它 html-webpack-plugin 的配置和之前使用没有什么区别。 参考链接 [html-webpack-plugin用法全解](https://links.jianshu.com/go?to=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000007294861) [插件 html-webpack-plugin 的详解](https://links.jianshu.com/go?to=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000013883242) [手摸手,带你用合理的姿势使用webpack4(上)](https://www.jianshu.com/p/1fcc2f8ed8bb) > https://www.jianshu.com/p/08a60756ffda # DllPlugin 生产第三方 dll > [https://webpack.docschina.org/plugins/dll-plugin/](https://webpack.docschina.org/plugins/dll-plugin/) DllPlugin & DllReferencePlugin 这一方案,实际上也是属于代码分割的范畴,但与 [SplitChunksPlugin](http://webpack.docschina.org/plugins/split-chunks-plugin/) 不一样的是,它不仅仅是把公用代码提取出来放到一个独立的文件供不同的页面来使用,它更重要的一点是:**只需要编译一次,在之后的构建过程中被动态链接库包含的模块(公用代码)将不会在重新编译,而是直接使用动态链接库中的代码**。这有什么好处呢?很简单,业务代码常改,而公用代码不常改,那么,我们在日常修改业务代码的过程中,就可以**省出编译公用代码那一部分所耗费的时间**了。 整个过程大概是这样的: 1. 利用 DllPlugin 把公用代码打包成一个**Dll文件**(其实本质上还是 js,只是套用概念而已);除了 Dll 文件外,DllPlugin 还会生成一个 `manifest.json` 文件作为公用代码的索引供`DllReferencePlugin`使用。  2. 在业务代码的 webpack 配置文件中配置好`DllReferencePlugin`并进行编译,达到利用`DllReferencePlugin`让业务代码和 Dll 文件实现关联的目的。 3. 在各个页面中,先加载 Dll 文件,再加载业务代码文件。 ## 适用范围 由于 Dll 文件(动态链接库)中大多数包含的是不常改动的第三方模块,例如 react、react-dom,尤其是本身就庞大或者依赖众多的库,只要不升级这些模块的版本,动态链接库就不用重新编译。 如果你自己整理了一套成熟的框架,开发项目时只需要在上面添砖加瓦的,那么也可以把这套框架也打包进Dll文件里,甚至可以做到多个项目共用这一份 Dll 文件。 ## 具体配置 构建输出的以下这四个文件 ~~~ ├── polyfill.dll.js ├── polyfill.manifest.json ├── react.dll.js └── react.manifest.json ~~~ 和以下这一个文件 ~~~ ├── main.js ~~~ 动态链接库文件相关的文件需要由一份独立的构建输出,用于给主构建使用。 新建一个 Webpack 配置文件`webpack_dll.config.js`专门用于构建它们,如下: ~~~ const path = require('path'); const DllPlugin = require('webpack/lib/DllPlugin'); module.exports = { // JS 执行入口文件 entry: { // 把 React 相关模块的放到一个单独的动态链接库 react: ['react', 'react-dom'], // 把项目需要所有的 polyfill 放到一个单独的动态链接库 polyfill: ['core-js/fn/object/assign', 'core-js/fn/promise', 'whatwg-fetch'], }, output: { // 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称, // 也就是 entry 中配置的 react 和 polyfill filename: '[name].dll.js', // 输出的文件都放到 dll 目录下 path: path.resolve(__dirname, 'vendors'), // 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react // 之所以在前面加上 _dll_ 是为了防止全局变量冲突 library: '_dll_[name]', }, plugins: [ // 接入 DllPlugin new DllPlugin({ // 动态链接库的全局变量名称,需要和 output.library 中保持一致 // 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值 // 例如 react.manifest.json 中就有 "name": "_dll_react" name: '_dll_[name]', // 描述动态链接库的 manifest.json 文件输出时的文件名称 path: path.join(__dirname, 'dist', '[name].manifest.json'), }), ], }; ~~~ 主 Webpack 配置文件如下: ~~~ ... entry: { // 定义入口 Chunk main: './main.js' }, output: { // 输出文件的名称 filename: '[name].js', // 输出文件都放到 dist 目录下 path: path.resolve(__dirname, 'dist'), }, ... plugins: [ // 告诉 Webpack 使用了哪些动态链接库 new DllReferencePlugin({ // 描述 react 动态链接库的文件内容 manifest: require('./vendors/react.manifest.json'), }), new DllReferencePlugin({ // 描述 polyfill 动态链接库的文件内容 manifest: require('./vendors/polyfill.manifest.json'), }), ], devtool: 'source-map' ... ~~~ 以 DllPlugin 生成出 动态链接库文件 `react.dll.js`文件为例,其文件内容大致如下: ``` var _dll_react = (function(modules) { // ... 此处省略 webpackBootstrap 函数代码 }([ function(module, exports, __webpack_require__) { // 模块 ID 为 0 的模块对应的代码 }, function(module, exports, __webpack_require__) { // 模块 ID 为 1 的模块对应的代码 }, // ... 此处省略剩下的模块对应的代码 ])); ``` 可见一个动态链接库文件中包含了大量模块的代码,这些模块存放在一个数组里,用数组的索引号作为 ID。 并且还通过`_dll_react`变量把自己暴露在了全局中,也就是可以通过`window._dll_react`可以访问到它里面包含的模块。 ## 执行构建 在修改好以上两个 Webpack 配置文件后,需要重新执行构建。 重新执行构建时要注意的是需要先把动态链接库相关的文件编译出来,因为主 Webpack 配置文件中定义的 DllReferencePlugin 依赖这些文件。 执行构建时流程如下: 1. 如果动态链接库相关的文件还没有编译出来,就需要先把它们编译出来。方法是执行`webpack --progress --colors --config ./webpack-dll.config.js`命令。 2. 在确保动态链接库存在时,才能正常的编译出入口执行文件。方法是执行`webpack`命令。这时你会发现构建速度有了非常大的提升。 ## 后续操作 1. 如何在业务代码里使用Dll文件打包的module/资源? 不需要刻意做些什么,该怎么 import 就怎么 import,webpack 都会帮你处理好的了。 2. 如何整合 Dll?  在每个页面里,都要按这个顺序来加载js文件:Dll 文件 => SplitChunksPlugin生成的公用chunk文件(如果有) => 页面本身的入口文件。 有两个注意事项: 1. 如果你是像我一样利用 HtmlWebpackPlugin 来生成 HTML 并自动加载 chunk 的话,还需要在`html`中手写插入`react.dll.js`。 2. 为了完全分离源文件和编译后生成的文件,也为了方便在编译前可以清空 build 目录,不应直接把 Dll 文件编译生成到build 目录里,我建议可以先生成到源文件 src 目录里,再用 file-loader 给原封不动搬运过去。 3. 自动动注入`DLL`到`html`中 另外可以通过 [html-webpack-tags-plugin](https://www.npmjs.com/package/html-webpack-tags-plugin) 自动注入`DLL`到`html`中 # DefinePlugin [DefinePlugin](https://webpack.js.org/plugins/define-plugin/#usage) 允许我们**创建全局变量**,可以在编译时进行设置,因此我们可以使用该属性来设置全局变量来区分开发环境和正式环境。这就是 DefinePlugin 的基本功能。 因此我们可以在 `webpack.config.js` 中添加如下代码配置全局变量信息了,因为当 webpack 进行编译的时候会全局设置变量;如下代码: ``` ... module.exports = { plugins: [ // 设置环境变量信息 new webpack.DefinePlugin({ PRODUCTION: JSON.stringify(true), VERSION: JSON.stringify('5fa3b9'), BROWSER_SUPPORTS_HTML5: true, TWO: '1+1', 'typeof window': JSON.stringify('object'), 'process.env': { NODE_ENV: JSON.stringify(process.env.NODE_ENV) } }) ] } ``` 在 打包命令上,前面加上了 `NODE_ENV=development` 命令,在 build 打包命令上前面加上了 `NODE_ENV=production`,因此继续查看代码结果变为如下: ~~~ console.log('Running App version ' + VERSION); // 打印 Running App version 5fa3b9 console.log(PRODUCTION); // 打印 true console.log(process.env); // 打印 { NODE_ENV: 'development' } ~~~ 可以看到这个时候 `process.env.NODE_ENV` 才有值,因此在项目打包中,为了区分开发环境和正式环境像如上配置即可,然后在 `webpack.config.js` 中通过 `process.env.NODE_ENV` 这个来区分正式环境还是开发环境即可。 > [一个被忽视的webpack插件](https://github.com/akira-cn/FE_You_dont_know/issues/14)