webpack必知必会基础篇(二)

175 阅读4分钟

purgecss-webpack-plugin

  • 可以去除未使用的 css,一般与 glob、glob-all 配合使用
  • 必须和mini-css-extract-plugin配合使用
  • paths路径是绝对路径
npm i -D purgecss-webpack-plugin mini-css-extract-plugin glob
const glob = require('glob');
const PurgecssPlugin = require('purgecss-webpack-plugin');

module.exports = {
    mode: 'development',
    plugins: [
        new PurgecssPlugin({
            paths: glob.sync(`${path.join(__dirname, 'src')}/**/*`)
      }),
    ],
}

plugins: [
    new MiniCssExtractPlugin({
        filename: '[name].css',
        chunkFilename: '[id].css'
    }),

    {
        test: /\.css/,
        include: path.resolve(__dirname, 'src'),
        exclude: /node_modules/,
        use: [{
            loader: MiniCssExtractPlugin.loader
        }, 'css-loader']
    }
]

DLL

  • .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')
        })
    ]
}

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

html中使用

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

多进程处理

thread-loader:

  • 把这个loader放置在其他loader之前,放置在这个loader之后的loader就会在一个单独的worker池(worker pool)中运行
{
    test: /\.(js)$/
    use: [
       {
        loader:'thread-loader',
        options:{
          workers:3
        }
      }, 
      {
        loader:'babel-loader'
      }
    ]
  }

接入CDN

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

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

Tree Shaking

  • 一个模块可以有多个方法,只要其中某个方法使用到了,则整个文件都会被打到bundle里面去,tree shaking就是只把用到的方法打入bundle,没用到的方法会uglify阶段擦除掉
  • 原理是利用es6模块的特点,只能作为模块顶层语句出现,import的模块名只能是字符串常量

开启:

  • webpack默认支持,在.babelrc里设置module:false即可在production mode下默认开启
  • 还要注意把devtool设置为null 没有导入和使用
function func1(){
    return 'func1';
  }
  function func2(){
       return 'func2';
  }
  export {
    func1,
    func2
  }
  
import {func2} from './functions';
var result2 = func2();
console.log(result2);

代码分割

  • 对于大的Web应用来讲,将所有的代码都放在一个文件中显然是不够有效的,特别是当你的某些代码块是在某些特殊的时候才会被用到。
  • webpack有一个功能就是将你的代码库分割成chunks语块,当代码运行到需要它们的时候再进行加载。 适用的场景
  • 抽离相同代码到一个共享块
  • 脚本懒加载,使得初始下载的代码更小 hello.js
module.exports = "hello";

index.js

document.querySelector('#clickBtn').addEventListener('click',() => {
    import('./hello').then(result => {
        console.log(result.default);
    });
});
  • webpack将会基于以下条件自动分割代码块:
    • 新的代码块被共享或者来自node_modules文件夹
    • 新的代码块大于30kb(在min+giz之前)
    • 按需加载代码块的请求数量应该<=5
    • 页面初始化时加载代码块的请求数量应该<=3

默认配置

optimization: {
    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",
                test: /node_modules/,//条件
                priority: -10 ///优先级,一个chunk很可能满足多个缓存组,会被抽取到优先级高的缓存组中,为了能够让自定义缓存组有更高的优先级(默认0),默认缓存组的priority属性为负值.
            },
            commons: {
                chunks: "initial",
                minSize: 0,//最小提取字节数
                minChunks: 2, //最少被几个chunk引用
                priority: -20,
                reuseExistingChunk: true//    如果该chunk中引用了已经被抽取的chunk,直接引用该chunk,不会重复打包代码
            }
        }
    },
}

用HMR提高开发效率

DevServer 默认不会开启模块热替换模式,要开启该模式,只需在启动时带上参数 --hot

const webpack = require('webpack');
module.exports = {
entry:{
  main:'./src/index.js',
},
plugins: [
  // 该插件的作用就是实现模块热替换,实际上当启动时带上 `--hot` 参数,会注入该插件,生成 .hot-update.json 文件。
  new webpack.NamedModulesPlugin(), // 用于启动 HMR 时可以显示模块的相对路径
  new webpack.HotModuleReplacementPlugin(), // Hot Module Replacement 的插件
],
devServer:{
  // 告诉 DevServer 要开启模块热替换模式
  hot: true,      
}  
};

代码实现

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'));
  });
}
  • module.hot 是当开启模块热替换后注入到全局的 API,用于控制模块热替换的逻辑
  • 当子模块发生更新时,更新事件会一层层往上传递,也就是从App.js文件传递到index.js文件, 直到有某层的文件接受了当前变化的模块
  • 如果事件一直往上抛到最外层都没有文件接受它,就会直接刷新网页
  • .css文件都会触发模块热替换的原因是style-loader会注入用于接受 CSS 的代码