likes
comments
collection
share

前端工程化6:Webpack5配置示例,看看这些最佳实践

作者站长头像
站长
· 阅读数 27

1. 为什么使用Webpack(应用场景)

  1. 支持新特性语言版本的编译
  2. 针对javascript模块化打包
  3. 针对所有资源,例如样式、图片、字体等进行模块化

对于1、2两点,grunt、gulp等构建工具可以很好的解决,但是无法解决第3点。Webpack能够解决前端整体的模块化能力。

2. 具备的能力

  • 模块打包器(Module bundler)—— 本身支持js模块化
  • 加载器(loader) —— 利用babel等进行语言特性编译,转换
  • 代码拆分(Code Splitting)—— 文件按需加载
  • 资源模块(Asset Module) —— 支持加载css、字体等资源

3. 基本配置

3.1 安装

npm install webpack --save-dev
npm install webpack-cli --save-dev

3.2 入口文件:webpack.config.js

配置:mode
  1. production 生产模式下,Webpack 会自动优化打包结果;(例如:代码的压缩混淆等)
  2. development 开发模式下,Webpack 会自动优化打包速度,添加一些调试过程中的辅助;
  3. none 模式下,Webpack 就是运行最原始的打包,不做任何额外处理;
配置:entry

entry 可以是相对路径,也可以是绝对路径

// entry 可以是多入口
entry: {
    index: './src/pages/index/index.js',
    album: './src/pages/album/album.js',
    work: './src/pages/work/work.js'
},
配置:output

output 必须是绝对路径

entry: {
    index: './src/main.js'
},
output: {
    filename: '[name]-[contenthash:8].bundle.js',// contenthash:8最常用,用于避免缓存问题
    path: path.join(__dirname, 'dist')
},
配置:cache

是否打开构建缓存

3.3 资源加载逻辑

可以将js作为入口文件,在js中import css等资源

  • js驱动整个前端应用
  • 符合资源加载逻辑,js需要这些资源
  • 保证前端项目的开发资源不缺失

前端工程化6:Webpack5配置示例,看看这些最佳实践

webpack兼容的几种模块化标准:ES Modules、AMD、COmmonJS

webpack加载模块的几种方式:

  • @import,@import(css)文件时
  • css中的background:url()函数
  • html中src属性、a标签的href(需要配置)
{
    test: /.html$/,
    use: {
        loader: 'html-loader',
        options: {
            attrs: ['img:src', 'a:href']
        }
    }
}

3.4 loader/plugin 对比,自定义实现 loader/plugin

  • loader:用于资源加载并处理各种语言的转换/编译(例如将es6+/ts转换为js,css加载等);
  • plugin:用于资源加载以外的其他打包/压缩/文件处理等功能;

参考文章:前端工程化7

3.5 loader加载资源文件

loader:大致分为三类
  1. 编译转换
  2. 文件操作
  3. 代码检查
loader:css样式编译(注意loader顺序,css-loader第一个执行)
  • 依赖:
> npm install css-loader --save-dev
> npm install style/core --save-dev
  • 配置:
  module: {
    rules: [
      {
        test: /.css$/, // 匹配文件
        use: [// use指定使用到的loader
          'style-loader', // 将css-loader转换后的结果放到style标签里面
          'css-loader'    // 先执行css-loader,而且是从后往前执行,所以需要放到下面 
        ]
      }
    ]
  }
loader:编译ES6+ => babel
  • 依赖:
> npm install babel-loader --save-dev         => babel 转换平台
> npm install @babel/core --save-dev          => babel 核心模块
> npm install @babel/preset-env --save-dev    => babel 转换语言包,env表示es6+全量包
  • 配置:
  1. babel-loader:
{// 转换js代码,es6+=>es5
    test: /\.js$/,
    exclude: /node_modules/,
    use: 'babel-loader'
},
  1. babel.config.js(配置可以提取出来:.babelrc、.babelrc.js、babel.config.js、package.json 文件):
// .babelrc 只会影响本项目中的代码;babel.config.js 会影响整个项目中的代码,包含node_modules中的代码
// 推荐使用:babel.config.js
module.exports = {
  presets: [
    '@babel/preset-env'
  ]
}
  1. babel 常用相关插件说明:
