从零开始学 Webpack 4.0(五)

138 阅读18分钟

原文本人写于 2022-05-12 23:11:40

一、前言

项目结构

本文会以 demo05 作为项目文件夹,各文件直接拿之前项目 demo04 的,接下来做些修改。

20220509_01.jpg

添加开发环境打包命令 dev-build

package.json

{
  "name": "demo05",
  ...
  "scripts": {
    "dev": "webpack-dev-server --config ./build/webpack.dev.js",
    "dev-build": "webpack --config ./build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.js"
  },
  ...
}

package-lock.json

{
  "name": "demo05",
  ...
}

设置下 splitChunks,使其支持同步以及异步的代码分割。

webpack.common.js

...
module.exports = {
  ...
  plugins: [
    new HtmlWebpackPlugin({
      ...
      title: 'demo05自定义title'
    }),
    ...
  ],
  optimization: {
    // splitChunks 一般按默认配置即可
    splitChunks: {
      chunks: 'all'
    }
  },
  ...
}

index.js(原文件内容清空)

// 同步
import _ from 'lodash'

function getComponent() {
  const element = document.createElement('div')
  element.innerHTML = _.join(['a', 'b', 'c'], '-')
  document.body.appendChild(element)
}

document.addEventListener('click', () => {
  getComponent()
})

安装依赖

npm ci

运行开发环境打包命令

npm run dev-build

打包成功,dist 目录生成内容如图所示。

20220509_02.jpg

dist 目录下 index.html 放入浏览器运行,点击一下页面,正常显示效果。

20220509_03.jpg

二、Lazy Loading

Lazy Loading,也就是常说的懒加载,通过一些优化手段去提高页面的性能。

首先我们来看下本项目,index.js 里采用的是同步引入模块 lodash,当用户第一次访问页面的时候,会去请求资源 index.html、vendors~main.js 以及 main.js,第二次访问的时候因为文件没有发生改变会直接在浏览器缓存取相关文件。

存在的问题是当请求的资源文件体积比较大的时候,第一次加载页面会比较慢,哪怕有些代码是一开始用不上的。

我们尝试把代码改为异步的形式

index.js

// 异步
function getComponent () {
  return import (/* webpackChunkName:"lodash" */ 'lodash').then(({default: _}) => {
    const element = document.createElement('div')
    element.innerHTML = _.join(['a', 'b', 'c'], '-')
    return element
  })
}

document.addEventListener('click', () => {
  getComponent().then(element => {
    document.body.appendChild(element)
  })
})

重新运行开发环境打包命令,打包成功后把 dist 目录下 index.html 放入浏览器运行,打开控制台观察 Network 并刷新页面。

一开始页面只会去请求资源 index.html 以及 main.js。

20220509_04.jpg

当我们点击页面的时候,页面需要用到 lodash 这个库,才会去请求资源 vendors~lodash.js,这就是一种懒加载。

20220510_01.jpg

这里的懒加载其实就是通过 import ('xxx') 去异步地加载一个模块,代码里什么时候真正去执行这个 import 语句,模块才会被载入,这种做法可以提高页面的加载速度。

懒加载实际上并不是 Webpack 里的概念,只是 Webpack 能够识别这种 import 的语法对引入模块进行代码分割并按需加载。

三、打包分析以及 Preload、Prefetch

打包分析

当我们进行完项目的打包后,可以利用一些工具对打包生成的文件进行分析,查看打包是否合理。

修改下 package.json 里的 scripts 脚本命令,在打包过程中把整个打包过程的一些描述放置到一个叫 stats.json 的文件里面去。

package.json

{
  ...
  "scripts": {
    ...
    "dev-build": "webpack --profile --json > stats.json --config ./build/webpack.dev.js",
    ...
  },
  ...
}

运行开发环境打包命令

npm run dev-build

这时候依然会进行打包,但是控制台不会输出打包结果,项目根路径下会生成一个 stats.json 文件存储这次打包的过程及结果。

