
# 原理
假设有代码如下:
// index.js
var add= require('add.js').default
console.log(add(1,2))
// add.js
export.default=function(a,b){
  return a+b;
}
webpack会解析依赖树,都引用了哪些包和对应的代码:
{
  "index.js":`
		var add= require('add.js').default
		console.log(add(1,2))
	`,
  'add.js':`
		export.default=function(a,b){
  		return a+b;
		}
	`
}
因为浏览器不认识export,那么需要生成一个export空对象:
var export ={}
并生成一个模拟require的函数,把执行对应文件中的模板字符串代码:
var require=function(file){
	 var export ={}
   // 防止变量污染
	 (function(file){
     eval(file)
   })(file)
 	 return export
}
最后传入依赖树并调用入口文件:
(function(list){
  var require=function(file){
	 var export ={}
   // 防止变量污染
	 (function(file){
     eval(list[file])
   })(file)
 	 return export
	}
  require("index.js")
})({
  "index.js":`
		var add= require('add.js').default
		console.log(add(1,2))
	`,
  'add.js':`
		export.default=function(a,b){
  		return a+b;
		}
	`
})
# 特殊名词
# module,chunk 和 bundle
普通文件属于 module
当我们写的 module 源文件传到 webpack 进行打包时,webpack 会根据文件引用关系生成 chunk 文件,webpack 会对这个 chunk 文件进行一些操作
处理好 chunk 文件后,最后会输出 bundle 文件,这个 bundle 文件包含了经过加载和编译的最终源文件,所以它可以直接在浏览器中运行。
 module,chunk 和 bundle 其实就是同一份逻辑代码在不同转换场景下的取了三个名字引用: webpack 中,module,chunk 和 bundle 的区别是什么? (opens new window)
# 文件指纹
文件指纹就是打包后输出的文件名的后缀
Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会修改
Chunkhash:和 webpack 打包的 chunk 有关,不同的 entry 会生成不同的 chunkhash 值
contenthash:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变
推荐使用 contenthash
文件缓存:
- webpack4之《缓存优化方案》 (opens new window)
 - [手摸手,带你用合理的姿势使用webpack4(下) (opens new window)](https://segmentfault.com/a/1190000015919928)
 
# 资源内联
使用场景:
- 页面初始化需要做的的初始化脚本
 - css 内联避免页面闪动
 - 减少网络请求(小图片或者字体内联)
 
# tree shaking(摇树优化)
- tree shaking 只能作用于 ES6 模块,Babel 的预置默认把任何模块转译成 CommonJS 模块,你可以简单设置 modules: false 来解决此问题。
 - production mode 的情况下默认开启
 - 在具有副作用的代码中 tree-shaking 会失效,可以使用 webpack-deep-scope-plugin 插件来优化,但是如果使用了 babel+代码具有副作用的情况下,这个插件还是会失效
 - webpack5 对 tree shaking 进行了进一步的优化,比如嵌套 tree-shaking,会对嵌套的 export 进行追踪,在生产模式下,默认启动
optimization.innerGraph对 import 和 export 的依赖进行分析,这样会检测出未使用的 export,从而省略更多打包的代码。更多 tree-shaking 优化可以查看sideEffects和usedExports 
# cli(命令行)
- --profile --progress 可以展示百分比进度
 
# entry(入口)
# 单入口
entry 是一个字符串
 module.exports={
    entry:'./path/file.js'
 }
# 多入口
entry 是一个对象
module.exports={
    entry:{
        app:'./src/app.js',
        adminApp:'./src/adminApp.js'
    }
}
# 多页面打包通用方案
动态获取 entry 和设置 html-webpack-plugin 数量
利用 glob.sync
# entry高级用法
通过entry数组写法分离bundle,例如我们想要从应用程序文件中输出 home 页面的 home.js 和 home.css,为 account 页面输出 account.js 和 account.css:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  mode: process.env.NODE_ENV,
  entry: {
    home: ['./home.js', './home.scss'],
    account: ['./account.js', './account.scss'],
  },
  output: {
    filename: '[name].js',
  },
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          // fallback to style-loader in development
          process.env.NODE_ENV !== 'production' ? 'style-loader' : MiniCssExtractPlugin.loader,
          'css-loader',
          'sass-loader',
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
    }),
  ],
};
# output(输出)
# 单入口配置
module.exports={
    output:{
        filename:'bundle.js',
        path:_dirname+'/dist'
    }
}
# 多入口配置
通过占位符确保文件名称的唯一
module.exports = {
  output: {
    filename: "[name].js", // 在webpack5中,此设置是默认的
    path: _dirname + "/dist",
  },
};
# mode
指定当前的构建环境是:production、development 还是 none(什么都不做), 设置 mode 可以使用 webpack 内置的函数,默认值为 production 
# devServer
devServer: {
    inline: true,
    open: true,       // 启动dev服务自动打开页面
    hot: true,      // 热更新,需要webpack-dev-server
    host: "0.0.0.0", // 默认webpack开发服务外部无法访问,设置host后可以通过ip访问
    useLocalIp: true, // 设置webpack使用ip打开页面
    stats: "errors-only", // 统计信息
},
# 统计信息 stats
| Preset | Alternative | Description | 
|---|---|---|
| 'errors-only' | none | 只在发生错误时输出 | 
| minimal | none | 只在发生错误或有新的编译时输出 | 
| none | false | 没有输出 | 
| normal | true | 标准输出 | 
| verbose | none | 全部输出 | 
生产环境配置
module.exports = {
    stats:'errors-only'   // 推荐
}
开发环境配置
devServer: {
    contentBase: './dist',
    hot: true,
    stats: 'errors-only',
},
优化命令行的构建日志,使用friendly-errors-webpack-plugin
# 热更新:webpack-dev-server
WDS 不刷新浏览器
WDS 不输出文件,而是放在内存中
使用的是 HotModuleReplacementPlugin 插件,设置 hot:true 之后会自动引用,不需要在 plugin 中添加
# watch
webpack 开启监听模式有两种方式:
- 启动 webpack 命令时,带上 --watch 参数
 - 再配置 webpack.config.js 中设置 watch:true
 
