Webpack 根底
Webpack 从一个或多个入口(Entry)开始寻找相干的依赖关系来建设依赖图,而后辨认他们的内容,webpack 通过加载器(Loader)来辨认模块的内容,再将内容整顿分类打包成不同的块,最初放到输入(Output)目录下。在这个过程中,能够应用一些有用的插件(Plugin)来辅助实现各种工作,例如注入环境变量、资产治理或打包优化等。
1. 装置与运行
首先计算机中必须事后装置好 node.js 运行环境,再装置 webpack 依赖。
npm install --save-dev webpack webpack-cli
最初通过 npx webpack
即可执行打包工作。
2. 外围配置
从 4.0.0 版本开始,Webpack 不用用一个配置文件去捆绑你的我的项目,默认状况下,webpack 应用 src/index.js
作为入口,输入的包是 dist/main.js
文件,然而也能够具体地配置各项参数来适配各自的需要。
2.1 创立配置文件
这时候就须要在我的项目外面新增一个 webpack.config.js
文件,这里有个工具能够在线创立。
2.2 多配置源
能够依据不同的状况抉择不同的配置文件:
"scripts": {"build": "webpack --config prod.config.js"}
2.3 编译指标
通过设置 target
属性,指定 webpack 编译输入的文件在什么样的环境中运行。例如服务器、浏览器别离可设置成 "target": "node"
和"target": "web"
。
2.4 入口配置
默认状况下应用./src/index.js
作为默认入口。若要配置其余入口,可通过设置配置文件的 entry
属性来实现:
module.exports = {entry: './path/to/my/entry/file.js',};
除了配置一个入口,还能够配置多个,多个入口就会对应多个输入,请确保每个输入的文件的名字要惟一,能够应用替换模版来确保名称的抵触。
{
entry: {
index: './index/file/path',
print: './print/file/path'
}
}
以上配置完当前,就会生成两个入口块 index 和 print。默认状况的名字是 main。
entry 属性能承受的数据结构有:门路字符串、门路字符串的数组、对象构造。
对于 entry 对象的具体配置如下:
dependOn
: The entry points that the current entry point depends on. They must be loaded before this entry point is loaded.filename
: Specifies the name of each output file on disk.import
: Module(s) that are loaded upon startup.library
: Specify library options to bundle a library from current entry.runtime
: The name of the runtime chunk. When set, a new runtime chunk will be created. It can be set tofalse
to avoid a new runtime chunk since webpack 5.43.0.publicPath
: Specify a public URL address for the output files of this entry when they are referenced in a browser. Also, see output.publicPath.
更多设置请查阅这里。
2.5 输入配置
默认状况下应用 ./dist/main.js
,若要改用其余设定,可设置配置文件的output
属性来实现:
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js',
},
};
此例子中,应用 path
来指定输入门路,__dirname
指定的事以后文件所在的根目录。filename
指定文件名称。
当须要动静的生成打包名称的时候,能够这样配置:
{
output: {filename: '[name].bundle.js',
}
}
在这个例子中,[name]
将被文件的原名替换。
更多配置请查阅这里。
2.6 加载器
通常状况下 Webpack 只能辨认 JavaScript 和 JSON 两种类型的文件,其余类型的模块要应用不同的载入器。总体上看,载入器的配置有两个:
test
确定哪些文件能够被转换;use
指定哪个载入器来执行这些转换。
module.exports = {
module: {rules: [{ test: /\.txt$/, use: 'raw-loader'}],
},
};
留神:test
属性前面的正则表达式没有引号,示意蕴含 .txt
结尾的文件,若蕴含引号则示意的是绝对路径,即以 .txt
结尾的文件。
2.7 插件
插件是用来执行一系列工作来辅助实现整个打包过程,例如打包优化、资产治理、注入环境变量等。如果要应用一个插件,首先要用 require()
将它导入,而后再增加到配置文件的 plugins
属性数组外面。
插件实质上是一个领有 apply 办法的 javascript 对象,apply 办法可能被 webpack 编译器调用,且能拜访整个汇编周期。
有很多插件能够在这里找到,还有很多用例值得摸索,相干信息在这里找到。
插件能够传入参数,所以在应用中,能够通过传入参数来新建一个实例,在把这个实例传入 webpack.config.js
的plugins
属性里。
2.8 模式
预设的有三种模式:development
, production
, none
,每种模式都有不同的优化细度。默认状况是production
。
module.exports = {mode: 'production',};
更多配置请查看这里。
2.9NPM 脚本
在 package.json 文件外面设置 scripts 脚本,能够更不便为当前提供 cli 命令。例如:
"scripts": {
"test": "echo \"Error: no test specified\"&& exit 1"
"build": "webpack"
},
当配置好 build
当前,能够通过命令行间接输出 npm run build
即可执行对应的命令:npx webpack
。
任何参数(包含 webpack.json 外面的参数)都能够加在脚本的前面,后面带上--
,例如"build": "webpack --mode=production"
。
3. 资产治理
webpack 会将我的项目所有相干的依赖文件都打包到一起(没有用到的除外),默认状况下,webpack 只能辨认 javascript 和 json 两种文件(外部曾经有了这两种文件的载入器或者资产模块反对),其余类型的文件还须要配置特定的加载器。
多个加载器,能够被前后链接,后面解决的后果能够被前面的加载器应用,直到最初一个加载器返回后果。
webpack 外部的资产模块(asset modules),是一种不须要配置额定加载器的模块,能够用来解决字体、图标等文件的模块。次要有以下:
asset/resource
,收回一个独自的文件并导出 URLasset/inline
,导出资产的数据 URIasset/source
,导出资产的源代码asset
,主动抉择导出数据 URI 和收回一个独自的文件。
通常,资产文件都是全局的放在一个 asset
目录之下,然而也能够把一些高度耦合的文件放在与它们关联的模块放在一起,这样打包进去的“公有”资产文件就会更加严密的与关联的模块放在一起。
3.1 载入 CSS
webpack 默认状况无奈辨认 css 文件,因而须要装置一个加载器:
npm install --save-dev style-loader css-loader
而后在 webpack 的模块局部进行相干配置:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
],
},
};
在以上例子中,css 文件应用了两个加载器来加载,别离是 styled-loader
和css-loader
,前一个解决的后果作为后一个的输出,最初由 css-loader
返回的后果作为 css 文件加载的最终后果。
当配置结束当前间接运行 npx webpack
就可实现打包。
3.2 载入图片
能够间接应用资产模块来加载图片:
{test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource'
}
所有配置类型的图片都会被加载器解决,而后放到输入目录下。任何援用图片的变量都会蕴含得有被解决的这些图片的 url 地址。
3.3 载入字体
同样的,字体也能够间接应用资产模块来加载:
{test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
}
通过在我的项目中引入字体文件(woff 和 woff2),再在 css 文件中引入,例如:
@font-face {
font-family: 'MyFont';
src: url('./my-font.woff2') format('woff2'),
url('./my-font.woff') format('woff');
font-weight: 600;
font-style: normal;
}
.hello {
color: red;
font-family: 'MyFont';
background: url('./icon.png');
}
最初间接打包我的项目即可。
3.4 载入数据
webpack 默认是反对加载 json 文件的,然而其余的数据文件,例如 CSVs
, TSVs
和XML
这些文件,就须要应用其余加载器。
npm install --save-dev csv-loader xml-loader
在 webpack 中配置:
{test: /\.(csv|tsv)$/i,
use: ['csv-loader'],
},
{
test: /\.xml$/i,
use: ['xml-loader'],
},
在我的项目里导入这类数据当前,运行打包命令即可。
其余类型的数据,例如 toml
, yaml
, json5
等,能够加载后作为 JSON 模块来应用,然而要应用到自定义的 解析器。
npm install toml yamljs json5 --save-dev
而后在 webpack 中增加配置:
{
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,},
},
最终运行打包命令即可。
4. 输入治理
4.1HtmlWebpackPlugin
如果要更改 webpack 的入口,而不想动输入目录下 index 文件外面的旧援用。则须要配置应用 HtmlWebpackPlugin
插件。
npm install --save-dev html-webpack-plugin
而后在 web pack.config.js 文件外面配置:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({title: 'Output Management',}),
]
};
通过这样的配置当前,运行打包,此插件将在输入目录下生成一个 index.html
文件(若文件已存在,将代替原文件)。
更多无关配置,请查看这里。
4.2 清理 /dist
目录
默认状况家,/dist
目录外面的文件不会被清理,每次运行打包,生成的新文件和旧文件会混合在一起。为了让每次打包都主动的清理旧的文件,能够通过配置来实现。
{
output: {
...
clean: true
}
}
4.3 生成清单(Manifest)
如果要理清开发的模块和生成的目录下的文件的对应关系,能够应用 WebpackManifestPlugin
插件。
5. 开发工具
5.1 源代码映射
当我的项目被打包到一个文件(bundle.js)外面当前,源文件外面蕴含有谬误,那么在 bundle.js 文件里的栈追踪就会很艰难。为了更容易地找到问题和正告,javascript 提供了 源映射(source map),它能映射编译后的代码和源代码。
简略的配置如下:
module.exports = {
...
devtool: "inline-source-map",
}
编译当前,在浏览器中关上,任何问题都能映射到源代码中。下面例子中的 inline-source-map
是远吗映射中的一个工具,还有很多,能够参阅这里。
5.2 开发选项
如果在开发过程中每次增加代码或者批改代码都要去运行npm run build
, 那么开发效率将会很低下。webpack 提供了几个配置选项供开发者应用:
- watch mode
- webpack-dev-server
- webpack-dev-middleware
5.3 监督模式
有多种形式启动这个模式,能够间接在 webpack.config.js 文件里使能watch:true
,当我的项目在第一次编译当前,我的项目就会继续去监督变动过程。另外一只是间接在命令行启动过程中增加参数npx webpack --watch
。
另外,在 webpack-dev-server 和 web pack-dev-middleware 两种开发工具激活的状况下,监督模式是主动关上的。
5.4webpack-dev-server
这个工具为开发者提供一个根本的 web 服务器,可能实时的重加载。
npm install --save-dev webpack-dev-server
批改配置文件web pack.config.js
:
module.exports={
devServer: {static: './dist',},
optimization: {runtimeChunk: 'single'}
}
如果我的项目中有多个入口则须要配置 Optimization.runtimeChunk: 'single'
以防止运行时谬误。
最初在 package.json 文件的 script 项增加运行命令:
"scripts": {"start": "webpack serve --open"}
更多对于 dev-server 的配置,请参阅这里。
5.5webpack-dev-middleware
这个包装器会把解决的文件发射到服务器。它是在 webpack-dev-server
外面应用的,然而它能够作为独立的包来反对自定义的设置。以下例子演示联合一个 express 服务器的用例(把文件发射到 express 服务器里):
npm install --save-dev express webpack-dev-middleware
设置 webpack.config.js
配置:
module.exports = {
...
output: {
...
publicPath: '/'
}
}
在脚本里应用的 publicPath
确保了文件在 http://localhost:3000
上运行正确,下一步配置 express 服务器:
在根目录下创立 server.js
文件:
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);
// Tell express to use the webpack-dev-middleware and use the webpack.config.js
// configuration file as a base.
app.use(
webpackDevMiddleware(compiler, {publicPath: config.output.publicPath,})
);
// Serve the files on port 3000.
app.listen(3000, function () {console.log('Example app listening on port 3000!\n');
});
最初在 package.json
文件里增加配置,并运行 npm run server
就能够了。
{
"scripts": {"server": "node server.js"}
}
无关热模块替换的应用,请参阅这里。
6. 代码宰割
当打包的代码过大,影响性能和效率的时候,就能够把打包的程序宰割成多个小包,分状况加载。罕用的代码宰割形式有:
- 多入口(entry point)
- 避免反复(prevent duplication)
- 动静导入(dynamic imports)
6.1 多入口
间接在 webpack 外面配置多个入口即可。然而这种形式有两个毛病:
- 如果多个入口块 (entry chunks) 之间反复地用了多个模块,则这些模块都将被蕴含在这些块里;
- 不够灵便,且不能用外围应用逻辑来动静的宰割代码
6.2 避免反复
通过配置 dependOn
选项来共享多个包之间的模块,当有多个入口块时,还须要配置optimization.runtimeChunk:'single'
。
const path = require('path');
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
another: './src/another-module.js',
index: {
import: './src/index.js',
dependOn: 'shared',
},
another: {
import: './src/another-module.js',
dependOn: 'shared',
},
shared: 'lodash',
},
output: {filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
optimization: {runtimeChunk: 'single'}
};
通过以上的配置,shared 会是一个独自的块,其余依赖它的块都会从它这里获取援用。
6.3 提取公共依赖
应用它能够把公共的依赖提取进去放到一个现有的入口块或者新的入口块外面,通过 SplitChunksPlugin 实现。
const path = require('path');
module.exports = {
...
optimization: {
splitChunks: {chunks: 'all',},
},
};
6.4 动静导入
动静导入次要有两种办法,第一种,也是举荐的办法,应用 import()
语法;第二种,采纳require.ensure
。
6.5 包剖析
这里有一些官网的剖析工具。
7.ESM 模块反对
默认状况下,webpack 会把 ESM 模块导出成其它类型的模块,也会把其余模块导入成 ESM 类型。webpack 也能自动检测文件是 ESM 类型还是其余类型。
NodeJs 在 package.json 文件外面预留了 "type":"mode"
来强制将所有当前目录之下的文件转换成 ESM 模块,果"type":"commonjs"
,则将所有转化成 commonjs 类型。
7.1 反对 TypeScript
首先装置相干依赖:
npm install --save-dev typescript ts-loader
我的项目中增加 tsconfig.json
文件,而后配置以下必备内容:
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "es6",
"target": "es5",
"jsx": "react",
"allowJs": true,
"moduleResolution": "node"
}
}
最初在 webpack.config.json
文件中增加配置:
const path = require('path');
module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {extensions: ['.tsx', '.ts', '.js'],
},
};
8. 缓存
为了放慢服务器读取我的项目的工夫,缩小网络流量,webpack 提供缓存机制。
8.1 文件名字
能够应用 output.filename
来定义输入的文件的名字。webpack 提供了一种应用括号括起来的名字作为模版文件名的办法,叫做替换法 (substitutions)。[contenthash]
将会被基于文件内容的哈希名替换。文件中的任何内容扭转之后,哈希名都会扭转。同样的,[name]
将会被原文件的名字替换掉。
8.2 提取样板
利用 splitChunksPlugin
插件将运行时代码分离出来放到一个隔离的块外面。只须要配置 optimization.runtimeChunk
选项 single
即可。
module.exports = {
"optimization": {"runtimeChunk": "single"}
}
如果要把第三方的库剥离进去放到一个独自的包里(vendor),就能进步开发效率,用户就能够始终缓存着第三方的库,更新那些常常扭转的包。要达到这样的成果,只须要通过以下配置即可:
module.exports = {
optimization: {
runtimeChunk: "single",
splitChunks: {
cacheGroups: {
vendor: {test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
}
}
8.3 模块标识符
如果我的项目中任何地位的代码产生了变动,将引起整个我的项目生成的文件都变动,因为每个模块的标识符默认基于解析程序都会自增,即每次解析程序扭转 ID 都会扭转。为了避免这样的状况,webpack 提供以下配置:
module.expects = {
//...
optimization: {moduleIds: 'deterministic'}
}
9. 环境变量
通过 CLI 设定的环境变量能够被 webpack.config.js 配置文件读取到,从而有针对性的运行我的项目。
环境变量在 CLI 中的设定是通过在 --env
前面跟上变量设定来实现的。
webpack.config.js
const path = require('path');
module.exports = (env) => {
// Use env.<YOUR VARIABLE> here:
console.log('Goal:', env.goal); // 'local'
console.log('Production:', env.production); // true
return {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
};
10. 解析
这个选项用来配置模块解析的形式。通常的模式如下:
module.exports = {
//...
resolve: {// configuration options},
};
10.1 别名
创立别名,以更容易地导入模块。例如创立以下门路的别名:
const path = require('path');
module.exports = {
//...
resolve: {
alias: {Utilities: path.resolve(__dirname, 'src/utilities/')
},
},
};
如上应用 Utilities
代表 src/utilities/
门路,在须要导入的中央就能够间接应用别名:
import Utility from 'Utilities/utility';
11. 模块
在模块化编程中,开发人员将程序分解成离散的功能块,叫做模块。编写良好的模块提供了松软的形象和封装边界,每个模块在整个利用中具备连贯的设计和明确的指标。