查看官方文档指南的代码分离下 bundle 分析(文末有官网链接),里面会推荐一些打包分析的工具,有些可能得科学上网才能正常访问。把 stats.json 上传到这些工具内进行分析,可以看到打包后的文件体积、是否有异常、模块之间的依赖关系等等。

现在我们来使用一个比较多人使用的打包分析工具 webpack-bundle-analyzer,它将打包内容展示为便捷的、交互式、可缩放的树状图形式。

安装 webpack-bundle-analyzer(本文安装的版本为4.5.0)

npm install webpack-bundle-analyzer -D

对生产环境的打包文件去进行分析,所以需要配置下生产环境的打包配置文件。

webpack.prod.js

...
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

const prodConfig = {
  ...
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

运行生产环境打包命令

npm run build

它会生成打包文件,并启动一个服务访问页面 http://127.0.0.1:8888/ ,展示包内含有文件及体积,协助我们去优化项目。

20220510_04.jpg

配置还原

package.json

{
  ...
  "scripts": {
    ...
    "dev-build": "webpack --config ./build/webpack.dev.js",
    ...
  },
  ...
}

webpack.prod.js

...
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = {
  ...
  plugins: [
    // new BundleAnalyzerPlugin()
  ]
}

Preload、Prefetch

在第二节 Lazy Loading 中异步加载的写法是 Webpack 推荐的写法,Webpack 在打包过程中希望开发者尽可能多地去写异步代码,这种是页面加载速度最快的。也许就是因为这样,splitChunks 里 chunks 的默认配置才是 'async' 而不是 'all'。

因为同步的代码只能增加一个缓存,提高下次访问页面的加载速度,但在页面初次加载的时候会加载很多没必要的代码,对性能的提升是比较有限的。

比如现在有一个网站,点击首页的登录按钮会弹出一个登录窗口,实际上这个登录窗口涉及的代码在首页加载时不应该去加载,只有当点击登录按钮的时候才应该去加载这个登录窗口相关的逻辑代码。但如果点击的时候才去加载,用户感觉到的反应速度会很慢,这时候就需要用到 Preload 以及 Prefetch。

我们可以等网站首页的核心代码都已经加载完毕,网络空闲了,在空闲的时候去把登录这块内容加载下来,而不是等用户点击登录按钮才去反应。

本项目中我们来实现类似的效果,先来运行下开发环境打包命令。

npm run dev-build

打包成功后把 dist 目录下 index.html 放入浏览器运行,打开控制台观察 Network 并刷新页面,点击页面正常显示效果。

20220509_03.jpg

点击页面后才会去请求资源 vendors~lodash.js,并往页面写入拼接的字符串 'a-b-c'。

20220510_02.jpg

修改 index.js 内的代码,使用魔法注释 webpackPrefetch: true。

index.js

function getComponent () {
  return import (/* webpackPrefetch: true , webpackChunkName:"lodash" */ 'lodash').then(({default: _}) => {
  ...
  })
}
...

再次运行开发环境打包命令,观察 Network 刷新页面,这时候我们还没有进行点击操作,它就会在核心代码加载完后去加载 vendors~lodash.js,也就达到我们想要的效果。

20220510_03.jpg

Preload 的魔法注释是 webpackPreload: true,Preload 会和核心代码一起去加载。

Prefetch 的魔法注释是 webpackPrefetch: true,Prefetch 会等页面核心代码加载完毕网络空闲下来的时候才去加载对应的代码,所以在本次演示中我们使用 Prefetch。但是这些魔法注释在某些浏览器上可能存在兼容性问题,还需多多注意。

在我们平时的开发中,应多使用这种异步的写法。在页面加载速度的优化上,不仅仅只是关注于利用缓存这个特性,还应该去考虑怎样让页面加载的 js 文件代码利用率达到最高。有一些交互之后才需要的代码,完全可以写到异步组件里面去,通过懒加载的形式提高性能。

注意的是,同步代码不能使用懒加载以及 Prefetch 这些魔法注释。

四、Chunk

先来修改下打包配置文件里的 output 配置项。

webpack.common.js

...
module.exports = {
  ...
  output: {
    filename: '[name].js',
    chunkFilename: '[name].chunk.js',
    path: path.resolve(__dirname, '../dist')
  }
}

运行开发环境打包命令,打包成功后观察 dist 目录下文件,原先的 vendors ~ lodash.js 变成 vendors ~ lodash.chunk.js。

20220510_05.jpg

在 Webpack 中我们可以简单的理解为打包后生成的文件,除了 html,每一个文件都是一个 chunk。

output 里的 filename 配置的是入口文件 entry 里的文件名,chunkFilename 配置的是入口文件内引入文件的文件名。

还有一种更简单的理解方式,打开 dist 目录下 index.html,会发现它引入了 main.js,这个 main.js 走的就是 filename 的命名,而 lodash 在 index.html 中找不到,它是通过 main.js 里的代码再去引入 lodash 相关代码,这种间接引入的模块在打包时生成的文件名就会走 chunkFilename 的命名。

五、CSS 文件的代码分割

在之前的文章中我们谈过 js 文件的代码分割,这次我们来看看 css 文件的代码分割怎么实现。

在 src 目录下新建 style.css 以及 style2.css。

style.css

@import './style2.css';

body{
  background: skyblue;
}

style2.css

body{
  font-size: 100px;
}

修改 index.js,并引入 style.css 以及 style2.css。

index.js

import './style.css'
import './style2.css'

document.write('test')

运行生产环境打包命令

npm run build

打包成功后观察 dist 目录下文件,发现并没有 css 文件生成。

20220510_06.jpg

把 dist 目录下 index.html 放入浏览器运行,正常显示效果。

20220510_07.jpg

这是因为 Webpack 在打包的时候,直接把 css 文件打包到 js 文件里面去了。

接下来我们要实现是当编写的业务代码中引入了 css 文件,在打包的时候单独打包到 dist 目录下生成一个 css 文件。

查看官网相关文档,需要用到 MiniCssExtractPlugin 插件。

安装 mini-css-extract-plugin

npm install mini-css-extract-plugin@0.5.0 -D

css 文件的代码分割会在生产环境使用

webpack.prod.js

...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  ...
  plugins: [
    new MiniCssExtractPlugin({})
  ]
}