文件监听的原理分析:轮询判断文件的最后编辑时间是否变化某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等 aggregateTimeout
module.export = {
  // 默认false,
  watch: true,
  // 只有开启监听模式时,watchOptions才有意义
  watchOptions: {
    // 默认为空,忽略文件夹,支持正则,支持数组
    ignored: /node_modules/,
    // 监听到变化发生后会等300ms再去执行,默认300ms
    aggregateTimeout: 300,
    // 轮询间隔,默认每秒检查一次变动
    poll: 1000,
  },
};
# optimization
在 webpack4 中如果配置了minimizer属性,会导致覆盖默认优化配置。
# runtimeChunk
runtime 指的是 webpack 的运行环境(具体作用就是模块解析, 加载) 和 模块信息清单, 模块信息清单在每次有模块变更(hash 变更)时都会变更, 所以我们想把这部分代码单独打包出来, 配合后端缓存策略, 这样就不会因为某个模块的变更导致包含模块信息的模块(通常会被包含在最后一个 bundle 中)缓存失效
# loaders
webpack 开箱即用只支持 JS 和 JSON 两种文件类型,通过 Loaders 去支持其他文件类型并且把它们转化成有效的模块,并且可以添加到依赖图中。
本身是一个函数,接受源文件作为参数,返回转换的结果。 test 对应匹配规则,use 匹配 loader
module.exports = {
  module: {
    rules: [
      {
        test: /\\.css$/,
        use: [
          // [style-loader](/loaders/style-loader)
          { loader: "style-loader" },
          // [css-loader](/loaders/css-loader)
          {
            loader: "css-loader",
            options: {
              modules: true,
            },
          },
          // [sass-loader](/loaders/sass-loader)
          { loader: "sass-loader" },
        ],
      },
    ],
  },
};
使用 loader 的 3 种方式:
- config 文件
 - inline,例:
require('raw'!../file.js)- 内联 loader 的配置,例如
?key=value&foo=bar,或者一个 json 对象,例如?{"key":"value","foo":"bar"} - 每个 loader 后边都要添加
!来进行分隔。每个部分都会相对于当前目录解析。import Styles from 'style-loader!css-loader?modules!./styles.css'; - 使用
!作为内联 import 语句的前缀,将禁用所有已配置的 normal loader(普通 loader) - 使用 
!!前缀,将禁用所有已配置的 loader(preLoader, loader, postLoader) 
 - 内联 loader 的配置,例如
 - CLI
 
