一、CDN

内容分发网络(Content Delivery Network 或 Content Distribution Network,缩写:CDN)

  • 是指通过相互连接的网络系统,利用最靠近每个用户的服务器;

  • 更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户;

  • 来提供高性能、可扩展性及低成本的网络内容传递给用户;

在开发中,我们使用 CDN 主要是两种方式:

  • 方式一:打包的所有静态资源,放到 CDN 服务器,用户所有资源都是通过 CDN 服务器加载的;

  • 方式二:一些第三方资源放到 CDN 服务器上;

所有的静态资源都想要放到 CDN 服务器上,需要购买自己的 CDN 服务器;

  • 目前阿里、腾讯、亚马逊、Google 等都可以购买 CDN 服务器;

  • 可以直接修改 publicPath,在打包时添加上自己的 CDN 地址;

第三方库 CDN

一些比较出名的开源框架都会将打包后的源码放到一些比较出名的、免费的 CDN 服务器上:

  • 国际上使用比较多的是 unpkg、JSDelivr、cdnjs;

  • 国内也有一个比较好用的 CDN 是 bootcdn;

在项目中,我们如何去引入这些 CDN 呢?

  • 在打包的时候我们不再需要对类似于 lodash 或者 dayjs 这些库进行打包;

  • 在 html 模块中,我们需要自己加入对应的 CDN 服务器地址;

  • 可以通过 webpack 配置,来排除一些库的打包:

  • 在 html 模块中,加入 CDN 服务器地址:

也就是先排除掉第三方库,然后再打包好后的 index.html 中自己导入 cdn 地址

一个问题

在生产环境中使用 CDN 是可以的,但是在开发环境中,本地启动的服务器就没有必要在设置 cdn 了,所以可以在 index.html 中通过 ejs 的 if 语法来进行判断

二、shimming

shimming 是一个概念,是某一类功能的统称:

  • shimming 翻译过来称之为垫片,相当于给我们的代码填充一些垫片来处理一些问题

  • 比如我们现在依赖一个第三方的库,这个第三方的库本身依赖 lodash,但是默认没有对 lodash 进行导入(认为全局存在 lodash)

  • 那么我们就可以通过 ProvidePlugin 来实现 shimming 的效果;

注意:webpack 并不推荐随意的使用 shimming

  • **Webpack 背后的整个理念是使前端开发更加模块化;
    **
  • 也就是说,需要编写具有封闭性的、不存在隐含依赖(比如全局变量)的彼此隔离的模块;

Shimming 预支全局变量

目前 lodash、dayjs 都使用了 CDN 进行引入,所以相当于在全局是可以使用_和 dayjs 的

假如一个文件中使用了 axios,但是没有对它进行引入,那么下面的代码是会报错的

可以通过使用 ProvidePlugin 来实现 shimming 的效果:

  • ProvidePlugin 能够帮助我们在每个模块中,通过一个变量来获取一个 package;

  • 如果 webpack 看到这个模块,它将在最终的 bundle 中引入这个模块;

  • 另外 ProvidePlugin 是 webpack 默认的一个插件,所以不需要专门导入;

webpack 不建议使用 shimming

三、MiniCssExtractPlugin

MiniCssExtractPlugin 可以帮助我们将 css 提取到一个独立的 css 文件中,该插件需要在 webpack4+才可以使用。

首先,我们需要安装 mini-css-extract-plugin:npm install mini-css-extract-plugin -D

配置 rules 和 plugins:

chunkFilename 用于动态导入的包

这个再生产环境中使用,但是在开发环境中不太需要,所以可以进行判断