以前打包 css 文件的最后一步是通过 style-loader 挂载到页面上,现在最后一步不使用 style-loader,改为使用 MiniCssExtractPlugin 提供的 loader,把 css 文件给单独生成一个文件。

先把 webpack.common.js 内关于 css 以及 scss 文件的打包处理移到 webpack.dev.js 里面去,开发环境对 css 文件的处理不需要改变。

webpack.common.js

...
module.exports = {
  ...
  module: {
    rules: [
      ...
      // {
      //   test: /\.css$/,
      //   use: ['style-loader', 'css-loader']
      // },
      // {
      //   test: /\.scss$/,
      //   use: [
      //     'style-loader',
      //     {
      //       loader: 'css-loader',
      //       options: {
      //         importLoaders: 2,
      //         modules: true
      //       }
      //     },
      //     'postcss-loader',
      //     'sass-loader'
      //   ]
      // },
      ...
    ]
  },
  ...
}

webpack.dev.js

...
const devConfig = {
  ...
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader', 
          'css-loader',
          // 顺便加上
          'postcss-loader'
        ]
      },
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              modules: true
            }
          },
          'postcss-loader',
          'sass-loader'
        ]
      }
    ]
  },
  plugins: [
    ...
  ]
}
...

接着配置生产环境对 css、scss 文件的打包处理。

webpack.prod.js

...
const prodConfig = {
  ...
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          // 顺便加上
          'postcss-loader'
        ]
      },
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              modules: true
            }
          },
          'postcss-loader',
          'sass-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({})
  ]
}
...

运行生产环境打包命令,打包成功后观察 dist 目录下文件,发现比上次打包多了 main.css 以及对应的 SourceMap 文件 main.css.map。

20220512_04.jpg

查看 main.css,发现它把文件之间引入的 css 代码合并到了一起。

20220511_02.jpg

index.html 内对 main.css 进行了引入。

20220511_05.jpg