> babel-loader           => webpack中转换babel的工具,相当于一个平台不做具体的工作
> @babel/core            => babel转换语言的核心功能,核心api等
> @babel/preset-env      => babel转换语言的内容包,包括es6+所有特性

// 以下内容待确认!
> @babel/cli             => 使node环境支持es6语法;@babel/core也能是node环境支持es6语法;
> @babel/polyfill        =>(一些全局方法和变量)Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。举例来说,ES6在Array对象上新增了Array.from方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片。babel-polyfill相对来说比较大。

> @babel/runtime  => 如果不想用babel-polyfill污染全局环境,就是用babel-runtime+babel-plugin-transform-runtime;
> @babel/plugin-transform-runtime  => babel-plugin-transform-runtime依赖于babel-runtime

> @babel/eslint-parser    => babel + eslint 的解析器(好像也没有使用?)

> 插件名互换:@babel/core === 等同于 babel-core
loader:文件/图片处理
  • 依赖:
> npm install file-loader --save-dev
> npm install url-loader --save-dev
  • 功能:
  1. file-loader 普通处理
{// 将图片文件复制到另一个目录
    test: /\.(png|svg|jpg|gif)$/,
    loader: 'file-loader',
    options: {
        name: '[name]-[contenthash:8].[ext]',
        outputPath: 'images',
        esModule: false // 新版loader需要配置,否则会产生错误:img src=[object Module]
    },
},
  1. url-loader 和 file-loader 二选一url-loader 对 file-loader 有依赖,需要提前安装;url-loader 将图片编码为 Base64 文件:对于小文件座大小限制进行处理,减少请求次数。
{// 将小于10kb的图片编码成base64
    test: /\.(png|svg|jpg|gif)$/,
    use: {
        loader: 'url-loader',  // url-loader对file-loader有依赖,需要提前安装
        options: {
            name: '[name]-[contenthash:8].[ext]',
            outputPath: 'images',
            limit: 10 * 1024,  // 10 KB 对文件大小进行限制,超过就不转换
            esModule: false    // 新版loader需要配置,否则会产生错误:img src=[object Module]
        },
    }
}
loader:html处理(用得少,使用其他插件替代)

依赖:

> npm install html-loader --save-dev

3.6 plugins插件处理其他任务

plugin:自动生成html文件插件
  • 依赖:html-webpack-plugin
  • 功能:
  1. 基础配置
// 用于生成 index.html
new HtmlWebpackPlugin({
    title: 'index首页',
    meta: {
        viewport: 'width=device-width'
    },
    minify: {
        removeAttributeQuotes: true     // 移除属性的引号
    },
    inject: true,                       // script是否至于body底部
    template: './src/templates/index.html',
    filename: 'index.html',
    // cache: false,
    chunks: ['index'],                  // 指定加载js文件,默认全部加载
    // showErrors: true,                // 如果 webpack 编译出现错误,webpack会将错误信息包裹在一个 pre 标签内,属性的默认值为 true ,也就是显示错误信息。
}),
  1. 在html模板中使用变量:
  <title>Home - <%= htmlWebpackPlugin.options.title %></title>
plugin:根据目录自动生成html文件
  • 依赖:auto-web-plugin
plugin:自动清除输出目录插件
  • 依赖:clean-webpack-plugin
plugin:拷贝文件的插件
  • 依赖:copy-webpack-plugin
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = merge(common, {
    mode: 'production',
    plugins: [
      new CleanWebpackPlugin(),
      new CopyWebpackPlugin({
        patterns: [
            { from: "./public/*.ico", to: "./"},
        ],
      })
    ]
})

4. 增强体验

4.1、热更新

方案1:webpack + browser-sync(本地磁盘可见文件)
  • 依赖:
> npm install browser-sync --save-dev
  • 功能:
  1. 自动编译,监控源文件;使用webpack自带配置
// 1、npm scripts中 => webpack携带命令行参数或者添加配置项
> webpack --watch

// 2、webpack.config.js => webpack配置项
watch: true, // 监听所有文件的更改,自动构建自动刷新浏览器
  1. 自动刷新浏览器,监控打包文件;使用工具browser-sync