loader 执行顺序:
- webpack 配置文件中 loaders 配置项
- use 中的 loader 从下到上地取值/执行
 
 - 代码文件中 request 或者 import 请求中指定的 loaders
 
- webpack 配置文件中 loaders 配置项
 
# url-loader
url-loader可以解析import或者require的文件,大于限制的文件可以转换成base64 URIs。
url-loader 工作分两种情况:1.文件大小小于 limit 参数,url-loader 将会把文件转为 DataURL;2.文件大小大于 limit,url-loader 会调用 file-loader 进行处理,参数也会直接传给 file-loader。因此我们只需要安装 url-loader 即可。
内联图片:
module.exports = {
  module: {
    rules: [
      {
        test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)(\?.*)?$/,
        use: {
          loader: "url-loader",
        },
      },
    ],
  },
};
# style-loader
匹配所有 css 文件,内联到 html 中
# style-resources-loader
https://www.npmjs.com/package/style-resources-loader
配置样式全局变量,例如:variables、mixins、functions等。支持css, sass, scss, less, stylus
module: {
        rules: [{
            test: /\.scss$/,
            use: ['style-loader', 'css-loader', 'sass-loader', {
                loader: 'style-resources-loader',
                options: {
                    patterns: [
                        './path/from/context/to/scss/variables/*.scss',
                        './path/from/context/to/scss/mixins/*.scss',
                    ]
                }
            }]
        }]
    },
# postcss-loader
{
    loader: 'postcss-loader',
    options: {
        plugins: () => [
            require('autoprefixer')({ // 自动添加 css 前缀
                browsers: ['last 2 version', '>1%', 'ios 7']
            })
        ]
    }
# px2rem-loader
需要在 html 文件中内联引入 lib-flexible,一起使用,可以添加 exclude 去掉不想转换的库,也可以添加/no/的语法去设置某一行样式不进行转换
{
    loader: 'px2rem-loader',
    options: {
        remUnit: 75,   // 如果是750的设计稿
        remPrecision: 8
    }
}
# raw-loader
https://webpack.docschina.org/loaders/raw-loader/#root
允许以字符串形式import,有两种使用方式:
- 全局设置:
 
module.exports = {
  module: {
    rules: [
      {
        test: /\.txt$/i,
        use: [
          {
            loader: "raw-loader",
            options: {
              esModule: false,
            },
          },
        ],
      },
    ],
  },
};
- 单独使用:
 
import txt from "raw-loader!./file.txt";
需要注意的是,如果被 import 的文件已经被其他 loader 加载,那么应该添加!!来禁用其他 loader:
import css from "!!raw-loader!./file.txt";
# image-webpack-loader
Imagemin 的优点分析
- 有很多定制选项
 - 可以引入更多第三方优化插件,例如 pngquant
 - 可以处理多种图片格式
 
Imagemin 的压缩原理
pngquant:是一款 PNG 压缩器,通过将图像转换为具有 alpha 通道(通常比 24/32 位 PNG 文件小 60-80%)的更高效的 8 位 PNG 格式,可显著减少文件大小。
pngcrush: 其主要目的是通过尝试不同的压缩级别和 PNG 过滤方法来降低 PNG IDAT 数据流的大小。
optipng:其设计灵感来自于 pngcrush。optipng 可将图像文件重新压缩为更小尺寸,而不会丢失任何信息。 tinypng:也是将 24 位 png 文件转化为更小有索引的 8 位图片,同时所有非必要的 metadata 也会被剥离掉
npm i image-webpack-loader -D
rules: [{
  test: /\.(gif|png|jpe?g|svg)$/i,
  use: [
    'file-loader',
    {
      loader: 'image-webpack-loader',
      options: {
        mozjpeg: {
          progressive: true,
          quality: 65
        },
        // optipng.enabled: false will disable optipng
        optipng: {
          enabled: false,
        },
        pngquant: {
          quality: [0.65, 0.90],
          speed: 4
        },
        gifsicle: {
          interlaced: false,
        },
        // the webp option will enable WEBP
        webp: {
          quality: 75
        }
      }
    },
  ],
}]
# thread-loader
webpack 官方维护插件。每次 webpack 解析一个模块,thread-loader 会将它及它的依赖分配给 worker 线程中。安装:npm install --save-dev thread-loader。注意,目前为止无法与extract-css-chunks-webpack-plugin一起使用。
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve("src"),
        use: [
          {
            loader:"thread-loader",
            options:{
              workers:3 // cpu核数*2-1
            }
          },  // thread-loader要写在loader最前面
          // your expensive loader (e.g babel-loader)
        ]
      }
    ]
  }
}
worker pool中的loader使用上是有限制的,例如无法使用自定义 loader api,无法获取webpack 配置项
# 过时loader
- happypack
 
