module.exports = {
entry: './src/index.js', // 打包的入口文件
output: './dist/main.js', // 打包的输出
};
const path = require('path');
module.exports = {
entry: {
app: './src/app.js',
admin: './src/admin.js',
},
output: {、
filename: '[name].[hash].js', //通过占位符确保文件名称的唯一,可选择设置hash
path: path.join(__dirname, 'dist'),
// publicPath用于设置加载静态资源的baseUrl,例如prod模式下指向cdn,dev模式下指向本地服务
publicPath: process.env.NODE_ENV === 'production' ? `//cdn.xxx.com` : '/', //
},
};
Loaders
函数接收文件类型作为参数,返回转换的结果。目前webpack
支持的两种类型分别为JS
和JSON
,其它类型均需转换
module:{
rules:[
{test:/.\(js|jsx|ts|tsx)$/,use:'ts-loader'} // 例如ts使用ts-loader
]
},
Loaders
还可以直接内联到代码中使用:
import 'style-loader!css-loader!less-loader!./style.less';
多个 Loaders
之间执行顺序是和 rules
配置相反的,即从右向左执行
loader
先进后出,对应出栈顺序从右向左
if (matchResourceData === undefined) {
for (const loader of loaders) allLoaders.push(loader);
for (const loader of normalLoaders) allLoaders.push(loader);
} else {
for (const loader of normalLoaders) allLoaders.push(loader);
for (const loader of loaders) allLoaders.push(loader); // 入栈
}
for (const loader of preLoaders) allLoaders.push(loader); // pre loaders入栈
通过配置 enforce
改变执行顺序,enforce
有四个枚举值,其执行顺序是pre
、normal
、inline
、post
module:{
rules:[
{
test:/\.less$/,
loader:'less-loader',
enforce:'pre' // 预处理
},
{
test: /\.less$/,
loader:'css-loader',
enforce:'normal' // 默认是normal
},
{
test: /\.less$/,
loader:'style-loader',
enforce:'post' // 后处理
},
]
},
Plugins
负责优化bundle
文件、资源管理和环境变量注入,webpack
内置了很多 plugin
。例如 DefinePlugin
全局变量注入插件、IgnorePlugin
排除文件插件、ProgressPlugin
打包进度条插件等
plugins: [new HtmlwebpackPlugin({ template: './src/index.html' })];
指定当前的构建环境,有三个选项,分别是:production
、development
和none
,当 mode
是 production
时会启用内置优化插件,比如TreeShaking
、ScopeHoisting
、压缩插件等
module.exports = {
mode: 'production', // 会写入到环境变量NODE_ENV
};
也可以通过 webpack cli
参数设置
webpack --mode=production
1 -> 2 -> A -> B
WebpackCompile
将JS
文件进行编译成Bundle
Bundle
文件运行在Bundle Server
,使得文件可通过localhost://xxx
访问bundle.js
文件给到浏览器1 -> 2 -> 3 -> 4
WebpackCompile
将JS
文件进行编译成Bundle
Bundle
文件运行在HMR Server
HMR Runtime
HMR Runtime
局部更新文件的变化WDS
提供了 bundle server
的能力,不输出文件,而是放在内存中,即生成的 bundle.js
文件可以通过 localhost://xxx
的方式去访问,同时它提供的livereload
能力,使得浏览器能够自动刷新
// package.json
"scripts":{
"dev":"webpack-dev-server --open"
}
HotMoudleReplacementPlugin
插件给 WDS
提供了热更新的能力,源自它拥有局部更新页面能力的HMR Runtime
。一旦磁盘里面的文件修改,HMR Server
就将有修改的js module
信息发送给HMR Runtime
// webpack.dev.js 仅在开发环境使用
module.exports = {
mode: 'development',
plugins: [new webpack.HotModuleReplacementPlugin()],
devServer: {
contentBase: './dist', //服务基础目录
hot: true, //开启热更新
},
};
监听到文件修改时,HotMoudleReplacementPlugin
会生成一个 mainifest
和 update file
,其中 mainifest
描述了发生变化的 modules
,紧接着webpack-dev-server
通过 websocket
通知 client
更新代码,client
使用 jsonp
请求 server
获取更新后的代码
WDM
将 webpack
输出的文件传输给服务器,适用于灵活的定制场景
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
}),
);
app.listen(3000, function () {
console.log('listening on port 3000');
});
文件指纹主要用于版本管理,表现于打包后文件名的后缀,如xxx//xxx_51773db.js
中的51773db
类型 | 含义 |
---|---|
Hash | 和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改 |
Chunkhash | 和 webpack 打包的 chunk 有关,不同的 entry 会生成不同的 chunkhash 值 |
Contenthash | 根据文件内容来定义 hash,文件内容不变,则 contenthash 不变 |
output
的filename
,使用[chunkhash]
filename: '[name][chunkhash:8].js';
MiniCssExtractPlugin
的filename
,使用[contenthash]
new MiniCssExtractPlugin({
filename: `[name][contenthash:8].css`,
});
file-loader
的name
,使用[hash]
rules: [
{
test: /\.(png|svg|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: 'img/[name][hash:8].[ext]',
},
},
],
},
];
// 占位符解释:[name]:文件名称,[ext]:资源后缀名
注意喔:hash是由代码和路径生成的。因此相同的代码在多台机器打包部署 hash 会不同,导致资源加载 404。一般通过一台机器打包,分发部署到不同机器
开发环境开启,线上环境关闭。线上排查问题的时候可以将 source map
上传到错误监控系统
module.exports = {
devtool: 'source-map',
};
类型 | 说明 |
---|---|
cheap-source-map | 没有列号,只有行号,速度快 |
cheap-module-source-map | 优化后的 cheap-source-map,避免 babel 等编译过代码行号对不上 |
eval | 通过内联代码 eval 函数 baseURL 确定代码路径 |
eval-source-map | sourcemap 放在 eval 函数后 |
inline-source-map | 放在打包代码最后 |
利用 mappings
映射表和 names
、sourcesContent
就可以还原出源码字符串
{
"version": 3, // Source Map版本
"file": "out.js", // 输出文件(可选)
"sourceRoot": "", // 源文件根目录(可选)
"sources": ["foo.js", "bar.js"], // 源文件列表
"sourcesContent": [null, null], // 源内容列表(可选,和源文件列表顺序一致)
"names": ["src", "maps", "are", "fun"], // mappings使用的符号名称列表
"mappings": "A,AAAB;;ABCDE;" // 带有编码映射数据的字符串
}
TreeShaking
会将以上视为废弃的代码在uglify
阶段消除
当
mode
设置为production
的情况下,是默认开启的。通过在.babelrc
里设置modules:false
进行取消
TreeShaking
是利用 ES6
模块的特点进行清除
import
只能作为模块顶层的语句出现,且模块名只能是字符串常量import
导入模块是静态加载,其获取的是变量引用,即当模块内部变更时,import
出的变量也会变更。因此 import
不能出现在条件、函数等语句中( export
类似),而 commonjs
中 require
获取的是模块的缓存
import binding
是immutable
的webpack
打包后,会给模块加上一层包裹,import
会被转换成__webpack_require
webpack
打包后是一个匿名闭包,接收的参数 modules
是一个数组,每一项是一个模块初始化函数。通过__webpack_require
加载模块,并返回modules.exports
,
modules
的每个模块成员都是用 __webpack_require__
加载的,installedModules
是加载模块的缓存,如果已经__webpack_require__
加载过无需再次加载。
构建后的代码存在大量的闭包代码,导致运行时创建的函数作用域增多,内存开销大,ScopeHoisting
将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突,从而减少函数声明代码和内存开销
对SEO
友好的服务端渲染SSR
的核心是减少请求,从而减少白屏时间。其实现原理是:服务端通过react-dom/server
的renderToString
方法将React
组件渲染成字符串,返回路由对应的模版。协助的客户端通过打包,生成针对服务端的组件
renderToString
携带有 data-reactid
属性可配合 hydrate
使用,会复用之前节点只进行事件绑定从而优化首次渲染速度。类似的方法还有 renderToStaticMarkup
node.js
中没有 document
和 window
,需通过打包环境进行适配在 react ssr
应用中,读取 document
和 window
可以在 useEffect
或 componentDidMount
中进行,当 nodejs
渲染时就会跳过这些执行,避免报错
isomorphic-fetch
或 axios
替换 fetch
和 xhr
node.js
无法解析 css
,可使用ignore-loader
忽略 css 的解析对于 antd
组件库,在babel-plugin-import
设置 style
为false
isomorphic-style-loader
替换 style-loader
使用打包后的HTML
为模板,服务端获取数据后替换占位符
<body>
<div id="root">
<!--HTML_PLACEHOLDER-->
</div>
<!--INITIAL_DATA_PLACEHOLDER-->
</body>
内置了uglifyjs-webpack-plugin
CommonsChunkPlugin
提取 chunks
中的公共模块减少总体积
使用optimize-css-assets-webpack-plugin
,同时使用cssnano
extract-text-webpack-plugin
将 css
从产物中分离。
html-webpack-plugin
通常用来定义 html
模板,也可以设置压缩 minify
参数(production
模式下自动设置 true
)
使用image-webpack-loader
利用 CleanWebpackPlugin
自动清理 output
指定的输出目录
首屏渲染的样式尽量选择内联或使用 styled-components
。资源内联可减少请求数,可避免首屏页面闪动,可进行相关上报打点,可初始化脚本
url-loader:小图片或字体内联
file-loader:可以解析项目中的 url 引入路径,修改打包后文件引用路径,指向输出的文件。
将基础包通过cdn
,而不压缩进bundle
中
plugins: [
new HtmlWebpackExternalsPlugin({
externals: [
{
module: 'react',
entry: '//11.url.cn/now/lib/15.1.0/react-with-addons.min.js?_bid=3123',
global: 'React',
},
],
}),
];
可将公共脚本、基础包以及页面公共文件分离
splitChunks:{
chunks:'async',// async:异步引入的库进行分离(默认) initial:同步引入的库进行分离 all:所有引入的库进行分离(推荐)
...
cacheGroups:{
// 1、公共脚本分离
vendors:{
test:/[\\/]node_modules[\\/]/,
priority:-10
},
// 2、基础包分离
commons:{
test:/(react|react-dom)/,
name:'vendors',
chunks:'all'
},
// 3、页面公共文件分离
commons:{
name:'commons',
chunks:'all',
minChunks:2
}
}
}
plugins: [
// 使用DLLPlugin进行分包
new webpack.DLLPlugin({
name: '[name]',
path: './build/library/[name].json',
}),
// DllReferencePlugin 对 manifest.json引用
new webpack.DllReferencePlugin({
manifest: require('./build/library/manifest.json'),
}),
];
多进程多实例构建,换句话说就是:每次webpack
解析一个模块,将它及它的依赖分配给worker
线程中,比如HappyPack
、ThreadLoader
babel-loader
、terser-webpack-plugin
cache-loader
、hard-source-webpack-plugin
loader
的 test
,使用 include
来缩小 loader
处理文件范围module.exports = {
module: {
rules: [
{
test: /\.js$/, // 尾部补充$号表示尾部匹配
use: ['babel-loader?cacheDirectory'], // babel-loader 通过 cacheDirectory 选项开启缓存
include: path.resolve(__dirname, 'src'), // 只处理src目录下代码,极大提升编译速度。(如果node_modules下有未编译过的库,这里不建议开启)
},
],
},
};
module.exports = {
resolve: {
modules: [path.resolve(__dirname, 'node_modules')], // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
extensions: ['.js', '.json'], // extensions尽量少,减少文件查找次数
noParse: [/\.min\.js$/], // noParse可以忽略模块的依赖解析,对于min.js文件一般已经打包好了
},
};
webpack-merge
合并配置merge = require('webpack-merge');
module.exports = merge(baseConfig, devConfig);
在package.json
文件的构建统计信息字段添加stats
"scripts":{
"build:stats":"webpack --env production --json > stats.json"
}
利用 speedMeasureWebpackPlugin
分析整个打包总耗时和每个插件和loader
的耗时情况
const speedMeasureWebpackPlugin = require("speed-measure-webpack-plugin")
const smp = new speedMeasureWebpackPlugin()
const webpackConfig = smp.wrap({
plugins:[
new MyPlugin()
...
]
})
利用bundleAnalyzerPlugin
分析依赖的第三方模块文件大小和业务里面的组件代码大小,构建完成后会在 8888 端口展示
const bundleAnalyzerPlugin = require('webpack-bundle-analyzer');
module.exports = {
plugins: [
new bundleAnalyzerPlugin({
analyzerMode: 'server',
analyzerHost: 'localhost',
analyzerPort: 8888, // 端口号
reportFilename: 'report.html',
defaultSizes: 'parsed',
openAnalyzer: true,
generateStatsFile: false, // 是否输出到静态文件
statsFilename: 'stats.json',
statsOptions: null,
logLevel: 'info',
}),
],
};
利用ProgressPlugin
分析编译进度和模块处理细节
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.ProgressPlugin({
activeModules: false,
entries: true,
handler(percentage, message, ...args) {
// 打印实时处理信息
console.info(percentage, message, ...args);
},
modules: true,
modulesCount: 5000,
profile: false,
dependencies: true, // 显示正在进行的依赖项计数消息
dependenciesCount: 10000,
percentBy: null,
}),
],
};
|