接下来我们需要把这些 css 代码进行压缩,可以使用官方推荐的 optimize-css-assets-webpack-plugin,注意的是它只能对生产环境下的 css 代码压缩。

安装

npm install optimize-css-assets-webpack-plugin@5.0.1 -D

在生产环境进行使用

webpack.prod.js

...
const optimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

const prodConfig = {
  ...
  optimization: {
    minimizer: [new optimizeCssAssetsWebpackPlugin({})]
  },
  plugins: [
    ...
  ]
}
...

运行生产环境打包命令,打包成功后观察 dist 目录下文件,发现这次没有生成 css 文件的 SourceMap 文件。

20220511_03.jpg

查看 main.css,里面的代码进行了去重合并以及压缩。

20220511_04.jpg

假设项目不只一个入口文件,我们想把多个入口文件用到的 css 都打包到一个 css 文件里面去。

在 src 目录下新建 index2.js

index2.js

import './style.css'

新增项目入口文件

webpack.common.js

...
module.exports = {
  entry: {
    main: './src/index.js',
    main2: './src/index2.js'
  },
  ...
}

运行生产环境打包命令,打包成功后观察 dist 目录下文件,main2 这个入口涉及的样式被单独打包成一个文件 main2.css。

20220511_06.jpg

配置下生产环境的 splitChunks

webpack.prod.js

...
const prodConfig = {
  ...
  optimization: {
    ...
    // MiniCssExtractPlugin 底层也要使用 splitchunksplugin。
    splitChunks: {
      cacheGroups: {
        styles: {
          chunks: 'all',
          test: /\.css$/,
          name: 'styles',
          // 这个设为 true 会忽略掉前面一些默认的参数,例如 minSize、maxSize 等。
          enforce: true
        }
      }
    }
  },
  ...
}
...

运行生产环境打包命令,打包成功后观察 dist 目录下文件,两个入口文件的 css 代码都打包到了 style.css 里面去。至于多出来的 styles.chunk.js 是 Webpack 加载分片的代码,在这里影响不大。

20220511_07.jpg

我们还可以根据入口文件的不同去自定义打包生成的 css 文件,还是用 splitChunks 里的 cacheGroups,当有该需求的时候去参考官方文档的配置写法即可,这里截图给大家简单了解一下。

20220511_08.jpg

本节内容涉及的 css 文件代码分割主要是运用在生产环境,在开发环境没有必要使用,而且有可能影响我们调试样式。

配置还原

webpack.common.js

...
module.exports = {
  entry: {
    main: './src/index.js',
    // main2: './src/index2.js',
  },
  ...
}

webpack.prod.js

...
const prodConfig = {
  ...
  optimization: {
    ...
    // splitChunks: {
    //   cacheGroups: {
    //     styles: {
    //       chunks: 'all',
    //       test: /\.css$/,
    //       name: 'styles',
    //       enforce: true
    //     }
    //   }
    // }
  },
  ...
}
...

拓展

我们还可以在打包配置文件的 MiniCssExtractPlugin 里配置生成的 css 文件名,同样支持占位符的写法。

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

按照网上及相关文档的说法,以同步的方式引入 css 就会走 filename,以异步的方式引入 css 就会走 chunkFilename。(但我尝试以同步或者异步的方式引入 css,只要设置了 chunkFilename 就会走 chunkFilename,当 chunkFilename 没有进行设置才会去走 filename 配置项,原因暂时未知。)

六、Webpack 与浏览器缓存(Caching)

初始配置

为了不干扰到后面的学习,我们先把生产环境的 SourceMap 关闭。

webpack.prod.js

...
const prodConfig = {
  mode: 'production',
  // devtool: 'cheap-module-source-map',
  ...
}
...

index.js 用回上面异步代码的例子。

index.js

function getComponent () {
  return import (/* webpackPrefetch: true , webpackChunkName:"lodash" */ 'lodash').then(({default: _}) => {
    const element = document.createElement('div')
    element.innerHTML = _.join(['a', 'b', 'c'], '-')
    return element
  })
}

