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