1
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
const commonConfig = (isProduction) => {
return {
entry: {
main: "./src/main.js",
index: "./src/index.js"
},
output: {
path: resolveApp("./build"),
filename: "js/[name].bundle.js",
chunkFilename: "js/[name].[hash:6].chunk.js",
publicPath: ""
},
resolve: {
extensions: [".wasm", ".mjs", ".js", ".json", ".jsx", ".ts", ".vue"],
alias: {
"@": resolveApp("./src"),
pages: resolveApp("./src/pages"),
},
},
optimization: {
// 对代码进行压缩相关的操作
minimizer: [
new TerserPlugin({
extractComments: false,
}),
],
// natural: 使用自然数(不推荐),
// named: 使用包所在目录作为name(在开发环境推荐)
// deterministic: 生成id, 针对相同文件生成的id是不变
// chunkIds: "deterministic",
splitChunks: {
// async异步导入
// initial同步导入
// all 异步/同步导入
chunks: "all",
// 最小尺寸: 如果拆分出来一个, 那么拆分出来的这个包的大小最小为minSize
minSize: 20000,
// 将大于maxSize的包, 拆分成不小于minSize的包
maxSize: 20000,
// minChunks表示引入的包, 至少被导入了几次
minChunks: 1,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
filename: "js/[id]_vendors.js",
// name: "vendor-chunks.js",
priority: -10
},
default: {
minChunks: 2,
filename: "common_[id].js",
priority: -20
}
}
},
// true/multiple
// single
// object: name
runtimeChunk: {
name: function(entrypoint) {
return `why-${entrypoint.name}`
}
}
},
module: {
rules: [
{
test: /\.jsx?$/i,
use: "babel-loader",
},
{
test: /\.vue$/i,
use: "vue-loader",
},
{
test: /\.css/i,
// style-lodader -> development
use: [
isProduction ? MiniCssExtractPlugin.loader: "style-loader",
"css-loader"],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./index.html",
}),
new VueLoaderPlugin(),
// 当在代码中遇到某一个变量找不到时, 我们会通过ProvidePlugin, 自动导入对应的库
// new webpack.ProvidePlugin({
// axios: "axios",
// get: ["axios", "get"]
// })
],
};
}

module.exports = function(env) {
const isProduction = env.production;
process.env.NODE_ENV = isProduction ? "production" : "development";

const config = isProduction ? prodConfig : devConfig;
const mergeConfig = merge(commonConfig(isProduction), config);

return mergeConfig;
};

四、Hash、ContentHash、ChunkHash

给打包的文件进行命名的时候,会使用 placeholder,placeholder 中有几个属性比较相似:hash、chunkhash、contenthash

hash 本身是通过 MD4 的散列函数处理后,生成一个 128 位的 hash 值(32 个十六进制);

  • hash 值的生成和整个项目有关系:

  • 比如我们现在有两个入口 index.js 和 main.js;

  • 它们分别会输出到不同的 bundle 文件中,并且在文件名称中我们有使用 hash;

  • 这个时候,如果修改了 index.js 文件中的内容,那么 hash 会发生变化;

  • 那就意味着两个文件的名称都会发生变化;

chunkhash 可以有效的解决上面的问题,它会根据不同的入口进行借来解析来生成 hash 值:

  • 比如我们修改了 index.js,那么 main.js 的 chunkhash 是不会发生改变的;

contenthash 表示生成的文件 hash 名称,只和内容有关系:

  • 比如我们的 index.js,引入了一个 style.css,style.css 有被抽取到一个独立的 css 文件中;

  • 这个 css 文件在命名时,如果我们使用的是 chunkhash;

  • 那么当 index.js 文件的内容发生变化时,css 文件的命名也会发生变化;

  • 这个时候我们可以使用 contenthash;

hash 一变都变 chunkHash 只改变当前入口 contenthash 只改变当前入口的某个文件

五、DLL

DLL 全程是动态链接库(Dynamic Link Library),是为软件在 Windows 中实现共享函数库的一种实现方式;

webpack 中也有内置 DLL 的功能,它指的是我们可以将可以共享,并且不经常改变的代码,抽取成一个共享的库;

这个库在之后编译的过程中,会被引入到其他项目的代码中;

DLL 库的使用分为两步:

  • 第一步:打包一个 DLL 库;

  • 第二步:项目中引入 DLL 库

注意:在升级到 webpack4 之后,React 和 Vue 脚手架都移除了 DLL 库

打包一个 DLL 库

pwebpack 帮助我们内置了一个 DllPlugin 可以帮助我们打包一个 DLL 的库文件;

使用打包的 DLL 库

代码中使用了 react、react-dom,我们有配置 splitChunks 的情况下,他们会进行分包,打包到一个独立的 chunk 中。

但是现在我们有了 dll_react,不再需要单独去打包它们,可以直接去引用 dll_react 即可:

  • 第一步:通过 DllReferencePlugin 插件告知要使用的 DLL 库;

  • 第二步:通过 AddAssetHtmlPlugin 插件,将我们打包的 DLL 库引入到 Html 模块

DLL 了解即可