# webpack4(二) webpack的高级概念
# Tree Shaking
tree shaking 只支持es的方式引入
tree shaking的目的是按需引入
例如
import {add} from './add';
add(1, 2);
2
3
如果不使用tree shaking,在打包的时候,会把add.js中的其他导出项也打包进来,这是不必要的。
在development模式下配置tree shaking
module.exports = {
//...
optimization: {
usedExports: true//开发环境里使用tree shaking(按需加载import)
},
//...
}
2
3
4
5
6
7
配置好optimization后,还有一个问题,在我们引入css或less文件时,是找不到导出的东西的,这个时候tree shaking就会认为这个css文件没有被引用从而不会将其打包进去。 为了解决这个问题还需要在package.json中配置 sideEffects
{
//...
"sideEffects": [
"*.css",
"*.less"
],
//...
}
2
3
4
5
6
7
8
9
在production模式下,自动会有tree shaking
# development 和 production 模式的区分打包
将webpack.config.js拆分为 webpack.dev.js(开发环境) webpack.prod.js(生产环境) webpack.common.js(公共配置) 安装webpack-merge
npm install webpack-merge --save-dev
webpack.dev.js
const merge = require('webpack-merge');
const webpack = require('webpack');
const commonConfig = require('./webpack.common');
const devConfig = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer:{
contentBase: './dist',
port: 8080,
open: true,// 启动服务打开一个网页
hot: true//热更新
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
optimization: {
usedExports: true//开发环境里使用tree shaking(按需加载import)
},
}
module.exports = merge(devConfig, commonConfig);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
webpack.prod.js
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common');
const prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map',
}
module.exports = merge(prodConfig, commonConfig);
2
3
4
5
6
7
8
9
10
webpack.common.js
const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: {
"main": './src/index.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
},
{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath:'images/',
limit: 1024// 图片大小小于限制的时候直接打包到js里,大于的话生成到images/下
// 其他用法和file-loader基本一样
}
},
}, {
test: /\.less$/,
use: [
//loader执行顺序从下向上
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,// 如果在样式文件里再import进其他的样式文件,则可能不再走下面两个loader,设置importLoaders让他重新再走一遍loader
//modules: true,//css模块化加载
}
},
'less-loader',
'postcss-loader',//自动增加兼容性前缀
],
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}), // 打包后在html中自动插入js
new CleanWebpackPlugin(),// 每次打包之前清除文件
],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '../dist')
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
我们可以把这些webpack的配置文件都放在一个scripts文件夹下 然后重新配置一下package.json 然后就可以通过命令来分别执行不同模式下的webpack配置了
{
//...
"scripts": {
"start": "webpack-dev-server --config ./scripts/webpack.dev.js",
"build": "webpack --config ./scripts/webpack.prod.js"
},
//...
}
2
3
4
5
6
7
8
# Code Splitting 代码分割
先搞个小demo引入一下
import _ from 'lodash'//先引入一个第三方库
// 业务代码
console.log(_.join(['a', 'b', 'c'], '***'))
// 此处省略10万行。。。
console.log(_.join(['a', 'b', 'c'], '***'))
2
3
4
5
6
假如引入的第三方库有1mb,业务代码有1mb,那么打包出来的main.js理论上有2mb,打包的文件会很大而且时间会很长。 而且当我们的业务代码进行调整的时候,会重新进行打包,重新访问我们的页面,又要很多时间
一种解决方案是,我们可以在src下再创建一个lodash.js的文件。然后引入lodash库,并将其挂载在window上。这样我们的业务代码和库就分开了。
import _ from 'lodash'//先引入一个第三方库
window._ = _;
2
同时我们还要修改webpack配置文件。这样,最后打包出来的就是两个文件了。 此时当业务逻辑改变时,页面只需重新加载main.js文件。
module.exports = {
entry: {
"main": './src/index.js',
"lodash": './src/lodash.js'
},
}
2
3
4
5
6
由此我们引出了code splitting。 在webpack中有配置自动为我们做代码分割。
1.同步代码,只需要在webpack.common.js中配置optimization
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'all'// 代码分割
}
},
//...
}
2
3
4
5
6
7
8
9
2.异步代码,无需任何配置,会自动进行代码分割
例如在index.js中异步引入
function getComponent() {
return import('lodash').then(({default: _}) => {
var element = document.createElement('div');
element.innerHTML = _.join(['hello', 'webpack'], '--');
return element;
})
}
getComponent().then(element => {
document.body.appendChild(element);
})
2
3
4
5
6
7
8
9
10
11
还需要先安装 @babel/plugin-syntax-dynamic-import 插件来使用异步引入
npm install @babel/plugin-syntax-dynamic-import --save-dev
在 .babelrc 中添加plugins
{
//...
"plugins": ["dynamic-import-webpack"]
}
2
3
4
打包可以发现,webpack为我们自动进行了代码分割。
# SplitChunksPlugin
webpack中代码分割底层使用了 SplitChunksPlugin 这个插件
我们在用代码分割的时候会发现,webpack为我们打包出来的文件叫做0.bundle.js而不是我们想要的lodash.js。 接下来就要对SplitChunksPlugin进行配置。
首先在index.js中添加魔法注释
function getComponent() {
return import(/* webpackChunkName: "lodash" */'lodash').then(({default: _}) => {
var element = document.createElement('div');
element.innerHTML = _.join(['hello', 'webpack'], '--');
return element;
})
}
getComponent().then(element => {
document.body.appendChild(element);
})
2
3
4
5
6
7
8
9
10
11
此时打包,文件会变为vendors~lodash.bundle.js。 我们还需要继续进行配置
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'all', // 如果选择async,则webpack只对异步引入的文件进行代码分割。选择all后则对同时对同步异步进行分割,在对同步代码进行分割的时候,还需要再对cacheGroups进行配置。
minSize: 30000, // 当模块大于30000时才进行代码分割,小于的话就不进行分割
maxSize: 0, // 通常不用
minChunks: 1, // 当文件被用 1 次后进行代码分割
maxAsyncRequests: 5, // 同时被加载到模块数最多是 5 个
maxInitialRequests: 3, // 入口文件最多做三个代码分割
automaticNameDelimiter: '~',// 通过~做连接符
automaticNameMaxLength: 30,
name: true,
cacheGroups: { //缓存组。打包的时候先把所有遇见的模块放在缓存组中,遇到node_modules中的模块放在vendors组中,遇到其他模块放在default组中,在最后一起进行打包
vendors: {
test: /[\\/]node_modules[\\/]/,// 当chunks选择all时,执行同步代码分割的话,会先走到这里,对其进行检测是否是node_modules下的库,如果是的话,就将其打包到vendors里,这时我们可以看到打包出来的文件是vendors~lodash.bundle.js
priority: -10 //值越大优先级越高
},
default: {
// 如果引入的是自定义的文件,而不是node_modules下的文件,此时将会进入default组内
minChunks: 2,
priority: -20,
reuseExistingChunk: true // 如果打包的时候发现一个模块已经被引用打包过了,那么就不再对其进行打包
}
}
}
},
//...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Lazy Loading 懒加载
# 懒加载
在index.js中
function getComponent() {
return import(/* webpackChunkName: "lodash" */'lodash').then(({default: _}) => {
var element = document.createElement('div');
element.innerHTML = _.join(['hello', 'webpack'], '--');
return element;
})
}
document.addEventListener('click', () => {
getComponent().then(element => {
document.body.appendChild(element);
})
})
2
3
4
5
6
7
8
9
10
11
12
13
当刷新页面时,只加载了html和main.js,当点击之后才加载lodash.js 这用的就是懒加载
懒加载就是当需要用的时候才会去加载对应的文件,以此来提高运行速度
# chunks
当使用代码分割时,打包过后会被拆分成多个文件,每个被拆分的文件都是一个chunk
# 打包分析 preloading prefetching
# 打包分析
对我们打包生成的文件进行分析
首先先生成打包描述文件
在package.json中添加 --profile --json > stats.json 从而在打包的过程中生成一个stats.json文件
{
//...
"scripts": {
"bundle": "webpack --profile --json > stats.json --config ./scripts/webpack.dev.js",
},
//...
}
2
3
4
5
6
7
然后在刚才的网站中点击analyse会跳转到网站 分析的地方
# preloading prefetching
为了提高页面性能,我们通常使用缓存来进行提高,其实我们更应该关注的是代码利用率。 在一些交互代码中,webpack建议我们使用异步加载来提高性能 可以在控制台中使用coverage来查看
例如,当点击时加载
function getComponent() {
var element = document.createElement('div');
element.innerHTML = 'Hello webpack';
document.body.appendChild(element);
}
2
3
4
5
我们可以进行如下优化
在click.js中
function handlclick() {
var element = document.createElement('div');
element.innerHTML = 'Hello webpack';
document.body.appendChild(element);
}
export default handlclick;
2
3
4
5
6
在index.js中
document.addEventListener('click', () => {
import('./click.js').then(({default: _}) => {
_();
})
})
2
3
4
5
这样能够提高我们的代码使用率
那么如何能够更好的提高性能呢 当然是在核心代码加载完后,趁着空余的带宽将暂时不用的文件代码加载进来 这就是preloading 和 prefetching 要做的事情
在index.js中添加魔法注释,使用 prefetching
document.addEventListener('click', () => {
import(/* webpackPrefetching: true */'./click.js').then(({default: _}) => {
_();
})
})
2
3
4
5
# CSS文件的代码分割
首先安装 mini-css-extract-plugin
npm install mini-css-extract-plugin --save-dev
mini-css-extract-plugin插件只对生产环境有效 所以我们需要更改webpack.prod.js文件 引入 mini-css-extract-plugin 插件 然后将module的关于样式的部分的loader进行替换,使用 MiniCssExtractPlugin.loader 同时也要把公共的部分的loader剔除掉以免发生报错
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')// 新增;
const prodConfig = {
//...
module: {
rules: [
{
test: /\.less$/,
use: [
//loader执行顺序从下向上
MiniCssExtractPlugin.loader,// 使用css代码分割
{
loader: 'css-loader',
options: {
importLoaders: 2,// 如果在样式文件里再import进其他的样式文件,则可能不再走下面两个loader,设置importLoaders让他重新再走一遍loader
//modules: true,//css模块化加载
}
},
'less-loader',
'postcss-loader',//自动增加兼容性前缀
],
}, {
test: /\.css$/,
use: [
//loader执行顺序从下向上
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 1,// 如果在样式文件里再import进其他的样式文件,则可能不再走下面两个loader,设置importLoaders让他重新再走一遍loader
//modules: true,//css模块化加载
}
},
'postcss-loader',//自动增加兼容性前缀
],
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
})
],
//...
}
module.exports = merge(prodConfig, commonConfig);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
接下来我们想让打包好的css文件直接进行代码压缩
npm install --save-dev optimize-css-assets-webpack-plugin
引入插件
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');// 新增;
const prodConfig = {
//...
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})],
},
//...
}
module.exports = merge(prodConfig, commonConfig);
2
3
4
5
6
7
8
9
10
11
12
13
# webpack与浏览器缓存(caching)
当我们的代码放在服务器上供用户使用时,用户再次刷新页面时,会使用缓存进行读取,可以提高速度。但是如果我们的代码发生变化,但是打包出来的名字还是原来的,这时用户还是会读取缓存里的内容。 这样不好。 做一下改善 在webpack.prod.js中给output加入contenthash占位符 同时修改dev和common文件
const prodConfig = {
//...
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js'
},
}
2
3
4
5
6
7
这样当文件没有改变时,打包出来的文件名的hash值不会变,即用户可以使用缓存来进行访问。当文件内容发生改变,hash值也会进行变化,用户就会重新加载文件访问。以此来提高效率。
# shimming 垫片
当我们在引入其他人的文件或者库时,例如里面用了jquery,但是在他的文件里面有没有引入jquery(import $ from jquery),这个时候会报错。即便我们在外面引用这个文件的地方引入了$ 依然会报错。 此时就用到了shimming 我们在webpack.common.js中引入webpack,然后引用插件 new webpack.ProvidePlugin({}) 我们希望在遇到$时自动去加载jquery,而不需要我们手动去加载这个库
var webpack = require(webpack);
module.exports = {
//...
plugins: [
new webpack.ProvidePlugin({
$: 'jquery'// 垫片,在遇到"$"时,就会自动加载jquery库
})
],
//...
}
2
3
4
5
6
7
8
9
10
我们在模块中输出this,发现this并没有指向window,如果我们想让this指向window我们需要再安装一个loader
npm install imports-loader --save-dev
然后修改webpack.common.js 这样就可以指向window了
module.exports = {
//...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{loader: "babel-loader"},
{loader: "imports-loader?this=>window"},
]
},
]
}
//...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16