> npm install browser-sync --save-dev
  "scripts": {
    "dev": "webpack --config webpack.dev.js",   // 配置项配了watch就可以不带参数--wacth
    "sync": "browser-sync dist --files '**/*'", // 带参数--files
  }
  1. 需要同时启用两个命令行:
webpack-sample> npm run dev
webpack-sample> npm run sync
方案2:webapck-dev-server工具(内存中看不到文件)
  • 依赖:
> npm install webapck-dev-server --save-dev
  • 功能:集成了自动编译和自动刷新浏览器,注意watch和target的配置
  • 基本配置 :
1. contentBase  => 基本路径 
2. open         => 自动打开浏览器
3. host         => 虚拟主机地址
4. port         => 端口号
5. proxy(代理) => 代理配置
  • 完整示例:
    // watch: true,             // 热更新方案1:监听源码文件更改自动构建,监听磁盘文件自动刷新浏览器(配合browser-sync刷新浏览器)
    target: "web",              // webpack5的一个bug,需要打开才能自动刷新浏览器,参考:https://blog.csdn.net/xiaolongbaobushibao/article/details/116664883
                                // 使用devServer是不用打开watch
                                // 使用devServer常见不刷新浏览器情况:
    devServer: {                // 热更新方案2:本地开发服务器,监听源码文件自动构建和刷新浏览器,打包结果存在内存而不是磁盘中
        host: '127.0.0.1',  
        port: '8080',
        // hot: true,           // 模块热替换,如果热替换失败就自动回到页面自动刷新
        // hotOnly: true,       // 模块热替换,如果热替换失败不会自动刷新页面
        contentBase: path.resolve(__dirname,'dist'),// 指定静态资源路径的根目录,需要对应output:path为dist,
        open: true,             // 自动打开浏览器
        // watchContentBase: true,
        inline: true
        // proxy: {             // 配置代理,防止跨域问题
        //     '/api': {
        //         target: 'https://api.github.com', // http://localhost:8080/api/users -> https://api.github.com/api/users
        //         pathRewrite: {                    // http://localhost:8080/api/users -> https://api.github.com/users
        //             '^/api': ''
        //         },
        //         // 不能使用 localhost:8080 作为请求 GitHub 的主机名
        //         changeOrigin: true
        //     }
        // }
    },
  • webpack dev server不能只能刷新浏览器的问题
  1. webpack5的一个bug,参考:https://blog.csdn.net/xiaolon...
// webpack.config.js中添加配置项
target: "web",
  1. 提取了公用代码,一些配置路径错误等问题;例如:检查optimization、path、publicPath、contentBase等配置项
热更新最佳实践(个人认为):
  • 本地开发最初调试阶段 (dev)=> 使用 webpack-dev-server,只有浏览器内存能看到代码
  • 本地测试或者联调阶段 (dev/pre)=> 使用 webpack + browser-sync,能看到本地代码

    因为开发阶段很多文件没有合并压缩,跟生产环境差异太大。用webpack-dev-server有些东西测不出来。
  • 预上线、生产上线阶段 (sit、prd) => 使用文件合并、压缩过后的完整代码

4.2、模块热加载

webapck-dev-server中 HMR 模块的热更新:

注意事项:

  1. HMR需要新增一些代码,用来手动处理JS模块的热替换、图片模块热替换等(因为不同的业务场景需要处理的数据不同)。
  2. HMR新增代码与业务无关,所以会增加一定工作量;而且写起来比较麻烦,建议结合框架使用完善的HMR方案。
  3. HMR新增代码与业务无关,但是在webpack打包过后其实是删除了的,对生产环境没有影响。

使用场景:

  1. 在页面中有一个编辑器,在编辑器中输入内容然后再修改样式。webapck-dev-server负责刷新整体页面,如果每次修改了样式后页面刷新,编辑器中的内容就会丢失。HMR插件能实现在页面不刷新的情况下,局部模块的更新。
  2. 在开发过程中频繁修改页面某个模块的样式,例如:背景图片。

如何使用:

  1. 命令行参数: webpack-dev-server --hot
  2. 配置文件:
// 1、引入webpack
const webpack = require('webpack')


