This page was saved using WebZIP 7.0.3.1030 offline browser on 12/02/19 14:55:01.
Address: http://www.zhufengpeixun.cn/ahead/html/26.webpack-2-optimize.html
Title: 珠峰架构师成长计划  •  Size: 79991  •  Last Modified: Sun, 01 Dec 2019 11:37:00 GMT



1. purgecss-webpack-plugin #

npm i -D purgecss-webpack-plugin glob

webpack.config.js

+ const glob = require('glob');
+ const PurgecssPlugin = require('purgecss-webpack-plugin');

module.exports = {
  mode: 'development',
  plugins: [
+    new PurgecssPlugin({
+      paths: glob.sync(`${path.join(__dirname, 'src')}/**/*`, 
+      {nodir: true}), // 不匹配目录,只匹配文件
+    }),
  ],
}

2.DLL #

.dll为后缀的文件称为动态链接库,在一个动态链接库中可以包含给其他模块调用的函数和数据

2.1 定义Dll #

const path=require('path');
const DllPlugin=require('webpack/lib/DllPlugin');
module.exports={
    entry: {
        react:['react','react-dom']
    },// 把 React 相关模块的放到一个单独的动态链接库
    output: {
        path: path.resolve(__dirname,'dist'),// 输出的文件都放到 dist 目录下
        filename: '[name].dll.js',//输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称
        library: '_dll_[name]',//存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
    },
    plugins: [
        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 --config webpack.dll.config.js --mode development

2.2 使用动态链接库文件 #

const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
plugins: [
  new DllReferencePlugin({
    manifest:require('./dist/react.manifest.json')
  })
]
webpack --config webpack.config.js --mode development

2.3 html中使用 #

<script src="react.dll.js"></script>
<script src="bundle.js"></script>

3. 多进程处理 #

3.1 HappyPack #

npm i [email protected] -D
const HappyPack = require('happypack');
    rules: [
    {
        test: /\.js$/,
        // 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
        use: ['happypack/loader?id=babel'],
        exclude: path.resolve(__dirname, 'node_modules'),
    },
    {
        test: /\.css$/,
        // 把对 .css 文件的处理转交给 id 为 css 的 HappyPack 实例
        use: ['happypack/loader?id=css']
    }
]
new Happypack({
            //ID是标识符的意思,ID用来代理当前的happypack是用来处理一类特定的文件的
            id: 'js',
            use: [{
                loader: 'babel-loader',
                //options=query都是向插件传递参数的
                options: {
                    presets: [["@babel/preset-env", { modules: false }], "@babel/preset-react"],
                    plugins: [
                        ["@babel/plugin-proposal-decorators", { "legacy": true }],
                        ["@babel/plugin-proposal-class-properties", { "loose": true }],
                    ]
                }
            }]
        }),
        new Happypack({
            //ID是标识符的意思,ID用来代理当前的happypack是用来处理一类特定的文件的
            id: 'css',
            use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],
            threads: 4,//你要开启多少个子进程去处理这一类型的文件
            verbose: true//是否要输出详细的日志 verbose
        })

3.2 thread-loader #

{
        test: /\.(js)$/,
        use: [
           {
            loader:'thread-loader',
            options:{
              workers:3
            }
          }, 
          {
            loader:'babel-loader'
          }
        ],
      }

3.3 webpack-parallel-uglify-plugin #

cnpm i webpack-parallel-uglify-plugin -D
let ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
new ParallelUglifyPlugin({});

4. CDN #

cdn

4.1 使用缓存 #

4.2 域名限制 #

4.3 接入CDN #

要给网站接入 CDN,需要把网页的静态资源上传到 CDN 服务上去,在服务这些静态资源的时候需要通过 CDN 服务提供的 URL 地址去访问

    output: {
        path: path.resolve(__dirname, 'dist'),
+        filename: '[name]_[hash:8].js',
+        publicPath: 'http://img.zhufengpeixun.cn'
    },

5.Tree Shaking #

5.1 开启 #

    "presets":[
+        ["@babel/preset-env",{"modules":true}],//转译 ES6 ES7
        "@babel/preset-react"//转译JSX语法
    ],

5.2 没有导入和使用 #

functions.js

function func1(){
  return 'func1';
}
function func2(){
     return 'func2';
}
export {
  func1,
  func2
}
import {func2} from './functions';
var result2 = func2();
console.log(result2);

5.3 代码不会被执行,不可到达 #

if(false){
 console.log('false')
}

5.4 代码执行的结果不会被用到 #

import {func2} from './functions';
func2();

5.4 代码中只会影响死变量,只写不读 #

var aabbcc='aabbcc';
aabbcc='eeffgg';

6.代码分割 #

6.1 代码分割的意义 #

6.2 Entry Points #

entry: {
        index: "./src/index.js",
        login: "./src/login.js"
}

6.2 动态导入和懒加载 #

cnpm i @babel/plugin-syntax-dynamic-import --save-dev
{plugins:["@babel/plugin-syntax-dynamic-import"]}

hello.js

module.exports = "hello";

index.js

document.querySelector('#clickBtn').addEventListener('click',() => {
    import('./hello').then(result => {
        console.log(result.default);
    });
});

index.html

<button id="clickBtn">点我</button>

6.3 提取公共代码 #

6.1 为什么需要提取公共代码 #

大网站有多个页面,每个页面由于采用相同技术栈和样式代码,会包含很多公共代码,如果都包含进来会有问题

6.2 如何提取 #

默认配置

  optimization: {
    // 这里放着优化的内容
    minimizer: [
      // 表示放优化的插件
      new TerserWebpackPlugin({
               parallel:true,//开启多进程并行压缩
               cache:true//开启缓存
      }),
      new OptimizeCssAssetsWebpackPlugin({
        assetNameRegExp: /\.css$/g, // 指定要压缩的模块的正则
        // cssnano是PostCSS的CSS优化和分解插件。cssnano采用格式很好的CSS,并通过许多优化,以确保最终的生产环境尽可能小。
        cssProcessor: require('cssnano'),
      }),
    ],
    splitChunks: {
        chunks: "all",//默认作用于异步chunk,值为all/initial/async
        minSize: 30000,  //默认值是30kb,代码块的最小尺寸
        minChunks: 1,  //被多少模块共享,在分割之前模块的被引用次数
        maxAsyncRequests: 5,  //按需加载最大并行请求数量
        maxInitialRequests: 3,  //一个入口的最大并行请求数量
        name: true,  //打包后的名称,默认是chunk的名字通过分隔符(默认是~)分隔开,如vendor~
        automaticNameDelimiter:'~',//默认webpack将会使用入口名和代码块的名称生成命名,比如 'vendors~main.js'
        cacheGroups: { //设置缓存组用来抽取满足不同规则的chunk,下面以生成common为例
            vendors: {
              chunks: "initial",
              name: 'vendors',  //可以通过'name'配置项来控制切割之后代码块的命名,给多个分割之后的代码块分配相同的名称,所有的vendor 模块被放进一个共享的代码块中,不过这会导致多余的代码被下载所以并不推荐
              test: /node_modules/,//条件
              priority: -10 ///优先级,一个chunk很可能满足多个缓存组,会被抽取到优先级高的缓存组中,为了能够让自定义缓存组有更高的优先级(默认0),默认缓存组的priority属性为负值.
            },
             commons: {
              chunks: "initial",
              name: 'commons',
              minSize: 0,//最小提取字节数
              minChunks: 1, //最少被几个chunk引用
              priority: -20,
              reuseExistingChunk: true//    如果该chunk中引用了已经被抽取的chunk,直接引用该chunk,不会重复打包代码
            }
      }
    },
    //runtime包含:在模块交互时,连接模块所需的加载和解析逻辑。包括浏览器中的已加载模块的连接,以及懒加载模块的执行逻辑.
    //设置optimization.runtimeChunk=true ,将每一个入口添加一个只包含runtime的额外代码块.然而设置值为single,只会为所有生成的代码块创建一个共享的runtime文件.runtime:连接模块化应用程序的所有代码.
    runtimeChunk:{
        name:'manifest'
    }
  },

6.3 提取公共代码 #

pageA.js

import utils1 from './utils1';
import utils2 from './utils2';
import $ from 'jquery';
console.log(utils1,utils2,$);

pageB.js

import utils1 from './utils1';
import utils2 from './utils2';
import $ from 'jquery';
console.log(utils1,utils2,$);;

pageC.js

import utils3 from './utils3';
import utils1 from './utils1';
import $ from 'jquery';
console.log(utils1,utils3,$);

    entry: {
        pageA: './src/pageA',
        pageB: './src/pageB',
        pageC: './src/pageC'
    },
    output: {
        path: path.resolve(__dirname,'dist'),
        filename: '[name].js'
    },
  plugins:[
       new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'pageA.html',
            chunks: ['pageA'],
            minify: {
                removeAttributeQuotes: true
            }
        }),
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'pageB.html',
            chunks: ['pageB'],
            minify: {
                removeAttributeQuotes: true
            }
        }),


        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'pageC.html',
            chunks: ['pageC'],
            minify: {
                removeAttributeQuotes: true
            }
        })
    ]