# Plugins
插件用于 bundle 文件的优化,资源管理和环境变量注入。作用于整个构建过程,本质是一个 js 函数或者 js 类,通过监听 webpack 事件钩子触发,功能万恒后调用 webpack 提供的回调。
module.exports={
    plugins:[new HtmlWebpackPlugin({template:'./src/index.html'})]
}
# html-webpack-plugin
压缩 html 文件,production mode 模式会删除所有注释
# html-inline-css-webpack-plugin
内联 css 插件,和 MiniCssExtractPlugin 一起使用时,应该使用html-inline-css-webpack-plugin
const HTMLInlineCSSWebpackPlugin = require("html-inline-css-webpack-plugin")
  .default;
module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      filename: "bundle.css",
    }),
    new HTMLInlineCSSWebpackPlugin(),
  ],
  module: {
    rules: [
      {
        test: /\.s?css/i,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
};
# html-webpack-inline-source-plugin
功能:内联 js 注意:html-webpack-inline-source-plugin之前的版本有问题,至少使用1.0.0-beta.2
const HtmlWebpackPlugin = require("html-webpack-plugin");
const HtmlWebpackInlineSourcePlugin = require("html-webpack-inline-source-plugin");
module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      inlineSource: ".(js)$",
      template: Path.resolve(__dirname, "../src/index.html"),
    }),
    new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin),
  ],
};
# script-ext-html-webpack-plugin (opens new window)
增强html-webpack-plugin中的script部署选项,比如:async、preload、prefetch、defer、module和自定义属性,还有内联
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
// 注意一定要在HtmlWebpackPlugin之后引用
// inline 的name 和你 runtimeChunk 的 name保持一致
new ScriptExtHtmlWebpackPlugin({
  //`runtime` must same as runtimeChunk name. default is `runtime`
  inline: /runtime\..*\.js$/
});
# html-webpack-externals-plugin
基础库分离(vue、react),将基础包通过 cdn 引入,不打入 bundle 中
# DefinePlugin
注入全局变量
new webpack.DefinePlugin({
  PRODUCTION: JSON.stringify(true),
  VERSION: JSON.stringify("5fa3b9"),
  BROWSER_SUPPORTS_HTML5: true,
  TWO: "1+1",
  "typeof window": JSON.stringify("object"),
});
# terser-webpack-plugin
多进程/多实例:并行压缩
const TerserPlugin=require('terser-webpack-plugin');
module.exports={
    optimization:{
        minimizer:[
            new TerserPlugin({
                parallel:true, // 多进程
                cache:true // 开缓存
            })
        ]
    }
}
# mini-css-extract-plugin
作用与 css 压缩
# css-minimizer-webpack-plugin
https://webpack.docschina.org/plugins/css-minimizer-webpack-plugin/#getting-started
内部使用 cssnano 去优化 css。
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      ...,
      new CssMinimizerPlugin({
        minimizerOptions: {
          preset: [
            "default",
            {
              discardComments: { removeAll: true },
            },
          ],
        },
      }),
    ],
  },
};
# purgecss-webpack-plugin
https://purgecss.com/plugins/webpack.html 简单介绍下purge的tree-shaking原理:
查找HTML中的classes,它不会尝试解析你的 HTML,寻找类属性或者动态执行你的 JavaScript,它只是在整个文件中寻找任何与这个正则表达式匹配的字符串。
这意味着避免 在代码中避免动态字符串拼接创建 classes 这点非常重要,否则 PurgeCSS 不会知道保留这些 classes。
不要使用字符串连接来创建 class 名:
<!-- bad -->
<div :class="text-{{ error ? 'red' : 'green' }}-600"></div>
<!-- good -->
<div :class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>
只要类名完全出现在模板中,PurgeCSS 就不会删除它。
purge配置项中会提供path,来映射所有包含class类名的文件。例如,如果您的项目中有一个 JS 文件可以动态切换 HTML 中的某些类,那么您应该确保将该文件包含在这个列表中。
参考:https://xxholly32.github.io/tailwind-learning/#/doc/controlling-file-size
# speed-measure-webpack-plugin
- 可以查看每个loder和插件的执行耗时
 - 红色字体表示时间过长,黄色还可以,绿色是ok的
 
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasureWebpackPlugin();
module.exports = smp.wrap({   // 用smp.wrap包裹配置文件
})
# friendly-errors-webpack-plugin
plugins:[
    new FriendlyErrorsWebpackPlugin()
],
stats:'errors-only'  // 需要搭配这条命令使用
# clean-webpack-plugin
const CleanWebpackPlugin = require('clean-webpack-plugin');
plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new CleanWebpackPlugin(),
        new FriendlyErrorsWebpackPlugin()
    ].concat(htmlWebpackPlugins)