document.addEventListener('click', () => {
  getComponent().then(element => {
    document.body.appendChild(element)
  })
})

运行生产环境打包命令,打包过程中看到一些警告是 Webpack 对资源文件性能上的一些提示,我们可以在打包配置文件里把它关了。

20220511_11.jpg

webpack.common.js

...
module.exports = {
  ...
  performance: false,
  output: {
    ...
  }
}

打包成功后观察 dist 目录下文件。

20220511_09.jpg

修改一下 cacheGroups 配置,让生成的文件名 vendors~lodash.chunk.js 固定为 vendors.chunk.js。

webpack.common.js

...
module.exports = {
  ...
  plugins: [
    ...
  ],
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          name: 'vendors'
        }
      }
    }
  },
  ...
}

运行生产环境打包命令,打包成功后观察 dist 目录下文件。

20220511_10.jpg

案例讲述

假设 dist 目录的内容部署到服务器上,当用户首次访问页面的时候,会去请求 index.html,接着去请求 main.js 以及 vendors.chunk.js,当用户第二次访问页面的时候会去浏览器缓存里取 main.js 以及 vendors.chunk.js。

当我们修改了业务代码重新进行打包,然后把打包文件上传到服务器。这时候用户去刷新页面还是会看到之前的内容,因为打包新生成的文件名没有发生改变,浏览器会直接取缓存里上次的文件。

这时候我们需要配置下生产环境的打包文件名。

先把 webpack.common.js 里 output 关于文件生成名的设置移到 webpack.dev.js 里面去,因为在开发环境不需要担心缓存的问题,每次它都会单独打包以及 HMR 会去进行相应的处理。

webpack.common.js

...
module.exports = {
  ...
  output: {
    path: path.resolve(__dirname, '../dist')
  }
}

webpack.dev.js

...
const devConfig = {
  ...
  output: {
    filename: '[name].js',
    chunkFilename: '[name].chunk.js'
  }
}
...

设置生产环境的 output 配置项。

webpack.prod.js

...
const prodConfig = {
  ...
  output: {
    // contenthash 通过文件内容去生成一个哈希字符串,
    // 当代码没有发生改变时,contenthash 这个值永远不会变。
    // 注意,contenthash 和 HMR 不能同时使用,不过一般也不会同时去使用。
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].chunk.js'
  }
}
...

运行生产环境打包命令,打包成功后观察 dist 目录下文件。

20220511_12.jpg

现在,当用户访问页面时,如果业务代码没有发生改变,浏览器就会去缓存里取相关文件。当业务代码发生改变了,打包生成文件的 contenthash 值发生相应更改,浏览器就会去请求新的文件来更新页面内容。

当老版本的 Webpack 没有更新业务代码,结果两次打包生成的文件 contenthash 不一样,这时候需要进行 runtimeChunk 的设置,新版本也可以设置这个。

webpack.common.js

...
module.exports = {
  ...
  optimization: {
    runtimeChunk: {
      name: 'runtime'
    },
    ...
  },
  ...
}
...

运行生产环境打包命令,打包成功后观察 dist 目录下文件,多了一个 runtime 文件。

20220511_13.jpg

实际上在做 Webpack 打包的时候,main.js 存放的是业务逻辑,vendors.js 存放的是工具库,业务逻辑与库之间关联的这块内置代码叫做 manifest,默认 manifest 存在于 main.js 以及 vendors.js 中。但 manifest 在旧版的 Webpack 中每次打包的时候可能有些差异,于是每次打包的时候即使源代码没有发生改变,生成文件中的代码却发生了改变,导致 contenthash 的改变。

当我们进行了 runtimeChunk 的相关设置,就会把 manifest 这块代码抽离出来,放到一个 runtime 文件里面去。

这时候就通过 contenthash 这个配置项满足了我们的需求。用户访问过的页面资源浏览器会直接去缓存里取,当业务代码发生变更重新打包部署到服务器上,打包生成的文件名发生变更,用户再次访问页面,浏览器就会去请求新的文件,更新页面内容。

配置还原

webpack.prod.js