splitchunks

7.开启 Scope Hoisting #

7.1 开发环境插件配置 #

module.exports = {
  resolve: {
    // 针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
    mainFields: ['jsnext:main', 'browser', 'main']
  },
  plugins: [
    // 开启 Scope Hoisting
    new webpack.optimize.ModuleConcatenationPlugin(),
  ],
};

7.2 代码 #

hello.js

export default 'Hello';

index.js

import str from './hello.js';
console.log(str);

输出的结果main.js

"./src/index.js":
(function(module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
var hello = ('hello');
console.log(hello);
 })

函数由两个变成了一个,hello.js 中定义的内容被直接注入到了 main.js 中

8. 用 HMR 提高开发效率 #

8.1 模块热替换原理 #

hotupdate

8.2 配置 #

8.2.1 配置hot #

8.2.2 代码实现 #

import React from 'react';
import { render } from 'react-dom';
import App from './App';
import './index.css';
render(<App/>, document.getElementById('root'));

// 只有当开启了模块热替换时 module.hot 才存在
if (module.hot) {
  // accept 函数的第一个参数指出当前文件接受哪些子模块的替换,这里表示只接受 ./AppComponent 这个子模块
  // 第2个参数用于在新的子模块加载完毕后需要执行的逻辑
  module.hot.accept(['./App'], () => {
    // 新的 AppComponent 加载成功后重新执行下组建渲染逻辑
    let App=require('./App').default;  
    render(<App/>, document.getElementById('root'));
  });
}

8.2.3 react-hot-loader #

npm install --save-dev react-hot-loader
8.2.3.1 #

Add react-hot-loader/babel to your .babelrc:

// .babelrc
{
  "plugins": ["react-hot-loader/babel"]
}
// App.js
import { hot } from 'react-hot-loader/root';
const App = () => <div>Hello World!</div>;
export default hot(App);