webpack5
# webpack基础
webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。在 webpack 看来, 前端的所有资源文件(js/json/css/img/less/...)都会作为模块处理。它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。
# 为什么需要webpack
在打包工具出现之前,在浏览器中运行 JavaScript 有两种方法:
第一种方案
<!-- 引入外部的 JavaScript 文件 --> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.core.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/twitterbootstrap/5.0.2/js/bootstrap.min.js"></script> <!-- 引入我自己的 JavaScript 文件 --> <script src="./scripts/common.js"></script> <script src="./scripts/user.js"></script> <script src="./scripts/authentication.js"></script> <script src="./scripts/product.js"></script> <script src="./scripts/inventory.js"></script> <script src="./scripts/payment.js"></script> <script src="./scripts/checkout.js"></script> <script src="./scripts/shipping.js"></script>
1
2
3
4
5
6
7
8
9
10
11
12
13此解决方案很难扩展,因为加载太多脚本会导致网络瓶颈。同时如果你不小心更改了JavaScript文件的加载顺序,这个项目可能要崩溃。
第二种方案
把所有js文件放到一个文件里面
<script src="./scripts/bundle.33520ba89e.js"></script>
1这种方式解决了方式一的问题,但会导致作用域、文件大小、可读性和可维护性方面的问题。
# 如何解决作用域问题
早先前,我们使用 Grunt 和 Gulp 两个工具来管理我们项目的资源
这两个工具称为任务执行器,它们将所有项目文件拼接在一起。利用了 立即调用函数表达式(IIFE) - Immediately invoked function expressions
, 解决了大型项目的作用域问题;当脚本文件被封装在 IIFE 内部时,你可以安全地拼接或安全地组合所有文件,而不必担心作用域冲突。
IIFE
当函数变成立即执行的函数表达式时,表达式中的变量不能从外部访问。
(function() { var msg = 'msg' } )() console.log(msg) // caught ReferenceError: msg is not defined
1
2
3
4
5将 IIFE 分配给一个变量,不是存储 IIFE 本身,而是存储 IIFE 执行后返回的结果。
const result = (function() { var msg = 'msg'; return msg } )() undefined console.log(result) // msg
1
2
3
4
5
6
Grunt , Gulp 解决了作用域问题。但是,修改一个文件意味着必须重新构建整个文件。拼接可以做到很容易地跨文件重用脚本,却使构建结果的优化变得更加困难。如何判断代码是否实际被使用?
即使你只用到 lodash 中的某个函数,也必须在构建结果中加入整个库,然后将它们压缩在一起。大规模地实现延迟加载代码块及无用代码的去除,需要开发人员手动地进行大量工作。
# 如何解决代码拆分问题
参开文章:前端模块化——彻底搞懂AMD、CMD、UMD、ESM和CommonJS (opens new window)
Node.js 是一个 JavaScript 运行时,可以在浏览器环境之外的计算机和服务器中使用。webpack 运行在 Node.js 中。
CommonJS 问世并引入了 require 机制,它允许你在当前文件中加载和使用某个模块。导入需要的每个模块,这一开箱即用的功能,帮助我们解决了代码拆分的问题。
但 CommonJS 没有浏览器支持。没有 live binding(实时绑定)。循环引用存在问题。同步执行的模块解析加载器速度很慢。虽然 CommonJS 是 Node.js 项目的绝佳解决方案,但浏览器不支持模块
# 打包构建工具
Webpack
Webpack 为处理资源管理和分割代码而生,可以包含任何类型的文件。灵活,插件多。
Parcel
Parcel 是 0 配置工具, 用户一般无需再做其他配置即可开箱即用。
Rollup
Rollup 用标准化的格式(es6)来写代码,通过减少死代码尽可能地缩小包体积。一般只用来打包JS。
总结
构建一个简单的应用并让它快速运行起来?使用 Parcel。
构建一个类库只需要导入很少第三方库?使用 Rollup。
构建一个复杂的应用,需要集成很多第三方库?需要代码分拆,使用静态资源文件,
还有 CommonJS 依赖?使用 webpack。
Vite
# webpack初体验
# 安装 Webpack
在安装webpack之前,我们要确保安装了node.js。
node -v
安装webpack
npm install --save-dev webpack
# 或指定版本
npm install --save-dev webpack@<version>
2
3
是否使用 --save-dev 取决于你的应用场景。假设你仅使用 webpack 进行构建操作,那么建议你在安装时使用 --save-dev 选项,因为可能你不需要在生产环境上使用 webpack。如果需要应用于生产环境,请忽略 --save-dev 选项。
如果你使用 webpack v4+ 版本,并且想要在命令行中调用 webpack ,你还需要安装CLI。
npm install --save-dev webpack-cli
全局安装(不推荐)
npm install --global webpack
不推荐 全局安装 webpack。这会将你项目中的 webpack 锁定到指定版本,并且在使用不同的 webpack 版本的项目中, 可能会导致构建失败。
# 建项目
npm init
npm install webpack webpack-cli
2
src/hello.js
function Hello() {
console.log('hello webpack')
}
export default Hello
2
3
4
5
src/index.js
import Hello from './hello'
Hello()
2
3
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>index.html</h1>
<script src="./src/index.js"></script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
运行webpack
命令:
npx webpack
PS D:\@面面试试\前前端端\webpack5\webpack01基基础础> npx webpack
npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.
asset main.js 52 bytes [emitted] [minimized] (name: main)
orphan modules 79 bytes [orphan] 1 module
./src/index.js + 1 modules 117 bytes [built] [code generated]
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
webpack 5.88.1 compiled with 1 warning in 244 ms
2
3
4
5
6
7
8
9
10
11
12
在这里,我们在没有任何配置的情况下运行 Webpack(通常你会为 Webpack 提供一个配置文件,现在,自Webpack4 开始,可以使用默认配置来打包文件了)。
这里还有一个警告:“mode” 选项尚未设置。
我们可以看到webpack为我们创建了一个dist目录,并且生成了main.js文件
这个文件是怎么生成出来的呢?使用命令
npx webpack --stats detailed
PS D:\@面面试试\前前端端\webpack5\webpack01基基础础> npx webpack --stats detailed
npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.
PublicPath: auto
asset main.js 52 bytes {179} [compared for emit] [minimized] (name: main)
Entrypoint main 52 bytes = main.js
chunk {179} (runtime: main) main.js (main) 117 bytes [entry] [rendered]
> ./src main
orphan modules 79 bytes [orphan] 1 module
./src/index.js + 1 modules [271] 117 bytes {179} [depth 0] [built] [code generated]
[no exports]
[no exports used]
......
2
3
4
5
6
7
8
9
10
11
12
# 自定义webpack配置
实际上, webpack-cli 给我们提供了丰富的终端命令行指令,可以通过 webpack --help 来查看
npx webpack --help
npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.
Usage: webpack [entries...] [options]
Alternative usage to run commands: webpack [command] [options]
The build tool for modern web applications.
Options:
-c, --config <pathToConfigFile...> Provide path to one or more webpack configuration files to process, e.g. "./webpack.config.js".
--config-name <name...> Name(s) of particular configuration(s) to use if configuration file exports an array of multiple configurations.
-m, --merge Merge two or more configurations using 'webpack-merge'.
--disable-interpret Disable interpret for loading the config file.
--env <value...> Environment variables passed to the configuration when it is a function, e.g. "myvar" or "myvar=myval".
--node-env <value> Sets process.env.NODE_ENV to the specified value.
--analyze It invokes webpack-bundle-analyzer plugin to get bundle information.
--progress [value] Print compilation progress during build.
-j, --json [pathToJsonFile] Prints result as JSON or store it in a file.
--fail-on-warnings Stop webpack-cli process with non-zero exit code on warnings from webpack.
-e, --extends <value...> Path to the configuration to be extended (only works when using webpack-cli).
-d, --devtool <value> A developer tool to enhance debugging (false | eval |
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map).
--no-devtool Negative 'devtool' option.
--entry <value...> A module that is loaded upon startup. Only the last one is exported.
--extends <value...> Path to the configuration to be extended (only works when using webpack-cli).
--mode <value> Enable production optimizations or development hints.
--name <value> Name of the configuration. Used when loading multiple configurations.
-o, --output-path <value> The output directory as **absolute path** (required).
--stats [value] Stats options object or preset name.
--no-stats Negative 'stats' option.
-t, --target <value...> Environment to build for. Environment to build for. An array of environments to build for all of them when
possible.
--no-target Negative 'target' option.
-w, --watch Enter watch mode, which rebuilds on file change.
--no-watch Negative 'watch' option.
--watch-options-stdin Stop watching when stdin stream has ended.
--no-watch-options-stdin Negative 'watch-options-stdin' option.
Global options:
--color Enable colors on console.
--no-color Disable colors on console.
-v, --version Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands.
-h, --help [verbose] Display help for commands and options.
Commands:
build|bundle|b [entries...] [options] Run webpack (default command, can be omitted).
configtest|t [config-path] Validate a webpack configuration.
help|h [command] [option] Display help for commands and options.
info|i [options] Outputs information about your system.
serve|server|s [entries...] Run the webpack dev server and watch for source file changes while serving. To see all available options you need
to install 'webpack', 'webpack-dev-server'.
version|v [options] Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands.
watch|w [entries...] [options] Run webpack and watch for files changes.
To see list of all supported commands and options run 'webpack --help=verbose'.
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
虽然通过命令可以附加参数来启动,但是这样太麻烦,每次启动都需要加参数
webpack.config.js
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist')
},
mode: 'none'
}
2
3
4
5
6
7
8
9
10
index.html 引入改为 ./dist/bundle.js
# 自动引入资源
到目前为止,我们都是在 index.html 文件中手动引入所有资源,然而随着应用程序增长,如果继续手动管理 index.html 文件,就会变得困难起来。
# 插件
插件是webpack的核心功能。插件可以用于执行一些特定的任务,包括:打包优化,资源管理,注入环境变量等。Webpack自身也是构建于你在webpack配置中用到的相同的插件系统之上!
想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建一个插件实例。
Webpack 提供很多开箱即用的 插件 (opens new window)。
# HtmlWebpackPlugin
npm install --save-dev html-webpack-plugin
webpack.config.js
const path = require('path')
`const HtmlWebpackPlugin = require('html-webpack-plugin')`
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist')
},
` plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin()
],`
mode: 'none'
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
执行打包命令 npx webpack
打包后,我们发现这个 dist/index.html 似乎与先前的 index.html 并没有关系, HtmlWebpackPlugin 会默认生成它自己的 index.html 文件,并且所有的bundle(bundle.js) 会自动添加到 html 中。
这是生成后的dist目录下的index.html文件,并不是我们自定义的
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Webpack App</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script defer src="bundle.js"></script>
</head>
<body>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
使用自定义的html
自定义的html中不需要引入打包生成后的dist/bundle.js。插件会自己帮我们引入
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist')
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
` template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true`
})
],
mode: 'none'
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
这次打包应用到了我们的模板文件 index.html , 并且生成了新的文件 app.html , 文件里自动引用的 bundle.js 也从 <header>
迁移到了 <body>
里。
但是之前的dist/index.html并没有被清理
# 清理dist
我们发现 dist/index.html 仍旧存在,这个文件是上次生成的残留文件,已经没有用了。可见,webpack 将生成文件并放置在 /dist 文件夹中,但是它不会追踪哪些文件是实际在项目中用到的。通常比较推荐的做法是,在每次构建前清理 /dist 文件夹,这样只会生成用到的文件。让我们使用 output.clean 配置项实现这个需求。
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
` // 打包前清理 dist 文件夹
clean: true`
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
})
],
mode: 'none'
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 搭建开发环境
我们只能通过复制 dist/index.html 完整物理路径到浏览器地址栏里访问页面。现在来看看如何设置一个开发环境,使我们的开发体验变得更轻松一些
# mode选项
在开始前,我们先将 mode 设置为 'development'
(opens new window)
webpack.config.js
module.exports = {
......
` // 开发模式
mode: 'development'`
}
2
3
4
5
# 使用 source map
当webpack 打包源代码时,可能会很难追踪到 error(错误) 和 warning(警告) 在源代码中的原始位置。例如,如果将三个源文件( a.js , b.js 和 c.js )打包到一个bundle( bundle.js )中,而其中一个源文件包含一个错误,那么堆栈跟踪就会直接指向到 bundle.js 。你可能需要准确地知道错误来自于哪个源文件,所以这种提示这通常不会提供太多帮助。
为了更容易地追踪 error 和 warning,JavaScript 提供了 source maps (opens new window) 功能,可以将编译后的代码映射回原始源代码。如果一个错误来自于 b.js ,source map 就会明确的告诉你。
未添加 devtool: 'inline-source-map'
之前报错,并不会跳到源码错误的那一行
webpack.config.js
module.exports = {
......
// 开发模式
mode: 'development',
// 在开发模式下追踪代码
devtool: 'inline-source-map'
}
2
3
4
5
6
7
加了配置以后,直接跳到源码报错的那一行
# watch mode观察模式
在每次编译代码时,手动运行 npx webpack 会显得很麻烦。
我们可以在 webpack 启动时添加 "watch" 参数。如果其中一个文件被更新,代码将被重新编译,所以你不必再去手动运行整个构建。
命令
npx webpack --watch
现在,保存文件并检查 terminal(终端) 窗口。应该可以看到 webpack 自动地重新编译修改后的模块!
唯一的缺点是,为了看到修改后的实际效果,你需要刷新浏览器。如果能够自动刷新浏览器就更好了,因此接下来我们会尝试通过 webpack-dev-server 实现此功能。
# webpack-dev-server
webpack-dev-server
为你提供了一个基本的 web server,并且具有 live reloading(实时重新加载) 功能。先安装:
npm install --save-dev webpack-dev-server
修改配置文件,告知 dev server,从什么位置查找文件:
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true
},
` // dev-server
devServer: {
static: './dist'
},`
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
})
],
// 开发模式
mode: 'development',
// 在开发模式下追踪代码
devtool: 'inline-source-map'
}
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
以上配置告知 webpack-dev-server ,将 dist 目录下的文件作为 web 服务的根目录。
webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文件保留在内存中,然后将它们 serve 到 server 中,就好像它们是挂载在 server根路径上的真实文件一样
执行命令
npx webpack server --open
# 资源模块
目前为止,我们的项目可以在控制台上显示 "Hello world~~~"。现在我们尝试混合一些其他资源,比如 images,看看 webpack 如何处理。
在 webpack 出现之前,前端开发人员会使用 grunt (opens new window) 和 gulp (opens new window) 等工具来处理资源,并将它们从 /src 文件夹移动到 /dist 或 /build 目录中。webpack 最出色的功能之一就是,除了引入 JavaScript,还可以内置的资源模块 Asset Modules (opens new window) 引入任何其他类型的文件。
在 webpack 5 之前,通常使用:
raw-loader
(opens new window) 将文件导入为字符串url-loader
(opens new window) 将文件作为 data URI 内联到 bundle 中file-loader
(opens new window) 将文件发送到输出目录
资源模块类型(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
,并且配置资源体积限制实现。
# Resource资源
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
})
],
// 开发模式
mode: 'development',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
`// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource'
}
]
}`
}
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
准备资源文件,在入口文件中引入,并显示在页面上:
src/index.js
import Hello from './hello'
import imgSrc from './assets/image/webpack.png'
Hello()
const img = document.createElement('img')
img.src = imgSrc
document.body.appendChild(img)
2
3
4
5
6
7
8
执行打包命令
npx webpack
发现图片(.png)文件已经打包到了dis目录下
执行启动服务命令:
npx webpack serve --open
自定义输出文件名默认情况下, asset/resource 模块以[contenthash][ext][query]
文件名
发送到输出目录。可以通过在 webpack 配置中设置 output.assetModuleFilename (opens new window) 来修改此模板字符串:
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
....
` assetModuleFilename: 'images/[contenthash][ext][query]'`
},
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource'
}
]
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
执行打包命令 npx webpack
另一种自定义输出文件名的方式是,将某些资源发送到指定目录,修改配置:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]'
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
})
],
// 开发模式
mode: 'development',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
` // 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}`
}
]
}
}
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
输出结果与 assetModuleFilename 设置一样
# inline资源
module.exports = {
.......
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
` {
test: /\.svg/,
type: 'asset/inline',
}`
]
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
index.js
import Hello from './hello'
import imgSrc from './assets/image/webpack.png'
import svgSrc from './assets/image/webpack-logo.svg'
Hello()
const img = document.createElement('img')
img.src = imgSrc
document.body.appendChild(img)
const svgImg = document.createElement('img')
svgImg.src = svgSrc
document.body.appendChild(svgImg)
2
3
4
5
6
7
8
9
10
11
12
13
npx webpack serve --open
- 自定义 data URI 生成器
webpack 输出的 data URI,默认是呈现为使用 Base64 算法编码的文件内容。如果要使用自定义编码算法,则可以指定一个自定义函数来编码文件内容。
npm install mini-svg-data-uri -D
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
`const svgToMiniDataURI = require('mini-svg-data-uri')`
module.exports = {
.....
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
` generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}`
}
]
}
}
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
现在,所有 .svg 文件都将通过 mini-svg-data-uri 包进行编码。重新启动服务,在浏览器查看效果:
# source 资源
source资源,导出资源的源代码。修改配置文件,添加
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
module.exports = {
.....
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
` {
test: /\.txt/,
type: 'asset/source',
}`
]
}
}
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
assets里创建一个 test.txt 文件
hello webpack
index.js
import Hello from './hello'
import imgSrc from './assets/image/webpack.png'
import svgSrc from './assets/image/webpack-logo.svg'
import text from './assets/test.txt'
Hello()
const img = document.createElement('img')
img.src = imgSrc
document.body.appendChild(img)
const svgImg = document.createElement('img')
svgImg.src = svgSrc
style.cssText = 'width: 200px; height: 200px'
document.body.appendChild(svgImg)
const div = document.createElement('div')
div.textContent = text
div.cssText = 'background: aliceblue'
document.body.appendChild(div)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 通用资源类型
通用资源类型 asset , 在导出一个 data URI 和发送一个单独的文件之间自动选择。
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
module.exports = {
......
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
` {
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
}`
]
}
}
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
现在,webpack 将按照默认条件,自动地在 resource 和 inline 之间进行选择:小于 8kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类
可以通过在 webpack 配置的 module rule 层级中,设置Rule.parser.dataUrlCondition.maxSize (opens new window) 选项来修改此条件
在 assets 目录下创建 .jpg 文件,然后在入口文件中引入:
index.js
import Hello from './hello'
import imgSrc from './assets/image/webpack.png'
import svgSrc from './assets/image/webpack-logo.svg'
import text from './assets/test.txt'
import jpgSrc from './assets/image/test.jpg'
Hello()
const img = document.createElement('img')
img.src = imgSrc
document.body.appendChild(img)
const svgImg = document.createElement('img')
svgImg.src = svgSrc
svgImg.style.cssText = 'width: 200px; height: 40px'
document.body.appendChild(svgImg)
const div = document.createElement('div')
div.textContent = text
div.style.cssText = 'background: aliceblue'
document.body.appendChild(div)
const img2 = document.createElement('img')
img2.src = jpgSrc
img2.style.cssText = 'width: 200px; height: 40px'
document.append(img2)
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
执行打包命令 npx webpack
发现当前的 .jpg 文件被打包成了单独的文件,因为此文件大小超过了 4k
如果修改限制为20KB大小,则不会被打包,会变成base64
# 管理资源
除了资源模块,我们还可以通过loader引入其他类型的文件
# 什么是loader
webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块 (opens new window),以供应用程序使用,以及被添加到依赖图中
在 webpack 的配置中,loader 有两个属性
test 属性,识别出哪些文件会被转换。
use 属性,定义出在进行转换时,应该使用哪个 loader。
const path = require('path');
module.exports = {
output: {
filename: 'my-first-webpack.bundle.js',
},
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
},
};
2
3
4
5
6
7
8
9
以上配置中,对一个单独的 module 对象定义了 rules 属性,里面包含两个必须属性: test 和 use 。这告诉 webpack 编译器(compiler) 如下信息
“嘿,webpack 编译器,当你碰到「在 require() / import 语句中被解析为'.txt' 的路径」时,在你对它打包之前,先 use(使用) raw-loader 转换一下。”
# 加载CSS
为了在 JavaScript 模块中 import 一个 CSS 文件,你需要安装 style-loader 和 css-loader (opens new window),并在 module 配置 中添加这些 loader:
npm install --save-dev style-loader css-loader
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]'
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
})
],
// 开发模式
mode: 'development',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.css/i,
use: ['style-loader', 'css-loader']
}
]
}
}
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
模块 loader 可以链式调用。链中的每个 loader 都将对资源进行转换。链会逆序执行(从右往左)。第一个 loader 将其结果(被转换后的资源)传递给下一个 loader,依此类推。最后,webpack 期望链中的最后的 loader 返回 JavaScript。
应保证 loader 的先后顺序: 'style-loader'
在前,而 'css-loader'
在后。如果不遵守此约定,webpack 可能会抛出错误。webpack 根据正则表达式,来确定应该查找哪些文件,并将其提供给指定的 loader。在这个示例中,所有以 .css 结尾的文件,都将被提供给 style-loader 和 css-loader 。
这使你可以在依赖于此样式的 js 文件中 import './style.css' 。现在,在此模块执行过程中,含有 CSS 字符串的 <style>
标签,将被插入到 html 文件的<head>
中。
style.css
.hello {
color: red;
}
2
3
index.js
import Hello from './hello'
import imgSrc from './assets/image/webpack.png'
import svgSrc from './assets/image/webpack-logo.svg'
import text from './assets/test.txt'
import jpgSrc from './assets/image/test.jpg'
`import './assets/css/style.css'`
Hello()
const img = document.createElement('img')
img.src = imgSrc
document.body.appendChild(img)
const svgImg = document.createElement('img')
svgImg.src = svgSrc
svgImg.style.cssText = 'width: 200px; height: 40px'
document.body.appendChild(svgImg)
const div = document.createElement('div')
div.textContent = text
div.style.cssText = 'background: aliceblue'
`div.classList.add('hello')`
document.body.appendChild(div)
const img2 = document.createElement('img')
img2.src = jpgSrc
img2.style.cssText = 'width: 200px; height: 40px'
document.body.appendChild(img2)
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
npx webpack serve --open
运行
现有的 loader 可以支持任何你可以想到的 CSS 风格 sass (opens new window) 和 less 等。安装less-loader:
npm install less less-loader --save-dev
修改配置文件
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]'
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
})
],
// 开发模式
mode: 'development',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
` {
test: /\.css/i,
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
}`
]
}
}
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
style.less
@color: rgb(29, 76, 13);
.bg {
background-color: @color;
}
2
3
4
在入口文件中引入
import './assets/css/style.less'
document.body.classList.add('bg')
2
3
4
npx webpack serve --open
报错解决方案(没有匹配(/\.less$/i
))
# 抽离和压缩CSS
在多数情况下,我们也可以进行压缩CSS,以便在生产环境中节省加载时间,同时还可以将CSS文件抽离成一个单独的文件。实现这个功能,需要 mini-css-extract-plugin 这个插件来帮忙。安装插件:
npm install mini-css-extract-plugin --save-dev
本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
本插件基于 webpack v5 的新特性构建,并且需要 webpack 5 才能正常工作。之后将 loader 与 plugin 添加到你的 webpack 配置文件中:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
`const MiniCssExtractPlugin = require('mini-css-extract-plugin')`
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]'
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
}),
`new MiniCssExtractPlugin()`
],
// 开发模式
mode: 'development',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
` {
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},`
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
}
]
}
}
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
npx webapck
单独的 mini-css-extract-plugin 插件不会将这些 CSS加载到页面中。这里 html-webpack-plugin 帮助我们自动生成 link 标签或者在创建 index.html 文件时使用 link 标签。
指定生成的文件名
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
2
3
4
5
6
7
8
9
10
11
12
app.html同样会更新
发现文件并没有压缩和优化,为了压缩输出文件,请使用类似于 css-minimizer-webpack-plugin 这样的插件
npm install css-minimizer-webpack-plugin --save-dev
配置插件
const CssMinimizerPlugin = require('css-minimizer-webpackplugin')
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
`const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')`
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]'
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
// 开发模式
`mode: 'production',`
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
}
]
},
` // 优化配置
optimization: {
minimizer: [
new CssMinimizerPlugin()
]
}`
}
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
编译 npx webpack
mode: 'production'
不修改为production不会进行代码压缩
# 加载 images 图像
假如,现在我们正在下载 CSS,但是像 background 和 icon 这样的图像,要如何处理呢?在 webpack 5 中,可以使用内置的 Asset Modules,我们可以轻松地将这些内容混入我们的系统中,这个我们在"资源模块"一节中已经介绍了。这里再补充一个知识点,在 css 文件里也可以直接引用文件,修改 style.css 和入口index.js
style.css
.hello {
color: red;
}
.div-bg {
background: url('../image/webpack-logo.svg');
}
2
3
4
5
6
index.js
import Hello from './hello'
import imgSrc from './assets/image/webpack.png'
import svgSrc from './assets/image/webpack-logo.svg'
import text from './assets/test.txt'
import jpgSrc from './assets/image/test.jpg'
import './assets/css/style.css'
import './assets/css/style.less'
Hello()
document.body.classList.add('bg')
const img = document.createElement('img')
img.src = imgSrc
document.body.appendChild(img)
const svgImg = document.createElement('img')
svgImg.src = svgSrc
svgImg.style.cssText = 'width: 200px; height: 40px'
document.body.appendChild(svgImg)
const div = document.createElement('div')
div.textContent = text
div.style.cssText = 'background: aliceblue'
div.classList.add('hello')
`div.classList.add('div-bg')`
document.body.appendChild(div)
const img2 = document.createElement('img')
img2.src = jpgSrc
img2.style.cssText = 'width: 200px; height: 40px'
document.body.appendChild(img2)
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
提示性能需要优化
performance: { hints: false }
浏览器中打开: npx webpack serve --open
# 加载fonts字体
那么,像字体这样的其他资源如何处理呢?使用 Asset Modules 可以接收并加载任何文件,然后将其输出到构建目录。这就是说,我们可以将它们用于任何类型的文件,也包括字体。让我们更新 webpack.config.js 来处理字体文件:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
entry: './src/index.js',
performance: { hints: false },
output: {
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]'
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
// 开发模式
mode: 'production',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
},
` {
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'
}`
]
},
// 优化配置
optimization: {
minimizer: [
new CssMinimizerPlugin()
]
}
}
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
在项目中添加一些字体文件:
配置好 loader 并将字体文件放在合适的位置后,你可以通过一个 @font-face 声明将其混合。本地的 url(...) 指令会被 webpack 获取处理,就像它处理图片一样:
style.css
.hello {
color: red;
}
.div-bg {
width: 200px;
height: 40px;
background: url('../image/webpack-logo.svg') no-repeat !important;
background-size: 100% 100%;
}
@font-face {
font-family: 'iconfont';
src: url('../fonts/iconfont.ttf') format('truetype');
}
.icon {
font-family: "iconfont" !important;
font-size: 30px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
index.js
const span = document.createElement('span')
span.classList.add('icon')
span.innerHTML = ''
document.body.appendChild(span)
2
3
4
npx webpack serve --open
# 加载数据
此外,可以加载的有用资源还有数据,如 JSON 文件,CSV、TSV 和 XML。类似于NodeJS,JSON 支持实际上是内置的,也就是说 import Data from'./data.json' 默认将正常运行。要导入 CSV、TSV 和 XML,你可以使用 csv-loader (opens new window) 和 xml-loader (opens new window)。让我们处理加载这三类文件:
npm install --save-dev csv-loader xml-loader
添加配置
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'
},
` {
test: /\.(csv|tsv)$/i,
use: ['csv-loader']
},
{
test: /\.xml$/i,
use: ['xml-loader']
}`
]
},
// 优化配置
optimization: {
minimizer: [
new CssMinimizerPlugin()
]
}
}
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
你可以 import 这四种类型的数据(JSON, CSV, TSV, XML)中的任何一种,所导入的 Data 变量,将包含可直接使用的已解析 JSON:
assets/data/data.xml
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Mary</to>
<from>John</from>
<heading>Reminder</heading>
<body>Call Cindy on Tuesday</body>
</note>
2
3
4
5
6
7
assets/data/data.csv
to,from,heading,body
Mary,John,Reminder,Call Cindy on Tuesday
Zoe,Bill,Reminder,Buy orange juice
Autumn,Lindsey,Letter,I miss you
2
3
4
在入口文件里加载数据模块,并在控制台上打印导入内容:
index.js
import dataXml from './assets/data/data.xml'
import dataCsv from './assets/data/data.csv'
console.log(dataXml)
console.log(dataCsv)
2
3
4
5
# 自定义 JSON 模块 parser
通过使用自定义 parser (opens new window) 替代特定的 webpack loader,可以将任何 toml 、 yaml 或json5 文件作为 JSON 模块导入。
假设你在 src 文件夹下有一个 data.toml 、一个 data.yaml 以及一个data.json5 文件:
assets/data/data.toml
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
dob = 1979-05-27T07:32:00Z
2
3
4
5
6
assets/data/data.yaml
title: YAML Example
owner:
name: Tom Preston-Werner
organization: GitHub
bio: |-
GitHub Cofounder & CEO
Likes tater tots and beer.
dob: 1979-05-27T07:32:00.000Z
2
3
4
5
6
7
8
assets/data/data.json5
{
// comment
title: 'JSON5 Example',
owner: {
name: 'Tom Preston-Werner',
organization: 'GitHub',
bio: 'GitHub Cofounder & CEO\n\
Likes tater tots and beer.',
dob: '1979-05-27T07:32:00.000Z'
}
}
2
3
4
5
6
7
8
9
10
11
首先安装 toml , yamljs 和 json5 的 packages:
npm install toml yamljs json5 --save-dev
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
`const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');`
module.exports = {
entry: './src/index.js',
performance: { hints: false },
output: {
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]'
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
// 开发模式
mode: 'production',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader']
},
{
test: /\.xml$/i,
use: ['xml-loader']
},
`{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
}`
]
},
// 优化配置
optimization: {
minimizer: [
new CssMinimizerPlugin()
]
}
}
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
index.js
import toml from './assets/json/data.toml'
import yaml from './assets/json/data.yaml'
import json from './assets/json/data.json5'
console.log(toml.title); // output `TOML Example`
console.log(toml.owner.name); // output `Tom Preston-Werner`
console.log(yaml.title); // output `YAML Example`
console.log(yaml.owner.name); // output `Tom Preston-Werner`
console.log(json.title); // output `JSON5 Example`
console.log(json.owner.name); // output `Tom Preston-Werner`
2
3
4
5
6
7
8
9
10
# babel-loader
# 为什么需要babel-loader
webpack 自身可以自动加载JS文件,就像加载JSON文件一样,无需任何 loader。可是,加载的JS文件会原样输出,即使你的JS文件里包含ES6+的代码,也不会做任何的转化。这时我们就需要Babel来帮忙。Babel 是一个 JavaScript 编译器,可以将ES6+转化成ES5。在Webpack里使用Babel,需要使用 babel-loader 。
# 使用babel-loader
npm install -D babel-loader @babel/core @babel/preset-env
babel-loader
: 在webpack里应用 babel 解析ES6的桥梁@babel/core : babel核心模块
@babel/preset-env : babel预设,一组 babel 插件的集合
index.js
function Hello() {
console.log('hello webpack~~~')
}
function getString() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello__webpack')
},2000)
})
}
async function helloWebpack() {
let string = await getString()
console.log(string)
}
helloWebpack()
export default Hello
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在 webpack 配置中,需要将 babel-loader 添加到 module 列表中,就像下面这样:
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');
module.exports = {
entry: './src/index.js',
performance: { hints: false },
output: {
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]'
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
// 开发模式
mode: 'production',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader']
},
{
test: /\.xml$/i,
use: ['xml-loader']
},
{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},
` {
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}`
]
},
// 优化配置
optimization: {
minimizer: [
new CssMinimizerPlugin()
]
}
}
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
编译npx webpack
可以看到,箭头函数已经转换为普通的函数 async 和 await语法也被也被编译了
# regeneratorRuntime 插件
在使用过程中并没有报以下错误,所以以下步骤并没有执行
regeneratorRuntime 是webpack打包生成的全局辅助函数,由babel生成,用于兼容async/await的语法。
regeneratorRuntime is not defined 这个错误显然是未能正确配置babel。
正确的做法需要添加以下的插件和配置:
# 这个包中包含了regeneratorRuntime,运行时需要
npm install --save @babel/runtime
# 这个插件会在需要regeneratorRuntime的地方自动require导包,编译时需要
npm install --save-dev @babel/plugin-transform-runtime
2
3
4
5
参考文档:https://babeljs.io/docs/en/babel-plugin-transform-runtime
接着改一下babel的配置:
webpack.config.js
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
[
'@babel/plugin-transform-runtime'
]
]
}
}
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 代码分离
代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
常用的代码分离方法有三种:
入口起点:使用 entry (opens new window) 配置手动地分离代码。
防止重复:使用 Entry dependencies (opens new window) 或者 SplitChunksPlugin (opens new window) 去重和分离chunk。
动态导入:通过模块的内联函数调用来分离代码。
# 入口起点
这是迄今为止最简单直观的分离代码的方式。不过,这种方式手动配置较多,并有一些隐患,我们将会解决这些问题。先来看看如何从 main bundle 中分离 anothermodule(另一个模块):
在 src 目录下创建 another-module.js 文件:
import _ from 'lodash'
console.log(_.join(['Another', 'module', 'loaded!'], ' '))
2
3
这个模块依赖了 lodash ,需要安装一下:
npm install lodash --save-dev
修改配置
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// entry: './src/index.js',
// 代码分离方式一:入口起点
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
output: {
// filename: 'bundle.js',
filename: '[name].bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
执行打包命令:npx webpack
我们可以看到another.bundle.js打包后的体积1.38M,我们发现 lodash.js也被打包到 another.bundle.js 中。
app.html
可以看到两个js文件都被引入了
我们在index.js中也引入这个函数(another-module.js中也引入有这个库)
import _ from 'lodash'
// console.log(_.join(['Another', 'module', 'loaded!'], ' '))
console.log(_.join(['index', 'module', 'loaded!'], ' '))
2
3
4
重新打包
npx webpack
可以看到index.bundle.js文件的体积也变大了
我们发现: index.bundle.js 文件大小也骤然增大了,可以 lodash.js 也被打包到了 index.bundle.js 中了。
正如前面提到的,这种方式的确存在一些隐患:
- 如果入口 chunk 之间包含一些重复的模块,那些重复模块都会被引入到各个bundle 中。
- 这种方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来。
以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在./src/index.js 中也引入过 lodash ,这样就在两个 bundle 中造成重复引用。
# 防止重复
配置 dependOn option (opens new window) 选项,这样可以在多个 chunk 之间共享模块:
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');
module.exports = {
// entry: './src/index.js',
// 代码分离方式一:入口起点
// entry: {
// index: './src/index.js',
// another: './src/another-module.js'
// },
//代码分离方式二:防止重复
entry: {
index: {
import: './src/index.js',
dependOn: 'shared'
},
another: {
import: './src/another-module.js',
dependOn: 'shared'
},
shared: 'lodash'
},
performance: { hints: false },
output: {
// filename: 'bundle.js',
filename: '[name].bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]'
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
// 开发模式
mode: 'production',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader']
},
{
test: /\.xml$/i,
use: ['xml-loader']
},
{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
// 优化配置
optimization: {
minimizer: [
new CssMinimizerPlugin()
]
}
}
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
执行打包命令 npx webpack
index.bundle.js 与 another.bundle.js 共享的模块 lodash.js 被打包到一个单独的文件 shared.bundle.js 中。
- SplitChunksPlugin
SplitChunksPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk中,或者提取到一个新生成的 chunk。让我们使用这个插件,将之前的示例中重复的 lodash 模块去除:
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');
module.exports = {
// entry: './src/index.js',
// 代码分离方式一:入口起点
// entry: {
// index: './src/index.js',
// another: './src/another-module.js'
// },
//代码分离方式二:防止重复
// entry: {
// index: {
// import: './src/index.js',
// dependOn: 'shared'
// },
// another: {
// import: './src/another-module.js',
// dependOn: 'shared'
// },
// shared: 'lodash'
// },
`//代码分离方式二:借助插件 SplitChunksPlugin
entry: {
index: './src/index.js',
another: './src/another-module.js'
},`
performance: { hints: false },
output: {
// filename: 'bundle.js',
filename: '[name].bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]'
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
// 开发模式
mode: 'production',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader']
},
{
test: /\.xml$/i,
use: ['xml-loader']
},
{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
// 优化配置
optimization: {
minimizer: [
new CssMinimizerPlugin()
],
` // 代码分离,抽取公共代码
splitChunks: {
chunks: 'all',
}`
}
}
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
执行打包 npx webpack
使用 optimization.splitChunks (opens new window) 配置选项之后,现在应该可以看出,index.bundle.js 和 another.bundle.js 中已经移除了重复的依赖模块。需要注意的是,插件将 lodash 分离到单独的 chunk,并且将其从 main bundle中移除,减轻了大小
# 动态导入
当涉及到动态代码拆分时,webpack 提供了两个类似的技术。第一种,也是推荐选择的方式是,使用符合 ECMAScript 提案 (opens new window) 的 import() (opens new window) 语法 来实现动态导入。第二种,则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure (opens new window) 。
创建async-module.js文件
function getComponent() {
return import('lodash').then(({default: _}) => {
const element = document.createElement('div')
element.innerHTML = _.join(['Hello', 'webpack'], ' ')
element.style.cssText = 'color: yellow'
return element
}).catch((error) => {
alert(error)
})
}
getComponent().then(elm => {
document.body.appendChild(elm)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
在入口文件index.js中引入
import './async-module'
执行编译命令 npx webpack
视频中打包回多一个文件,这里测试并没有。 用的"webpack": "^5.88.1",
从打印的结果看,除了公共的 lodash 代码被单独打包到一个文件外,还生成了一个 vendors-node_modules_babel_runtime_regenerator_index_js node_modules_css-loader_dist_runtime_-86adfe.bundle.js 文件。
# 懒加载
懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。
创建一个 math.js 文件,在主页面中通过点击按钮调用其中的函数:
export const add = (a, b) => {
return a + b
}
export const mul = (a, b) => {
return a * b
}
2
3
4
5
6
7
在index.js中点击按钮,触发事件
const button = document.createElement('button')
button.textContent = '点我执行加法运算'
button.addEventListener('click', function() {
// 打包的时候name是math (输出的命名格式filename: '[name].bundle.js',)
import(/* webpackChunkName: 'math' */'./math.js').then(({add}) => {
console.log('1 + 2 = ',add(1,2))
})
})
document.body.appendChild(button)
2
3
4
5
6
7
8
9
这里有句注释,我们把它称为 webpack 魔法注释: webpackChunkName: 'math' ,告诉webpack打包生成的文件名为 math 。
第一次加载完页面, math.bundle.js 不会加载,当点击按钮后,才加载math.bundle.js 文件。
# 预获取/预加载模块
Webpack v4.6.0+ 增加了对预获取和预加载的支持。在声明 import 时,使用下面这些内置指令,可以让 webpack 输出 "resourcehint(资源提示)",来告知浏览器:
prefetch(预获取)
:将来某些导航下可能需要的资源preload(预加载)
:当前导航下可能需要资源
index.js
const button = document.createElement('button')
button.textContent = '点我执行加法运算'
button.addEventListener('click', function() {
// webpackChunkName 打包的时候name是math (输出的命名格式filename: '[name].bundle.js',)
// webpackPrefetch 告诉 webpack 执行预获取
import(/* webpackChunkName: 'math', webpackPrefetch: true*/'./math.js').then(({add}) => {
console.log('1 + 2 = ',add(1,2))
})
})
document.body.appendChild(button)
2
3
4
5
6
7
8
9
10
这会生成 <link rel="prefetch" href="math.js">
并追加到页面头部,指示着浏览器在闲置时间预取 math.js 文件。
我们发现,在还没有点击按钮时, math.bundle.js 就已经下载下来了。同时,在app.html 里webpack自动添加了一句
与 prefetch 指令相比,preload 指令有许多不同之处:
preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk会在父 chunk 加载结束后开始加载。
preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
浏览器支持程度不同
创建print.js
export const print = () => {
console.log('preload chunk')
}
2
3
index.js中引入
const button2 = document.createElement('button')
button2.textContent = "点我执行打印"
button2.addEventListener('click', function() {
// webpackChunkName 打包的时候name是print (输出的命名格式filename: '[name].bundle.js',)
// webpackPreload 告诉 webpack 执行预获取
import(/* webpackChunkName: 'print', webpackPreload: true*/'./print.js').then(({print}) => {
print()
})
})
document.body.appendChild(button2)
2
3
4
5
6
7
8
9
10
仔细观察,发现 print.bundle.js 未被下载,因为我们配置的是webpackPreload , 是在父 chunk 加载时,以并行方式开始加载。点击按钮才加载的模块不会事先加载的。
修改index.js
// const button2 = document.createElement('button')
// button2.textContent = "点我执行打印"
// button2.addEventListener('click', function() {
// // webpackChunkName 打包的时候name是print (输出的命名格式filename: '[name].bundle.js',)
// // webpackPreload 告诉 webpack 执行预获取
// import(/* webpackChunkName: 'print', webpackPreload: true*/'./print.js').then(({print}) => {
// print()
// })
// })
// document.body.appendChild(button2)
import(/* webpackChunkName: 'print', webpackPreload: true*/'./print.js').then(({print}) => {
print()
})
2
3
4
5
6
7
8
9
10
11
12
13
14
print.bundle.js 被加载下来,是和当前 index.bundle.js 并行加载的。
# 缓存
以上,我们使用 webpack 来打包我们的模块化后的应用程序,webpack 会生成一个可部署的 /dist 目录,然后把打包后的内容放置在此目录中。只要 /dist 目录中的内容部署到 server 上,client(通常是浏览器)就能够访问此 server 的网站及其资源。而最后一步获取资源是比较耗费时间的,这就是为什么浏览器使用一种名为缓存 (opens new window) 的技术。可以通过命中缓存,以降低网络流量,使网站加载速度更快,然而,如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就会使用它的缓存版本。由于缓存的存在,当你需要获取新的代码时,就会显得很棘手。
# 输出文件的文件名
我们可以通过替换 output.filename
中的 substitutions (opens new window) 设置,来定义输出文件的名称。webpack 提供了一种使用称为 substitution(可替换模板字符串) 的方式,通过带括号字符串来模板化文件名。其中,[contenthash]
substitution 将根据资源内容创建出唯一 hash。当资源内容发生变化时,[contenthash]
也会发生变化。
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');
module.exports = {
// entry: './src/index.js',
// 代码分离方式一:入口起点
// entry: {
// index: './src/index.js',
// another: './src/another-module.js'
// },
//代码分离方式二:防止重复
// entry: {
// index: {
// import: './src/index.js',
// dependOn: 'shared'
// },
// another: {
// import: './src/another-module.js',
// dependOn: 'shared'
// },
// shared: 'lodash'
// },
//代码分离方式二:借助插件 SplitChunksPlugin
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
performance: { hints: false },
output: {
// filename: 'bundle.js',
// filename: '[name].bundle.js',
// 内容改变就换文件名,防止被缓存
filename: '[name].[contenthash].js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]'
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
// 开发模式
mode: 'production',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader']
},
{
test: /\.xml$/i,
use: ['xml-loader']
},
{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
// 优化配置
optimization: {
minimizer: [
new CssMinimizerPlugin()
],
// 代码分离,抽取公共代码
splitChunks: {
chunks: 'all',
}
}
}
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
可以看到,bundle 的名称是它内容(通过 hash)的映射。如果我们不做修改,然后再次运行构建,文件名会保持不变。
# 缓存第三方库
将第三方库(library)(例如 lodash )提取到单独的 vendor chunk 文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。因此通过实现以上步骤,利用 client 的长效缓存机制,命中缓存来消除请求,并减少向 server 获取资源,同时还能保证 client 代码和 server 代码版本一致。 我们在optimization.splitChunks
添加如下 cacheGroups
参数并构建:
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');
module.exports = {
// entry: './src/index.js',
// 代码分离方式一:入口起点
// entry: {
// index: './src/index.js',
// another: './src/another-module.js'
// },
//代码分离方式二:防止重复
// entry: {
// index: {
// import: './src/index.js',
// dependOn: 'shared'
// },
// another: {
// import: './src/another-module.js',
// dependOn: 'shared'
// },
// shared: 'lodash'
// },
//代码分离方式二:借助插件 SplitChunksPlugin
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
performance: { hints: false },
output: {
// filename: 'bundle.js',
// filename: '[name].bundle.js',
// 内容改变就换文件名,防止被缓存
filename: '[name].[contenthash].js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]'
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
// 开发模式
mode: 'production',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader']
},
{
test: /\.xml$/i,
use: ['xml-loader']
},
{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
// 优化配置
optimization: {
minimizer: [
new CssMinimizerPlugin()
],
// 代码分离,抽取公共代码
splitChunks: {
// 缓存第三方库
` cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}`
}
}
}
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# 将js放到一个文件夹中
目前,全部 js 文件都在 dist 文件夹根目录下,我们尝试把它们放到一个文件夹中,这个其实也简单,修改配置文件
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// entry: './src/index.js',
// 代码分离方式一:入口起点
// entry: {
// index: './src/index.js',
// another: './src/another-module.js'
// },
//代码分离方式二:防止重复
// entry: {
// index: {
// import: './src/index.js',
// dependOn: 'shared'
// },
// another: {
// import: './src/another-module.js',
// dependOn: 'shared'
// },
// shared: 'lodash'
// },
//代码分离方式二:借助插件 SplitChunksPlugin
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
performance: { hints: false },
output: {
// filename: 'bundle.js',
// filename: '[name].bundle.js',
// 内容改变就换文件名,防止被缓存
`filename: 'scripts/[name].[contenthash].js',`
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]'
}
}
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
# 拆分开发环境和生产环境的配置
现在,我们只能手工的来调整 mode 选项,实现生产环境和开发环境的切换,且很多配置在生产环境和开发环境中存在不一致的情况,比如开发环境没有必要设置缓存,生产环境还需要设置公共路径等等。
# 公共路径
publicPath (opens new window) 配置选项在各种场景中都非常有用。你可以通过它来指定应用程序中所有资源的基础路径。
基于环境设置
在开发环境中,我们通常有一个 assets/ 文件夹,它与索引页面位于同一级别。这没太大问题,但是,如果我们将所有静态资源托管至 CDN,然后想在生产环境中使用呢?
想要解决这个问题,可以直接使用一个 environment variable(环境变量)。假设我们有一个变量 ASSET_PATH :
import webpack from 'webpack'; // 尝试使用环境变量,否则使用根路径 const ASSET_PATH = process.env.ASSET_PATH || '/'; export default { output: { publicPath: ASSET_PATH, }, plugins: [ // 这可以帮助我们在代码中安全地使用环境变量 new webpack.DefinePlugin({ 'process.env.ASSET_PATH': JSON.stringify(ASSET_PATH), }) ] }
1
2
3
4
5
6
7
8
9
10
11
12
13
14Automatic publicPath
有可能你事先不知道 publicPath 是什么,webpack 会自动根据import.meta.url (opens new window) 、 document.currentScript (opens new window) 、 script.src 或者self.location 变量设置 publicPath。你需要做的是将 output.publicPath (opens new window)设为 'auto'
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');
module.exports = {
// entry: './src/index.js',
// 代码分离方式一:入口起点
// entry: {
// index: './src/index.js',
// another: './src/another-module.js'
// },
//代码分离方式二:防止重复
// entry: {
// index: {
// import: './src/index.js',
// dependOn: 'shared'
// },
// another: {
// import: './src/another-module.js',
// dependOn: 'shared'
// },
// shared: 'lodash'
// },
//代码分离方式二:借助插件 SplitChunksPlugin
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
performance: { hints: false },
output: {
// filename: 'bundle.js',
// filename: '[name].bundle.js',
// 内容改变就换文件名,防止被缓存
filename: 'scripts/[name].[contenthash].js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]',
`// 公共路径
publicPath: 'http://localhost:8080/'`
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
// 开发模式
mode: 'production',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader']
},
{
test: /\.xml$/i,
use: ['xml-loader']
},
{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
// 优化配置
optimization: {
minimizer: [
new CssMinimizerPlugin()
],
// 代码分离,抽取公共代码
splitChunks: {
// 缓存第三方库
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
}
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# 环境变量
想要消除 webpack.config.js 在 开发环境 (opens new window) 和 生产环境 (opens new window) 之间的差异,你可能需要环境变量(environment variable)
webpack 命令行 环境配置 (opens new window) 的 --env 参数,可以允许你传入任意数量的环境变量。而在 webpack.config.js 中可以访问到这些环境变量。例如, --env production
npx webpack --env production
对于我们的 webpack 配置,有一个必须要修改之处。通常, module.exports 指向配置对象。要使用 env 变量,你必须将 module.exports 转换成一个函数
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');
`module.exports = (env) => {
return {`
// entry: './src/index.js',
// 代码分离方式一:入口起点
// entry: {
// index: './src/index.js',
// another: './src/another-module.js'
// },
//代码分离方式二:防止重复
// entry: {
// index: {
// import: './src/index.js',
// dependOn: 'shared'
// },
// another: {
// import: './src/another-module.js',
// dependOn: 'shared'
// },
// shared: 'lodash'
// },
//代码分离方式二:借助插件 SplitChunksPlugin
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
performance: { hints: false },
output: {
// filename: 'bundle.js',
// filename: '[name].bundle.js',
// 内容改变就换文件名,防止被缓存
filename: 'scripts/[name].[contenthash].js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]',
// 公共路径
publicPath: 'http://localhost:8080/'
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
// 开发模式
// mode: 'production',
`// 根据命令行参数 env 来设置不同环境的 mode
mode: env.production ? 'production' : 'development',`
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader']
},
{
test: /\.xml$/i,
use: ['xml-loader']
},
{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
// 优化配置
optimization: {
minimizer: [
new CssMinimizerPlugin()
],
// 代码分离,抽取公共代码
splitChunks: {
// 缓存第三方库
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
}
`}`
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
执行命令 npx webpack --env production
npx webpack --env development
# 拆分配置文件
目前,生产环境和开发环境使用的是一个配置文件,我们需要将这两个文件单独放到不同的配置文件中。如 webpack.config.dev.js (开发环境配置)和webpack.config.prod.js (生产环境配置)。在项目根目录下创建一个配置文件夹 config 来存放他们。
config/webpack.config.dev.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');
module.exports = {
// entry: './src/index.js',
// 代码分离方式一:入口起点
// entry: {
// index: './src/index.js',
// another: './src/another-module.js'
// },
//代码分离方式二:防止重复
// entry: {
// index: {
// import: './src/index.js',
// dependOn: 'shared'
// },
// another: {
// import: './src/another-module.js',
// dependOn: 'shared'
// },
// shared: 'lodash'
// },
//代码分离方式二:借助插件 SplitChunksPlugin
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
performance: { hints: false },
output: {
// filename: 'bundle.js',
// filename: '[name].bundle.js',
// 内容改变就换文件名,防止被缓存
filename: 'scripts/[name].[contenthash].js',
// 输出文件夹必须定义为绝对路径
// 打包的dist文件夹要放到上一层目录(因为配置文件在config下)
path: path.resolve(__dirname, '../dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]',
// 公共路径
publicPath: 'http://localhost:8080/'
},
// dev-server
devServer: {
static: './dist'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
// 开发模式
mode: 'development',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader']
},
{
test: /\.xml$/i,
use: ['xml-loader']
},
{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
// css压缩这些不要
// // 优化配置
// optimization: {
// minimizer: [
// new CssMinimizerPlugin()
// ],
// // 代码分离,抽取公共代码
// splitChunks: {
// // 缓存第三方库
// cacheGroups: {
// vendor: {
// test: /[\\/]node_modules[\\/]/,
// name: 'vendors',
// chunks: 'all'
// }
// }
// }
// }
}
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
config/webpack.prod.js (去掉devServe,追踪代码,性能提示,开启代码压缩)
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');
module.exports = {
// entry: './src/index.js',
// 代码分离方式一:入口起点
// entry: {
// index: './src/index.js',
// another: './src/another-module.js'
// },
//代码分离方式二:防止重复
// entry: {
// index: {
// import: './src/index.js',
// dependOn: 'shared'
// },
// another: {
// import: './src/another-module.js',
// dependOn: 'shared'
// },
// shared: 'lodash'
// },
//代码分离方式二:借助插件 SplitChunksPlugin
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
// 关闭webpack的性能提示
performance: { hints: false },
output: {
// filename: 'bundle.js',
// filename: '[name].bundle.js',
// 内容改变就换文件名,防止被缓存
filename: 'scripts/[name].[contenthash].js',
// 打包的dist文件夹要放到上一层目录(因为配置文件在config下)
path: path.resolve(__dirname, '../dist'),
// 打包前清理 dist 文件夹
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]',
// 公共路径
publicPath: 'http://localhost:8080/'
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
// 开发模式
mode: 'production',
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader']
},
{
test: /\.xml$/i,
use: ['xml-loader']
},
{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
// 优化配置
optimization: {
minimizer: [
new CssMinimizerPlugin()
],
// 代码分离,抽取公共代码
splitChunks: {
// 缓存第三方库
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
}
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
拆分成两个配置文件后,分别运行这两个文件
开发环境:
npx webpack serve -c ./config/webpack.config.dev.js
生产环境
npx webpack -c ./config/webpack.config.prod.js
# npm脚本
配置 npm 脚本来简化命令行的输入,这时可以省略 npx :
{
"name": "webpack01",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
` "serve": "webpack serve -c ./config/webpack.config.dev.js",
"build": "webpack -c ./config/webpack.config.prod.js",`
"dev": "webpack-dev-server"
},
"author": "",
"license": "ISC",
"dependencies": {
"webpack": "^5.88.1",
"webpack-cli": "^5.1.4"
},
"devDependencies": {
"@babel/core": "^7.22.9",
"@babel/preset-env": "^7.22.9",
"babel-loader": "^9.1.3",
"css-loader": "^6.8.1",
"css-minimizer-webpack-plugin": "^5.0.1",
"csv-loader": "^3.0.5",
"html-webpack-plugin": "^5.5.3",
"json5": "^2.2.3",
"less": "^4.1.3",
"less-loader": "^11.1.3",
"lodash": "^4.17.21",
"mini-css-extract-plugin": "^2.7.6",
"mini-svg-data-uri": "^1.4.4",
"style-loader": "^3.3.3",
"toml": "^3.0.0",
"webpack-dev-server": "^4.15.1",
"xml-loader": "^1.2.1",
"yamljs": "^0.3.0"
}
}
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
运行项目
npm run serve
打包
npm run build
# 提取公共配置
这时,我们发现这两个配置文件里存在大量的重复代码,可以手动的将这些重复的代码单独提取到一个文件里,
webpack.config.common.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const svgToMiniDataURI = require('mini-svg-data-uri')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');
module.exports = {
// entry: './src/index.js',
// 代码分离方式一:入口起点
// entry: {
// index: './src/index.js',
// another: './src/another-module.js'
// },
//代码分离方式二:防止重复
// entry: {
// index: {
// import: './src/index.js',
// dependOn: 'shared'
// },
// another: {
// import: './src/another-module.js',
// dependOn: 'shared'
// },
// shared: 'lodash'
// },
//代码分离方式二:借助插件 SplitChunksPlugin
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, '../dist'),
assetModuleFilename: 'images/[contenthash][ext][query]',
},
plugins: [
// 实例化 html-webpack-plugin 插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
// 配置资源文件
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
// 优先级高于 assetModuleFilename
generator: {
filename: 'images/[contenthash][ext][query]'
}
},
{
test: /\.svg/,
type: 'asset/inline',
generator: {
dataUrl: content => {
content = content.toString();
return svgToMiniDataURI(content);
}
}
},
{
test: /\.txt/,
type: 'asset/source',
},
{
test: /\.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
// 设置4kb以内使用asset/inline,大于4Kb使用asset/resource
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader']
},
{
test: /\.xml$/i,
use: ['xml-loader']
},
{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
}
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
webpack.config.dev.js
module.exports = {
output: {
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js',
},
// dev-server
devServer: {
static: './dist'
},
// 开发模式
mode: 'development',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
webpack.config.prod.js
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
performance: { hints: false },
output: {
// 生产环境需要缓存
filename: 'scripts/[name].[contenthash].js',
// 公共路径
publicPath: 'http://localhost:8080/'
},
// 生产模式
mode: 'production',
// 优化配置
optimization: {
minimizer: [
new CssMinimizerPlugin()
],
// 代码分离,抽取公共代码
splitChunks: {
// 缓存第三方库
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
}
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
# 合并配置文件
webpack-merge (opens new window)
安装 webpack-merge :
npm install webpack-merge -D
新增webpack.config.js
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.config.common')
const developmentConfig = require('./webpack.config.dev')
const productionConfig = require('./webpack.config.prod')
module.exports = (env) => {
switch(true) {
case env.development: return merge(commonConfig, developmentConfig)
case env.production: return merge(commonConfig, productionConfig)
default: throw new Error('配置文件错误')
}
}
2
3
4
5
6
7
8
9
10
11
12
修改package.json
{
"name": "webpack01",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
` "serve": "webpack serve -c ./config/webpack.config.js --env development",
"build": "webpack -c ./config/webpack.config.js --env production",`
"dev": "webpack-dev-server"
},
"author": "",
"license": "ISC",
"dependencies": {
"webpack": "^5.88.1",
"webpack-cli": "^5.1.4"
},
"devDependencies": {
"@babel/core": "^7.22.9",
"@babel/preset-env": "^7.22.9",
"babel-loader": "^9.1.3",
"css-loader": "^6.8.1",
"css-minimizer-webpack-plugin": "^5.0.1",
"csv-loader": "^3.0.5",
"html-webpack-plugin": "^5.5.3",
"json5": "^2.2.3",
"less": "^4.1.3",
"less-loader": "^11.1.3",
"lodash": "^4.17.21",
"mini-css-extract-plugin": "^2.7.6",
"mini-svg-data-uri": "^1.4.4",
"style-loader": "^3.3.3",
"toml": "^3.0.0",
"webpack-dev-server": "^4.15.1",
"webpack-merge": "^5.9.0",
"xml-loader": "^1.2.1",
"yamljs": "^0.3.0"
}
}
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
# webpack高级
搭建环境
npm init -y
npm install webpack webpack-cli webpack-dev-server html-webpack-plugin -D
2
创建index.html,src/index.js
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
// 开发模式
mode: 'development',
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 true
})
],
// dev-server
devServer: {
static: './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
package.json
{
"name": "webpack03",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"serve": "webpack serve --open",
"build": "webpack"
},
"author": "",
"license": "ISC",
"devDependencies": {
"html-webpack-plugin": "^5.5.3",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 开发规范
# source-map
webpack已经内置了sourcemap的功能,我们只需要通过简单的配置,将可以开启它。
module.exports = {
// 开启 source map
// 开发中推荐使用 'source-map'
// 生产环境一般不开启 sourcemap
devtool: 'source-map',
}
2
3
4
5
6
production 中devtool的默认值为none
development中devtool的默认值为eval,eval函数的最后有注释,可以让我们在报错的时候追溯到哪个文件报错
当我们执行打包命令之后,我们发现bundle的最后一行总是会多出一个注释,指向打包出的bundle.map.js(sourcemap文件)。 sourcemap文件用来描述 源码文件和bundle文件的代码位置映射关系。基于它,我们将bundle文件的错误信息映射到源码文件上。
除开'source-map'外,还可以基于我们的需求设置其他值,webpack——devtool一共提供了7种SourceMap模式:
模式 | 解释 |
---|---|
eval | 每个module会封装到 eval 里包裹起来执行,并且会在末尾追加注释 //@ sourceURL![]() ![]() |
source-map | 生成一个SourceMap文件![]() ![]() |
hidden-source-map | 和 source-map 一样,但不会在 bundle 末尾追加注释.![]() ![]() |
inline-source-map | 生成一个 DataUrl 形式的 SourceMap 文件.![]() ![]() |
eval-source-map | 每个module会通过eval()来执行,并且生成一个DataUrl形式的SourceMap.![]() ![]() |
cheap-source-map | 生成一个没有列信息(column-mappings)的SourceMaps文件,不包含loader的 sourcemap(譬如 babel 的sourcemap)![]() ![]() |
cheap-module-source-map | 生成一个没有列信息(column-mappings)的SourceMaps文件,同时 loader 的 sourcemap 也被简化为只包含对应行的。![]() ![]() |
要注意的是,生产环境我们一般不会开启sourcemap功能,主要有两点原因:
通过bundle和sourcemap文件,可以反编译出源码————也就是说,线上产物有soucemap文件的话,就意味着有暴漏源码的风险。
我们可以观察到,sourcemap文件的体积相对比较巨大,这跟我们生产环境的追求不同(生产环境追求更小更轻量的bundle)。
有时候我们期望能第一时间通过线上的错误信息,来追踪到源码位置,从而快速解决掉bug以减轻损失。但又不希望sourcemap文件报漏在生产环境,有比较好的方案吗?
- 生产环境和开发环境分离: 在Webpack配置中,将生产环境和开发环境的配置分开。在开发环境中,可以使用能够提供更详细错误信息的sourcemap配置。而在生产环境中,则使用不包含sourcemap文件或者只包含压缩过的sourcemap文件。
- 使用cheap和eval选项: 在开发环境中,可以选择使用
cheap
和eval
选项,例如devtool: 'cheap-module-eval-source-map'
。这样配置可以快速构建sourcemap,但不会暴露原始的源代码,而是使用经过简化和转换的代码。 - 不生成sourcemap: 在生产环境中,可以将
sourcemap
配置设置为空,或者禁用sourcemap的生成,例如devtool: false
。这样在生产环境中不会生成sourcemap文件,避免了泄露源代码的风险。 - 使用source-map-explorer: 如果您担心在生产环境中完全禁用sourcemap会影响问题排查,可以在构建过程中生成sourcemap,但在部署时将sourcemap文件从构建结果中移除。然后,您可以使用
source-map-explorer
工具来手动检查生成的sourcemap文件,以便在需要时进行问题排查。 - 通过服务器生成sourcemap: 将sourcemap文件存储在服务器上,而不是在公开访问的位置,可以确保不会泄露源代码。在出现问题时,只有特定的团队成员可以访问sourcemap文件来进行问题排查。
- 使用JavaScript错误收集服务: 考虑使用错误收集服务,例如Sentry、Rollbar或Bugsnag等。这些服务可以帮助您收集和管理生产环境中的错误信息,而无需在生产环境中公开sourcemap文件。
# devServer
开发环境下,我们往往需要启动一个web服务,方便我们模拟一个用户从浏览器中访问我们的web服务,读取我们的打包产物,以观测我们的代码在客户端的表现。webpack内置了这样的功能,我们只需要简单的配置就可以开启它。
安装
npm install webpack-dev-server -D
devServer.proxy基于强大的中间件 http-proxy-middleware
实现的,因此它支持很多的配置项,我们基于此,可以做应对绝大多数开发场景的定制化配置。
# 开启压缩
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
` devServer: {
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000
},`
// 开发模式
mode: 'development',
devtool: 'cheap-module-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 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
30
31
32
33
为了方便,我们配置一下工程的脚本命令,在package.json的scripts里。
{
//...
"scripts": {
//...
"dev": "webpack serve --mode development"
}
}
2
3
4
5
6
7
注意!如果您需要指定配置文件的路径,请在命令的后面添加 --config [path], 比如:
webpack serve --mode development --config webpack.config.js
# 添加响应头
有些场景需求下,我们需要为所有响应添加headers,来对资源的请求和响应打入标志,以便做一些安全防范,或者方便发生异常后做请求的链路追踪。比如:
module.exports = {
.....
devServer: {
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
headers: {
'd-header-id': 'abcdefg'
}
}
....
}
2
3
4
5
6
7
8
9
10
11
12
13
14
headers的配置也可以传一个函数:
module.exports = {
.....
devServer: {
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
}
},
....
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 开启代理
我们打包出的 js bundle 里有时会含有一些对特定接口的网络请求(ajax/fetch).要注意,此时客户端地址是在 http://localhost:3000/ 下,假设我们的接口来自http://localhost:4001/ ,那么毫无疑问,此时控制台里会报错并提示你跨域。如何解决这个问题? 在开发环境下,我们可以使用devServer自带的proxy功能:
为了模拟跨域,我们先来编写一个服务 src/server.js
const http = require('http')
const app = http.createServer((req,res) => {
if(req.url === '/api/hello') {
res.end('hello, node')
}
})
app.listen(9000, 'localhost', () => {
console.log('localhost:9000服务启动成功')
})
2
3
4
5
6
7
8
9
10
11
启动服务: node ./serve.js
src/index.js 添加发送请求的事件
const button = document.createElement('button')
button.onclick = function() {
fetch('http://localhost:9000/api/hello')
.then(response => response.text())
.then(result => {
console.log(result)
})
}
button.innerHTML = '点我发送请求'
document.body.appendChild(button)
2
3
4
5
6
7
8
9
10
提示存在跨域问题
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
devServer: {
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
` proxy: {
'/api': 'http://localhost:9000'
}`
},
// 开发模式
mode: 'development',
devtool: 'cheap-module-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body' // true|'head'|'body'|false,默认值为 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
现在,对 /api/users 的请求会将请求代理到 http://localhost:9000/api/users 。 如果不希望传递/api,则需要重写路径:
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:9000',
pathRewrite: { '^/api': '' }
}
}
}
}
2
3
4
5
6
7
8
9
10
11
默认情况下,将不接受在 HTTPS 上运行且证书无效的后端服务器。 如果需要,可以这样修改配置:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
devServer: {
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
` secure: false`
}
}
}
}
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
# https
如果想让我们的本地http服务变成https服务,我们只需要这样配置
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
....
devServer: {
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
`https: 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
30
31
32
注意,此时我们访问http://localhost:port 是无法访问我们的服务的,我们需要在地址栏里加前缀:https: 注意:由于默认配置使用的是自签名证书,所以有得浏览器会告诉你是不安全的,但我们依然可以继续访问它。 当然我们也可以提供自己的证书——如果有的话:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
....
devServer: {
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
https: {
cacert: './server.pem',
pfx: './server.pfx',
key: './server.key',
cert: './server.crt',
passphrase: 'webpack-dev-server',
requestCert: 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
30
31
32
33
34
35
36
37
38
39
# http2
如果想要配置http2,那么直接设置:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
....
devServer: {
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
// https: {
// cacert: './server.pem',
// pfx: './server.pfx',
// key: './server.key',
// cert: './server.crt',
// passphrase: 'webpack-dev-server',
// requestCert: true,
// }
` http2: 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
30
31
32
33
34
35
36
37
38
39
40
即可,http2默认自带https自签名证书,当然我们仍然可以通过https配置项来使用自己的证书。
# historyApiFallback
如果我们的应用是个SPA(单页面应用),当路由到/some时(可以直接在地址栏里输入),会发现此时刷新页面后,控制台会报错。
此时打开network,刷新并查看,就会发现问题所在———浏览器把这个路由当作了静态资源地址去请求,然而我们并没有打包出/some这样的资源,所以这个访问无疑是404的。 如何解决它? 这种时候,我们可以通过配置来提供页面代替任何404的静态资源响应:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
...
devServer: {
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
// https: {
// cacert: './server.pem',
// pfx: './server.pfx',
// key: './server.key',
// cert: './server.crt',
// passphrase: 'webpack-dev-server',
// requestCert: true,
// }
http2: true,
`historyApiFallback: true` // 访问不存在的路径默认会跳到index.html
}
}
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
此时重启服务刷新后发现请求变成了index.html。 当然,在多数业务场景下,我们需要根据不同的访问路径定制替代的页面,这种情况下,我们可以使用rewrites这个配置项。 类似这样
配置打包多个html文件
现有文件:src/index.html,src/cart.html,src/js/jquery.js,src/js/common.js,src/js/index.js,src/js/cart.js。
要求:将jquery.js和common.js打包在一起生成一个chunk,作为通用库,index.html和cart.html都需要引用这个打包出来的通用库
index.js和cart.js分别打包生成自己的chunk
index.html中还要引入index.js打包后的chunk,cart中还要引入cart.js打包后的chunk
webpack.config.js文件:
const{resolve}=require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports={
entry:{
vender:['./src/js/jquery.js','./src/js/common.js'],
index:'./src/js/index.js',
cart:'./src/js/cart.js'
},
output:{
path:resolve(__dirname,'build'),
filename:'[name].js'
},
mode:'development',
module:{
rules:[]
},
plugins:[//这里需要打包两个html,所以有两个HtmlWebpackPlugin对象各自进行配置
new HtmlWebpackPlugin({
template:'./src/index.html',
filename:'index.html',
chunks:['vender','index']//配置html需要引入的chunk
}),
new HtmlWebpackPlugin({
template:'./src/cart.html',
filename:'cart.html',
chunks:['vender','cart']//配置html需要引入的chunk
}),
]
}
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
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
devServer: {
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
// https: {
// cacert: './server.pem',
// pfx: './server.pfx',
// key: './server.key',
// cert: './server.crt',
// passphrase: 'webpack-dev-server',
// requestCert: true,
// }
http2: true,
// historyApiFallback: true // 访问不存在的路径默认会跳到index.html
` historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html'},
{ from: /^\/some/, to: '/404.html' },
{ from: /./, to: '/views/404.html' }
]
}`
},
// 开发模式
mode: 'development',
devtool: 'cheap-module-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
` chunks: ['index'] // 要引入的js文件`
}),
` new HtmlWebpackPlugin({
template: './src/views/404.html', // 打包生成的文件的模板
filename: '404.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['']
})`
]
}
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
# 开发服务器主机
如果你在开发环境中起了一个devserve服务,并期望你的同事能访问到它,你只需要配置:
module.exports = {
//...
devServer: {
host: '0.0.0.0',
}
}
2
3
4
5
6
这时候,如果你的同事跟你处在同一局域网下,就可以通过局域网ip来访问你的服务啦。
# 模块热替换与热加载
# 模块热替换
模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除 模块,而无需重新加载整个页面。
启用 webpack 的热模块替换特性,需要配置devServer.hot参数:
webpack.config.js
module.exports = {
//...
devServer: {
`hot: true`
}
}
2
3
4
5
6
HMR 加载样式 如果你配置了style-loader,那么现在已经同样支持样式文件的热替换功能了
先安装
npm install style-loader css-loader -D
然后创建src/assets/index.css
.red {
color: red
}
2
3
配置webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
devServer: {
` hot: true,`
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
// https: {
// cacert: './server.pem',
// pfx: './server.pfx',
// key: './server.key',
// cert: './server.crt',
// passphrase: 'webpack-dev-server',
// requestCert: true,
// }
http2: true,
// historyApiFallback: true // 访问不存在的路径默认会跳到index.html
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html'},
{ from: /^\/some/, to: '/404.html' },
{ from: /./, to: '/views/404.html' }
]
},
// host: '0.0.0.0' // 局域网下共享服务
},
// 开发模式
mode: 'development',
devtool: 'cheap-module-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['index'] // 要引入的js文件
}),
new HtmlWebpackPlugin({
template: './src/views/404.html', // 打包生成的文件的模板
filename: '404.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['']
})
],
` module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}`
}
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
这是因为style-loader的实现使用了module.hot.accept,在CSS依赖模块更新之后,会对 style 标签打补丁。从而实现了这个功能。
当我们更改css样式的时候,页面也会跟着变化
热加载(文件更新时,自动刷新我们的服务和页面) 新版的webpack-dev-server默认已经开启了热加载的功能。 它对应的参数是devServer.liveReload,默认为true。 注意,如果想要关掉它,要将liveReload设置为false的同时,也要关掉hot
module.exports = {
//...
devServer: {
liveReload: false //默认为true,即开启热更新功能。
}
}
2
3
4
5
6
# eslint
eslint是用来扫描我们所写的代码是否符合规范的工具。 往往我们的项目是多人协作开发的,我们期望统一的代码规范,这时候可以让eslint来对我们进行约束。 严格意义上来说,eslint配置跟webpack无关,但在工程化开发环境中,它往往是不可或缺的。
npm install eslint -D
配置eslint,只需要在根目录下添加一个.eslintrc文件(或者.eslintrc.json, .js等)。 当然,我们可以使用eslint工具来自动生成它
npx eslint@^7.32.0 --init
并生成了一个配置文件(.eslintrc.json),这样我们就完成了eslint的基本规则配置。eslint配置文件里的配置项含义如下:
env 指定脚本的运行环境。每种环境都有一组特定的预定义全局变量。此处使用的 browser 预定义了浏览器环境中的全局变量,es6 启用除了 modules 以外的所有 ECMAScript 6 特性(该选项会自动设置 ecmaVersion 解析器选项为 6)。
globals 脚本在执行期间访问的额外的全局变量。也就是 env 中未预定义,但我们又需要使用的全局变量。
extends 检测中使用的预定义的规则集合。
rules 启用的规则及其各自的错误级别,会合并 extends 中的同名规则,定义冲突时优先级更高。
parserOptions ESLint 允许你指定你想要支持的 JavaScript 语言选项。ecmaFeatures 是个对象,表示你想使用的额外的语言特性,这里 jsx 代表启用JSX。ecmaVersion 用来指定支持的 ECMAScript 版本 。默认为 5,即仅支持es5,你可以使用 6、7、8、9 或 10 来指定你想要使用的 ECMAScript 版本。你也可以用使用年份命名的版本号指定为 2015(同 6),2016(同 7),或2017(同 8)或 2018(同 9)或 2019 (same as 10)。上面的 env 中启用了es6,自动设置了ecmaVersion 解析器选项为 6。 plugins plugins 是一个 npm包,通常输出 eslint 内部未定义的规则实现。rules 和 extends 中定义的规则,并不都在 eslint 内部中有实现。比如 extends 中的plugin:react/recommended,其中定义了规则开关和等级,但是这些规则如何生效的逻辑是在其对应的插件 ‘react’ 中实现的
上述命令帮我们生成的.eslintrc.json
{
"env": {
"browser": true,
"es2021": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"windows"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"never"
]
}
}
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
接下来,我们在这个配置文件里额外添加一个规则:
{
...
"rules": {
"no-console": "warn" // 我们在rules里自定义我们的约束规范
}
}
2
3
4
5
6
我们通过命令来让elisnt检测代码——在我们的package.scripts里添加一个脚本命令:
{
"scripts": {
"serve": "webpack serve --open",
"build": "webpack",
"eslint": "eslint ./src"
}
}
2
3
4
5
6
7
- 结合webpack使用
我们期望eslint能够实时提示报错而不必等待执行命令。 这个功能可以通过给自己的IDE(代码编辑器)安装对应的eslint插件来实现。 然而,不是每个IDE都有插件,如果不想使用插件,又想实时提示报错,那么我们可以结合 webpack 的打包编译功能来实现。
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
devServer: {
hot: true,
liveReload: false, // 默认为true,即开启热更新功能。
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
// https: {
// cacert: './server.pem',
// pfx: './server.pfx',
// key: './server.key',
// cert: './server.crt',
// passphrase: 'webpack-dev-server',
// requestCert: true,
// }
http2: true,
// historyApiFallback: true // 访问不存在的路径默认会跳到index.html
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html'},
{ from: /^\/some/, to: '/404.html' },
{ from: /./, to: '/views/404.html' }
]
},
// host: '0.0.0.0' // 局域网下共享服务
},
// 开发模式
mode: 'development',
devtool: 'cheap-module-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['index'] // 要引入的js文件
}),
new HtmlWebpackPlugin({
template: './src/views/404.html', // 打包生成的文件的模板
filename: '404.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['']
})
],
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
` {
test: /\.(js|jsx)$/,
use: ['babel-loader', 'eslint-loader']
}`
]
}
}
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
因为我们使用了devServer,因此需要在devServer下添加一个对应的配置参数:
module.exports = {
//...
devServer: {
liveReload: false, //默认为true,即开启热更新功能。
}
}
2
3
4
5
6
现在我们就可以实时地看到代码里的不规范报错啦。
npm install babel-loader eslint-loader -D
# git-hooks与husky
为了保证团队里的开发人员提交的代码符合规范,我们可以在开发者上传代码时进行校验。 我们常用 husky 来协助进行代码提交时的 eslint 校验。在使用husky之前,我们先来研究一下 git-hooks 。
# git-hooks
我们回到项目的根目录下。运行 ls -a 命令 ———— “-a”可以显示隐藏目录(目录名的第一位是.)。
我们可以看到,存在一个".git"名称的文件夹。
事实上,在我们项目中根目录下运行git命令时,git会根据它来工作。
接来下我们进入到这个文件夹,进一步查看它内部的内容。
cd .git
ls
2
可以看到,当前目录下存在一个hooks文件夹,顾名思义,这个文件夹提供了git 命令相关的钩子。
我们可以看到有很多git命令相关的文件名。比如"pre-commit.sample pre-push.sample"。
# cat命令可以查看一个文件的内容
cat pre-commit.sample
2
OK,它返回了这样的内容,是一串shell注释。翻译过来大概意思是,这是个示例钩子,然后我们看到了这一句话
意思是要启用这个钩子的话,我们就把这个文件的后缀名去掉。
虽然这样对我们本地来讲是可行的,但要注意,.git文件夹的改动无法同步到远端仓库。
所以我们期望将git-hook的执行权移交到外面来。
好的,我们回到项目的根目录下,然后我们新建一个文件夹,暂时命名为".mygithooks"
然后在此文件夹下,新增一个git-hook文件,命名为"pre-commit",并写入以下内容:
echo pre-commit执行啦
好了,我们新建了自己的git-hook,但此时git并不能识别。下面我们执行这行命令
# 项目根目录下
git config core.hooksPath .mygithooks
2
上述命令给我们自己的文件,配置了git-hook的执行权限。
但这个时候我们git commit的话,可能会报这样的waring,并且没有执行我们的shell:
hint: The 'pre-commit' hook was ignored because it's not set as
executable.
hint: You can disable this warning with `git config
advice.ignoredHook false`
2
3
4
这是因为我们的操作系统没有给出这个文件的可执行权限
chmod +x .mygithooks/pre-commit
icacls .mygithooks\pre-commit /grant Everyone:RX
2
现在我们尝试执行git add . && git commit -m "any meesage"。
我们发现控制台日志会先打印 “pre-commit执行啦”。
总结
也就是说,我们搞git-hook的话,要分三步走:
新增任意名称文件夹以及文件pre-commit(这个文件名字比如跟要使用的git-hook名字一致)!
执行以下命令来移交git-hook的配置权限
git config core.hooksPath .mygithooks
1给这个文件添加可执行权限:
chmod +x .mygithooks/pre-commit
1
这时候我们可以在pre-commit里写任意脚本,比如:
eslint src
当eslint扫描代码,出现error时,会在结束扫描时将退出码设为大于0的数字。也就是会报错,这时候commit就无法往下执行啦,我们成功的拦截了此次错误操作。
# husky
husky (opens new window)在升级到7.x后,做了跟我们上述同样的事。
安装它之前,我们需要在package.json中的script里,先添加
"sctript": {
//...others
"prepare": "husky install"
}
2
3
4
prepare是一个npm钩子,意思是安装依赖的时候,会先执行husky install命令。
这个命令就做了上述的123这三件事!
我们安装了7.x的husky会发现,项目根目录下生成了.husky的文件夹。
当然,7.x的husky似乎是有bug的,如果不能正常使用,那么我们只需要验证两件事:
是否移交了git-hook的配置权限?
执行命令 "git config --list"查看core.hooksPath配置是否存在,是否正确指向了.husky。
如果没有,我们只需要手动的给加上就行:
git config core.hooksPath .husky
是否是可执行文件?
参考上述总结中的3即可
这时我们的husky就正常了。
使用步骤
1. npm install husky -D
2. npx husky install
3.
"scripts": {
"prepare": "husky install"
},
2
3
4
5
6
在.husky目录下创建pre-commit文件
npx eslint ./src
Cannot spawn .husky/pre-commit: No such file or directory
报错解决方案:https://blog.csdn.net/fd214333890/article/details/120007434
# 模块与依赖
在模块化编程中,开发者将程序分解为功能离散的文件,并称之为模块。 每个模块都拥有小于完整程序的体积,使得验证、调试及测试变得轻而易举。 精心编写的模块提供了可靠的抽象和封装界限,使得应用程序中每个模块都具备了条理清晰的设计和明确的目的。
Node.js 从一开始就支持模块化编程。 但,浏览器端的模块化还在缓慢支持中——截止到2021,大多主流浏览器已支持ESM模块化,因此基于ESM的打包工具生态逐渐开始活跃。
在前端工程化圈子里存在多种支持 JavaScript 模块化的工具,这些工具各有优势和限制。 Webpack从这些系统中汲取了经验和教训,并将 模块 的概念应用到项目的任何文件中。
# Webpack 模块与解析原理
# webpack 模块
何为 webpack 模块
能在webpack工程化环境里成功导入的模块,都可以视作webpack模块。 与Node.js 模块相比,webpack 模块 能以各种方式表达它们的依赖关系。下面是一些示例:
ES2015 import 语句
CommonJS require() 语句
AMD define 和 require 语句
css/sass/less 文件中的 @import 语句
stylesheet url(...) 或者 HTML
<img src=...>
文件中的图片链接支持的模块类型 Webpack 天生支持如下模块类型:
ECMAScript 模块
CommonJS 模块
AMD 模块
Assets
WebAssembly 模块
而我们早就发现——通过 loader 可以使 webpack 支持多种语言和预处理器语法编写的模块。loader 向 webpack 描述了如何处理非原生模块,并将相关依赖引入到你的 bundles中。包括且不限于:
TypeScript
Sass
Less
JSON
YAML
# compiler与Resolvers
在我们运行webpack的时候(就是我们执行webpack命令进行打包时),其实就是相当于执行了下面的代码:
const webpack = require('webpack');
const compiler = webpack({
// ...这是我们配置的webpackconfig对象
})
2
3
4
webpack的执行会返回一个描述webpack打包编译整个流程的对象,我们将其称之为compiler。 compiler对象描述整个webpack打包流程———它内置了一个打包状态,随着打包过程的进行,状态也会实时变更,同时触发对应的webpack生命周期钩子。 (简单点讲,我们可以将其类比为一个Promise对象,状态从打包前,打包中到打包完成或者打包失败。) 每一次webpack打包,就是创建一个compiler对象,走完整个生命周期的过程。
而webpack中所有关于模块的解析,都是compiler对象里的内置模块解析器去工作的————简单点讲,你可以理解为这个对象上的一个属性,我们称之为Resolvers。 webpack的Resolvers解析器的主体功能就是模块解析,它是基于
enhanced-resolve 这个包实现的。换句话讲,在webpack中,无论你使用怎样的模块引入语句,本质其实都是在调用这个包的api进行模块路径解析。
# 模块解析(resolve)
webpack通过Resolvers实现了模块之间的依赖和引用。举个例子:
import _ from 'lodash';
// 或者
const add = require('./utils/add');
2
3
所引用的模块可以是来自应用程序的代码,也可以是第三方库。 resolver 帮助webpack 从每个 require/import 语句中,找到需要引入到 bundle 中的模块代码。当打包模块时,webpack 使用 enhanced-resolve 来解析文件路径。
(webpack_resolver的代码实现很有思想,webpack基于此进行treeshaking,这个概念我们后面会讲到)。
# webpack中的模块路径解析规则
通过内置的enhanced-resolve,webpack 能解析三种文件路径:
绝对路径
import '/home/me/file'; import 'C:\\Users\\me\\file';
1
2相对路径
import '../utils/reqFetch'; import './styles.css';
1
2这种情况下,使用 import 或 require 的资源文件所处的目录,被认为是上下文目录。 在 import/require 中给定的相对路径,enhanced-resolve会拼接此上下文路径,来生成模块的绝对路径(path.resolve(__dirname, RelativePath) 。 这也是我们在写代码时最常用的方式之一,另一种最常用的方式则是模块路径
模块路径
import 'module'; import 'module/lib/file';
1
2也就是在resolve.modules中指定的所有目录检索模块(node_modules里的模块已经被默认配置了)。 你可以通过配置别名的方式来替换初始模块路径, 具体请参照下面resolve.alias 配置选项
# resolve
- alias
上文中提到我们可以通过 resolve.alias 来自定义配置模块路径。现在我们来是实现一下: 首先,我们src目录下新建一个utils文件夹,并新建一个add.js文件,对外暴露出一个add函数
src/utils/add.js
export default function add(a, b) {
return a + b
}
2
3
然后我们在src/index.js中基于相对路径引用并使用它:
import add from './utils/add'
console.log(add(1,2))
2
3
很好,代码跑起来了并且没有报错。 这时我们期望能用@/utils/add的方式去引用它,于是我们这样写了:
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
` resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},`
devServer: {
hot: true,
liveReload: false, // 默认为true,即开启热更新功能。
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
// https: {
// cacert: './server.pem',
// pfx: './server.pfx',
// key: './server.key',
// cert: './server.crt',
// passphrase: 'webpack-dev-server',
// requestCert: true,
// }
http2: true,
// historyApiFallback: true // 访问不存在的路径默认会跳到index.html
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html'},
{ from: /^\/some/, to: '/404.html' },
{ from: /./, to: '/views/404.html' }
]
},
// host: '0.0.0.0' // 局域网下共享服务
},
// 开发模式
mode: 'development',
devtool: 'cheap-module-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['index'] // 要引入的js文件
}),
new HtmlWebpackPlugin({
template: './src/views/404.html', // 打包生成的文件的模板
filename: '404.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['']
})
],
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(js|jsx)$/,
use: ['babel-loader', 'eslint-loader']
}
]
}
}
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
更改引入方式
import add from '@/utils/add'
console.log(add(1,2))
2
3
- extentions
上述代码中我们发现,只需要“import add from '@utils/add'”, webpack就可以帮我们找到add.js。 事实上,这与import add from '@utils/add.js' 的效果是一致的。 为什么会这样? 原来webpack的内置解析器已经默认定义好了一些 文件/目录 的路径解析规则。 比如当我们
import utils from './utils';
utils是一个文件目录而不是模块(文件),但webpack在这种情况下默认帮我们添加了后缀“/index.js”,从而将此相对路径指向到utils里的index.js。 这是webpack解析器默认内置好的规则。 那么现在有一个问题: 当utils文件夹下同时拥有add.js,add.json时,"@utils/add"会指向谁呢? @utils/add.json
{
"name": "add"
}
2
3
我们发现仍然指向到add.js。 当我们删掉add.js,会发现此时的引入的add变成了一个json对象。 上述现象似乎表明了这是一个默认配置的优先级的问题。 而webpack对外暴露了配置属性: resolve.extentions , 它的用法形如:
module.exports = {
//...
resolve: {
extensions: ['.js', '.json', '.wasm']
}
}
2
3
4
5
6
webpack会按照数组顺序去解析这些后缀名,对于同名的文件,webpack总是会先解析列在数组首位的后缀名的文件。
# 外部扩展(Externals)
有时候我们为了减小bundle的体积,从而把一些不变的第三方库用cdn的形式引入进来,比如jQuery: index.html
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js"></script>
这个时候我们想在我们的代码里使用引入的jquery———但似乎三种模块引入方式都不行,这时候怎么办呢? webpack给我们提供了Externals的配置属性,让我们可以配置外部扩展模块:
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
` externals: {
jquery: 'jQuery'
},`
devServer: {
hot: true,
liveReload: false, // 默认为true,即开启热更新功能。
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
// https: {
// cacert: './server.pem',
// pfx: './server.pfx',
// key: './server.key',
// cert: './server.crt',
// passphrase: 'webpack-dev-server',
// requestCert: true,
// }
http2: true,
// historyApiFallback: true // 访问不存在的路径默认会跳到index.html
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html'},
{ from: /^\/some/, to: '/404.html' },
{ from: /./, to: '/views/404.html' }
]
},
// host: '0.0.0.0' // 局域网下共享服务
},
// 开发模式
mode: 'development',
devtool: 'cheap-module-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['index'] // 要引入的js文件
}),
new HtmlWebpackPlugin({
template: './src/views/404.html', // 打包生成的文件的模板
filename: '404.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['']
})
],
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(js|jsx)$/,
use: ['babel-loader', 'eslint-loader']
}
]
}
}
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
src/index.js
import $ from 'jquery'
console.log($)
2
3
先打包npx webpack,在 npx webpack serve --open
或者采用另外一种方式,这种方式不需要在index.html中引入jquery
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
` externalsType: 'script',
externals: {
// key值必须和包的名称(也就是 import $ from 'jquery'中的from后面的内容一致)
// jquery: 'jQuery' // 这种方式需要在index.html中通过script引入jquery
jquery: [
'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js',
'$'
]
},`
devServer: {
hot: true,
liveReload: false, // 默认为true,即开启热更新功能。
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
// https: {
// cacert: './server.pem',
// pfx: './server.pfx',
// key: './server.key',
// cert: './server.crt',
// passphrase: 'webpack-dev-server',
// requestCert: true,
// }
http2: true,
// historyApiFallback: true // 访问不存在的路径默认会跳到index.html
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html'},
{ from: /^\/some/, to: '/404.html' },
{ from: /./, to: '/views/404.html' }
]
},
// host: '0.0.0.0' // 局域网下共享服务
},
// 开发模式
mode: 'development',
devtool: 'cheap-module-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['index'] // 要引入的js文件
}),
new HtmlWebpackPlugin({
template: './src/views/404.html', // 打包生成的文件的模板
filename: '404.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['']
})
],
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(js|jsx)$/,
use: ['babel-loader', 'eslint-loader']
}
]
}
}
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
运行之后
# 依赖图
每当一个文件依赖另一个文件时,webpack 会直接将文件视为存在依赖关系。 这使得 webpack 可以获取非代码资源,如 images 或 web 字体等。并会把它们作为 依赖 提供给应用程序。 当 webpack 开始工作时,它会根据我们写好的配置,从 入口(entry) 开始,webpack 会递归的构建一个 依赖关系图,这个依赖图包含着应用程序中所需的每个模块,然后将所有模块打包为bundle(也就是output的配置项)。
bundle 分析(bundle analysis)工具:
官方分析工具 (opens new window) 是一个不错的开始。还有一些其他社区支持的可选项
webpack-chart (opens new window): webpack stats 可交互饼图。
webpack-visualizer (opens new window): 可视化并分析你的 bundle,检查哪些模块占用空间,哪些可能是重复使用的。
webpack-bundle-analyzer (opens new window):一个 plugin 和 CLI 工具,它将 bundle 内容展示为一个便捷的、交互式、可缩放的树状图形式。
webpack bundle optimize helper (opens new window):这个工具会分析你的 bundle,并提供可操作的改进措施,以减少 bundle 的大小。
bundle-stats (opens new window):生成一个 bundle 报告(bundle 大小、资源、模块),并比较不同构建之间的结果。
我们来使用 webpack-bundle-analyzer 实现。
# 首先安装这个插件作为开发依赖
# NPM
npm install --save-dev webpack-bundle-analyzer
# Yarn
yarn add -D webpack-bundle-analyzer
2
3
4
5
然后在webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
`const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin`
module.exports = {
entry: {
index: './src/index.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
externalsType: 'script',
externals: {
// key值必须和包的名称(也就是 import $ from 'jquery'中的from后面的内容一致)
// jquery: 'jQuery' // 这种方式需要在index.html中通过script引入jquery
jquery: [
'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js',
'$'
]
},
devServer: {
hot: true,
liveReload: false, // 默认为true,即开启热更新功能。
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
// https: {
// cacert: './server.pem',
// pfx: './server.pfx',
// key: './server.key',
// cert: './server.crt',
// passphrase: 'webpack-dev-server',
// requestCert: true,
// }
http2: true,
// historyApiFallback: true // 访问不存在的路径默认会跳到index.html
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html'},
{ from: /^\/some/, to: '/404.html' },
{ from: /./, to: '/views/404.html' }
]
},
// host: '0.0.0.0' // 局域网下共享服务
},
// 开发模式
mode: 'development',
devtool: 'cheap-module-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['index'] // 要引入的js文件
}),
new HtmlWebpackPlugin({
template: './src/views/404.html', // 打包生成的文件的模板
filename: '404.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['']
}),
`new BundleAnalyzerPlugin()`
],
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(js|jsx)$/,
use: ['babel-loader', 'eslint-loader']
}
]
}
}
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
108
这时我们执行打包命令,发现控制台里打印出下面这样的日志:
npx webpack
执行打包命令后,自动弹出这个页面http://127.0.0.1:8888/
注意: 对于 HTTP/1.1 的应用程序来说,由 webpack 构建的 bundle 非常强大。当浏览器发起请求时,它能最大程度的减少应用的等待时间。 而对于 HTTP/2 来说,我们还可以使用代码分割进行进一步优化。(开发环境观测的话需要在DevServer里进行配置{http2:true, https:false})。
# 扩展功能
# PostCSS 与 CSS模块
PostCSS (opens new window) 是一个用 JavaScript 工具和插件转换 CSS 代码的工具。比如可以使用Autoprefixer (opens new window) 插件自动获取浏览器的流行度和能够支持的属性,并根据这些数据帮我们自动的为 CSS 规则添加前缀,将最新的 CSS 语法转换成大多数浏览器都能理解的语法。
CSS 模块 (opens new window) 能让你永远不用担心命名太大众化而造成冲突,只要用最有意义的名字就行了。
# PostCSS
PosetCSS 与 Webpack 结合,需要安装 style-loader
, css-loader
,postcss-loader
三个loader:
npm install style-loader css-loader postcss-loader -D
npm install autoprefixer postcss-nested -D
2
然后webpack.config.js配置
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
entry: {
index: './src/index.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
externalsType: 'script',
externals: {
// key值必须和包的名称(也就是 import $ from 'jquery'中的from后面的内容一致)
// jquery: 'jQuery' // 这种方式需要在index.html中通过script引入jquery
jquery: [
'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js',
'$'
]
},
devServer: {
hot: true,
liveReload: false, // 默认为true,即开启热更新功能。
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
// https: {
// cacert: './server.pem',
// pfx: './server.pfx',
// key: './server.key',
// cert: './server.crt',
// passphrase: 'webpack-dev-server',
// requestCert: true,
// }
http2: true,
// historyApiFallback: true // 访问不存在的路径默认会跳到index.html
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html'},
{ from: /^\/some/, to: '/404.html' },
{ from: /./, to: '/views/404.html' }
]
},
// host: '0.0.0.0' // 局域网下共享服务
},
// 开发模式
mode: 'development',
devtool: 'cheap-module-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['index'] // 要引入的js文件
}),
new HtmlWebpackPlugin({
template: './src/views/404.html', // 打包生成的文件的模板
filename: '404.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['']
}),
new BundleAnalyzerPlugin()
],
module: {
rules: [
` {
test: /\.css$/,
exclude: /node_modules/,
// use: ['style-loader', 'css-loader']
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
}
},
{
loader: 'postcss-loader'
}
]
},`
{
test: /\.(js|jsx)$/,
use: ['babel-loader', 'eslint-loader']
}
]
}
}
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
- 这里的
importLoaders: 1
是在css-loader
之后指定1个数量的loader(即 postcss-loader)来处理import进来的资源
然后在项目根目录下创建 postcss.config.js :
module.exports = {
plugins: [
require('autoprefixer'),
require('postcss-nested')
]
}
2
3
4
5
6
插件 autoprefixer 提供自动给样式加前缀去兼容浏览器, postcss-nested 提供编写嵌套的样式语法。
最后,在 package.json 内增加如下实例内容:
"browserslist": [
"> 1%",
"last 2 versions"
]
2
3
4
last 2 versions : 每个浏览器中最新的两个版本。
>1% or >= 1% : 全球浏览器使用率大于1%或大于等于1%。
我们写一些样式
src/assets/css/index.css
.flex {
display: flex;
justify-content: center;
align-items: center;
}
2
3
4
5
src/index.js
const div = document.createElement('div')
div.innerHTML = '<div>div里面的内容</div>'
div.classList.add('flex')
document.body.appendChild(div)
2
3
4
运行
# css模版
目前还有一个问题,就是多人编写的样式可能会冲突,开启 CSS 模块可以解决这个问题。 webpack.config.js 配置:
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
` // 开启css模块
modules: true`
}
},
'postcss-loader'
]
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
css还可以支持嵌套 src/assets/css/index.css
.flex {
display: flex;
justify-content: center;
align-items: center;
.box {
background-color: green;
width: 100px;
height: 100px;
}
}
2
3
4
5
6
7
8
9
10
在 js 文件里导入 css 文件:
import './assets/css/index.css'
import add from '@/utils/add'
`// 开启 css 模块后,可以导入模块
import style from './assets/css/index.css'`
import $ from 'jquery'
console.log($)
console.log(add(1,2))
const h1 = document.createElement('h1')
h1.innerHTML = 'index.html'
`h1.classList.add(style.red)`
document.body.appendChild(h1)
console.log('hello, 更改了')
const div = document.createElement('div')
div.innerHTML = `<div class="${style.box}">div里面的内容</div>`
div.classList.add(style.flex)
// div.children[0].classList.add(style.box)
document.body.appendChild(div)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
运行
也可以部分开启 CSS 模块模式,比如全局样式可以冠以 .global 前缀,如:
*.global.css 普通模式
*.css css module模式
这里统一用 global 关键词进行识别。用正则表达式匹配文件
// css module
{
test: new RegExp(`^(?!.*\\.global).*\\.css`),
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[hash:base64:6]'
}
},
{
loader: 'postcss-loader'
}
],
exclude:[path.resolve(__dirname, '..', 'node_modules')]
}
// 普通模式
{
test: new RegExp(`^(.*\\.global).*\\.css`),
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
},
{
loader: 'postcss-loader'
}
],
exclude:[path.resolve(__dirname, '..', 'node_modules')]
}
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
# Web Works
有时我们需要在客户端进行大量的运算,但又不想让它阻塞我们的js主线程。你可能第一时间考虑到的是异步。
但事实上,运算量过大(执行时间过长)的异步也会阻塞js事件循环,甚至会导致浏览器假死状态。
这时候,HTML5的新特性 WebWorker就派上了用场。
在此之前,我们简单的了解下这个特性。
html5之前,打开一个常规的网页,浏览器会启用几个线程?
一般而言,至少存在三个线程(公用线程不计入在内):
分别是js引擎线程(处理js)、GUI渲染线程(渲染页面)、浏览器事件触发线程(控制交互)。
当一段JS脚本长时间占用着处理机,就会挂起浏览器的GUI更新,而后面的事件响应也被排在队列中得不到处理,从而造成了浏览器被锁定进入假死状态。
现在如果遇到了这种情况,我们可以做的不仅仅是优化代码————html5提供了解决方案,webworker。
webWorkers提供了js的后台处理线程的API,它允许将复杂耗时的单纯js逻辑处理放在浏览器后台线程中进行处理,让js线程不阻塞UI线程的渲染。多个线程间也是可以通过相同的方法进行数据传递。
//new Worker(scriptURL: string | URL, options?: WorkerOptions)
new Worker("someWorker.js");
2
也就是说,需要单独写一个js脚本,然后使用new Worker来创建一个Work线程实例。
这意味着并不是将这个脚本当做一个模块引入进来,而是单独开一个线程去执行这个脚本。
我们知道,常规模式下,我们的webpack工程化环境只会打包出一个bundle.js,那我们的worker脚本怎么办?
也许你会想到设置多入口(Entry)多出口(ouotput)的方式。
事实上不需要那么麻烦,webpack4的时候就提供了worker-loader专门配置webWorker。
令人开心的是,webpack5之后就不需要用loader啦,因为webpack5内置了这个功能。
第一步
创建一个work脚本 work.js,我们甚至不需要写任何内容,我们的重点不是webWorker的使用,而是在webpack环境中使用这个特性。当然,也可以写点什么,比如:
self.onmessage = ({data: { question } }) => { console.log('接受到的参数', question) self.postMessage({ answer: 100 }) }
1
2
3
4
5
6在 index.js 中使用它
const worker = new Worker(new URL('./work.js', import.meta.url)) worker.postMessage({ question: '请告诉我你想要的数字' }) worker.onmessage = ({ data: {answer} }) => { console.log(answer) }
1
2
3
4
5
6
7(import.meta.url这个参数能够锁定我们当前的这个模块——注意,它不能在commonjs中使用。)
这时候我们执行打包命令,会发现,dist目录下除了bundle.js之外,还有另外一个xxx.bundle.js!
这说明我们的webpack5自动的将被new Work使用的脚本单独打出了一个bundle。
我们加上刚才的问答代码,执行npm run dev,发现它是能够正常工作。
并且在network里也可以发现多了一个src_worker_js.bundle.js。
# TypeScript
首先,当然是安装我们的ts和对应的loader。
npm install --save-dev typescript ts-loader
接下来我们需要在项目根目录下添加一个ts的配置文件————tsconfig.json,我们可以用ts自带的工具来自动化生成它。
npx tsc --init
我们发现生成了一个tsconfig.json,里面注释掉了绝大多数配置。现在,根据我们想要的效果来打开对应的配置。
好了,接下来我们新增一个src/index.ts,内置一些内容。
lodash这里会报错,请看下面的使用第三方类库
如果想在ts里面使用lodash,直接使用npm install lodash是会报错的
import _ from 'lodash'
const age: number = 18
console.log(age)
console.log(_.join([]))
2
3
4
然后我们别忘了更改我们的entry及配置对应的loder。
当然,还有resolve.extensions,将.ts放在.js之前,这样它会先找.ts。
注意,如果我们使用了sourceMap,一定记得和上面的ts配置一样,设置sourcemap为true。
也别忘记在我们的webpack.config.js里,添加sourcemap,就像我们之前课程里讲的那样。
更改如下:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
entry: {
index: './src/index.js',
index2: './src/index2.ts'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
},
`extensions: ['.tsx', '.ts', '.js']`
},
externalsType: 'script',
externals: {
// key值必须和包的名称(也就是 import $ from 'jquery'中的from后面的内容一致)
// jquery: 'jQuery' // 这种方式需要在index.html中通过script引入jquery
jquery: [
'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js',
'$'
]
},
devServer: {
hot: true,
liveReload: false, // 默认为true,即开启热更新功能。
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
// https: {
// cacert: './server.pem',
// pfx: './server.pfx',
// key: './server.key',
// cert: './server.crt',
// passphrase: 'webpack-dev-server',
// requestCert: true,
// }
http2: true,
// historyApiFallback: true // 访问不存在的路径默认会跳到index.html
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html'},
{ from: /^\/some/, to: '/404.html' },
{ from: /./, to: '/views/404.html' }
]
},
// host: '0.0.0.0' // 局域网下共享服务
},
// 开发模式
mode: 'development',
devtool: 'cheap-module-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
`chunks: ['index', 'index2'] // 要引入的js文件`
}),
new HtmlWebpackPlugin({
template: './src/views/404.html', // 打包生成的文件的模板
filename: '404.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['']
}),
new BundleAnalyzerPlugin()
],
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
// use: ['style-loader', 'css-loader']
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
// 开启css模块
modules: true
}
},
{
loader: 'postcss-loader'
}
]
},
{
test: /\.(js|jsx)$/,
use: ['babel-loader', 'eslint-loader']
},
` {
test: /\.(ts|tsx)$/,
use: 'ts-loader',
exclude: /node_modules/
}`
]
}
}
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
使用第三方类库
在从 npm 上安装第三方库时,一定要记得同时安装这个库的类型声明文件(typing definition)。
我们可以从 TypeSearch中找到并安装这些第三方库的类型声明文件(https://www.typescriptlang.org/dt/search?search=)
举个例子,如果想安装 lodash 类型声明文件,我们可以运行下面的命令:
npm install --save-dev @types/lodash
1eslint & ts
注意,如果要使用eslint,使用初始化命令的时候,记得选择“使用了typesctipt”。
npx eslint --init # 往下选择的时候选择使用了typesctipt
1
2如果已经配置了eslint,但没有配置ts相关的配置,那么我们需要先安装对应的plugin
yarn add -D @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
1
2注意如果需要用到react的话,记得也要安装
yarn add -D eslint-plugin-react@latest
1vue或者其他常用框架同样如此,一般都会有专门的plugin。
然后我们队.esilntrc进行更改~
{ "env": { "browser": true, "es2021": true }, "extends": [ "eslint:recommended", // 如果需要react的话 "plugin:react/recommended", "plugin:@typescript-eslint/recommended" ], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaFeatures": { "jsx": true }, // 如果需要react的话 "ecmaVersion": 13, "sourceType": "module" }, "plugins": [ "react", "@typescript-eslint" ], "rules": { // ...一些自定义的rules "no-console": "error" } }
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
# 多页面应用
# entry配置
单个入口(简写)语法
用法:
entry: string | [string]
webpack.config.js
module.exports = { entry: './path/to/my/entry/file.js' }
1
2
3entry 属性的单个入口语法,参考下面的简写:
webpack.config.js
module.exports = { entry: { main: './path/to/my/entry/file.js' } }
1
2
3
4
5我们也可以将一个文件路径数组传递给 entry 属性,这将创建一个所谓的 "multi-main entry"。在你想要一次注入多个依赖文件,并且将它们的依赖关系绘制在一个"chunk" 中时,这种方式就很有用。
module.exports = { entry: ['./src/file_1.js', './src/file_2.js'], output: { filename: 'bundle.js' } }
1
2
3
4
5
6当你希望通过一个入口(例如一个库)为应用程序或工具快速设置 webpack 配置时,单一入口的语法方式是不错的选择。然而,使用这种语法方式来扩展或调整配置的灵活性不大。
对象语法
用法:
entry: { <entryChunkName> string | [string] } | {}
module.exports = { entry: { app: './src/app.js', adminApp: './src/adminApp.js' } }
1
2
3
4
5
6描述入口的对象:
用于描述入口的对象。你可以使用如下属性:
dependOn
: 当前入口所依赖的入口。它们必须在该入口被加载前被加载。filename
: 指定要输出的文件名称。import
: 启动时需加载的模块。library
: 指定 library 选项,为当前 entry 构建一个 library。runtime
: 运行时 chunk 的名字。如果设置了,就会创建一个新的运行时chunk。在 webpack 5.43.0 之后可将其设为 false 以避免一个新的运行时chunk。publicPath
: 当该入口的输出文件在浏览器中被引用时,为它们指定一个公共URL 地址。请查看 output.publicPath。
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
` entry: {
// index: './src/index.js',
// index2: './src/index2.ts'
main: {
import: ['./src/index.js', './src/index2.ts'],
dependOn: 'lodash',
// 这个名字优先级高于output中的filename
filename: 'script1/[name].js'
},
main2: {
import: ['./src/index3.js', './src/index4.js'],
dependOn: 'lodash',
filename: 'script2/[name].js'
},
lodash: {
import: 'lodash',
filename: 'common/[name].js'
}
},`
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
},
extensions: ['.tsx', '.ts', '.js']
},
externalsType: 'script',
externals: {
// key值必须和包的名称(也就是 import $ from 'jquery'中的from后面的内容一致)
// jquery: 'jQuery' // 这种方式需要在index.html中通过script引入jquery
jquery: [
'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js',
'$'
]
},
devServer: {
hot: true,
liveReload: false, // 默认为true,即开启热更新功能。
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
// https: {
// cacert: './server.pem',
// pfx: './server.pfx',
// key: './server.key',
// cert: './server.crt',
// passphrase: 'webpack-dev-server',
// requestCert: true,
// }
http2: true,
// historyApiFallback: true // 访问不存在的路径默认会跳到index.html
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html'},
{ from: /^\/some/, to: '/404.html' },
{ from: /./, to: '/views/404.html' }
]
},
// host: '0.0.0.0' // 局域网下共享服务
},
// 开发模式
mode: 'development',
devtool: 'cheap-module-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
` chunks: ['main', 'main2', 'lodash'] // 要引入的js文件`
}),
new HtmlWebpackPlugin({
template: './src/views/404.html', // 打包生成的文件的模板
filename: '404.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['']
}),
new BundleAnalyzerPlugin()
],
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
// use: ['style-loader', 'css-loader']
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
// 开启css模块
modules: true
}
},
{
loader: 'postcss-loader'
}
]
},
{
test: /\.(js|jsx)$/,
use: ['babel-loader', 'eslint-loader']
},
{
test: /\.(ts|tsx)$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
}
}
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# 配制index.html模版
生成多个HTML文件
要生成多个HTML文件,请在插件数组中多次声明插件。
webpack.config.js
{ entry: 'index.js', output: { path: __dirname + '/dist', filename: 'index_bundle.js' }, plugins: [ new HtmlWebpackPlugin(), // Generates default index.html new HtmlWebpackPlugin({ // Also generate a test.html filename: 'test.html', template: 'src/assets/test.html' }) ] }
1
2
3
4
5
6
7
8
9
10
11
12
13
14编写自己的模板
如果默认生成的HTML不能满足您的需要,您可以提供自己的模板。最简单的方法是使用 template 选项并传递自定义HTML文件。html 网页包插件将自动将所有必要的CSS、JS、manifest和favicon文件注入标记中。
plugins: [ new HtmlWebpackPlugin({ title: 'Custom template', // Load a custom template (lodash by default) template: 'index.html' }) ]
1
2
3
4
5
6
7index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> </body> </html>
1
2
3
4
5
6
7
8
9
多应用页面配置
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
entry: {
// index: './src/index.js',
// index2: './src/index2.ts'
main: {
import: ['./src/index.js', './src/index2.ts'],
dependOn: 'lodash',
// 这个名字优先级高于output中的filename
filename: 'script1/[name].js'
},
main2: {
import: ['./src/index3.js', './src/index4.js'],
dependOn: 'lodash',
filename: 'script2/[name].js'
},
lodash: {
import: 'lodash',
filename: 'common/[name].js'
}
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
},
extensions: ['.tsx', '.ts', '.js']
},
externalsType: 'script',
externals: {
// key值必须和包的名称(也就是 import $ from 'jquery'中的from后面的内容一致)
// jquery: 'jQuery' // 这种方式需要在index.html中通过script引入jquery
jquery: [
'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js',
'$'
]
},
devServer: {
hot: true,
liveReload: false, // 默认为true,即开启热更新功能。
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
// https: {
// cacert: './server.pem',
// pfx: './server.pfx',
// key: './server.key',
// cert: './server.crt',
// passphrase: 'webpack-dev-server',
// requestCert: true,
// }
http2: true,
// historyApiFallback: true // 访问不存在的路径默认会跳到index.html
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html'},
{ from: /^\/some/, to: '/404.html' },
{ from: /./, to: '/views/404.html' }
]
},
// host: '0.0.0.0' // 局域网下共享服务
},
// 开发模式
mode: 'development',
devtool: 'cheap-module-source-map',
` plugins: [
new HtmlWebpackPlugin({
title: '自定义标题',
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['main', 'main2', 'lodash'], // 要引入的js文件
publicPath: 'http://www.a.com/'
}),
new HtmlWebpackPlugin({
template: './src/views/404.html', // 打包生成的文件的模板
filename: '404.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: [''],
publicPath: 'http://www.b.com/'
}),
new BundleAnalyzerPlugin()
],`
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
// use: ['style-loader', 'css-loader']
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
// 开启css模块
modules: true
}
},
{
loader: 'postcss-loader'
}
]
},
{
test: /\.(js|jsx)$/,
use: ['babel-loader', 'eslint-loader']
},
{
test: /\.(ts|tsx)$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
}
}
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
在多页面应用程序中,server 会拉取一个新的 HTML 文档给你的客户端。页面重新加载此新文档,并且资源被重新下载。然而,这给了我们特殊的机会去做很多事,例如使用 optimization.splitChunks (opens new window) 为页面间共享的应用程序代码创建bundle。由于入口起点数量的增多,多页应用能够复用多个入口起点之间的大量代码/模块,从而可以极大地从这些技术中受益。
# 单页面应用和多页面应用的区别
单页面应用
指只有一个主页面的应用,一开始只需加载一次
js,css
等相关资源。所有的内容都包含在主页面,对每一个功能模块组件化 (opens new window)。单页应用跳转,就是切换相关组件,仅刷新局部资源。单页面应用(single page web application,SPA): 只有一个页面,并在用户与应用程序交互时动态更新该页面的Web应用程序。
优点: 1、用户体验好,快,内容改变不需要加载整个页面,对服务器压力较小。 2、前后端分离 (opens new window)。 3、页面转场体验好。
缺点: 1、不利于SEO,因为搜索引擎在做网页排名的时候,要根据网页内容给网页权重,来进行网页的排名。搜索引擎是可以识别HTML内容的,而多页应用的每个页面所有的内容都放在HTML中,所以SEO排名效果好,反之单页面应用效果不好。 2、导航不可用,需要自己实现导航。例如:vue中的vue-router。 3、初次加载耗时长。 4、页面复杂度更高。
多页面应用
指有多个独立的页面的应用,每个页面必须重复加载
js,css
等相关资源。多页应用跳转,需要整页资源刷新。多页面应用(multi page web application,MPA): 一个应用中有多个页面,页面跳转时是整页刷新的Web应用程序。
优点: 1、利于SEO。 2、更容易扩展。 3、更易数据分析。
缺点: 1、程序开发成本高。 2、服务器端压力大。 3、用户体验相对较差。
# Tree shaking
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 (opens new window) 特性,例如 import 和export 。这个术语和概念实际上是由 ES2015 模块打包工具 rollup 普及起来的。
webpack 2 正式版本内置支持 ES2015 模块(也叫做 harmony modules)和未使用模块检测能力。新的 webpack 4 正式版本扩展了此检测能力,通过 package.json的 "sideEffects" 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 "pure(纯正 ES2015 模块)",由此可以安全地删除文件中未使用的部分
utils/add.js
export function add(a, b) {
return a + b
}
2
3
app.js
import { add } from './utils/add'
console.log(add(1, 2))
2
未加optimization: {usedExports: true}
之前,并且模式是development
我们把模式调为生产模式production
,并且加上optimization: {usedExports: true}
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
entry: {
// index: './src/index.js',
// index2: './src/index2.ts'
app: './src/app.js',
main: {
import: ['./src/index.js', './src/index2.ts'],
dependOn: 'lodash',
// 这个名字优先级高于output中的filename
filename: 'script1/[name].js'
},
main2: {
import: ['./src/index3.js', './src/index4.js'],
dependOn: 'lodash',
filename: 'script2/[name].js'
},
lodash: {
import: 'lodash',
filename: 'common/[name].js'
}
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
},
extensions: ['.tsx', '.ts', '.js']
},
externalsType: 'script',
` mode: 'production',
optimization: {
usedExports: 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
打包后发现无用的代码全部都消失了。
处于好奇,webpack是如何完美的避开没有使用的代码的呢?
就是 Webpack 没看到你使用的代码。Webpack 跟踪整个应用程序的import/export 语句,因此,如果它看到导入的东西最终没有被使用,它会认为那是未引用代码(或叫做“死代码”—— dead-code ),并会对其进行 tree-shaking 。
// 这会被看作“活”代码,不会做 tree-shaking
import { add } from './math'
console.log(add(5, 6))
// 导入并赋值给 JavaScript 对象,但在接下来的代码里没有用到
// 这就会被当做“死”代码,会被 tree-shaking
import { add, minus } from './math'
console.log(add(5, 6))
// 导入但没有赋值给 JavaScript 对象,也没有在代码里用到
// 这会被当做“死”代码,会被 tree-shaking
import { add, minus } from './math'
console.log('hello webpack')
// 导入整个库,但是没有赋值给 JavaScript 对象,也没有在代码里用到
// 非常奇怪,这竟然被当做“活”代码,因为 Webpack 对库的导入和本地代码导入的处理方式不同。
import { add, minus } from './math'
import 'lodash'
console.log('hello webpack')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 这会被看作“活”代码,不会做 tree-shaking
import { add } from './math'
console.log(add(1, 2))
2
3
// 导入并赋值给 JavaScript 对象,但在接下来的代码里没有用到
// 这就会被当做“死”代码,会被 tree-shaking
import { add, minus } from './math'
console.log(add(1, 2))
2
3
4
// 导入但没有赋值给 JavaScript 对象,也没有在代码里用到
// 这会被当做“死”代码,会被 tree-shaking
import { add, minus } from './math'
console.log('hello webpack')
2
3
4
// 导入整个库,但是没有赋值给 JavaScript 对象,也没有在代码里用到
// 非常奇怪,这竟然被当做“活”代码,因为 Webpack 对库的导入和本地代码导入的处理方式不同。
import { add, minus } from './math'
import 'lodash'
console.log('hello webpack')
2
3
4
5
# sideEffects
注意 Webpack 不能百分百安全地进行 tree-shaking。有些模块导入,只要被引入,就会对应用程序产生重要的影响。一个很好的例子就是全局样式表,或者设置全局配置的JavaScript 文件。
Webpack 认为这样的文件有“副作用”。具有副作用的文件不应该做 tree-shaking,因为这将破坏整个应用程序。
Webpack 的设计者清楚地认识到不知道哪些文件有副作用的情况下打包代码的风险,因此webpack4默认地将所有代码视为有副作用。这可以保护你免于删除必要的文件,但这意味着 Webpack 的默认行为实际上是不进行 tree-shaking。值得注意的是webpack5默认会进行 tree-shaking。
如何告诉 Webpack 你的代码无副作用,可以通过 package.json 有一个特殊的属性sideEffects,就是为此而存在的。
可能有3种情况:
true
如果不指定其他值的话。这意味着所有的文件都有副作用,也就是没有一个文件可以 tree-shaking。
false
告诉 Webpack 没有文件有副作用,所有文件都可以 tree-shaking。
数组[…]
是文件路径数组。它告诉 webpack,除了数组中包含的文件外,你的任何文件都没有副作用。因此,除了指定的文件之外,其他文件都可以安全地进行 tree-shaking。
webpack4 曾经不进行对 CommonJs 导出和 require() 调用时的导出使用分析。webpack 5 增加了对一些 CommonJs 构造的支持,允许消除未使用的 CommonJs导出,并从 require() 调用中跟踪引用的导出名称。
package.json
{
"name": "webpack03",
"version": "1.0.0",
"description": "",
"main": "index.js",
` "sideEffects": ["*.css", "*.global.js"],`
"scripts": {
"serve": "webpack serve --open",
"build": "webpack",
"eslint": "eslint ./src",
"prepare": "husky install"
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/lodash": "^4.14.196",
"autoprefixer": "^10.4.14",
"babel-loader": "^9.1.3",
"css-loader": "^6.8.1",
"eslint": "^7.32.0",
"eslint-loader": "^4.0.2",
"html-webpack-plugin": "^5.5.3",
"husky": "^8.0.3",
"postcss-loader": "^7.3.3",
"postcss-nested": "^6.0.1",
"style-loader": "^3.3.3",
"ts-loader": "^9.4.4",
"typescript": "^5.1.6",
"webpack": "^5.88.2",
"webpack-bundle-analyzer": "^4.9.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.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
# 渐进式网络应用程序 PWA
渐进式网络应用程序(progressive web application - PWA),是一种可以提供类似于native app(原生应用程序) 体验的 web app(网络应用程序)。PWA 可以用来做很多事。其中最重要的是,在**离线(offline)**时应用程序能够继续运行功能。这是通过使用名为 Service Workers (opens new window) 的 web 技术来实现的。
# 非离线环境下运行
到目前为止,我们一直是直接查看本地文件系统的输出结果。通常情况下,真正的用户是通过网络访问 web app;用户的浏览器会与一个提供所需资源(例如, .html ,.js 和 .css 文件)的 server 通讯。
我们通过搭建一个拥有更多基础特性的 server 来测试下这种离线体验。这里使用http-server package: npm install http-server --save-dev
。还要修改package.json 的 scripts 部分,来添加一个 start script:
{
...
"scripts": {
"start": "http-server dist"
}
...
}
2
3
4
5
6
7
注意:默认情况下,webpack DevServer (opens new window) 会写入到内存。我们需要启用devserverdevmiddleware.writeToDisk (opens new window) 配置项,来让 http-server 处理 ./dist 目录中的文件。
devServer: {
devMiddleware: {
index: true,
writeToDisk: true
}
}
2
3
4
5
6
如果你之前没有操作过,先得运行命令 npm run build 来构建你的项目。然后运行命令 npm start 。应该产生以下输出:
如果你打开浏览器访问 http://localhost:8080 (即 http://127.0.0.1 ),你应该会看到 webpack 应用程序被 serve 到 dist 目录。如果停止 server 然后刷新,则 webpack 应用程序不再可访问
# 添加 Workbox
添加 workbox-webpack-plugin 插件,然后调整 webpack.config.js 文件:
npm install workbox-webpack-plugin --save-dev
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
`const WorkboxPlugin = require('workbox-webpack-plugin');`
module.exports = {
entry: {
// index: './src/index.js',
// index2: './src/index2.ts'
app: './src/app.js',
main: {
import: ['./src/index.js', './src/index2.ts'],
dependOn: 'lodash',
// 这个名字优先级高于output中的filename
filename: 'script1/[name].js'
},
main2: {
import: ['./src/index3.js', './src/index4.js'],
dependOn: 'lodash',
filename: 'script2/[name].js'
},
lodash: {
import: 'lodash',
filename: 'common/[name].js'
}
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
},
extensions: ['.tsx', '.ts', '.js']
},
externalsType: 'script',
externals: {
// key值必须和包的名称(也就是 import $ from 'jquery'中的from后面的内容一致)
// jquery: 'jQuery' // 这种方式需要在index.html中通过script引入jquery
jquery: [
'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js',
'$'
]
},
devServer: {
hot: true,
liveReload: false, // 默认为true,即开启热更新功能。
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
// https: {
// cacert: './server.pem',
// pfx: './server.pfx',
// key: './server.key',
// cert: './server.crt',
// passphrase: 'webpack-dev-server',
// requestCert: true,
// }
// http2: true,
// historyApiFallback: true // 访问不存在的路径默认会跳到index.html
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html'},
{ from: /^\/some/, to: '/404.html' },
{ from: /./, to: '/views/404.html' }
]
},
// host: '0.0.0.0' // 局域网下共享服务
},
// 开发模式
// mode: 'development',
mode: 'production',
// devtool: 'inline-source-map',
plugins: [
new HtmlWebpackPlugin({
title: '自定义标题',
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: ['main', 'main2', 'lodash'], // 要引入的js文件
// publicPath: 'http://www.a.com/'
}),
new HtmlWebpackPlugin({
template: './src/views/404.html', // 打包生成的文件的模板
filename: '404.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: [''],
// publicPath: 'http://www.b.com/'
}),
new BundleAnalyzerPlugin(),
` new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何“旧的” ServiceWorkers
clientsClaim: true,
skipWaiting: true
})`
],
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
// use: ['style-loader', 'css-loader']
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
// 开启css模块
modules: true
}
},
{
loader: 'postcss-loader'
}
]
},
// {
// test: /\.(js|jsx)$/,
// use: ['babel-loader', 'eslint-loader']
// },
{
test: /\.(ts|tsx)$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
optimization: {
usedExports: 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
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
现在你可以看到,生成了两个额外的文件: service-worker.js
和名称冗长的workbox-0858eadd.js
。 service-worker.js
是 Service Worker 文件, workbox-0858eadd.js
是 service-worker.js 引用的文件,所以它也可以运行。你本地生成的文件可能会有所不同;但是应该会有一个 service-worker.js 文件。
# 注册 Service Worker
src/index.js
if('ServiceWorkers' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/server-work.js').then(registration => {
console.log('SW registered: ', registration)
}).catch(registrationError => {
console.log('SW registration failed: ', registrationError)
})
})
}
2
3
4
5
6
7
8
9
再次运行 npx webpack 来构建包含注册代码版本的应用程序。然后用 npm start启动服务。访问 http://localhost:8080 并查看 console 控制台。在那里你应该看到:
当我们把应用关掉以后,我们会发现,还可以运行
点击取消注册就可以删除了
# shimming 预置依赖
webpack
compiler 能够识别遵循 ES2015 模块语法、CommonJS 或 AMD 规范编写的模块。然而,一些 third party(第三方库) 可能会引用一些全局依赖(例如 jQuery中的 $ )。因此这些 library 也可能会创建一些需要导出的全局变量。这些 "brokenmodules(不符合规范的模块)" 就是 shimming(预置依赖) 发挥作用的地方。
shim 另外一个极其有用的使用场景就是:当你希望 polyfill (opens new window) 扩展浏览器能力,来支持到更多用户时。在这种情况下,你可能只是想要将这些 polyfills 提供给需要修补(patch)的浏览器(也就是实现按需加载)。
# Shimming 预置全局变量
让我们开始第一个 shimming 全局变量的用例。还记得我们之前用过的 lodash吗?出于演示目的,例如把这个应用程序中的模块依赖,改为一个全局变量依赖。要实现这些,我们需要使用 ProvidePlugin
插件。
使用 ProvidePlugin (opens new window) 后,能够在 webpack 编译的每个模块中,通过访问一个变量来获取一个 package。如果 webpack 看到模块中用到这个变量,它将在最终bundle 中引入给定的 package。让我们先移除 lodash
的import
语句,改为通过插件提供它:
src/app2.js
console.log('app2.js', _.join(['hello', 'webpack'], ' '))
webpack.config.js
const path = require('path')
`const webpack = require('webpack')`
const HtmlWebpackPlugin = require('html-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const WorkboxPlugin = require('workbox-webpack-plugin');
module.exports = {
entry: {
app2: './src/app2.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
},
extensions: ['.tsx', '.ts', '.js']
},
externalsType: 'script',
externals: {
// key值必须和包的名称(也就是 import $ from 'jquery'中的from后面的内容一致)
// jquery: 'jQuery' // 这种方式需要在index.html中通过script引入jquery
jquery: [
'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js',
'$'
]
},
devServer: {
hot: true,
liveReload: false, // 默认为true,即开启热更新功能。
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
},
// 开发模式
// mode: 'development',
mode: 'production',
// devtool: 'inline-source-map',
plugins: [
new HtmlWebpackPlugin({
title: '自定义标题',
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
// chunks: ['main', 'main2', 'lodash'], // 要引入的js文件
chunks: ['app2']
// publicPath: 'http://www.a.com/'
}),
new BundleAnalyzerPlugin(),
new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何“旧的” ServiceWorkers
clientsClaim: true,
skipWaiting: true
}),
` new webpack.ProvidePlugin({
_: 'lodash'
})`
],
optimization: {
usedExports: 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
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
我们本质上所做的,就是告诉 webpack……
如果你遇到了至少一处用到 _ 变量的模块实例,那请你将 lodash package 引入进来,并将其提供给需要用到它的模块。
还可以使用 ProvidePlugin 暴露出某个模块中单个导出,通过配置一个“数组路径”(例如 [module, child, ...children?] )实现此功能。所以,我们假想如下,无论 join 方法在何处调用,我们都只会获取到 lodash 中提供的 join 方法。
src/index.js
console.log(join(['hello', 'webpack'], ' '))
webpack.config.js
const webpack = require('webpack')
module.exports = {
mode: 'development',
entry: './src/index.js',
plugins: [
new webpack.ProvidePlugin({
// _: 'lodash'
join: ['lodash', 'join']
})
]
}
2
3
4
5
6
7
8
9
10
11
这样就能很好的与 tree shaking (opens new window) 配合,将 lodash library 中的其余没有用到的导出去除。
# 细粒度 Shimming
一些遗留模块依赖的 this 指向的是 window 对象。在接下来的用例中,调整我们的 index.js当模块运行在 CommonJS 上下文中,这将会变成一个问题,也就是说此时的 this
指向的是module.exports
。在这种情况下,你可以通过使用 imports-loader (opens new window) 覆盖 this 指向:
npm install imports-loader -D
src/app2.js
this.alert('hello webpack')
webpack.config.js
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const WorkboxPlugin = require('workbox-webpack-plugin');
module.exports = {
entry: {
// index: './src/index.js',
// index2: './src/index2.ts'
app: './src/app.js',
main: {
import: ['./src/index.js', './src/index2.ts'],
dependOn: 'lodash',
// 这个名字优先级高于output中的filename
filename: 'script1/[name].js'
},
main2: {
import: ['./src/index3.js', './src/index4.js'],
dependOn: 'lodash',
filename: 'script2/[name].js'
},
lodash: {
import: 'lodash',
filename: 'common/[name].js'
},
app2: './src/app2.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
},
extensions: ['.tsx', '.ts', '.js']
},
externalsType: 'script',
externals: {
// key值必须和包的名称(也就是 import $ from 'jquery'中的from后面的内容一致)
// jquery: 'jQuery' // 这种方式需要在index.html中通过script引入jquery
jquery: [
'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js',
'$'
]
},
devServer: {
hot: true,
liveReload: false, // 默认为true,即开启热更新功能。
// static: {
// directory: path.join(__dirname, 'dist')
// }, // 默认是把/dist目录当作web服务的根目录
compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
port: 3000,
// headers: {
// 'd-header-id': 'abcdefg'
// }
headers: () => { // headers配置也可以传递一个函数
// return { 'X-Bar': ['key1=value1', 'key2=value2'] }
return {
'X-Random': Math.random(),
'x-header-id': 'abcdefg',
'X-Bar': ['key1=value1', 'key2=value2']
}
},
proxy: {
'/api': {
target: 'http://localhost:9000',
// pathRewrite: { '^/api': '' },
secure: false
}
},
// https: true
// https: {
// cacert: './server.pem',
// pfx: './server.pfx',
// key: './server.key',
// cert: './server.crt',
// passphrase: 'webpack-dev-server',
// requestCert: true,
// }
// http2: true,
// historyApiFallback: true // 访问不存在的路径默认会跳到index.html
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html'},
{ from: /^\/some/, to: '/404.html' },
{ from: /./, to: '/views/404.html' }
]
},
// host: '0.0.0.0' // 局域网下共享服务
},
// 开发模式
// mode: 'development',
mode: 'production',
// devtool: 'inline-source-map',
plugins: [
new HtmlWebpackPlugin({
title: '自定义标题',
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
// chunks: ['main', 'main2', 'lodash'], // 要引入的js文件
chunks: ['app2']
// publicPath: 'http://www.a.com/'
}),
new HtmlWebpackPlugin({
template: './src/views/404.html', // 打包生成的文件的模板
filename: '404.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
chunks: [''],
// publicPath: 'http://www.b.com/'
}),
new BundleAnalyzerPlugin(),
new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何“旧的” ServiceWorkers
clientsClaim: true,
skipWaiting: true
}),
new webpack.ProvidePlugin({
_: 'lodash'
})
],
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
// use: ['style-loader', 'css-loader']
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
// 开启css模块
modules: true
}
},
{
loader: 'postcss-loader'
}
]
},
// {
// test: /\.(js|jsx)$/,
// use: ['babel-loader', 'eslint-loader']
// },
{
test: /\.(ts|tsx)$/,
use: 'ts-loader',
exclude: /node_modules/
},
` {
test: require.resolve('./src/app2.js'),
use: 'imports-loader?wrapper=window',
}`
]
},
optimization: {
usedExports: 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
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# 全局 Exports
让我们假设,某个 library 创建出一个全局变量,它期望 consumer(使用者) 使用这个变量。为此,我们可以在项目配置中,添加一个小模块来演示说明
npm install exports-loader -D
src/global.js
const file = 'example.txt'
const helpers = {
test: function () {
console.log('test something')
},
parse: function () {
console.log('parse something')
}
}
2
3
4
5
6
7
8
9
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
app2: './src/app2.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
// 开发模式
// mode: 'development',
mode: 'production',
// devtool: 'inline-source-map',
plugins: [
new HtmlWebpackPlugin({
title: '自定义标题',
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
// chunks: ['main', 'main2', 'lodash'], // 要引入的js文件
chunks: ['app2']
// publicPath: 'http://www.a.com/'
}),
],
module: {
rules: [
{
test: require.resolve('./src/app2.js'),
use: 'imports-loader?wrapper=window',
},
` {
test: require.resolve('./src/global.js'),
use: 'exports-loader?type=commonjs&exports=file,multiple|helpers.parse|parse',
}`
]
},
optimization: {
usedExports: 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
此时,在我们的 entry 入口文件中(即 src/app2.js ),可以使用 const {file, parse } = require('./global.js'); ,可以保证一切将顺利运行。
const {file, parse} = require('./global')
console.log(file, parse)
2
# 加载 Polyfills
有很多方法来加载 polyfill。例如,想要引入 @babel/polyfill (opens new window) 我们只需如下操作:
npm install --save @babel/polyfill
然后,使用 import 将其引入到app2.js:
import '@babel/polyfill'
console.log(Array.from([1, 2, 3], x => x + x))
2
注意,这种方式优先考虑正确性,而不考虑 bundle 体积大小。为了安全和可靠,polyfill/shim 必须运行于所有其他代码之前,而且需要同步加载,或者说,需要在所有 polyfill/shim 加载之后,再去加载所有应用程序代码。 社区中存在许多误解,即现代浏览器“不需要”polyfill,或者 polyfill/shim 仅用于添加缺失功能 - 实际上,它们通常用于修复损坏实现(repair broken implementation),即使是在最现代的浏览器中,也会出现这种情况。 因此,最佳实践仍然是,不加选择地和同步地加载所有polyfill/shim,尽管这会导致额外的 bundle 体积成本。
# 进一步优化 Polyfills
不建议使用 import @babel/polyfilll
。因为这样做的缺点是会全局引入整个polyfill包,比如 Array.from 会全局引入,不但包的体积大,而且还会污染全局环境。
babel-preset-env
package 通过 browserslist (opens new window) 来转译那些你浏览器中不支持的特性。这个 preset 使用 useBuiltIns (opens new window) 选项,默认值是 false
,这种方式可以将全局babel-polyfill
导入,改进为更细粒度的 import
格式:
import 'core-js/modules/es7.string.pad-start';
import 'core-js/modules/es7.string.pad-end';
import 'core-js/modules/web.timers';
import 'core-js/modules/web.immediate';
import 'core-js/modules/web.dom.iterable';
2
3
4
5
安装 @babel/preset-env 及 相关的包
npm i babel-loader @babel/core @babel/preset-env -D
npm i core-js -D
2
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
app2: './src/app2.js'
},
output: {
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname, './dist'),
// 打包前清理 dist 文件夹
clean: true,
// 开发模式不需要配置缓存
filename: 'scripts/[name].js'
},
// 开发模式
// mode: 'development',
mode: 'production',
// devtool: 'inline-source-map',
plugins: [
new HtmlWebpackPlugin({
title: '自定义标题',
template: './index.html', // 打包生成的文件的模板
filename: 'index.html', // 打包生成的文件名称。默认为index.html
// 也就是<script src="bundle.js"></script>的位置
inject: 'body', // true|'head'|'body'|false,默认值为 true
// chunks: ['main', 'main2', 'lodash'], // 要引入的js文件
chunks: ['app2']
// publicPath: 'http://www.a.com/'
})
],
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
// use: ['style-loader', 'css-loader']
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
// 开启css模块
modules: true
}
},
{
loader: 'postcss-loader'
}
]
},
` {
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
targets: [
'last 1 version',
'> 1%'
],
useBuiltIns: 'usage',
// 添加corejs配置
corejs: 3
}
]
]
}
}
}`
]
},
optimization: {
usedExports: 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
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
useBuiltIns: 参数有 “entry”、”usage”、false 三个值
默认值是 false ,此参数决定了babel打包时如何处理@babel/polyfilll 语句。
**“entry”😗*会将文件中 import @babel/polyfilll 语句 结合 targets ,转换为一系列引入语句,去掉目标浏览器已支持的 polyfilll 模块,不管代码里有没有用到,只要目标浏览器不支持都会引入对应的 polyfilll 模块。
“usage”: 不需要手动在代码里写 import @babel/polyfilll ,打包时会自动根据实际代码的使用情况,结合 targets 引入代码里实际用到部分 polyfilll 模块
false: 对 import‘@babel/polyfilll’不作任何处理,也不会自动引入 polyfilll 模块。需要注意的是在 webpack 打包文件配置的 entry 中引入的 @babel/polyfill 不会根据useBuiltIns 配置任何转换处理。
由于@babel/polyfill在7.4.0中被弃用,我们建议直接添加corejs并通过corejs选项设置版本。
# polyfill和babel的关系
Babel与Polyfill的关系和区别 (opens new window)
Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API。
- babel只转化新的语法,不负责实现新版本js中新增的api
- polyfill 负责实现新版本js中新增的api
- 所以在兼容的时候一般是 babel + polyfill都用到,所以babel-polyfill 一步到位
//使用babel转码前的代码
require('babel-polyfill');
(x => x * 2)(1);
var b = Array.of(1,2,4);
2
3
4
//使用babel转码后的代码
'use strict';
require('babel-polyfill');
(function (x) {
return x * 2;
})(1);
var b = Array.of(1, 2, 4);
2
3
4
5
6
7
8
这个例子中,Babel转换了ES6的箭头函数句法,但对于Array.of并没有转换,因为Array.of是ES6的API。有些同学可能还是不清楚哪些是句法,哪些是API。
if (!Array.of) {
Array.of = function() {
return Array.prototype.slice.call(arguments);
};
2
3
4
我们都知道Array是ES5就存在的一个对象了,但是该对象没有of方法,这个方法就是一个API,但ES6有这个API了。对于不支持ES6的浏览器,我们通过引入babel-polyfill使其支持ES6的API,类似于上面的代码来实现的。
# 创建 library
src/index.js
export function add(x, y) {
return x + y
}
2
3
第一种方式
webpack.config.js
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'mylib.js',
library: 'mylib'
}
}
// 或者
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'mylib.js',
// library: 'mylib'
library: {
name: 'mylib',
type: 'window'
}
}
}
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
demo/index.html
<script src="../dist/mylib.js"></script>
<script>
console.log(mylib.add(5, 6))
</script>
2
3
4
打包: npx webpack
运行 npx http-server
第二种方式
webpack.config.js
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'mylib.js',
// library: 'mylib'
library: {
name: 'mylib',
type: 'commonjs'
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
demo/app.js
const {mylib} = require('../dist/mylib')
console.log(mylib)
console.log(mylib.add(5, 6))
2
3
第三种方式
webpack.config.js
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'mylib.js',
// library: 'mylib'
// library: {
// name: 'mylib',
// type: 'commonjs'
// }
library: {
name: 'mylib',
type: 'umd'
},
globalObject: 'globalThis'
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
这种方式引入srcipt标签可以用,require也可以使用
# 创建一个library
假设我们正在编写一个名为 webpack-numbers 的小的 library,可以将数字 1 到 5转换为文本表示,反之亦然,例如将 2 转换为 'two'。
使用 npm 初始化项目,然后安装 webpack , webpack-cli 和 lodash :
npm i webpack webpack-cli lodash -D
我们将 lodash 安装为 devDependencies 而不是 dependencies ,因为我们不需要将其打包到我们的库中,否则我们的库体积很容易变大。
src/ref.json
[
{
"num": 1,
"word": "One"
},
{
"num": 2,
"word": "Two"
},
{
"num": 3,
"word": "Three"
},
{
"num": 4,
"word": "Four"
},
{
"num": 5,
"word": "Five"
},
{
"num": 0,
"word": "Zero"
}
]
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
src/index.js
import _ from 'lodash'
import numRef from './ref.json'
export function numToWord(num) {
return _.reduce(numRef, (accum, ref) => {
return ref.num === num ? ref.word : accum
}, '')
}
export function wordToNum(word) {
return _.reduce(numRef, (accum, ref) => {
return ref.word === word ? ref.num : accum
}, -1)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
webpack.config.js
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'webpack-numbers.js'
}
}
2
3
4
5
6
7
8
9
# 导出 Library
到目前为止,一切都应该与打包应用程序一样,这里是不同的部分 - 我们需要通过output.library 配置项暴露从入口导出的内容。
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
library: "webpackNumbers"
}
}
2
3
4
5
6
7
8
9
我们暴露了 webpackNumbers ,以便用户可以通过 script 标签使用。
<script src="https://example.org/webpack-numbers.js"></script>
<script>
window.webpackNumbers.wordToNum('Five');
</script>
2
3
4
然而它只能通过被 script 标签引用而发挥作用,它不能运行在 CommonJS、AMD、Node.js 等环境中。
作为一个库作者,我们希望它能够兼容不同的环境,也就是说,用户应该能够通过以下方式使用打包后的库:
CommonJS module require:
const webpackNumbers = require('webpack-numbers'); // ... webpackNumbers.wordToNum('Two');
1
2
3AMD module require:
require(['webpackNumbers'], function (webpackNumbers) { // ... webpackNumbers.wordToNum('Two'); });
1
2
3
4script tag:
<!DOCTYPE html> <html> ... <script src="https://example.org/webpack-numbers.js"> </script> <script> // ... // Global variable webpackNumbers.wordToNum('Five'); // Property in the window object window.webpackNumbers.wordToNum('Five'); // ... </script> </html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14我们更新 output.library 配置项,将其 type 设置为 '
umd
' :webpack.config.js
const path = require('path') module.exports = { mode: 'production', entry: './src/index.js', ` output: { path: path.resolve(__dirname, './dist'), filename: 'webpack-numbers.js', library: { name: 'webpackNumbers', type: 'umd' }, globalObject: 'globalThis' }` }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
现在 webpack 将打包一个库,其可以与 CommonJS、AMD 以及 script 标签使用。
# 外部化lodash
现在,如果执行 webpack
,你会发现创建了一个体积相当大的文件。如果你查看这个文件,会看到 lodash 也被打包到代码中。在这种场景中,我们更倾向于把lodash
当作 peerDependency
。也就是说,consumer(使用者) 应该已经安装过lodash
。因此,你就可以放弃控制此外部 library ,而是将控制权让给使用 library的 consumer。
这可以使用 externals 配置来完成:
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'webpack-numbers.js',
library: {
name: 'webpackNumbers',
type: 'umd'
},
globalObject: 'globalThis'
},
` externals: {
lodash: {
commonjs: 'lodash',
commonjs2: 'lodash',
amd: 'lodash',
root: '_'
}
}`
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
这意味着你的 library 需要一个名为 lodash 的依赖,这个依赖在 consumer 环境中必须存在且可用。
# 外部化限制
对于想要实现从一个依赖中调用多个文件的那些 library:
import A from 'library/one';
import B from 'library/two';
2
无法通过在 externals 中指定整个 library 的方式,将它们从 bundle 中排除。而是需要逐个或者使用一个正则表达式,来排除它们。
module.exports = {
//...
externals: [
'library/one',
'library/two',
// 匹配以 "library/" 开始的所有依赖
/^library\/.+$/
]
}
2
3
4
5
6
7
8
9
# 优化输出
为优化生产环境下的输出结果,我们还需要将生成 bundle 的文件路径,添加到package.json 中的 main 字段中。
package.json
{
...
"main": "dist/webpack-numbers.js"
...
}
2
3
4
5
或者,按照这个 指南,将其添加为标准模块:
{
...
"module": "src/index.js",
...
}
2
3
4
5
这里的 key(键) main 是参照 package.json (opens new window) 标准,而 module 是参照 一个提案,此提案允许 JavaScript 生态系统升级使用 ES2015 模块,而不会破坏向后兼容性
# 发布为 npm package
现在,你可以 将其发布为一个 npm package (opens new window),并且在 unpkg.com (opens new window) 找到它,并分发给你的用户。
进入npm官网 (opens new window),注册账号
测试包
完成创建包后,您应该在本地对其进行测试,以了解在 npm 上部署它后它的行为。
要在本地测试您的包,首先,在您的项目目录中运行以下命令:
npm link
1上面的命令允许您在本地引用您的包,就像使用 npm 托管的包一样。
然后,在项目的根目录中创建另一个文件夹并 cd 进入它。
mkdir test cd test
1
2在 test 文件夹中,运行以下命令将您的包链接到该文件夹:
npm link name-of-your-package
1最后,创建一个虚拟文件来测试你的包的功能。
创建一个test/app.js
const webpackNumbers = require('webpack-numbers') console.log(webpackNumbers.wordToNum('One'))
1
2完成测试后,您可以安全地删除测试文件夹。
在您的系统上登录 npm
这里只想临时使用默认的镜像源
npm login --registry https://registry.npmjs.org/
1注意这里不能是淘宝源
默认镜像: https://registry.npmjs.org/
npm config get registry # 当前的npm registry npm config set registry <registry-url> # 修改 registry npm config set registry https://registry.npm.taobao.org/ # 修改为淘宝的
1
2
3如果您只需要临时更改npm registry,可以使用以下命令,在不更改默认registry的情况下,在单个命令中将npm registry传递给npm
npm install <package-name> --registry <registry-url>
1
发布包
npm publish --registry https://registry.npmjs.org/
1报错,网上找了一下原因,可能是包名重复,我们在package.json中修改name
发布成功
最后可以测试一下,能否使用
创建一个test文件夹
npm init
npm i webpack-numbers-dep
2
创建app.js
const webpackNumbers = require('webpack-numbers-dep')
console.log(webpackNumbers.wordToNum('One'))
2
然后node app.js
运行测试
# 模块联邦
# 什么是模块联邦
精读《Webpack5 新特性 - 模块联邦》 (opens new window)
多个独立的构建可以组成一个应用程序,这些独立的构建之间不应该存在依赖关系,因此可以单独开发和部署它们。
这通常被称作微前端,但并不仅限于此。
Webpack5 模块联邦让 Webpack 达到了线上 Runtime 的效果,让代码直接在项目间利用 CDN 直接共享,不再需要本地安装 Npm 包、构建再发布了!
我们知道 Webpack 可以通过 DLL 或者 Externals 做代码共享时 Common Chunk,但不同应用和项目间这个任务就变得困难了,我们几乎无法在项目之间做到按需热插拔。
NPM 方式共享模块
如下图所示,正常的代码共享需要将依赖作为 Lib 安装到项目,进行 Webpack 打包构建再上线,如下图:
对于项目 Home 与 Search,需要共享一个模块时,最常见的办法就是将其抽成通用依赖并分别安装在各自项目中。
虽然 Monorepo 可以一定程度解决重复安装和修改困难的问题,但依然需要走本地编译。
UMD 方式共享模块
真正 Runtime 的方式可能是 UMD 方式共享代码模块,即将模块用 Webpack UMD模式打包,并输出到其他项目中。这是非常普遍的模块共享方式:
对于项目 Home 与 Search,直接利用 UMD 包复用一个模块。但这种技术方案问题也很明显,就是包体积无法达到本地编译时的优化效果,且库之间容易冲突。
微前端方式共享模块
微前端:micro-frontends (MFE) 也是最近比较火的模块共享管理方式,微前端就是要解决多项目并存问题,多项目并存的最大问题就是模块共享,不能有冲突。
由于微前端还要考虑样式冲突、生命周期管理,所以本文只聚焦在资源加载方式上。微前端一般有两种打包方式:
子应用独立打包,模块更解耦,但无法抽取公共依赖等。
整体应用一起打包,很好解决上面的问题,但打包速度实在是太慢了,不具备水平扩展能力。
模块联邦
终于提到本文的主角了,作为 Webpack5 内置核心特性之一的 FederatedModule:
从图中可以看到,这个方案是直接将一个应用的包应用于另一个应用,同时具备整体应用一起打包的公共依赖抽取能力。
# 应用案例
本案例模拟三个应用: Nav 、 Search 及 Home 。每个应用都是独立的,又通过模块联邦联系到了一起。
Nav导航
创建nav文件夹
npm init -y
npm i webpack webpack-cli webpack-dev-server html-webpack-plugin -D
2
src/header.js
const Header = () => {
const header = document.createElement('h1')
header.innerHTML = '网站公共头部内容'
return header
}
export default Header
2
3
4
5
6
7
src/index.js
import Header from './header'
const div = document.createElement('div')
div.appendChild(Header)
document.body.appendChild(div)
2
3
4
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
mode: 'production',
entry: './src/index.js',
plugins: [
new HtmlWebpackPlugin(),
new ModuleFederationPlugin({
// 模块联邦名字
name: 'nav',
// 外部访问的资源名字
filename: 'remoteEntry.js',
// 引用的外部资源
remotes: {},
// 暴露给外部的资源列表
exposes: {
'./header': './src/header.js'
},
// 共享模块,如lodash
shared: {}
})
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
启动应用
npx webpack serve --port 3003
Home首页
src/HomeList
const HomeList = (num) => {
let str = '<ul>'
for (let i = 0; i < num; i++) {
str += '<li>item ' + i + '</li>'
}
str += '</ul>'
return str
}
export default HomeList
2
3
4
5
6
7
8
9
10
src/index.js
import HomeList from "./HomeList";
import('nav/header').then((Header) => {
const div = document.createElement('div')
div.appendChild(Header.default())
document.body.appendChild(div)
document.body.innerHTML += HomeList(5)
})
2
3
4
5
6
7
8
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
mode: 'production',
entry: './src/index.js',
plugins: [
new HtmlWebpackPlugin(),
new ModuleFederationPlugin({
// 模块联邦名字
name: 'home',
// 外部访问的资源名字
filename: 'remoteEntry.js',
// 引用的外部资源
remotes: {
nav: 'nav@http://localhost:3003/remoteEntry.js'
},
// 暴露给外部的资源列表
exposes: {
'./HomeList': './src/HomeList.js'
},
// 共享模块,如lodash
shared: {}
})
]
}
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
启动应用
npx webpack serve --port 3001
search搜索
src/index.js
Promise.all([import('nav/header'), import('home/HomeList')])
.then(([{default: Header}, {default: HomeList}]) => {
document.body.appendChild(Header())
document.body.innerHTML += HomeList(4)
console.log('执行了')
console.log(Header, HomeList)
})
2
3
4
5
6
7
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
mode: 'production',
entry: './src/index.js',
plugins: [
new HtmlWebpackPlugin(),
new ModuleFederationPlugin({
// 模块联邦名字
name: 'search',
// 外部访问的资源名字
filename: 'remoteEntry.js',
// 引用的外部资源
remotes: {
nav: 'nav@http://localhost:3003/remoteEntry.js',
home: 'home@http://localhost:3001/remoteEntry.js'
},
// 暴露给外部的资源列表
exposes: {},
// 共享模块,如lodash
shared: {}
})
]
}
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
要保证3001和3003都启动,再启动3002
# 提升构建性能
# 通用环境
无论你是在 开发环境 (opens new window) 还是在 生产环境 (opens new window) 下运行构建脚本,以下最佳实践都会有所帮助。
更新到最新版本
使用最新的 webpack 版本。我们会经常进行性能优化。webpack 的最新稳定版本是:
将 Node.js 更新到最新版本,也有助于提高性能。除此之外,将你的 package 管理工具(例如 npm 或者 yarn )更新到最新版本,也有助于提高性能。较新的版本能够建立更高效的模块树以及提高解析速度。
loader
将 loader 应用于最少数量的必要模块。而非如下:
module.exports = { //... module: { rules: [ { test: /\.js$/, loader: 'babel-loader' } ] } }
1
2
3
4
5
6
7
8
9
10
11通过使用 include 字段,仅将 loader 应用在实际需要将其转换的模块:
const path = require('path'); module.exports = { //... module: { rules: [ { test: /\.js$/, include: path.resolve(__dirname, 'src'), loader: 'babel-loader' } ] } }
1
2
3
4
5
6
7
8
9
10
11
12
13引导(bootstrap)
每个额外的 loader/plugin 都有其启动时间。尽量少地使用工具。
解析
以下步骤可以提高解析速度:
减少
resolve.modules
,resolve.extensions
,resolve.mainFiles
,resolve.descriptionFiles
中条目数量,因为他们会增加文件系统调用的次数。如果你不使用 symlinks(例如
npm link
或者yarn link
),可以设置resolve.symlinks: false
。如果你使用自定义 resolve plugin 规则,并且没有指定 context 上下文,可以设置
resolve.cacheWithContext: false
。
小即是快(smaller = faster)
减少编译结果的整体大小,以提高构建性能。尽量保持 chunk 体积小。
使用数量更少/体积更小的 library。
在多页面应用程序中使用
SplitChunksPlugin
。在多页面应用程序中使用
SplitChunksPlugin
,并开启 async 模式。移除未引用代码。
只编译你当前正在开发的那些代码。
持久化缓存
在 webpack 配置中使用 cache 选项。使用 package.json 中的 "postinstall"清除缓存目录。
将 cache 类型设置为内存或者文件系统。 memory 选项很简单,它告诉 webpack在内存中存储缓存,不允许额外的配置:
webpack.config.js
module.exports = { //... cache: { type: 'memory' } }
1
2
3
4
5
6自定义 plugin/loader
对它们进行概要分析,以免在此处引入性能问题。
dll
使用
DllPlugin
为更改不频繁的代码生成单独的编译结果。这可以提高应用程序的编译速度,尽管它增加了构建过程的复杂度。worker 池(worker pool)
thread-loader
可以将非常消耗资源的 loader 分流给一个 worker pool。不要使用太多的 worker,因为 Node.js 的 runtime 和 loader 都有启动开销。最小化 worker 和 main process(主进程) 之间的模块传输。进程间通讯(IPC,inter process communication)是非常消耗资源的。
Progress plugin
将 ProgressPlugin 从 webpack 中删除,可以缩短构建时间。请注意,
ProgressPlugin 可能不会为快速构建提供太多价值,因此,请权衡利弊再使用。
# 开发环境
增量编译
使用 webpack 的 watch mode(监听模式)。而不使用其他工具来 watch 文件和调用webpack 。内置的 watch mode 会记录时间戳并将此信息传递给 compilation 以使缓存失效。
在某些配置环境中,watch mode 会回退到 poll mode(轮询模式)。监听许多文件会导致 CPU 大量负载。在这些情况下,可以使用 watchOptions.poll 来增加轮询的间隔时间。
在内存中编译
下面几个工具通过在内存中(而不是写入磁盘)编译和 serve 资源来提高性能:
webpack-dev-server
webpack-hot-middleware
webpack-dev-middleware
stats.toJson 加速
webpack 4 默认使用
stats.toJson()
输出大量数据。除非在增量步骤中做必要的统计,否则请避免获取 stats 对象的部分内容。 webpack-dev-server 在 v3.1.3 以后的版本,包含一个重要的性能修复,即最小化每个增量构建步骤中,从 stats 对象获取的数据量。Devtool
需要注意的是不同的 devtool 设置,会导致性能差异。
"eval"具有最好的性能,但并不能帮助你转译代码。
如果你能接受稍差一些的 map 质量,可以使用
cheap-source-map
变体配置来提高性能使用 eval-source-map 变体配置进行增量编译。
在大多数情况下,最佳选择是
eval-cheap-module-source-map
。避免在生产环境下才会用到的工具
某些 utility, plugin 和 loader 都只用于生产环境。例如,在开发环境下使用TerserPlugin 来 minify(压缩) 和 mangle(混淆破坏) 代码是没有意义的。通常在开发环境下,应该排除以下这些工具:
TerserPlugin
[fullhash] / [chunkhash] / [contenthash]
AggressiveSplittingPlugin
AggressiveMergingPlugin
ModuleConcatenationPlugin
最小化 entry chunk
Webpack 只会在文件系统中输出已经更新的 chunk。某些配置选项(HMR,
output.chunkFilename
的[name] / [chunkhash]/[contenthash]
,[fullhash]
)来说,除了对已经更新的 chunk 无效之外,对于 entry chunk 也不会生效。确保在生成 entry chunk 时,尽量减少其体积以提高性能。下面的配置为运行时代码创建了一个额外的 chunk,所以它的生成代价较低:
避免额外的优化步骤
Webpack 通过执行额外的算法任务,来优化输出结果的体积和加载性能。这些优化适用于小型代码库,但是在大型代码库中却非常耗费性能:
module.exports = { // ... optimization: { removeAvailableModules: false, removeEmptyChunks: false, splitChunks: false } }
1
2
3
4
5
6
7
8输出结果不携带路径信息
Webpack 会在输出的 bundle 中生成路径信息。然而,在打包数千个模块的项目中,这会导致造成垃圾回收性能压力。在
options.output.pathinfo
设置中关闭:module.exports = { // ... output: { pathinfo: false } }
1
2
3
4
5
6Node.js 版本 8.9.10-9.11.1
Node.js v8.9.10 - v9.11.1 中的 ES2015 Map 和 Set 实现,存在 性能回退 (opens new window)。
Webpack 大量地使用这些数据结构,因此这次回退也会影响编译时间。
之前和之后的 Node.js 版本不受影响。
TypeScript loader
你可以为 loader 传入
transpileOnly
选项,以缩短使用 ts-loader 时的构建时间。使用此选项,会关闭类型检查。如果要再次开启类型检查,请使用ForkTsCheckerWebpackPlugin (opens new window) 。使用此插件会将检查过程移至单独的进程,可以加快 TypeScript 的类型检查和 ESLint 插入的速度module.exports = { // ... test: /\.tsx?$/, use: [ { loader: 'ts-loader', options: { transpileOnly: true } } ] }
1
2
3
4
5
6
7
8
9
10
11
12