...
const prodConfig = {
  ...
  devtool: 'cheap-module-source-map',
  ...
}
...

七、Shimming 的作用

Shimming,翻成中文就是垫片。

在 Webpack 的打包过程中往往需要做一些代码的兼容或者说打包过程的兼容。就像我们通过 babel 的 polyfill 去给低版本的浏览器自动构建一些类似 promise 的全局变量,从而使代码能够在低版本浏览器上运行,这种就是 Webpack 的垫片。

接下来我们结合案例来看下垫片的作用。

首先我们来安装下 jquery(本文安装的版本为3.6.0)

npm install jquery --save

在 src 目录下新建 jquery.ui.js

jquery.ui.js

export function ui () {
  $('body').css('background', 'green')
}

修改 index.js 的代码,引入 jquery、lodash 以及 jquery.ui.js 进行使用。

index.js

import _ from 'lodash'
import $ from 'jquery'
import { ui } from './jquery.ui'

ui()

const dom = $('<div>');
dom.html(_.join(['a', 'b', 'c'], ' -- '))
$('body').append(dom)

接下来我们启动开发环境的服务去打开页面查看效果

npm run dev

浏览器会直接报错,页面无法正常展示效果。

20220512_01.jpg

这是因为我们的 jquery 是在 index.js 中引入的,jquery.ui.js 内无法获取到 index.js 中引入的 jquery。

Webpack 是基于模块打包的,模块这些变量只能在当前模块这个文件内去使用,通过这种形式保证了模块与模块之间不会有任何的耦合。当代码出现问题的时候直接到自己的模块里找就行了,不会因为一个模块影响到另外一个模块,变量之间是隔离的。

修改下 jquery.ui.js,引入 jquery。

jquery.ui.js

import $ from 'jquery'
...

刷新页面,这次就能正常显示效果。

20220512_02.jpg

在该案例中的 jquery.ui.js 我们可以理解为是一个写法比较旧的第三方库,它依赖于 jquery,但现在因为 Webpack 模块之间的变量隔离导致它无法正常运行。在案例中我们可以手动引入一下 jquery,但在实际开发中,这些第三方库的 npm 包是在 node_modules 目录下,我们不可能去改动它的源代码。

这时候就可以借助 Webpack 自带的插件,以垫片的形式来解决该问题。

去除 jquery.ui.js 对 jquery 的引入。

jquery.ui.js

// import 'jquery'
...

修改打包配置文件

webpack.common.js

...
const webpack = require('webpack')

module.exports = {
  ...
  plugins: [
    ...
    new webpack.ProvidePlugin({
      // 当一个模块使用了 $,就会去帮你自动引入 jquery 这个模块
      $: 'jquery'
    })
  ],
  ...
}

修改了配置文件,需要重新运行命令启动服务,打开页面能够正常显示效果。

还有一种写法,我们修改下相应文件。

index.js

// 不在该模块中引入 lodash 以及 jquery
import { ui } from './jquery.ui'

ui()

const dom = $('<div>');
// _.join 换成 _join
dom.html(_join(['a', 'b', 'c'], ' -- '))
$('body').append(dom)

webpack.common.js

...
module.exports = {
  ...
  plugins: [
    ...
    new webpack.ProvidePlugin({
      ...
      // 打包时发现用了 _join,就会把 lodash 下面的 join 打包放到对应的模块里。
      _join: ['lodash', 'join']
    })
  ],
  ...
}

重新运行命令启动服务,打开页面能够正常显示效果。

shimming 这个概念很宽泛,涉及的内容也非常多,在未来的开发中会遇到各种各样意想不到的需要我们做 shimming 的场景,根据不同的场景去寻找对应的解决方法即可。

八、环境变量的使用方法

在之前的案例中,我们是通过不同的配置文件去生成不同环境打包结果,现在我们通过一个配置文件结合环境变量去进行打包。

webpack.dev.js

const webpack = require('webpack')

const devConfig = {
  ...
}

module.exports = devConfig

webpack.prod.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const optimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

const prodConfig = {
  ...
}

module.exports = prodConfig

