Webpack入门教程(二):基础用法
前言
在上一章我们对 webpack 有了一个初体验,从这章开始我们就来正式了解一下 webpack 的一些核心概念。
webpack 有很多重要的概念,一般认为 webpack 的核心概念包括入口(entry)、输出(output)、加载器(loader)、插件(plugins)和模式(mode)共计 5 个。
1 入口 Entry
入口起点(entry point) 定义 webpack 从哪个文件开始构建依赖关系图,进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
它的默认值是 ./src/index.js,但是我们可以通过在配置文件中配置 entry属性来指定一个(或多个)不同的入口起点。例如:
1.1 单个入口(简写)语法
module.exports = {
entry: './path/to/my/entry/file.js',
};
这种单入口写法是下面的简写
module.exports = {
entry: {
main: './path/to/my/entry/file.js',
},
};
1.2 多入口语法
我们可以通过给 entry 属性传递数组、对象或者函数来实现,例如:
module.exports = {
entry: ['./src/file_1.js', './src/file_2.js']
};
或者
module.exports = {
entry: {
app: './src/app.js',
adminApp: './src/adminApp.js',
},
};
1.3 描述入口的对象
用于描述入口的对象。你可以使用如下属性:
dependOn: 默认情况下,每个入口chunk保存了全部其用的模块(modules)。使用dependOn选项你可以与另一个入口chunk共享模块:
module.exports = {
//...
entry: {
app: { import: './app.js', dependOn: 'react-vendors' },
'react-vendors': ['react', 'react-dom', 'prop-types'],
},
};
这样,app 这个 chunk 就不会包含 react-vendors 拥有的模块了。
dependOn 选项的也可以为字符串数组。
filename: 默认情况下,入口chunk的输出文件名是从output.filename中提取出来的,但你可以为特定的入口指定一个自定义的输出文件名。(Output 我们后面会讲)
module.exports = {
//...
entry: {
app: './app.js',
home: { import: './contact.js', filename: 'pages/[name][ext]' },
about: { import: './about.js', filename: 'pages/[name][ext]' },
},
};
import: 指定启动时需加载的模块,可以传递字符串或者一个数组。
module.exports = {
//...
entry: {
app: { import: ['./app.js', './app2.js'] }
};
library: 指定library选项,为当前entry构建一个library。
使用 library 属性指定库名称时,entry 中的代码将打包为一个单独的文件,并暴露出该库的全局变量。以下是一个示例:
module.exports = {
entry: {
main: {
import: "./src/index.js",
library: {
name: "HelloWorld",
type: "global",
},
},
},
};
在上面的例子中,我们使用了 library.name 定义了库名称,library.type 指定了方式,type: 'global' 表示会将库挂载到全局对象 window 上。
然后就可以使用 HelloWorld 全局变量来访问该库的方法和属性。例如:
<head>
<script src="./dist/HelloWorld.js"></script>
</head>
<body>
<script>
HelloWorld.sayHello();
</script>
</body>
runtime: 运行时chunk的名字。如果设置了,就会创建一个新的运行时chunk。在webpack 5.43.0之后可将其设为false以避免一个新的运行时 chunk。publicPath: 当该入口的输出文件在浏览器中被引用时,为它们指定一个公共URL地址。请查看output.publicPath。
chunk: 在webpack中,chunk可以理解为经过webpack分割出来的代码块。具体来说,当打包项目时,webpack会将入口文件及其依赖项(import、require)全部打包在一起。当项目变得庞大时,打包成单个bundle.js文件可能会导致加载时间变长,因为浏览器需要下载整个文件才能执行应用程序。这就是webpack开发人员决定将代码拆分成更小的“chunks”的原因。
2 输出 Output
output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。
2.1 基础用法
在 webpack 配置中,output 属性的最低要求是,将它的值设置为一个对象,然后为将输出文件的文件名配置为一个 output.filename,但是一般我们还会想要自定义生成的路径,这个时候就可以使用 path 这个属性:
module.exports = {
...
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js',
},
};
在上面的示例中,我们通过 output.filename 和 output.path 属性,来告诉 webpack bundle 的名称,以及我们想要 bundle 生成(emit)到哪里。
2.2 多个入口起点
如果配置中创建出多于一个 "chunk",则应该使用 占位符(substitutions) 来确保每个文件具有唯一的名称。
module.exports = {
entry: {
app: './src/app.js',
search: './src/search.js',
},
output: {
filename: '[name].js',
path: __dirname + '/dist',
},
};
像上面的配置就会生成两个chunk,一个叫 app.js,一个叫 search.js
常见的占位符有
[name]:模块名称,对于entry定义的每个入口点,设置该选项可以确保生成唯一的文件名。[id]:模块标识符,通常是从0开始自增的整数。[chunkhash]:代码块内容的哈希值,每个代码块有一个独特的哈希值。当在生产环境使用时,可用于实现长期缓存。[contenthash]:生成文件的内容哈希值,在文件内容发生变化时,该哈希值也会变化。[hash]:compilation对象的哈希值,基于当前构建过程中所有webpack特定的信息计算而来,任意文件修改都会导致哈希值的变化。
这些占位符可以用来创建具有唯一性和可追踪性的文件名,并且可用于实现长期缓存等功能。例如,使用 [hash] 占位符将文件名设置为 main.[hash].js 将确保在每次构建过程中该文件名都会发生变化,即使只改动了单个文件的内容。
3 加载器 Loader
loader 是 webpack 一个很重要的概念,它用于将非 javascript 文件(如图像、样式等)转换为 webpack 能够理解和打包的模块。
在 webpack 中,每个文件都被视为模块,并且在构建时会根据依赖关系进行打包。但是,webpack 只能理解 javascript 和 json 格式,因此需要使用 loader 来处理其他类型的文件,并将它们转化为 webpack 的模块。
在 webpack 的配置中,loader 有两个属性:
test属性,识别出哪些文件会被转换。use属性,定义出在进行转换时,应该使用哪个loader。
3.1 示例
例如,你可以使用 loader 告诉 webpack 加载 CSS 文件,或者将 TypeScript 转为 JavaScript。为此,首先安装相对应的 loader:
npm install --save-dev css-loader ts-loader
然后指示 webpack 对每个 .css 使用 css-loader,以及对所有 .ts 文件使用 ts-loader:
module.exports = {
module: {
rules: [
{ test: /\.css$/, use: 'css-loader' },
{ test: /\.ts$/, use: 'ts-loader' },
],
},
};
以上配置中,对一个单独的 module 对象定义了 rules 属性,rules 是一个数组,里面包含两个对象,每个对象两个必须属性:test 和 use。这告诉 webpack 编译器(compiler) 如下信息:
“嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 '.css'/'.ts' 的路径」时,在你对它打包之前,先 use(使用) css-loader/ts-loader转换一下。”
3.2 使用 loader 的两种方式
- 方式一:配置方式(推荐):在
webpack.config.js文件中指定loader。
module.rules 允许我们在 webpack 配置中指定多个 loader。 这种方式是展示 loader 的一种简明方式,并且有助于使代码变得简洁和易于维护。同时让你对各个 loader 有个全局概览:
loader 从右到左(或从下到上) 地取值(evaluate)/执行(execute)。在下面的示例中,从 sass-loader 开始执行,然后继续执行 css-loader,最后以 style-loader 为结束。
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{ loader: 'sass-loader' },
],
},
],
},
};
- 方式二:内联方式:在每个
import语句中显式指定loader。
下面是关于内联 Loader 常见的标记和它们的含义:
!(感叹号):用于分隔多个Loader,表示在当前模块中使用多个Loader,Loader的执行顺序是从右往左,即从后往前执行。例如:
import Style from 'style-loader!css-loader!./styles.css'
表示在 ./styles.css 模块中先使用 css-loader,再使用 style-loader。
?(问号):用于向loader传递参数。参数的格式是键值对的形式,多个参数之间使用&分隔。例如:
import Style from('style-loader!css-loader?modules&localIdentName=[name]__[local]')
表示在使用 css-loader 时传递了两个参数,modules 和localIdentName。
!(感叹号前缀):将禁用所有已配置的normal loader(普通 loader),例如:
import Style from '!style-loader!css-loader!./styles.css'
表示在 ./styles.css 模块中只使用 style-loader 和 css-loader,不执行配置文件中的普通 loader。
!!(双感叹号):用于禁用所有已配置的loader(preLoader, loader, postLoader),例如:
import Style from '!!style-loader!css-loader!./styles.css'
-!(减号和感叹号):禁用所有已配置的 preLoader 和 loader,但是不禁用 postLoaders,例如:
import Style from '-!style-loader!css-loader!./styles.css'
这些内联 loader 的标记在 webpack 中可以用来灵活地控制 loader 的执行顺序、跳过配置文件中的 loader、禁用配置文件中的 loader,并向 loader 传递参数,从而满足不同的需求。但需要注意的是,过多地使用内联loader 可能会导致代码难以维护和理解,建议在实际项目中谨慎使用。
注意在
webpack v4版本可以通过CLI使用loader,但是在webpack v5中被弃用。
3.3 loader 的特性
loader支持链式调用。链中的每个loader会将转换应用在已处理过的资源上。一组链式的loader将按照相反的顺序执行。链中的第一个loader将其结果(也就是应用过转换后的资源)传递给下一个loader,依此类推。最后,链中的最后一个loader,返回webpack所期望的JavaScript。loader可以是同步的,也可以是异步的。loader运行在Node.js中,并且能够执行任何操作。loader可以通过options对象配置(仍然支持使用 query 参数来设置选项,但是这种方式已被废弃)。- 除了常见的通过
package.json的main来将一个npm模块导出为loader,还可以在module.rules中使用loader字段直接引用一个模块。 - 插件(plugin)可以为
loader带来更多特性。 loader能够产生额外的任意文件。
4 资源模块 Asset Module
资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。
在 webpack 5 之前,通常使用:
raw-loader将文件导入为字符串url-loader将文件作为data URI内联到bundle中file-loader将文件发送到输出目录
资源模块类型(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,并且配置资源体积限制实现。
当在 webpack 5 中使用旧的 assets loader(如 file-loader/url-loader/raw-loader 等)和 asset 模块时,你可能想停止当前 asset 模块的处理,并再次启动处理,这可能会导致 asset 重复,你可以通过将 asset 模块的类型设置为 'javascript/auto' 来解决。
4.1 Resource 资源
webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource'
}
]
},
};
src/index.js
import mainImage from './images/main.png';
img.src = mainImage; // '/dist/151cfcfa1bd74779aadb.png'
所有 .png 文件都将被发送到输出目录,并且其路径将被注入到 bundle 中。
默认情况下,asset/resource 模块以 [hash][ext][query] 文件名发送到输出目录。
可以通过在 webpack 配置中设置 output.assetModuleFilename 来修改此模板字符串:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
assetModuleFilename: 'images/[hash][ext][query]'
},
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource'
}
]
},
};
另一种自定义输出文件名的方式是,将某些资源发送到指定目录:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
assetModuleFilename: 'images/[hash][ext][query]'
},
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource'
}
},
{
test: /\.html/,
type: 'asset/resource',
generator: {
filename: 'static/[hash][ext][query]'
}
}
]
},
};
使用此配置,所有 html 文件都将被发送到输出目录中的 static 目录中。
4.2 inline 资源(inlining asset)
webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.svg/,
type: 'asset/inline'
}
]
}
};
src/index.js
import metroMap from './images/metro.svg';
block.style.background = `url(${metroMap})`; // url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo...vc3ZnPgo=)
所有 .svg 文件都将作为 data URI 注入到 bundle 中。
4.3 source 资源(source asset)
webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.txt/,
type: 'asset/source',
}
]
}
};
src/example.txt
Hello world
src/index.js
import exampleText from './example.txt';
block.textContent = exampleText; // 'Hello world'
所有 .txt 文件将原样注入到 bundle 中。
4.4 通用资源类型
webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.txt/,
type: 'asset',
}
]
},
};
现在,webpack 将按照默认条件,自动地在 resource 和 inline 之间进行选择:小于 8kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类型。
可以通过在 webpack 配置的 module rule 层级中,设置 Rule.parser.dataUrlCondition.maxSize 选项来修改此条件:
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.txt/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 // 4kb
}
}
}
]
},
};
5 插件 Plugin
插件 是 webpack 的 支柱 功能。Webpack 自身也是构建于你在 webpack 配置中用到的 相同的插件系统 之上!
插件目的在于解决 loader 无法实现的其他事。Webpack 提供很多开箱即用的 插件。
5.1 插件的本质
webpack 插件本质上是一个具有 apply 方法的 JavaScript 对象。apply 方法会被 webpack compiler 调用,并且在 整个 编译生命周期都可以访问 compiler 对象。
ConsoleLogOnBuildWebpackPlugin.js
const pluginName = 'ConsoleLogOnBuildWebpackPlugin';
class ConsoleLogOnBuildWebpackPlugin {
apply(compiler) {
compiler.hooks.run.tap(pluginName, (compilation) => {
console.log('webpack 构建正在启动!');
});
}
}
module.exports = ConsoleLogOnBuildWebpackPlugin;
compiler hook 的 tap 方法的第一个参数,应该是驼峰式命名的插件名称。建议为此使用一个常量,以便它可以在所有 hook 中重复使用。
5.2 插件的使用
由于插件可以携带参数/选项,你必须在 webpack 配置中,向 plugins 属性传入一个 new 实例。
取决于你的 webpack 用法,对应有多种使用插件的方式。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // 访问内置的插件
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
filename: 'my-first-webpack.bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: 'babel-loader',
},
],
},
plugins: [
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({ template: './src/index.html' }),
],
};
ProgressPlugin 用于自定义编译过程中的进度报告,HtmlWebpackPlugin 将生成一个 HTML 文件,并在其中使用 script 引入一个名为 my-first-webpack.bundle.js 的 JS 文件。
6 构建模式 Mode
Webpack 有三种模式:development、production 和 none。通过设置不同的模式,可以启用不同的内置优化。
6.1 三种模式的区别
| 选项 | 描述 |
|---|---|
| development | 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development. 为模块和 chunk 启用有效的名。 |
| production | 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin 和 TerserPlugin 。 |
| none | 不使用任何默认优化选项 |
如果没有设置,webpack 会给 mode 的默认值设置为 production。
6.2 用法
在配置对象中设置 mode 选项:
module.exports = {
mode: 'development',
};
或者从 CLI 参数中传递:
webpack --mode=development
7 source-map
我们的代码通常是通过打包压缩运行在浏览器上,也就是真实跑在浏览器上的代码,和我们编写的代码其实是有差异的;
- 比如
ES6的代码可能被转换成ES5; - 比如对应的代码行号、列号在经过编译后肯定会不一致;
- 比如代码进行丑化压缩时,会将编码名称等修改;
- 比如我们使用了
TypeScript等方式编写的代码,最终转换成JavaScript;
这样做会导致调试转换后的代码变得困难,我们能保证代码不出错吗?不可能。
那么如何可以调试这种转换后不一致的代码呢?
答案就是 source-map
source map 是一种文件格式,用于将压缩后的 javascript(或 css)代码的源代码映射回原始的、未压缩的文件中。 该文件提供了一个映射表,可使浏览器调试器能够让开发人员轻松地跟踪和调试代码。
7.1 如何使用source-map
如何可以使用 source-map 呢?两个步骤:
- 第一步:根据源文件,生成
source-map文件,webpack在打包时,可以通过配置生成source-map; - 第二步:在转换后的代码,最后添加一个注释,它指向
sourcemap;
//# sourceMappingURL=common.bundle.js.map
浏览器会根据我们的注释,查找相应的 source-map,并且根据 source-map 还原我们的代码,方便进行调试。
在 Chrome 中,我们可以按照如下的方式打开 source-map:

7.2 分析 source-map
最初 source-map 生成的文件大小是原始文件的 10 倍,第二版减少了约50%,第三版又减少了 50%,所以目前一个 133kb 的文件, 最终的 source-map 的大小大概在 300kb。
下图是一个简单的 source-map 文件

version:当前使用的版本,也就是最新的第三版;- `file: 打包后的文件(浏览器加载的文件);
mappings:source-map 用来和源文件映射的信息(比如位置信息等),一串base64 VLQ(veriable-length quantity可变 长度值)编码;sources:从哪些文件转换过来的source-map和打包的代码(最初始的文件);sourceContent:转换前的具体代码信息(和sources是对应的关系);names:转换前的变量和属性名称sourceRoot:所有的sources相对的根目录;
7.3 生成 source-map
如何在使用 webpack 打包的时候,生成对应的 source-map 呢?
webpack为我们提供了非常多的选项(目前是26个),来处理source-map;- webpack.docschina.org/configurati…
- 选择不同的值,生成的
source-map会稍微有差异,打包的过程也会有性能的差异,可以根据不同的情况进行选择;
虽然 devtool 的选项多达 26 种,但是这么多种配置项其实只是下面七个关键字 eval、source-map、cheap、module 、 inline、hidden和nosources 的组合
| 关键字 | 含义 |
|---|---|
| source-map | 产生.map 文件 |
| eval | 使用 eval 包裹模块代码 |
| cheap | 不包含列信息,也不包含 loader 的 sourcemap |
| module | 包含 loader 的 sourcemap(比如 jsx to js ,babel 的 sourcemap),否则无法定义源文件 |
| inline | 将.map 作为 DataURI 嵌入,不单独生成.map 文件 |
| hidden | 会生成sourcemap,但是不会对source-map文件进行引用 |
| nosources | 会生成sourcemap,但是生成的sourcemap只有错误信息的提示,不会生成源代码文件; |
具体详解:
文件源码如下
console.log("hello wolrd");
1. source-map 处理后输出结果: 定位信息最全,但也 .map 文件最大,效率最低
console.log("hello wolrd");
//# sourceMappingURL=bundle.js.map
2. eval 处理输出结果:用eval 包裹源代码进行执行,利用字符串可缓存从而提效
(() => {
var __webpack_modules__ = {
138: () => {
eval('console.log("hello wolrd");\n\n\n//# sourceURL=webpack://webpack-demo/./src/index.js?');
},
},
__webpack_exports__ = {};
__webpack_modules__[138]();
})();
3. inline-source-map 处理输出结果:inline会将 map 作为 DataURI 嵌入,不单独生成 .map 文件减少文件。
console.log("hello wolrd");
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVuZGxlLmpzIiwibWFwcGluZ3MiOiJBQUFBQSxRQUFRQyxJQUFJIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vd2VicGFjay1kZW1vLy4vc3JjL2luZGV4LmpzIl0sInNvdXJjZXNDb250ZW50IjpbImNvbnNvbGUubG9nKFwiaGVsbG8gd29scmRcIik7XG4iXSwibmFtZXMiOlsiY29uc29sZSIsImxvZyJdLCJzb3VyY2VSb290IjoiIn0=
4. cheap-source-map 处理后输出结果:cheap 的错误信息只会定义到行,而不会定义到列,精准度降低换取文件内容的缩小。
console.log("hello wolrd");
//# sourceMappingURL=bundle.js.map
cheap-source-map 和 source-map的区别

5. cheap-module-source-map 处理后输出结果和cheap-source-map相似,但是 cheap-module-source-map 对源自 loader 的 sourcemap 处理会更好。
cheap-source-map 和 cheap-module-source-map的区别

6. hidden-source-map 处理后输出结果:会生成 sourcemap,但是不会对source-map 文件进行引用; 相当于删除了打包文件中对 sourcemap 的引用注释
// 被删除掉的
//# sourceMappingURL=bundle.js.map
如果我们手动添加进来,那么 sourcemap 就会生效了
7. hidden-source-map 处理后输出结果:会生成 sourcemap,但是生成的 sourcemap 只有错误信息的提示,不会生成源代码文件
正确的错误提示:

点击错误提示,无法查看源码:

上面我们讲了 webpack 给我门提供的26个值是有以上七个关键词组合的,他们的组合规则如下:
- inline-|hidden-|eval:三个值时三选一;
- nosources:可选值;
- cheap可选值,并且可以跟随module的值;
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
那么在开发中,最佳的实践是什么呢?
- 开发阶段:推荐使用
source-map或者cheap-module-source-map✓ 这分别是vue和react使用的值,可以获取调试信息,方便快速开发; - 测试阶段:推荐使用
source-map或者cheap-module-source-map✓ 测试阶段我们也希望在浏览器下看到正确的错误提示; - 发布阶段:
false、缺省值(不写)
8 开发环境模式
目前我们开发的代码,为了运行需要有两个操作:
- 操作一:
npm run build,编译相关的代码; - 操作二:通过
live server或者直接通过浏览器,打开index.html代码,查看效果;
这个过程经常操作会影响我们的开发效率,我们希望可以做到,当文件发生变化时,可以自动的完成 编译 和 展示;
为了完成自动编译,webpack 提供了几种可选的方式:
webpack watch mode;webpack-dev-server(常用);webpack-dev-middleware;
8.1 使用 watch mode(观察模式)
我们可以指示 webpack "watch" 依赖图中所有文件的更改。如果其中一个文件被更新,代码将被重新编译,所以你不必再去手动运行整个构建。
- 我们添加一个用于启动
webpack watch mode的npm scripts:
"scripts": {
"watch": "webpack --watch",
},
- 运行命令
npm run watch
现在只要当你更改了 ./src/index.js 中的代码,应该就可以看到 webpack 自动地重新编译修改后的模块
这个方法唯一的缺点是,为了看到修改后的实际效果,需要刷新浏览器。如果能够自动刷新浏览器就更好了,因此接下来我们会尝试通过 webpack-dev-server 实现此功能。
8.2 使用 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",
devtool: "source-map",
mode: "development",
devServer: {
static: "./dist",
},
plugins: [
new HtmlWebpackPlugin({
title: "Development",
}),
],
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist"),
clean: true,
},
};
以上配置告知 webpack-dev-server,将 dist 目录下的文件 serve 到 localhost:8080 下。
webpack-dev-server会从 output.path 中定义的目录中的 bundle 文件提供服务,即文件将可以通过http://[devServer.host]:[devServer.port]/[output.publicPath]/[output.filename]进行访问。
- 我们添加一个可以直接运行
dev server的script:
"start": "webpack serve --open",
- 现在,在命令行中运行
npm run start,我们会看到浏览器自动加载页面。如果你更改任何源文件并保存它们,web server将在编译代码后自动重新加载。

webpack-dev-server 具有许多可配置的选项。关于其他更多配置,可以查看 官网配置文档。
8.3 使用 webpack-dev-middleware
webpack-dev-middleware 是一中间件pack处理过的文件发送到一个server。事实上,webpack-dev-server在内部就是 wds + express 实现的,然而它也可以作为一个单独的package来使用,以便根据需求进行更多自定义设置。下面是一个webpack-dev-middleware配合express server` 的示例。
- 首先,安装
express和webpack-dev-middleware:
npm install --save-dev express webpack-dev-middleware
- 调整
webpack配置文件,以确保middleware(中间件) 功能能够正确启用
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
print: './src/print.js',
},
devtool: 'inline-source-map',
devServer: {
static: './dist',
},
plugins: [
new HtmlWebpackPlugin({
title: 'Development',
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
+ publicPath: '/',
},
};
- 我们将会在
server脚本使用publicPath,以确保文件资源能够正确地serve在http://localhost:3000下,稍后我们会指定port number(端口号)。接下来是设置自定义express server:
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);
// 告知 express 使用 webpack-dev-middleware,
// 以及将 webpack.config.js 配置文件作为基础配置。
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
})
);
// 将文件 serve 到 port 3000。
app.listen(3000, function () {
console.log('Example app listening on port 3000!\n');
});
- 添加一个
npm script,以使我们更方便地运行server:
"server": "node server.js",
- 现在,在 terminal(终端) 中执行
npm run server,将会有类似如下信息输出:

- 打开浏览器,访问
http://localhost:3000。应该看到 webpack 应用程序已经运行!
转载自:https://juejin.cn/post/7239259798269083707