# 过时的 plugin
- CommonsChunkPlugin(removed in webpack4)
 - optimize-css-assets-webpack-plugin
 - extract-text-webpack-plugin
 
# 常用配置
webpack官方最新配置(文档不一定是最新的):https://github.com/webpack/webpack/blob/master/schemas/WebpackOptions.json
# HTML
只介绍使用的 plugin 和 loader,详情查看具体 plugin 和 loader
# JS
- 压缩:uglifyjs-webpack-plugin
 - 内联:html-webpack-inline-source-plugin
 - 第三方库分离:html-webpack-externals-plugin
 - 公共包抽离:公共脚本分离(例:utils 中的工具)利用 webpack 内置的 optimization.splitChunks (opens new window) 进行公共脚本分离
 
# CSS
- js 中提取:mini-css-extract-plugin
 - 压缩:css-minimizer-webpack-plugin
 - tree shaking: purgecss-webpack-plugin
 - 内联:
 - px2rem: px2rem-loader
 - 将 px 单位转换为视口单位(vw, vh, vmin, vmax) 安装:
npm install postcss-px-to-viewport --save-dev
使用方法:配置postcss.config.js: 
"plugins": {
    "postcss-px-to-viewport": {
    viewportWidth: 750,  //视窗的宽度,对应的是我们设计稿的宽度,一般是750
    viewportHeight: 1334, // 视窗的高度,根据750设备的宽度来指定,一般指定1334,也可以不配置
    unitPrecision: 3,       // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)
    viewportUnit: 'vw',     // 指定需要转换成的视窗单位,建议使用vw
    selectorBlackList: ['.ignore', '.hairlines'],  // 指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名
    minPixelValue: 1,       // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值
    mediaQuery: false       // 允许在媒体查询中转换`px`
    },
  }
- 添加 css 前缀:postcss-loader
 - 全局变量:style-resources-loader
 
# file
- 内联:url-loader
 - 字符串形式导入:raw-loader
 - 图片压缩:image-webpack-loader
 - 清除打包文件:clean-webpack-plugin
 
# 优化打包
# 从何开始?
- 速度分析:使用 speed-measure-webpack-plugin
 - 体积分析:使用 webpack-bundle-analyzer
 