// 2、plugins里面添加内置插件:
plugins: [
    new webpack.HotModuleReplacementPlugin()
    ...
],

// 3、devServer中添加配置项:
// 注意:在热替换手动处理js热替换,如果报错热替换会启动失败;
// 注意:使用hotOly,热替换失败也不会自动刷新页面。
devServer: {
    // 可能出现的错误1:处理的热替换模块有错误,使用hotOnly配置
    // hot: true,  // 模块热替换,如果热替换失败就自动回到页面自动刷新
    hotOnly: true, // 模块热替换,如果热替换失败不会自动刷新页面便于定位问题
    ...
}

处理JS模块的热替换

  1. webpack中的热替换api:module.hot.accept 用于注册模块处理函数
  2. 示例场景:修改编辑器模块代码后,热更新编辑器模块
// 可能出现的错误2:plugins没有开启配置会报错,需要在热替换代码中对module.hot进行判断
if (module.hot) {
  let lastEditor = editor

  // 1、编辑器的热替换
  // 参数1:模块
  // 参数2:处理函数 => 实现的功能:简单来说就是用js代码更新一下模块内需要更新的内容(重要),例如下面更新了编辑器。
  module.hot.accept('./editor', () => {

    //移除原来编辑器,记录数据
    const value = lastEditor.innerHTML
    document.body.removeChild(lastEditor) 

    //更新原来的数据到新的编辑器
    const newEditor = createEditor()
    newEditor.innerHTML = value
    document.body.appendChild(newEditor)
    lastEditor = newEditor
  })

  // 2、图片的热替换
  module.hot.accept('./better.png', () => {
    img.src = background
    console.log(background)
  })
}
  1. 可能出现的错误:
  2. 可能出现的错误1:处理的热替换模块有错误,使用hotOnly配置
  3. 可能出现的错误2:plugins没有开启配置会报错,需要在热替换代码中对module.hot进行判断

处理图片模块的热替换

  // 2、图片的热替换
  module.hot.accept('./better.png', () => {
    img.src = background
    console.log(background)
  })

VUE + HMR 方案

参考:[自己网上找]()

REACT + HMR 方案

参考:[自己网上找]()

4.3、配置Source Map (便于查看调试代码)

记录代码转换前后的映射关系

Source Map配置方案

webpack中的devTool,按照以下3中规则配置:

  1. eval => 使用eval执行模块代码,没有生成对应的.map文件;只能看到错误对应的文件名称;
  2. source-map => 包含错误的行列信息
  3. cheap => 不包含错误的列信息
  4. module => 不要loader处理源码
  5. inline => 将.map文件以data-url的形式生成放到url中,很占体积

带eval不生成.map文件:

  • eval:构建速度最快,只能定位错误文件是哪个;
  • eval-source-map:错误文件名称 + 错误的行列;
  • cheap-eval-source-map:错误文件名称 + 错误的行;
  • cheap-module-eval-source-map:错误文件名称 + 错误的行 + 代码未被loader处理;

不带eval都生成了.map文件:

  • source-map:错误文件名称 + 错误的行列;
  • cheap-source-map:错误文件名称 + 错误的行 + 代码未被loader处理;
  • hidden-source-map:代码中看不见source-map源码(适合开发第三方工具,需要的时候再使用);
  • nosources-source-map:看不到源码,只能看到错误行列号(生产环境可以保护源码);

带inline不生成.map文件,将其放到url中(不推荐使用inline):

  • inline-source-map:将source-map以data-url方式嵌入到url中,导致代码体积变大;

最佳实践:

  • 开发模式:eval-cheap-module-source-map//webpack5中这几个单词顺序不能变,前提是:

    1、代码每行不超过80字符2、Loader转换过后代码差异大,所以需要看源码3、这种模式启动慢,但是重新打包速度快4、注意:webpack5中对这几个单词顺序有要求,必须是:

    • ^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$
    • eval-cheap-module-source-map => ok
    • cheap-module-eval-source-map => old这种就会报错
  • 生产模式:none、nosources-source-map

    1、这样可以避免暴露源码

4.4、配置路径别名

alias: {
    '@': resolve('src'),
    '@config': resolve('config')
}

