webpack
1.webpack 介绍
webpack是一个工具,可以打包 JavaScript 应用程序 (支持 ESM 和 CommonJS),可以扩展为支持许多不同的静态资源,例如: images , fonts 和 stylesheets 。 webpack 关心性能和加载时间;它始终在改进或添加新功能,例如:异步地加载和 预先加载代码文件,以便为你的项目和用户提供最佳体验。
运行npm init ,初始化一个项目。初始化成功之后就会出现package.json文件
安装webpcak
npm install --save-dev webpack
# 或指定版本
npm install --save-dev webpack@<version>
提示: 是否使用 --save-dev 取决于你的应用场景。假设你仅使用 webpack 进行构建 操作,那么建议你在安装时使用 --save-dev 选项,因为可能你不需要在生产环 境上使用 webpack。如果需要应用于生产环境,请忽略 --save-dev 选项
如果你使用 webpack v4+ 版本,并且想要在命令行中调用 webpack ,你还需要安装 CLI。
npm install --save-dev webpack-cli
2.第一次允许webpack
运行webpack
我们先创建一些html和js文件。
创建的目录如下:
在js中写一些函数,然后在html中引用。
index.js
function hello(){
console.log("hello webpack")
}
hello();
index.html
<body>
<script src="./index.js"></script>
</body>
运行webpack
先进入项目目录,我的就是1.运行webpack这个目录。如果是全局安装的webpack,可以使用 webpack命令。如果只是局部安装,用下面命令
npx webpack
看见如下截图
可以看见项目目录多了一个文件夹dist
自定义 Webpack 配置
实际上, webpack-cli 给我们提供了丰富的终端命令行指令,可以通过 webpack -- help 来查看:
$ webpack -- help
Usage: webpack [entries...] [options]
Alternative usage to run commands: webpack [command] [options]
The build tool for modern web applications.
Options:
-c, --config <value...> Provide path to a webpack configuration file e.g. ./webpack.config.js.
--config-name <value...> Name of the configuration to use.
-m, --merge Merge two or more configurations using 'webpack-merge'.
--env <value...> Environment passed to the configuration when it is a function.
--node-env <value> Sets process.env.NODE_ENV to the specified value.
--progress [value] Print compilation progress during build.
-j, --json [value] Prints result as JSON or store it in a file.
-d, --devtool <value> Determine source maps to use.
--no-devtool Do not generate source maps.
--entry <value...> The entry point(s) of your application e.g. ./src/main.js.
--mode <value> Defines the mode to pass to webpack.
--name <value> Name of the configuration. Used when loading multiple configurations.
-o, --output-path <value> Output location of the file generated by webpack e.g. ./dist/.
--stats [value] It instructs webpack on how to treat the stats e.g. verbose.
--no-stats Disable stats output.
-t, --target <value...> Sets the build target e.g. node.
--no-target Negative 'target' option.
-w, --watch Watch for files changes.
--no-watch Do not watch for file changes.
--watch-options-stdin Stop watching when stdin stream has ended.
--no-watch-options-stdin Do not stop watching when stdin stream has ended.
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. To see all available options you need to
install 'webpack', 'webpack-dev-server'.
version|v [commands...] 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'.
Webpack documentation: https://webpack.js.org/.
CLI documentation: https://webpack.js.org/api/cli/.
Made with ♥ by the webpack team.
是命令行不方便也不直观,而且还不利于保存配置的内容。因此,webpack 还给 我们提供了通过配置文件,来自定义配置参数的能力。我们在项目的目录下新建一个webpack.config.js来自定义配置webpack.
module.exports = {
// entry为文件入口
entry: './src/index.js',
// 为打包输出出口
output: {
// 自定义输出的名字
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: './dist'
},
// 开发模式
mode: 'none'
}
entry:是定义打包文件的入口
output: 是打包文件的出口,里面包含了filename,定义的输出打包的文件名称,path将打包文件放在那里。
在用 npx webpack来进行运行,发现报错
这里输出的路径需要用到绝对路径,我们用path模块来获取文件的真实路径。先引入path模块,在path.resolve(__dirname,'./dist')来获得真实的路径。
const path = require("path")
module.exports = {
// entry为文件入口
entry: './src/index.js',
// 为打包输出出口
output: {
// 自定义输出的名字
filename: 'bundle.js',
// 输出文件夹必须定义为绝对路径
path: path.resolve(__dirname,'./dist')
},
// 开发模式
mode: 'none'
}
再运行npx webpack,就会发现成功了。
然后去html中将打包的index引入,运行html成功。
3.自动引入资源
到目前为止,我们都是在 index.html 文件中手动引入所有资源,然而随着应用程 序增长,如果继续手动管理 index.html 文件,就会变得困难起来。然而,通过一 些插件可以使这个过程更容易管控。通过插件来达到让打包的js文件自动引入到index.html中。
插件
插件 是 webpack 的 核心 功能。插件可以用于执行一些特定的任务,包括:打包优 化,资源管理,注入环境变量等。Webpack 自身也是构建于你在 webpack 配置中 用到的 相同的插件系统 之上! 想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。 多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而 多次使用同一个插件,这时需要通过使用 new 操作符来创建一个插件实例。 Webpack 提供很多开箱即用的 插件。怎么去寻找这些插件呢?
[点击这里去] www.webpackjs.com/plugins/
使用 HtmlWebpackPlugin
HtmlWebpackPlugin就是可以打包html文件,并且可以自动将js文件引入到html文件中。
创建2.HtmlWebpackPlugin项目,将1中的文件复制进2中。
安装插件
npm install --save-dev html-webpack-plugin
我们先将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>
<script src="./index.js"></script>
hello webpack
</body>
</html>
调整 webpack.config.js 文件:将插件HtmlWebpackPlugin引入项目。然后通过plugins属性引入。然后实例化插件
const path = require("path")
// 引入HtmlWebpackPlugin插件
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname,'./dist')
},
mode: 'none',
plugins:[
// 实例化插件
new HtmlWebpackPlugin()
]
}
然后运行npx webpack
打包后,我们发现这个 dist/index.html 似乎与先前的 index.html 并没有关 系, HtmlWebpackPlugin 会默认生成它自己的 index.html 文件,并且所有的 bundle(bundle.js) 会自动添加到 html 中。 能否基于原有的 index.html 文件打包生成新的 index.html 呢?可以通过阅读 HtmlWebpackPlugin 插件提供的全部的功能和选项来找到答案。
怎么才能让打包的html不是默认的,而是我们自己的呢?
修改webpack.config.js,修改plugins中的内容
const path = require("path")
// 引入HtmlWebpackPlugin插件
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname,'./dist')
},
mode: 'none',
plugins:[
// 实例化插件
new HtmlWebpackPlugin({
template: './index.html', // 打包生成的html模板
filename: 'index.html', // 打包生成的文件名,默认为app.html
inject: 'body' // 设置资源文件注入的位置,也就是引入js引入到那个位置。
})
]
}
运行npx webpack
可以看见生成了app.index,并且这个文件就是我们之前自己写的文件。
清理dist
仔细留意一下,我们发现 dist/index.html 仍旧存在,这个文件是上次生成的残留 文件,已经没有用了。可见,webpack 将生成文件并放置在 /dist 文件夹中,但是 它不会追踪哪些文件是实际在项目中用到的。通常比较推荐的做法是,在每次构建前 清理 /dist 文件夹,这样只会生成用到的文件。让我们使用 output.clean 配置项 实现这个需求。
output: {
// 打包前清理 dist 文件夹
clean: true
}
4.搭建开发环境
截止目前,我们只能通过复制 dist/index.html 完整物理路径到浏览器地址栏里访 问页面。现在来看看如何设置一个开发环境,使我们的开发体验变得更轻松一些.
mode
我们之前一直忽略了一个参数,属性,这个属性就是就是mode,用来设置开发环境的。
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'),
clean: true
},
// 设置开发模式
mode: 'development',
plugins:[
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'app.html',
inject: 'body'
})
]
}
使用 source map
当 webpack 打包源代码时,可能会很难追踪到 error(错误) 和 warning(警告) 在源代 码中的原始位置。例如,如果将三个源文件( a.js , b.js 和 c.js )打包到一个 bundle( bundle.js )中,而其中一个源文件包含一个错误,那么堆栈跟踪就会直 接指向到 bundle.js 。你可能需要准确地知道错误来自于哪个源文件,所以这种提 示这通常不会提供太多帮助。为了更容易地追踪 error 和 warning,JavaScript 提供了 source maps 功能,可以 将编译后的代码映射回原始源代码。如果一个错误来自于 b.js ,source map 就会 明确的告诉你。 在本篇中,我们将使用 inline-source-map 选项:
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'),
clean: true
},
mode: 'development',
plugins:[
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'app.html',
inject: 'body'
})
],
// 追踪错误
devtool: 'inline-source-map'
}
使用 watch mode(观察模式)
在每次编译代码时,手动运行 npx webpack 会显得很麻烦。 我们可以在 webpack 启动时添加 "watch" 参数。如果其中一个文件被更新,代码将 被重新编译,所以你不必再去手动运行整个构建。
npx webpack --watch
这样就可以实现事实打包更新。唯一的缺点是,为了看到修改后的实际效果,你需要刷新浏览器。如果能够自动刷新 浏览器就更好了,因此接下来我们会尝试通过 webpack-dev-server 实现此功能。
webpack-dev-server 为你提供了一个基本的 web server,并且具有 live reloading(实时重新加载) 功能。先安装:
npm install --save-dev webpack-dev-server
修改配置文件,告知 dev server,从什么位置查找文件:
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'),
clean: true
},
mode: 'development',
plugins:[
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'app.html',
inject: 'body'
})
],
devtool: 'inline-source-map',
// 自动刷新
devServer:{
static: './dist'
}
}
以上配置告知 webpack-dev-server ,将 dist 目录下的文件作为 web 服务的根目 录。提示: webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文 件保留在内存中,然后将它们 serve 到 server 中,就好像它们是挂载在 server 根路径上的真实文件一样.
运行
npx webpack serve -open
通过 http://localhost:8080/app.indexl来访问,然后我们修改index.js中的
通过访问返现,打包的文件也同步更新了。
5.资源模块
目前为止,我们的项目可以在控制台上显示 "Hello world~~~"。现在我们尝试混合 一些其他资源,比如 images,看看 webpack 如何处理。 在 webpack 出现之前,前端开发人员会使用 grunt 和 gulp 等工具来处理资源,并 将它们从 /src 文件夹移动到 /dist 或 /build 目录中。webpack 最出色的功能 之一就是,除了引入 JavaScript,还可以内置的资源模块 Asset Modules 引入任何 其他类型的文件。资源模块(asset module)是一种模块类型,它允许我们应用Webpack来打包其他资 源文件(如字体,图标等) 资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些loader:
- asset/resource 发送一个单独的文件并导出 URL。
- asset/inline 导出一个资源的 data URI。
- asset/source 导出资源的源代码。
- asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。
Resource 资源
修改webpack.config.js配置
// 配置资源文件
module: {
rules: [{
test: /.png/,
type: 'asset/resource'
}]
},
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'),
clean: true
},
mode: 'development',
plugins:[
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'app.html',
inject: 'body'
})
],
devtool: 'inline-source-map',
devServer:{
static: './dist'
},
// 资源模块
module: {
rules:[
{
test: /.\jpg/,
type: 'asset/resource'
}
]
}
}
修改index.js文件,引入一张图片资源。创建assets文件夹,将图片引入。
import imgUrl from '../assets/cxy.jpg'
function hello(){
console.log("hello webpack hello world")
}
hello();
let img = document.createElement("img")
img.src = imgUrl
document.body.appendChild(img)
然后运行npx webpack serve --open,我们可以看见图片资源也被加载进来了。看浏览器也有图片了
- 自定义输出文件名:
默认情况下, asset/resource 模块以 contenthash[query] 文件名 发送到输出目录。 可以通过在 webpack 配置中设置 output.assetModuleFilename 来修改此模 板字符串:
output: {
// 设置输出图片名称
assetModuleFilename: 'images/[contenthash][ext][query]'
},
同样的也可以在rules里面设置
// 资源模块
module: {
rules:[
{
test: /.\jpg/,
type: 'asset/resource',
// 优先级会高于assetModuleFilename
generator: {
filename: 'img/[contenthash][ext][query]'
}
}
],
}
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'),
clean: true,
// 设置输出图片名称
assetModuleFilename: 'images/[contenthash][ext][query]'
},
mode: 'development',
plugins:[
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'app.html',
inject: 'body'
})
],
devtool: 'inline-source-map',
devServer:{
static: './dist'
},
// 资源模块
module: {
rules:[
{
test: /.\jpg/,
type: 'asset/resource',
// 优先级会高于assetModuleFilename
generator: {
filename: 'img/[contenthash][ext][query]'
}
}
],
}
}
inline 资源
修改 webpack.config.js 配置:
module: {
rules:[
{
test: /.\svg/,
type: 'asset/inline',
}
],
}
source 资源
source资源,导出资源的源代码。修改配置文件,添加:
module: {
rules: [
test: /.txt/,
type: 'asset/source'
]
}
在assets里创建一个 hello.txt 文件
在index.js入口文件里引入一个 .txt 文件,添加内容:
import helloTxt from '../assets/hello.txt'
const block = document.createElement('div')
block.style.cssText = `width: 200px; height: 200px; background:
aliceblue`
block.textContent = helloTxt
document.body.appendChild(block)
启动打开浏览器
资源已经加载出来了。
通用资源类型
通用资源类型 asset , 在导出一个 data URI 和发送一个单独的文件之间自动选择。 修改配置文件:
// 资源模块
module: {
rules:[
{
test: /.jpg/,
type: 'asset',
}
],
}
现在,webpack 将按照默认条件,自动地在 resource 和 inline 之间进行选择: 小于 8kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类 型。 可以通过在 webpack 配置的 module rule 层级中,设置 Rule.parser.dataUrlCondition.maxSize 选项来修改此条件:
// 资源模块
module: {
rules:[
{
test: /.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 // 4kb
}
}
}
],
}
6.管理资源
在上一章,我们讲解了四种资源模块引入外部资源。除了资源模块,我们还可以通过 loader引入其他类型的文件.
什么是loader
webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。 loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供 应用程序使用,以及被添加到依赖图中。 在 webpack 的配置中,loader 有两个属性:
- test 属性,识别出哪些文件会被转换。
- use 属性,定义出在进行转换时,应该使用哪个 loader。
module: {
rules:[
// loader
{
test: /.txt$/,
use: 'raw-loader',
}
],
}
以上配置中,对一个单独的 module 对象定义了 rules 属性,里面包含两个必须属 性: test 和 use 。这告诉 webpack 编译器(compiler) 如下信息: “嘿,webpack 编译器,当你碰到「在 require() / import 语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先 use(使用) raw-loader 转换一下。”
加载CSS
为了在javaScript模块中加载css,你在添加style-loader和css-loader,并在module中添加这个loader。
npm install --save-dev style-loader css-loader
修改配置文件
module: {
rules:[
// loader
{
test: /.css/,
use: ['style-loader','css-loader'],
}
],
}
模块 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 字符串的
.hello{
background: #000;
width: 100px;
height: 100px;
}
将css引入到index.js中
import './index.css'
document.body.classList.add("hello")
启动服务器发现背景加上去了npx webpack serve --open。现有的 loader 可以支持任何你可以想到的 CSS 风格 - sass 和 less 等。安装lessloader:
npm install less less-loader --save-dev
修改配置文件:
module: {
rules:[
{
test: /.css$/,
use: ['style-loader', 'css-loader','less-loader'],
}
],
}
抽离和压缩CSS
在多数情况下,我们也可以进行压缩CSS,以便在生产环境中节省加载时间,同时还 可以将CSS文件抽离成一个单独的文件。实现这个功能,需要 mini-css-extractplugin 这个插件来帮忙。安装插件:
npm install mini-css-extract-plugin --save-dev
本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文 件,并且支持 CSS 和 SourceMaps 的按需加载。 本插件基于 webpack v5 的新特性构建,并且需要 webpack 5 才能正常工作。 之后将 loader 与 plugin 添加到你的 webpack 配置文件中:
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
plugins:[
new MiniCssExtractPlugin()
],
module: {
rules:[
{
test: /.css$/,
use: [MiniCssExtractPlugin.loader,'css-loader']
}
],
}
进行编译:
npx webpack
单独的 mini-css-extract-plugin 插件不会将这些 CSS 加载到页面中。这里 htmlwebpack-plugin 帮助我们自动生成 link 标签或者在创建 index.html 文件时使 用 link 标签。
这时, link 标签已经生成出来了,把我们打包好的 main.css 文件加载进来。我们 发现, main.css 文件被打包抽离到 dist 根目录下,能否将其打包到一个单独的文 件夹里呢?修改配置文件:
plugins:[
new MiniCssExtractPlugin({
filename: 'css/[contenthash].css'
})
],
然后运行可以发现css文件已经单独出来了。
打开这个打包的css文件,发现并没有压缩和优化 。
/*!******************************************************************!*\
!*** css ../node_modules/css-loader/dist/cjs.js!./src/index.css ***!
******************************************************************/
.hello{
background: #000;
width: 100px;
height: 100px;
}
/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3NzLzMxZjI5NDM5YWNjZjNhMDk3NDQxLmNzcyIsIm1hcHBpbmdzIjoiOzs7QUFBQTtFQUNFLGdCQUFnQjtFQUNoQixZQUFZO0VBQ1osYUFBYTtBQUNmLEMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9zcmMvaW5kZXguY3NzIl0sInNvdXJjZXNDb250ZW50IjpbIi5oZWxsb3tcbiAgYmFja2dyb3VuZDogIzAwMDtcbiAgd2lkdGg6IDEwMHB4O1xuICBoZWlnaHQ6IDEwMHB4O1xufSJdLCJuYW1lcyI6W10sInNvdXJjZVJvb3QiOiIifQ==*/
为了压缩输出文件,我们用css-minimizer-webpack-plugin插件来压缩
npm install -S-D css-minimizer-webpack-plugin
修改配置文件
// 压缩css
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// 优化配置
optimization: {
minimizer: [
new CssMinimizerPlugin(),
],
}
再次运行发现,css已经被压缩了
加载 images 图像
假如,现在我们正在下载 CSS,但是像 background 和 icon 这样的图像,要如何处 理呢?在 webpack 5 中,可以使用内置的 Asset Modules,我们可以轻松地将这些 内容混入我们的系统中,这个我们在"资源模块"一节中已经介绍了。这里再补充一个 知识点,在 css 文件里也可以直接引用文件,修改 index.css 和入口 index.js :
index.css
.hello{
/* background: #000; */
width: 100px;
height: 100px;
background-image: url(../assets/cxy.jpg);
}
启动服务,打开浏览器
加载 fonts 字体
那么,像字体这样的其他资源如何处理呢?使用 Asset Modules 可以接收并加载任 何文件,然后将其输出到构建目录。这就是说,我们可以将它们用于任何类型的文 件,也包括字体。让我们更新 webpack.config.js 来处理字体文件:
module: {
rules: [
{
test: /.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
]
}
加载数据
此外,可以加载的有用资源还有数据,如 JSON 文件,CSV、TSV 和 XML。类似于 NodeJS,JSON 支持实际上是内置的,也就是说 import Data from './data.json' 默认将正常运行。要导入 CSV、TSV 和 XML,你可以使用 csvloader 和 xml-loader。让我们处理加载这三类文件:
npm install --save-dev csv-loader xml-loader
添加配置
module: {
rules: [
{
test: /.(csv|tsv)$/i,
use: ['csv-loader'],
},
{
test: /.xml$/i,
use: ['xml-loader'],
},
]
}
现在,你可以 import 这四种类型的数据(JSON, CSV, TSV, XML)中的任何一种,所 导入的 Data 变量,将包含可直接使用的已解析 JSON
7.使用 babel-loader
前面的章节里,我们应用 less-loader 编译过 less 文件,应用 xml-loader 编译过 xml 文件,那 js 文件需要编译吗?我们来做一个实验,修改 index.js 文 件:
function getString() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello world~~~')
}, 2000)
})
}
async function helloWorld() {
let string = await getString()
console.log(string)
}
我们编译执行之后发现es6的代码原样输出了。
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 插件的集合
在 webpack 配置中,需要将 babel-loader 添加到 module 列表中,就像下面这 样:
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
运行之后可以看出 async/await 的ES6语法被 babel 编译了。
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
# 更多参考这里
https://babeljs.io/docs/en/babel-plugin-transform-runtime
module.exports = {
//...
// 配置资源文件
module: {
rules: [{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
[
'@babel/plugin-transform-runtime'
]
]
}
}
},
//...
],
},
//...
}
8.代码分离
代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。 常用的代码分离方法有三种:
入口起点:使用 entry 配置手动地分离代码。
防止重复:使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离 chunk。
动态导入:通过模块的内联函数调用来分离代码
入口起点
这是迄今为止最简单直观的分离代码的方式。不过,这种方式手动配置较多,并有一 些隐患,我们将会解决这些问题。先来看看如何从 main bundle 中分离 another module(另一个模块):
我们在src下创建另一个js文件
import _ from 'lodash'
console.log(_.join(['Another', 'module', 'loaded!'], ' '))
这个模块依赖了 lodash ,需要安装一下:
npm install lodash --save-dev
修改配置文件:
module.exports = {
entry: {
index: './src/index.js',
another: './src/another-module.js',
},
output: {
filename: '[name].bundle.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname,'./dist'),
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]'
},
}
将其他的模块打包到anthor模块。
编译执行:
npx webpack
可以看见,dist里面多了一个anthor.js
如果我们在index.js中也倒入lodash这个模块,我们在打包的时候,就会重复的打包,这样做肯定是不符合我们的要求的。如果入口 chunk 之间包含一些重复的模块,那些重复模块都会被引入到各个 bundle 中。 这种方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来。 以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在 ./src/index.js 中也引入过 lodash ,这样就在两个 bundle 中造成重复引用。
防止重复
- 入口依赖 配置 dependOn option 选项,这样可以在多个 chunk 之间共享模块
module.exports = {
entry: {
index: {
import: './src/index.js',
dependOn: 'shared',
},
another: {
import: './src/another-module.js',
dependOn: 'shared',
},
shared: 'lodash',
}
}
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
// entry: './src/index.js',
// entry:{
// index: './src/index.js',
// anthor: './src/an.js'
// },
entry:{
index:{
import: './src/index.js',
dependOn: 'shared'
},
anthor:{
import: './src/an.js',
dependOn: 'shared'
},
shared:"lodash"
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname,'./dist'),
clean: true,
assetModuleFilename: 'images/[contenthash][ext][query]'
},
mode: 'production',
plugins:[
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'app.html',
inject: 'body'
}),
new MiniCssExtractPlugin({
filename: 'css/[contenthash].css'
}),
],
devtool: 'inline-source-map',
devServer:{
static: './dist'
},
module: {
rules:[
{
test: /.jpg/,
type: 'asset/resource',
generator: {
filename: 'img/[contenthash][ext][query]'
}
},
{
test: /.svg/,
type: 'asset/inline',
},
{
test: /.txt/,
type: 'asset/source',
},
{
test: /.jpg/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 // 4kb
}
}
},
// {
// test: /.css$/,
// use: ['style-loader', 'css-loader','less-loader'],
// },
{
test: /.css$/,
use: [MiniCssExtractPlugin.loader,'css-loader']
},
{
test: /.js$/,
// exclude: /node_modules/,
// use: {
// loader: 'babel-loader',
// options: {
// presets: ['@babel/preset-env']
// }
// },
//
// use: {
// loader: 'babel-loader',
// options: {
// presets: ['@babel/preset-env'],
// plugins: [
// [
// '@babel/plugin-transform-runtime'
// ]
// ]
// }
// }
}
],
},
optimization: {
minimizer: [
new CssMinimizerPlugin(),
],
},
}
运行发现就会多一个打包的js出来,将全部模块公共的东西放在这个js中
- SplitChunksPlugin SplitChunksPlugin
插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。让我们使用这个插件,将之前的示例中重 复的 lodash 模块去除:
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
//提取公共的部分
optimization: {
splitChunks: {
chunks: 'all',
},
},
9.缓存
我们使用 webpack 来打包我们的模块化后的应用程序,webpack 会生成一个 可部署的 /dist 目录,然后把打包后的内容放置在此目录中。只要 /dist 目录中 的内容部署到 server 上,client(通常是浏览器)就能够访问此 server 的网站及其 资源。而最后一步获取资源是比较耗费时间的,这就是为什么浏览器使用一种名为 缓存 的技术。可以通过命中缓存,以降低网络流量,使网站加载速度更快,然而, 如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就 会使用它的缓存版本。由于缓存的存在,当你需要获取新的代码时,就会显得很棘 手。本节通过必要的配置,以确保 webpack 编译生成的文件能够被客户端缓存,而在文 件内容变化后,能够请求到新的文件。
输出文件的文件名
我们可以通过替换 output.filename 中的 substitutions 设置,来定义输出文件的 名称。webpack 提供了一种使用称为 substitution(可替换模板字符串) 的方式,通 过带括号字符串来模板化文件名。其中, [contenthash] substitution 将根据资源 内容创建出唯一 hash。当资源内容发生变化时, [contenthash] 也会发生变化。
修改配置文件:
module.exports = {
output: {
filename: '[name].[contenthash].js',
},
};
缓存第三方库
将第三方库(library)(例如 lodash )提取到单独的 vendor chunk 文件中,是比较 推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。因此通过实现以上 步骤,利用 client 的长效缓存机制,命中缓存来消除请求,并减少向 server 获取资 源,同时还能保证 client 代码和 server 代码版本一致。 我们在 optimization.splitChunks 添加如下 cacheGroups 参数并构建:
optimization:{
splitChunks: {
cacheGroups: {
vendor: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
}
将 js 文件放到一个文件夹中
目前,全部 js 文件都在 dist 文件夹根目录下,我们尝试把它们放到一个文件夹中, 这个其实也简单,修改配置文件:
output: {
filename: 'scripts/[name].[contenthash].js',
},
10.拆分开发环境和生产环境配置
现在,我们只能手工的来调整 mode 选项,实现生产环境和开发环境的切换,且很多 配置在生产环境和开发环境中存在不一致的情况,比如开发环境没有必要设置缓存, 生产环境还需要设置公共路径等等。 本节介绍拆分开发环境和生产环境,让打包更灵活。
publicPath 配置选项在各种场景中都非常有用。你可以通过它来指定应用程序中所 有资源的基础路径。 基于环境设置 在开发环境中,我们通常有一个 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),
}),
],
}
Automatic publicPath
有可能你事先不知道 publicPath 是什么,webpack 会自动根据 import.meta.url 、 document.currentScript 、 script.src 或者 self.location 变量设置 publicPath。你需要做的是将 output.publicPath 设为 'auto' :
module.exports = {
output: {
publicPath: 'auto',
},
};
请注意在某些情况下不支持 document.currentScript ,例如:IE 浏览器,你 不得不引入一个 polyfill,例如 currentScript Polyfill 。
环境变量
webpack 命令行 环境配置 的 --env 参数,可以允许你传入任意数量的环境变量。而 在 webpack.config.js 中可以访问到这些环境变量。例如, --env production 或 --env goal=local 。
npx webpack --env goal=local --env production --progress
对于我们的 webpack 配置,有一个必须要修改之处。通常, module.exports 指向 配置对象。要使用 env 变量,你必须将 module.exports 转换成一个函数:
//...
module.exports = (env) => {
return {
//...
// 根据命令行参数 env 来设置不同环境的 mode
mode: env.production ? 'production' : 'development',
//...
}
}
拆分配置文件
目前,生产环境和开发环境使用的是一个配置文件,我们需要将这两个文件单独放到 不同的配置文件中。如 webpack.config.dev.js (开发环境配置)和 webpack.config.prod.js (生产环境配置)。在项目根目录下创建一个配置文件 夹 config 来存放他们。
webpack.config.dev.js 配置如下:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extractplugin')
const toml = require('toml')
const yaml = require('yaml')
const json5 = require('json5')
module.exports = {
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
output: {
filename: 'scripts/[name].js',
path: path.resolve(__dirname, './dist'),
clean: true,
assetModuleFilename: 'images/[contenthash][ext]'
},
mode: 'development',
devtool: 'inline-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
filename: 'app.html',
inject: 'body'
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
devServer: {
static: './dist'
},
module: {
rules: [
{
test: /.png$/,
type: 'asset/resource',
generator: {
filename: 'images/[contenthash][ext]'
}
},
{
test: /.svg$/,
type: 'asset/inline'
},
{
test: /.txt$/,
type: 'asset/source'
},
{
test: /.jpg$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 * 1024
}
}
},
{
test: /.(css|less)$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'lessloader']
},
{
test: /.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource'
},
{
test: /.(csv|tsv)$/,
use: 'csv-loader'
},
{
test: /.xml$/,
use: 'xml-loader'
},
{
test: /.toml$/,
type: 'json',
parser: {
parse: toml.parse
}
},
{
test: /.yaml$/,
type: 'json',
parser: {
parse: yaml.parse
}
},
{
test: /.json5$/,
type: 'json',
parser: {
parse: json5.parse
}
},
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
[
'@babel/plugin-transform-runtime'
]
]
}
}
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
}
合并配置文件
npm install webpack-merge -D
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.config.common.js')
const productionConfig = require('./webpack.config.prod.js')
const developmentConfig = require('./webpack.config.dev')
module.exports = (env) => {
switch(true) {
case env.development:
return merge(commonConfig, developmentConfig)
case env.production:
return merge(commonConfig, productionConfig)
default:
throw new Error('No matching configuration was found!');
}}