SourceMap
SourceMap
是一个映射关系,调试工具可以通过SourceMap
映射代码,让我们在源代码上断点调试。但是使用SourceMap
会让编译速度变慢,可到官网查看详细配置。
// webpack.config.js 相关代码片段
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: './src/index.js',
...
};
最佳实践:
- development 环境:
cheap-module-eval-source-map
- production 环境:
cheap-module-source-map
DevServer
DevServer
会启动一个 HTTP 服务器用于服务网页请求,同时会帮助启动 Webpack,并接收 Webpack 发出的文件变更信号,通过 WebSocket 协议自动刷新网页做到实时预览。
# 安装 DevServer
$ npm i -D webpack-dev-server
# 启动 DevServer
$ webpack-dev-server
实现修改代码时自动打包的两种方法:
第一种:修改 package.json,代码修改后能自动打包,不能自动刷新浏览器页面。
"scripts": {
"dev": "webpack --watch",
},
第二种:使用 DevServer,能自动刷新页面。
// webpack.config.js 添加下面配置
devServer: {
contentBase: './dist',
open: true, // 自动打开浏览器
proxy: {
'/api': 'http://localhost:3000'
}
}
// package.json
"scripts": {
"start": "webpack-dev-server"
},
Hot Module Replacement 模块热更新
此功能会在应用程序运行过程中,替换、添加或删除模块,而无需重新加载整个页面。例如改变样式代码时不会刷新页面,方便调试 CSS。
在webpack 5
中HMR
已自动支持,无需配置。
// webpack.config.js 相关代码片段
const webpack = require('webpack'); // 1.要引入 webpack 插件
devServer: {
contentBase: './dist',
open: true, // 自动打开浏览器
hot: true, // 2.开启热模块更新
hotOnly: true,// 即使热模块功能没生效,也不让浏览器自动刷新
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader', // 可为 transform 等样式自动添加厂商前缀
], // 执行顺序从右到左
}
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(), // 3.使用插件
],
使用 Babel 处理 ES6 语法
Babel
官网:https://babeljs.io
Tree Shaking
当项目达到一定体积时,像 JS 代码会分成模块管理,这样最终打包时可能会导入实际上未使用的代码。Tree Shaking
是一种通过消除最终文件中未使用的代码来优化体积的方法。
只有模块是通过static
方式引用时,Tree Shaking
才会起作用。CommonJS
模块的dynamic
性质意味着无法应用Tree Shaking
,因为在实际运行代码之前无法确定需要哪些模块。Tree Shaking
只支持ES Module
,因为这是static
的。
例如,有一个实现基数数学运算的JS文件math.js
:
// math.js
export function add (a, b) {
console.log(a + b);
}
export function minus (a, b) {
console.log(a - b);
}
在index.js
里,我们通过如下方式调用该文件:
import { add } from "./math";
add(1, 2);
使用webpack
打包后,发现即使仅导入并使用了add()
功能,我们也会看到文件中的所有功能都包含在最终输出中。使用Tree Shaking
就可以将未使用的模块从最终的打包文件中删除,甚至可删除从未访问过的导入对象中的特定属性。
// webpack.config.js 相关代码片段
mode: "development",
plugins: [...],
optimization: { // development 模式需配置,production 模式不用配置
usedExports: true
},
// package.json 相关代码片段
{
...,
"sideEffects": [ // 将某些文件标记为副作用
"./src/polyfill.js"
],
...,
}
Development 和 Production 模式的区分打包
1、将webpack.config.js
替换为webpack.common.js
、webpack.dev.js
和webpack.prod.js
三个文件。
webpack.dev.js
:开发环境使用。webpack.prod.js
:生产环境使用。webpack.common.js
:存放以上两个的公共代码部分。
$ npm install webpack-merge -D
// webpack.dev.js 需要引入 webpack-merge 和 webpack.common.js,webpack.prod.js 也类似
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const devConfig = {
mode: 'development',
...
};
module.exports = merge(commonConfig, devConfig);
2、在package.json
中进行下面配置。
// package.json
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js",
"build": "webpack --config webpack.prod.js",
},
Webpack 和 Code Splitting
项目中使用到lodash
这种第三方库时,会和业务逻辑代码一起打包到一个 JS 文件,这样会带来两个问题:(1)打包文件会很大,加载时间会长;(2)修改业务逻辑代码,重新访问页面时加载的内容也很多。
因此需要使用代码分割,将lodash
单独打包到一个 JS 文件。
通过配置webpack
实现打包时自动代码分割
// webpack.config.js 相关代码片段
optimization: {
splitChunks: {
chunks: 'all' // all:对同步代码和异步代码做分割,async:只对异步代码做分割
}
}
使用 Preloading,Prefetching 优化加载速度
webpack
中的SplitChunkPlugin
的默认配置是chunks: async
,只有当异步时才会进行代码分割。因为当我们在引入lodash
、jquery
库时,如果配置chunks: all
,那么肯定会帮助我们把lodash
、jquery
单独拆分,但是这样做并不能提升首页的代码性能,原因是在浏览器第一次加载时,还是需要我们加载lodash
、jquery
库,只有当我们的代码第二次加载时,浏览器才会从缓存中去找,提高第二次页面的加载访问速度。webpack
配置chunks: async
是想让我们第一次加载时,访问速度就是最快。
例如,有一个未优化的代码:
// index.js
document.addEventListener('click', () => {
const div = document.createElement('div');
div.innerHTML = 'hello webpack';
document.body.appendChild(div);
});
打包运行后,在浏览器控制台中按Ctrl + Shift + P
,然后在弹出的对话框中输入coverage
点击回车,然后再点击下面的小黑原点变为红原点,刷新页面会看到如下图所示的信息:
说明当前页面加载的文件代码利用率为74.6%
。实际上点击事件里的代码没必要在加载页面时也一起加载,可以将它们改为异步加载,这样再打包运行利用率就会提升到75%
。
// click.js
function handleClick () {
const div = document.createElement('div');
div.innerHTML = 'hello webpack';
document.body.appendChild(div);
}
export default handleClick;
// index.js
document.addEventListener('click', () => {
import('./click.js').then(({default: func}) => {
func();
})
});
这时如果click.js
文件很大,那加载的时间也会很长,可以考虑在加载完页面网络空闲的时候先把这些文件加载进来,可以使用Prefetching
。
// index.js
// 在异步加载的文件前面加上 /* webpackPrefetch: true */ 即可。
document.addEventListener('click', () => {
import(/* webpackPrefetch: true */'./click.js').then(({default: func}) => {
func();
})
});
Preloading
和Prefetching
的区别:Preloading
是和核心代码文件一起去加载,Prefetching
是在核心代码加载完成之后的空闲时间再去加载。
CSS 文件的代码分割
希望在打包生成代码的时候,如果引入了CSS
文件,那么把CSS
文件单独打包,而不是直接引入到JS
文件里面,可以使用一个插件来实现。
npm install --save-dev mini-css-extract-plugin
// 在 webpack.prod.js 使用
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map',
module: {
rules: [
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 2,
}
},
'sass-loader',
'postcss-loader',
],
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
],
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].chunk.css'
})
]
}
module.exports = merge(commonConfig, prodConfig);