4.5、配置涉及到的路径:publicPath (未完待续,待整理)

  • __dirname
  • process.cwd()
  • path.join(__dirname, 'dist')
  • path.resolve(__dirname, 'dist')
  • './dist'
  • '/dist/'
// ...

5. 生产环境配置

5.1 根据环境添加不同的配置

方案1:根据命令行参数判断

如何判断环境: 命令行--env production参数会传递给env;

> webpack --env production

接收参数:

webpack 除了可以直接导出配置;还可以导出一个function,返回配置:

module.exports = config
module.exports = (env, args)=> config
  1. 命令行webpack --env production参数会传递给function的参数env;
  2. 可以在function中对env环境进行判断:
// webpack提供了一个函数
// 参数1:env
// 参数2:args
// 返回值:配置项
module.exports = (env, args) => {
    const config = {
        //...
    }

    // env 接受参数:webpack --env production
    if (env === 'production') {
        config.mode = 'production'
        config.devtool = false
        config.plugins = [
          ...config.plugins,
          new CleanWebpackPlugin(),
          new CopyWebpackPlugin(['public'])
        ]
    }
    
    return config
}
方案2:提取公用配置到文件,将配置独立到三个文件:=> 最好用
  • 依赖:webpack-merge插件
> npm install webpack-merge --save-dev  => 专业用于合并webpack配置项对象的插件
  • 功能:将配置独立到三个文件,prod、dev合并公用配置
> webpack.common.js // 公用配置 
> webpack.prod.js   // 生产环境
> webpack.dev.js    // 开发环境
  • 例如单独配置prd环境的js,合并webpack.common.js中的配置
// 合并配置,比较专业的模块:webpack-merge
// 合并配置,最好不要用Object.assign,它在复制对象时后面的配置会完全覆盖掉前面的配置
const { merge } = require('webpack-merge')

const common = require('./webpack.common.js')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = merge(common, {
    mode: 'production',
    plugins: [
      new CleanWebpackPlugin(),
      new CopyWebpackPlugin(['public'])
    ]
})
方案3:plugin:DefinePlugin,向打包文件中注入环境变量在webpack打包时判断
  • 依赖:无,webpack内置插件:webpack.DefinePlugin
  • 功能:DefinePlugin插件可以向webpack打包文件中注入js代码直接使用,例如:
plugins: [
    new webpack.DefinePlugin({
      // 值要求的是一个代码片段
      TEST_URL: 'https://api.example.com',
      API_BASE_URL: JSON.stringify('https://api.example.com')
    })
  ]

在源码中使用变量,打包到 bundle.js ==>

// console.log(TEST_URL),注意没有引号,不是字符串
console.log(https://api.example.com)

// console.log(API_BASE_URL),所以需要JSON.stringify()
console.log('https://api.example.com')
  • DefinePlugin 配合 process.end.NODE_ENV 使用
  1. process.end.NODE_ENV 获取执行环境
  2. process是在nodejs环境中的全局变量,只能在webpack.config.js中使用;
  3. 想要在源码中使用process判断环境,需要使用插件:DefinePlugin将process变量注入到打包文件。
  4. 以下示例,将环境变量注入到源码boudle.js:process.env.NODE_ENV
new webpack.DefinePlugin({
    PRODUCTION: JSON.stringify(true),
    VERSION: JSON.stringify('0.0.1'),
    API_BASE_URL: JSON.stringify('https://api.example.com')
    'process.env': {
         NODE_ENV: JSON.stringify(process.env.NODE_ENV)
     }
});
  • process.end.NODE_ENV使用时的兼容性问题 ,在npm scripts中添加:

windows/linux兼容:

cross-env NODE_ENV=development

windows:

SET NODE_ENV=development

os x/linux:

export NODE_ENV=development

6. 优化打包性能

6.1 Tree Shaking 功能

基本使用

将未使用的代码处理掉,合并模块等功能;没有依赖项。

  • 生产环境下自动启用
  • 其他环境下开启配置
module.exports = {
  mode: 'none',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js'
  },
  optimization: {
    // 模块只导出被使用的成员
    usedExports: true,
    
    // 压缩输出结果,usedExports开启后会移除未被使用的成员
    // minimize: true,

    // 尽可能合并每一个模块到一个函数中(Scop Hosting)
    concatenateModules: true,
  }
}
Tree Shaking和Babel(未完待续)