const BundleAnalyzerPlugin=require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports={
    plugin:[
        new BundleAnalyzerPlugin()
    ]
}
构建完成后会在 8888 端口展示
# 打包后代码的优化
scope hoisting
production 模式默认开启
打包后有大量函数闭包包裹代码,导致体积增大,运行代码时创建的函数作用域变多,内存开销变大。 import 会被转换成__webopack_require
通过 scope hoisting 插件可以解决这个问题,webpack mode 为 production 时默认开启,必须是 ES6 语法
plugins:[
    new webpack.optimize.ModuleConcatenationPlugin()
]
# RuntimeChunk
提取runtimeChunk会防止资源缓存失效,又因为runtimeChunk通常很小,不值得做一次单独的请求,所以可以把它内联到html中去,内联使用script-ext-html-webpack-plugin插件
# 固定moduleId和chunkId
webpack 内部维护了一个自增的 id,每个 module 都有一个 id。所以当增加或者删除 module 的时候,id 就会变化,导致其它文件虽然没有变化,但由于 id 被强占,只能自增或者自减,导致整个 id 的顺序都错乱了。虽然我们使用 [chunkhash] 作为输出名,但仍然是不够的。
因为 chunk 内部的每个 module 都有一个 id,webpack 默认使用递增的数字作为 moduleId。
如果引入了一个新文件或删掉一个文件,都可能会导致其它文件的 moduleId 发生改变,
那这样缓存失效了。
这时我们设置:
// webpack4
optimization: {
   moduleIds: 'hash',
},
plugins:[
  // 参考vue-cli的config:https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-plugin-webpack-4/index.js#L92
	new webpack.NamedChunksPlugin(chunk => {
  	if (chunk.name) {
      return chunk.name
    }
    const hash = require('hash-sum')
    const joinedHash = hash(
      Array.from(chunk.modulesIterable, m => m.id).join('_')
    )
    return `chunk-` + joinedHash
	});
]
// webpack5
optimization: {
   moduleIds: 'deterministic',
   chunkIds: 'deterministic',
}
# 动态分割和动态 import
适用场景:
- 抽离相同代码到一个共享块
 - 脚本懒加载,使用初始下载的代码更小(首屏优化)懒加载 js 脚本的方式:
 - conmmonJS:require.ensure
 - ES6:动态 import(目前还没有原生支持,需要 babel 转换)
如何使用动态 import? - 安装 babel 插件
 
npm i @babel/plugin-syntax-dynamic-import --save-dev
- 配置.babelrc 文件
 
    "plugin":["@babel/plugin-syntax-dynamic-import"]
# 选择devtool
https://webpack.docschina.org/configuration/devtool/#devtool 这么多模式,到底使用哪个?
- 开发环境推荐:
eval-cheap-module-source-map - 生产环境推荐:
cheap-module-source-map - 相关解释:
 
- 大部分情况我们调试并不关心列信息,而且就算 sourcemap 没有列,有些浏览器引擎(例如 v8) 也会给出列信息,所以我们使用 cheap 模式可以大幅提高 souremap 生成的效率。
 - 使用 module 可支持 babel 这种预编译工具(在 webpack 里做为 loader 使用)。
 - 使用 eval 方式可大幅提高持续构建效率,参考 webapck devtool 速度对比列表,这对经常需要边改边调的前端开发而言非常重要
 - 直接将 sourceMap 放入打包后的文件,会明显增大文件的大小,不利于静态文件的快速加载;而外联.map 时,.map 文件只会在 F12 开启时进行下载(sourceMap 主要服务于调试),故推荐使用外联.map 的形式。
 
# 构建速度
升级 webpack 和 node 版本:
- 使用 webpack4:v8 带来的优化(for of 替代 forEach、Map 和 Set 替代 Object、includes 替代 indexOf)。默认使用更快的 md4 hash 算法,wepacks AST 可以直接从 loader 传递给 AST,减少解析时间
 - 使用字符串方法替代正则表达式
 
多进程/多实例构建: thread-loader
多进程/多实例并行压缩代码: terser-webppack-plugin
进一步分包-预编译资源模块:
- 使用 html-webpack-externals-plugin 的缺点:每一个基础包都要引入并配置,splitchunk 也会再次解析
 - 思路:将 reat\react-dom\redux\react-redux 基础包和业务基础包打包成一个文件方法:使用 DLLPlugin 进行分包,DllReferencePlugin 对 manifest.json 引用,一般需要单独创建 webpack.dll.js 配置文件
 
const path = require('path');
const webpack = require('webpack');
module.exports = {
    entry: {
        library: [
            'react',
            'react-dom'
        ]
    },
    output: {
        filename: '[name]_[chunkhash].dll.js',
        path: path.join(__dirname, 'build/library'),
        library: '[name]'
    },
    plugins: [
        new webpack.DllPlugin({
            name: '[name]_[hash]',
            path: path.join(__dirname, 'build/library/[name].json')
        })
    ]
};
在 webpack 中引用
module.exports={
    plugins:[
        new webpack.DllReferencePlugin({
            manifest:require('./build/library/manifest.json')
        })
    ]
}
在 webpack4 中性能方面提升不大,但在分包作用中还是有用的
缓存
- 目的:提升二次构建速度
 - 缓存思路:
- babel-loader 开启缓存
 - terser-webpack-plugin 开启缓存
 - 使用 cache-loader 或者 hard-source-webpack-plugin(推荐 hard-source-webpack-plugin,配置更简单)
 
 
缩小构建目标
- 目的:尽可能的少构建模块
 - 比如:babel-loader 不解析 node_modules (各种 exclude 和 include)减少文件搜索范围
- 优化 resolve.modules 配置(减少模块搜索层级)
 - 优化 resolve.mainFields 配置
 - 优化 resolve.extensions 配置
 - 合理使用 alias
 
 
modules.exports={
    resolve:{
        alias:{
            react:path.resolve(__dirname,'./node_modules/react/dist/react.min.js'),
        },
        modules:[path.resolve(__dirname,'node_modules')], // 限定在本项目目录上,不会再向上至全局查找
        extensions:['.js'], // 省略后缀
        mainFields:['main'] // 读取package.json的main字段,入口文件
    }
}
loader配置:思路主要是优化搜索时间、缩小文件搜索范围、减少不必要的编译工作:
module.exports = {
  module: {
    rules: [
      {
        // 如果项目源码中只有 文件,就不要写成/\jsx?$/,以提升正则表达式的性能
        test: /\.js$/,
        // babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
        use: ["babel-loader?cacheDirectory"],
        // 只对项目根目录下 src 目录中的文件采用 babel-loader
        include: path.resolve(__dirname, "src"),
        // 使用resolve.alias把原导入路径映射成一个新的导入路径,减少耗时的递归解析操作
        alias: {
          react: path.resolve(
            __dirname,
            "./node_modules/react/dist/react.min.js"
          ),
        },
        // 让 Webpack 忽略对部分没采用模块化的文件的递归解析处理
        noParse: "/jquery|lodash/",
      },
    ],
  },
};
# 构建体积
- 动态 polyfill,使用 Polyfill Service:
- 原理:识别 User Agent,下发不同的 Polyfill 通过插入
https://polyfill.io/v3/polyfill.min.js来实现动态 polyfill。国内可以使用阿里的服务http://polyfill.alicdn.com/polyfill.min.js, - 使用方法:html中插入
<script src="https://polyfill.io/v3/polyfill.min.js"></script> 
 - 原理:识别 User Agent,下发不同的 Polyfill 通过插入
 
# 更多
# 其他
# 打包组件库和基础库
- 在生成开发版文件和 min 版文件时,配置 mode:'none'来避免打包后文件都为压缩状态的情况
 
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
  mode: "none",
  entry: {
    "large-number": "./src/index.js", // 测试环境代码
    "large-number.min": "./src/index.js", // 线上环境代码
  },
  output: {
    filename: "[name].js", // 在webpack5中,此设置是默认的
    library: "largeNumber",
    libraryTarget: "umd", // 用于多环境引入
    libraryExport: "default", // 去除引用时额外的.default
  },
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        // TerserPlugin插件支持压缩ES6代码
        include: /\.min\.js$/, // 匹配只压缩.min.js文件
      }),
    ],
  },
};
# 冒烟测试
- 判断是否构建成功
 - 检测是否有内容输出:
 
- 是否有 js、css 等静态资源文件
 - 是否有 HTML 文件
 
# 构建配置管理的可选方案
- 通过多个配置文件管理不同环境的构建,webpack --config 参数进行控制
 - 将构建配置设计成一个库
 - 抽成一个工具进行管理,比如:create-react-app
 - 将所有的配置放在一个文件,通过--env 参数控制分支选择
 
# webpack 升级
# to v5 from v4
- cli 命令
- 不再支持
--colors参数 
 - 不再支持
 - 运行时
- webpack 5 不再引入 Node.js 变量的 polyfill,导致 process 未定义
 
 
# 问题
# 如何防止static中的文件被打包?
不要在代码中使用require或者import来引入static目录中的资源文件,应该使用http请求来获取
axios.get('/static/data.json').then(res=>{
	// 获取资源
})