目录
预获取/预加载模块(prefetch/preload module)
提取引导模板(extracting boilerplate)
起步
webpack用于编译javascript模块。
webpack-cli用于在命令中运行webpack。
资源管理
webpack将动态打包所有依赖(创建所谓的以来图)。每个模块都可以明确表述自身的依赖,可以避免打包未使用的模块。
webpack最出色的功能之一是,除了引入javascript,还可以通过loader或内置的Asset Modules引入任何其它类型的文件。
加载css
为了在js模块中import一个css文件,需要使用style-loader(把css插入到dom中)和css-loader(讲css转换成commonJs模块)进行解析。
rules.use中的loader,倒序执行,从后往前的顺序解析执行。第一个被执行的loader将结果(被转换后的资源)传递给下一个loader,依此类推,最后,webpack期望链中的最后的loader返回js。
加载images图像
用webpack5内置的Asset Modules实现。
webpack.config.js:
// ...
rules: [
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
}
]
// ...
加载fonts字体
用webpack5内置的Asset Modules实现。
// ...
rules: [
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
}
]
// ...
加载数据
可以加载的有用资源还有数据,如json文件,CSV,TSV和XML。
[{ test: /\.(csv|tsv)$/i,use: ['csv-loader'] },{ test: /\.(xml)$/i,use: ['xml-loader'] }]
管理输出
假如全部手动管理index.html应用程序复杂或出现文件名使用了hash输出多个bundle时,手动管理维护很困难。所以需要使用HtmlWebpackPlugin(维护index.html文件)来解决这个问题。
设置HtmlWebpackPlugin
webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
// ...
plugins:[
new HtmlWebpackPlugin({ title:"管理输出" })
]
// ...
清理 /dist文件夹
每次打包的时候,webpack会先将dist目录下的文件给删除,然后再将新打包出来的文件放在dist目录下。可以使用output.clean配置项实现这个需求。
manifest
webpack通过manifest,可以追踪所有模块输出bundle之间的映射。
通过WebpackManifestPlugin插件,可以将manifest数据提取为一个json文件以供使用。
开发环境
module.exports = { mode: "development", // ..... }
使用source map
为了方便跟踪error和warning,js提供了source map功能,可以将编译后的代码映射会原始源代码。
webpack-dev-middleware
webpack-dev-middleware是一个封装器,它可以把webpack处理过的文件发送到一个server。
webpack-dev-server内部使用了它,然后它也可以作为一个单独的package来使用,以便根据需求进行更多自定义设置。
代码分离
代码分离是webpack中最引人注目的特性之一。该特性能把代码分离到不同的bundle中,然后可以按需加载或并行加载这个文件。
代码分离可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,会极大印象加载时间。
常用代码分离的三种方法:
1.入口起点:使用entry配置手动分离代码(在entry属性配置多个入口),缺点:如果入口chunk之间包含一些重复的模块,那些重复模块都会被引入到各个bundle中;
2.防止重复:使用Entry dependencies或SplitChunksPlugin去重和分离chunk;
3.动态导入:通过模块的内联函数调用来分离代码;
防止重复
入口依赖(Entry dependencies)配置dependOn option选项,可以在多个chunk之间共享模块。
注:enrty为多个的情况。
运行结果会生成两个公共模块:shared.bundle.js和runtime.bundle.js;
SplitChunksPlugin
SplitChunksPlugin插件可以将公共的依赖模块提取到已有的入口chunk中,或提取到一个新生成的chunk。
module.exports = {
// ...
optimization:{
splitChunks:{
chunks:"all"
}
}
}
动态导入来分离代码
预获取/预加载模块(prefetch/preload module)
eg: <link rel="prefetch/repload" href="login-modal-chunk.js" />
缓存
打包完后会生成一个dist文件夹,将dist目录放在部署的server上,浏览器就能够访问此server网站和资源。但是获取资源比较耗时,所以浏览器使用了缓存技术。可以通过缓存技术来降低网络流量,使网站加载速度更快。
输出文件的文件名(output filename)
使用[contenthash]根据资源内容创建出唯一hash值。当资源内容发生变化时,[contenthash]也会发生变化。
// ....
outputs:{
filename: '[name].[contenthash].js'
},
// ....
提取引导模板(extracting boilerplate)
webpack还提供了一个优化功能,可以使optimization.runtimeChunk选项将runtime代码分为一个单独的chunk。
模板标识符(module identifier)
创建library
// ...
output:{
// ...
library: {
name:"webpackNumbers",
type:"umd"
}
}
// ...
创建自己的npm包:Contributing packages to the registry | npm Docs
构建性能
loader
将loader应用于最少数量的必须模块。
include字段,仅将loader应用在实际需要将其转换的模块。
// ...
rules:[
{
test:/\.js$/,
include:path.resolve(__dorname,"src"),
loader:"babel-loader"
}
]
引导(bootstrap)
每个额外的loader/plugin都有其启动时间。尽量少地使用工具。
解析
以下步骤可提高打包速度:
1.减少resolve.modules、resolve.extensions、resolve.mainFiles、resolve.descriptionFiles中条目数量,因为他们会增加文件系统调用的次数。
2.如果你不使用 symlinks(例如 npm link
或者 yarn link
),可以设置 resolve.symlinks: false;
3.如果你使用自定义 resolve plugin 规则,并且没有指定 context 上下文,可以设置 resolve.cacheWithContext: false
。
dll
使用 DllPlugin
为更改不频繁的代码生成单独的编译结果。这可以提高应用程序的编译速度,尽管它增加了构建过程的复杂度。
小即是快(smaller = faster)
减少编译结果的整体大小,以提高构建性能。尽量保持 chunk 体积小。
- 使用数量更少/体积更小的 library。
- 在多页面应用程序中使用
SplitChunksPlugin
。 - 在多页面应用程序中使用
SplitChunksPlugin
,并开启async
模式。 - 移除未引用代码。
- 只编译你当前正在开发的那些代码。
worker 池(worker pool)
thread-loader
可以将非常消耗资源的 loader 分流给一个 worker pool。
持久化缓存
在 webpack 配置中使用 cache 选项。使用 package.json
中的 "postinstall"
清除缓存目录。
module.exports = { cache: false }
自定义 plugin/loader
对它们进行概要分析,以免在此处引入性能问题。
开发环境 增量编译
使用 webpack 的 watch mode(监听模式)。
开发环境 在内存中编译
下面几个工具通过在内存中(而不是写入磁盘)编译和 serve 资源来提高性能:
webpack-dev-server
webpack-hot-middleware
webpack-dev-middleware
开发环境 避免在生产环境下才会用到的工具
开发环境 最小化 entry chunk
生产环境 Source Maps相当消耗性能
安装
"script": {
"build": "webpack --config webpack.config.js"
}
模块热替换
模块热替换(hot module replacement 或HMR)是webpack提供的最有用功能之一。它允许在运行时更新所有类型的模块,而无需完全刷新。
启动HMR
更新webpack-dev-server配置,然后使用webpack内置的HMR插件。webpack-dev-server v4.0.0开始,热模块替换默认开启的。
// ...
devServer: {
hot: true
}
// ...
HMR加载样式
借助于style-loader,使用模块热替换来加载css实际上极其简单。此 loader 在幕后使用了 module.hot.accept
,在 CSS 依赖模块更新之后,会将其 patch(修补) 到 <style>
标签中。
Tree Shaking
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性,例如 import 和 export。
将文件标记为 side-effect-free(无副作用)
通过 package.json 的 "sideEffects"
属性,来实现这种方式。
{
"name": "your-project",
"sideEffects": false
}
解释 tree shaking 和 sideEffects
将函数调用标记为无副作用
结论
我们学到为了利用 tree shaking 的优势, 你必须...
- 使用 ES2015 模块语法(即
import
和export
)。 - 确保没有编译器将您的 ES2015 模块语法转换为 CommonJS 的(顺带一提,这是现在常用的 @babel/preset-env 的默认行为,详细信息请参阅文档)。
- 在项目的
package.json
文件中,添加"sideEffects"
属性。 - 使用
mode
为"production"
的配置项以启用更多优化项,包括压缩代码与 tree shaking。
生产环境
配置
在开发环境中,我们需要:强大的 source map 和一个有着 live reloading(实时重新加载) 或 hot module replacement(热模块替换) 能力的 localhost server。而生产环境目标则转移至其他方面,关注点在于压缩 bundle、更轻量的 source map、资源优化等,通过这些优化方式改善加载时间。
建议:分别为两个环境编写彼此独立的webpack配置。写一个公共的配置:webpack.common.js,根据环境分别写一个配置文件:webpack.dev.js和webpack.prod.js,最后通过webpack-merge工具来合并处理。
package.json文件:
{
"name":"lowcode",
"start":"webpack serve --open --config webpack.dev.js",
"build":"webpack --config webpack.prod.js"
}
指定mode
process.env.NODE_ENV可获取环境变量。
压缩(Minification)
生产环境下默认使用 TerserPlugin,比较受欢迎的插件:ClosureWebpackPlugin;
源码映射(Source Mapping)
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
});
压缩css
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css",
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
optimization: {
minimizer: [
// For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line
// `...`,
new CssMinimizerPlugin(),
],
},
};
懒加载
懒加载或者按需加载,是一种很好的优化网页或应用的方式。
ECMAScript 模块
ECMAScript 模块(ESM)是在 Web 中使用模块的规范。 所有现代浏览器均支持此功能,同时也是在 Web 中编写模块化代码的推荐方式。
导出export、导入import、将模块标记为ESM;
Shimming 预置依赖
webpack
compiler 能够识别遵循 ES2015 模块语法、CommonJS 或 AMD 规范编写的模块。
TypeScript
用ts-loader进行解析;ts-loader可以解析ts文件和tsx文件;
// ...
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
// ...
如果使用了babel-loader进行代码转换,需要使用@babel/preset-typescript来编译ts文件,使用@babel/plugin-transform-typescript插件来做类型检测。
导入其他资源
想要在 TypeScript 中使用非代码资源(non-code asset),我们需要告诉 TypeScript 推断导入资源的类型。在项目里创建一个 custom.d.ts
文件,这个文件用来表示项目中 TypeScript 的自定义类型声明。
custom.d.ts
declare module '*.svg' {
const content: any;
export default content;
}
通过指定任何以 .svg
结尾的导入(import),将 SVG 声明(declare) 为一个新的模块(module),并将模块的 content
定义为 any
。我们可以通过将类型定义为字符串,来更加显式地将它声明为一个 url。同样的概念适用于其他资源,包括 CSS, SCSS, JSON 等。
Web Workers
从 webpack 5 开始,你可以使用 Web Workers 代替 worker-loader。
渐进式网络应用程序
公共路径
publicPath 配置选项在各种场景中都非常有用。你可以通过它来指定应用程序中所有资源的基础路径。
在运行时设置
webpack暴露了一个名为__webpack_public_path__的全局变量。
__webpack_public_path__ = process.env.ASSET_PATH;
资源模块
资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。
在 webpack 5 之前,通常使用:
- raw-loader 将文件导入为字符串
- url-loader 将文件作为 data URI 内联到 bundle 中
- file-loader 将文件发送到输出目录
资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:
asset/resource
发送一个单独的文件并导出 URL。之前通过使用file-loader
实现。asset/inline
导出一个资源的 data URI。之前通过使用url-loader
实现。asset/source
导出资源的源代码。之前通过使用raw-loader
实现。asset
在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用url-loader
,并且配置资源体积限制实现。
// ...
rules:[
{
test: /\.png/,
type: 'asset/resource'
}
]
// ...
自定义输出文件名
默认情况下,asset/resource
模块以 [hash][ext][query]
文件名发送到输出目录。
output:{
// ...
assetModuleFilename: 'images/[hash][ext][query]'
}
inline 资源(inlining asset)
rules:{
// ...
test: /\.svg/,
type: 'asset/inline'
}
// 使用
import metroMap from './images/metro.svg';
block.style.background = `url(${metroMap})`;
//url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo...vc3ZnPgo=)
所有 .svg
文件都将作为 data URI 注入到 bundle 中。
自定义 data URI 生成器
const svgToMiniDataURI = require('mini-svg-data-uri');
// ...
rules:[
{
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
}
]
// ...
现在,所有 .svg
文件都将通过 mini-svg-data-uri
包进行编码。
source 资源(source asset)
rules:{
test: /\.txt/,
type: 'asset/source',
}
// 使用
import exampleText from './example.txt';
block.textContent = exampleText; // 'Hello world'
所有 .txt
文件将原样注入到 bundle 中。