webpack.common.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const webpack = require('webpack')
const merge = require('webpack-merge')
const devConfig = require('./webpack.dev.js')
const prodConfig = require('./webpack.prod.js')

const commonConfig = {
  ...
}

// 全局变量 env,在运行打包命令时传进来。
module.exports = (env) => {
  console.log(env)
  // env.production 默认为 true
  if (env && env.production) {
    return merge(commonConfig, prodConfig)
  } else {
    return merge(commonConfig, devConfig)
  }
}

package.json

{
  ...
  "scripts": {
    "dev": "webpack-dev-server --config ./build/webpack.common.js",
    "dev-build": "webpack --config ./build/webpack.common.js",
    "build": "webpack --env.production --config ./build/webpack.common.js"
  },
  ...
}

设置完后我们运行下生产环境的打包命令,正常打包,并输出了 env 这个全局变量。

20220512_03.jpg

我们还可以这样写

package.json

{
  ...
  "scripts": {
    ...
    "build": "webpack --env.production=abc --config ./build/webpack.common.js"
  },
  ...
}

webpack.common.js

...
const commonConfig = {
  ...
}

module.exports = (env) => {
  if (env && env.production === 'abc') {
    return merge(commonConfig, prodConfig)
  } else {
    return merge(commonConfig, devConfig)
  }
}

以上,我们实现的就是在 Webpack 的打包过程中传入环境变量,来决定使用哪些配置项进行打包。

Webpack 命令行环境配置的 --env 参数,可以允许你传入任意数量的环境变量,然后在 Webpack 的打包配置文件中可以访问到这些变量。

后面我们还是会采用之前的形式去区分打包的环境,这里只是为了让大家去理解 Webpack 中环境变量的概念。

配置还原

webpack.dev.js

const webpack = require('webpack')
const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')

const devConfig = {
  ...
}

module.exports = merge(commonConfig, devConfig)

webpack.prod.js

const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const optimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

const prodConfig = {
  ...
}

module.exports = merge(commonConfig, prodConfig)

webpack.common.js

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

module.exports = {
  ...
}

package.json

{
  ...
  "scripts": {
    "dev": "webpack-dev-server --config ./build/webpack.dev.js",
    "dev-build": "webpack --config ./build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.js"
  },
  ...
}

九、官方文档

学习完以上内容,可以前往 Webpack 4.0 官网 阅读相关中文文档。

建议阅读的内容为(有些内容之前文章学过,补充一下):

  • (1)文档 > 指南,页面左侧的 安装。
  • (2)文档 > 指南,页面左侧的 tree shaking。
  • (3)文档 > 指南,页面左侧的 生产环境。
  • (4)文档 > 指南,页面左侧的 代码分离。
  • (5)文档 > 指南,页面左侧的 懒加载。
  • (6)文档 > 指南,页面左侧的 缓存。
  • (7)文档 > 指南,页面左侧的 shim 预置依赖。
  • (8)文档 > PLUGIN,页面左侧的 MiniCssExtractPlugin。

英语比较好的小伙伴可以直接阅读 Webpack 4.0 官网 的英文文档。

十、总结

通过以上的学习,我们知道了在 Webpack 的使用中编写异步代码实现懒加载、知道怎么对打包后的内容进行分析、知道魔法注释 Preload 以及 Prefetch 的用法、简单了解了什么是 Chunk、知道 CSS 文件如何进行代码分割、知道 contenthash 配置项结合浏览器缓存的运用、知道 Webpack 中的 Shimming 有何作用以及如何使用、知道了怎样通过一个配置文件结合环境变量去进行不同环境的打包。

这只是一个学习教程,涉及的一些案例代码量太少,一些配置项以及优化手段意义不大,甚至设置后的效果好像还更差了,这都是因为案例太小了,我们只需要了解其作用及使用场景,开发中碰到类似场景有思路去解决即可。

本文最后的代码我会上传到 码云(gitee.com/phao97)上,项目文件夹为 demo05。

如果觉得本篇文章对你有帮助,不妨点个赞或者给相关的 Git 仓库一个 Star,你的鼓励是我持续更新的动力!