据说是用babel时,tree shaking会失效?

  • tree shaking前提是 ESM Modules,由webpack打包的代码必须是用EMS
  • babel preset-env这个插件就是将es6+转换为es5,而且它是将ESM => 转换为CommonJS的方式,导致tree shaking失效(Babel到底是将什么ESM转换为CommonJS,待梳理~~~)
  • 最新版本的Babel自动关闭了 ESM 转换插件,ESM并没有转换;所以tree shaking可以正常工作。
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              // 如果 Babel 加载模块时已经转换了 ESM,则会导致 Tree Shaking 失效;最新版本的Babel自动关闭了 ESM 转换插件
              // ['@babel/preset-env', { modules: 'commonjs' }] // 强制将ESM转换为CommonJS,tree shaking失效
              // ['@babel/preset-env', { modules: false }] // 强制关闭转换插件
              // 也可以使用默认配置,也就是 auto,这样 babel-loader 会自动关闭 ESM 转换
              ['@babel/preset-env', { modules: 'auto' }]
            ]
          }
        }
      }
    ]
Tree Shaking与sideEffects(未完待续,使用场景待研究)

sideEffects使用场景:

如果有定义了很多模块(例如多个组件),但实际上只使用了一个;打包的时候需要删除多余的代码,可以用sideEffects配置。

  • 生产环境下自动启用
  • 开发环境下如下配置

sideEffects配置

webpack.config.js 下的配置

  // webpack.config.js 下的配置
  optimization: {
    sideEffects: true,//打开移除未使用的模块
  }

package.json下的配置:

// package.json的配置和webpack下的配置意义不同,此处只是标识代码没有副作用
"sideEffects": false // 标识代码没有副作用,webpack在打包时会删掉有副作用的代码

6.2 多入口打包 => 解决文件过大的问题

方案1:多个html-webpack-plugin
  • 依赖:
> npm install html-webpack-plugin --save-dev
  • 功能:entry 定义多个入口并使用多个 HtmlWebpackPlugin() 插件;具体参考示例源码;
方案2:使用auto-web-plugin插件按照目录生成html
  • 依赖:
> npm install auto-web-plugin --save-dev
  • 功能:(未完待续)
// ...

6.3 CodeSplit代码分片,按需加载 => 解决文件过大的问题

  • 依赖:无
  • 功能:使用动态加载语句:import('./components/banner.js').then(banner => {})
  • 实践:通常和路由相结合,不需要其他配置

const render = () => {
  const hash = window.location.hash || '#posts'
  const mainElement = document.querySelector('.main')
  mainElement.innerHTML = ''

  if (hash === '#posts') {
    // mainElement.appendChild(posts())
    // /* webpackChunkName: 'components' */为魔法注释,可用将两个模块合并到同一个文件名中
    import(/* webpackChunkName: 'components' */'./posts/posts').then(({ default: posts }) => {
      mainElement.appendChild(posts())
    })
  } else if (hash === '#album') {
    // mainElement.appendChild(album())
    import(/* webpackChunkName: 'components' */''./album/album').then(({ default: album }) => {
      mainElement.appendChild(album())
    })
  }
}

render()

window.addEventListener('hashchange', render)
  • 配合VUE-ROUTER示例:(未完待续)
const routes = [
  {
    path: '/',
    name: 'Index',
    component: Index
  },
  {
    path: '/detail/:id',
    name: 'Detail',
    props: true,
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "detail" */ '../views/Detail.vue')
  }
]

6.4 splitChunks,提取js公共代码

  • 依赖:无,直接在optimization中配置
  • 功能:取代了webpack4中的CommonsChunkPlugin,默认值是按需加载的 chunks;参考:https://webpack.docschina.org...
optimization: {
    splitChunks: {
        // 自动提取所有公共模块到单独 bundle;可选配置有:all,async 和 initial。
        // 设置为 all 可能特别强大,因为这意味着 chunk 可以在异步和非异步 chunk 之间共享
        chunks: 'all'
    }
},

6.5 MiniCssExtractPlugin,提取CSS到单个文件

  • 依赖:
> npm install mini-css-extract-plugin --save-dev             => 将样式存放到单独的css文件中,取代style-loader
> npm install optimize-css-assets-webpack-plugin --save-dev  => 手动打开css压缩代码
> npm install terser-webpack-plugin --save-dev               => 手动打开js压缩代码
  • 功能:
  1. 将css代码存放到单独的css文件中,是否也可以合并css文件?(待研究:https://webpack.docschina.org...
  2. 配合css-loader,不再需要style-loader;
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// ...

  optimization: {
    minimizer: [// 使用了minimizer,webpack认为不再启动自动压缩js代码
      new TerserWebpackPlugin(),            //手动打开js压缩代码
      new OptimizeCssAssetsWebpackPlugin()  //手动打开css压缩代码
    ]
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 'style-loader', // 将样式通过 style 标签注入
          MiniCssExtractPlugin.loader, // 将样式存放到单独的css文件中
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    ...,
    new MiniCssExtractPlugin()
  ]

6.6 ExtractTextPlugin、MiniCssExtractPlugin哪个更好用?

  1. MiniCssExtractPlugin基于 webpack v4 的新特性(模块类型)构建,并且需要 webpack 4 才能正常工作。
  2. 与 extract-text-webpack-plugin 相比,MiniCssExtractPlugin:

    https://webpack.docschina.org...
  3. 异步加载
  4. 没有重复的编译(性能)
  5. 更容易使用
  6. 特别针对 CSS 开发

6.7 文件名hash,解决缓存带来的问题

webpack中有三种hash:

  1. 项目级别:filename: '[name]-[hash].bundle.js' —— 项目中任何改动都会修改hash
  2. Chunk级别:filename: '[name]-[chunkhash].bundle.js' —— 同一路chunk改动会修改hash
  3. 内容级别:filename: '[name]-[contenthash].bundle.js' —— 文件发生变化时才会改变hash

最佳方案: 使用contenthash:8,并且后面可以指定位数:8位。

  output: {
    filename: '[name]-[contenthash:8].bundle.js'
  },
  ...
  plugins: [
    ...,
    new MiniCssExtractPlugin({
      filename: '[name]-[contenthash:8].bundle.css'
    })
  ]

6.8 Happypack:多线程并行打包

webpack4 之后推荐使用 thread-loader;而且 happypack 已经没怎么维护了,和 vue-loader 配合也会有一些配置问题;

6.9 其它提高构建性能的方案

官方建议:https://webpack.docschina.org...

如何对webpack构建性能进行分析:

参考文档:https://juejin.cn/post/691151...

> npm install --save-dev speed-measure-webpack-plugin  => 分析 webpack 打包速度
> npm install --save-dev webpack-bundle-analyzer       => 分析 webpack 打包模块大小

提升构建速度方案(主要的几个):

  1. 通过使用 include 字段减小打包范围
  2. 使用 DllPlugin 为更改不频繁的代码生成单独的打包文件;配置参考:https://webpack.docschina.org...
  3. 多使用分片按需加载功能,SplitChunksPlugin 配置;
  4. 使用 thread-loader,功能类似于happypack;创建多线程打包,配置参考:https://webpack.docschina.org...
1. 请仅在耗时的操作中使用 thread-loader,因为:在 worker 池中运行的 loader 是受到限制的;具体参考文档。
2. 不要使用太多的 worker,因为 Node.js 的 runtime 和 loader 都有启动开销。最小化 worker 和 main process(主进程) 之间的模块传输。进程间通讯(IPC, inter process communication)是非常消耗资源的。
  1. 使用配置项:cache,缓存生成的 webpack 模块和 chunk,来改善构建速度。cache 会在开发 模式被设置成 type: 'memory' 而且在 生产 模式 中被禁用。 参考:https://webpack.docschina.org...
  2. 开发阶段尽量使用 webpack-dev-server 在内存中构建
  3. 配置 devTool 生成映射文件时选择合适的选项
  4. // ...

7. 示例代码

https://gitee.com/ymcdhr/e-de...

8. 参考资料:

https://webpack.docschina.org...https://webpack.docschina.org...

特别鸣谢:拉勾教育前端高薪训练营