# webpack(四)

  • Webpack sideEffects
  • Code Splitting
  • mini-css-extract-plugin
  • 文件名 Hash

# Webpack sideEffects

副作用:模块执行时除了导出成员之外所做的事情

比如一个 css 文件没有导出,可认为有副作用

|--components
		--button.js
		--heading.js
		--link.js
		--index.js
|--index.js
// components/index.js

export { default as Button } from './button'
export { default as Link } from './link'
export { default as Heading } from './heading'

// 入口文件 index.js
import { Button } from './components'
// 只想载入 Button 却载入了所有模块
// webpack.config.js

module.exports = {
  mode: 'none',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      }
    ]
  },
  optimization: {
    sideEffects: true, // 标识为true就会去package.json中去检查有无副作用
  }
}

// package.json
{
  "name": "31-side-effects",
  "version": "0.1.0",
  "main": "index.js",
  "author": "zce <w@zce.me> (https://zce.me)",
  "license": "MIT",
  "scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "css-loader": "^3.2.0",
    "style-loader": "^1.0.0",
    "webpack": "^4.41.2",
    "webpack-cli": "^3.3.9"
  },
  // "sideEffects": false  //设置为false说明没有副作用,则多余的模块不会被打包
  "sideEffects": [
    "./src/extend.js", // 标识一下有副作用而不想被删除的模块,防止不会被打包
    "*.css"
  ]
}

# 代码分割

webpack将所有模块都打包到一起,但是并不是每个模块在启动时都是必要的。

更好的方案是把打包结果按一定的规则分离,按照应用的运行结果按需加载。

webpack 的理念就是把各个模块打包到一起,现在又要分离开来,是否矛盾?不矛盾,物极必反而已。

  • 多入口打包:一个页面一个入口,公共部分提取。
  • 动态导入:需要某个模块时,再去加载这个模块。

# 多入口打包配置

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'none',
  entry: {
    index: './src/index.js',  // 多入口
    album: './src/album.js'
  },
  output: {
    filename: '[name].bundle.js' // 生成对应的文件名
  },
  optimization: {
    splitChunks: {
      // 自动提取所有公共模块到单独 bundle 一个文件
      // 打包后: index.bundle.js, album.bundle.js, index~album.bundle.js(公共部分)
      chunks: 'all'
    }
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Multi Entry',
      template: './src/index.html',
      filename: 'index.html',
      chunks: ['index']  // index.html 只会引入打包后的 index.js
    }),
    new HtmlWebpackPlugin({
      title: 'Multi Entry',
      template: './src/album.html',
      filename: 'album.html',
      chunks: ['album'] // album.html 只会引入打包后的 album.js
    })
  ]
}

# 动态导入

需要某个模块时再去加载,动态导入的模块会被自动分包,例如常见的数字开头的 js 文件: 0.js 就是这类模块分包出来的文件。

// import posts from './posts/posts'
// import album from './album/album'
// 删除固定导入

const render = () => {
  const hash = window.location.hash || '#posts'

  const mainElement = document.querySelector('.main')

  mainElement.innerHTML = ''
  // 根据 hash 值动态导入
  if (hash === '#posts') {
    // 魔法注释可以设置打包后文件名
    import(/* webpackChunkName: 'components' */'./posts/posts').then(({ default: posts }) => {	
      mainElement.appendChild(posts())
    })
  } else if (hash === '#album') {
    // 魔法注释可以设置打包后文件名
    import(/* webpackChunkName: 'components' */'./album/album').then(({ default: album }) => {
      mainElement.appendChild(album())
    })
  }
}

render()

window.addEventListener('hashchange', render)

webpack SplitChunksPlugin实用指南 (opens new window)

Webpack之SplitChunks插件用法详解 (opens new window)

在研究splitChunks之前,我们必须先弄明白这三个名词是什么意思,主要是chunk的含义,要不然你就不知道splitChunks是在什么的基础上进行拆分。

  • module:就是js的模块化webpack支持commonJS、ES6等模块化规范,简单来说就是你通过import语句引入的代码。
  • chunk: chunk是webpack根据功能拆分出来的,包含三种情况:

1、你的项目入口(entry)

2、通过import()动态引入的代码(默认分包)

3、通过splitChunks拆分出来的代码

chunk包含着module,可能是一对多也可能是一对一。

  • bundle:bundle是webpack打包之后的各个文件,一般就是和chunk是一对一的关系,bundle就是对chunk进行编译压缩打包等处理之后的产出。
const resolve = require('path').resolve
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
    mode: 'none',
    entry: {
        entry1: './src/entry1.js',
        entry2: './src/entry2.js'
    },
    plugins: [
        new CleanWebpackPlugin()
    ],
    output: {
        filename: '[name].bundle.js',
        path: resolve(__dirname, 'dist')
    },
    optimization: {
        // splitChunks作用: 把 entry1.js 和 entry2.js 都共同引入的模块分包出来。
        // 假如 entry1.js 和 entry2.js 都引入了 element,那么 entry1.bundle.js 和 entry2.bundle.js
        // 不分包的话都会将 element 打包进去,两个文件就都会很大,所以需要分包处理
        splitChunks: {
            // 默认值就是async,分包的对象共同引入的异步模块。
            // initial: 分包的对象是共同引入的初始静态导入模块。all: 分包所有的共同引入的模块。
            chunks: 'all',
            // name代表打包出来的 chunk 的名称,有三种值:
            // 1.boolean = false (webpack5中不支持true)
            // 2.function (module, chunks, cacheGroupKey) => string 
            // 3.string
            // 一般都是使用 false,就是以 cacheGroups 中的 name 命名,如果没有 name,默认为 2.js, 3.js 这种数字类型的
            name: false,
            // 表示要被提取的模块最小被引用次数,引用次数超过或等于minChunks值,才能被提取
            // 这个例子里 subtract 被引用了 2 次,如果设置成 3 则不能被分包
            minChunks: 2,
            // 表示只有超过了多少字节才会被提取, 默认值是 20000,这里设置成 1 是为了这个例子里能分包 modules 中模块
            minSize: 1,
            // 模块打包生成的文件大小不能超过的值,如果超了要对其进行分割并打包生成新的文件。
            // webpack4中默认为0表示大小不限制, webpack5中取消了默认值设置为0时表示大于0的就会分割
            maxSize: 9999999999,
            // 动态引入的模块,并行请求的最大数量
            maxAsyncRequests: 5,
            // 打包后的入口文件加载时,能同时初始加载js文件的数量
            // 优先级: maxInitialRequest/maxAsyncRequests < maxSize < minSize
            maxInitialRequests: 3,
            // 命名连接符
            automaticNameDelimiter: '~',
            cacheGroups: {
                vendors: {
                    name: 'vendors',
                    test: /[\\/]node_modules[\\/]/,
                    priority: 10
                },
                modules: {
                    name: 'modules',
                    test: resolve('src/modules'),
                    priority: 10,
                    reuseExistingChunk: true
                }
            }
        }
    }
}

# mini-css-extract-plugin

mini-css-extract-plugin将css单独提取出来,如果 css 超过 150KB 时需要单独提取

提取出来后还需要用OptimizeCssAssetsWebpackPlugin进行压缩

因为 webpack 内置的压缩插件只打包 js 文件

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')

module.exports = {
  mode: 'none',
  entry: {
    main: './src/index.js'
  },
  optimization: {
    minimizer: [
      new TerserWebpackPlugin(),
      new OptimizeCssAssetsWebpackPlugin() 
    ]
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 'style-loader', // 将样式通过 style 标签注入
          MiniCssExtractPlugin.loader, // 直接通过 link 方式引入
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin()
  ]
}

为什么ptimizeCssAssetsWebpackPlugin插件放在这不放在 plugins 数组中呢 ?

因为放在 plugins 说明在任何时候都会加载这个插件,而我们放到 minimizer 中,只会在生产环境开启 minimizer 时工作

但是这时会发现 js 没有压缩,因为我们设置了minimizer,webpack会认为我们自定义了压缩插件,所以我们还要添加回去压缩 js 的插件TerserWebpackPlugin。

# 文件名 Hash

  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Dynamic import',
      template: './src/index.html',
      filename: 'index.html'
    }),
    new MiniCssExtractPlugin({
      filename: '[name]-[contenthash:8].bundle.css' // 文件级别的hash
      // filename: '[name]-[chunkhash].bundle.css'  // chunk级别
      // filename: '[name]-[hash].bundle.css'       // 项目级别,一个文件改变,所有hash全部改变
    })
  ]
}
最后更新时间: 9/3/2021, 7:07:07 PM