重构Webpack系列之六 ---- 配置文件
一、基本用法
webpack的配置文件不尽相同。这是因为webpack的配置文件是JavaScript文件,文件内导出了一个webpack配置的对象,然后webpack会根据该配置定义的属性进行处理。
本文的结论是,webpack的配置可以有许多不同的样式和风格。关键在于,为了易于维护和理解这些配置,需要再团队内部保持一致。本文基于webpack@5版本进行操作说明。
a.基本配置
const path = require('path');
module.exports = {
mode: 'development',
entry: './foo.js',
output: {
path: path.resolve(__dirname,'dist'),
filename: 'foo.bundle.js'
}
}
b.不同的配置文件
一般开发项目的过程中,会区分开发环境和生产环境,所以webpack对应的配置文件也要分开:
// package.json
"scripts": {
"build": "webpack --config webpack.prod.config.js",
"dev": "webpack --config webpack.dev.config.js"
}
c.其他配置
webpack完整的配置文件还有很多选项,让我们一一了解他:
const path = require('path');
module.exports = {
mode: "production", //"production" | "development" | "none"
// 选择不同的模式告诉webpack相应地使用其内置优化。
entry: "./app/entry.js", // string | object | array
// 默认为./src,这里应用程序开始执行,webpack开始打包
output: {
// webpack输出结果的相关选项
path: path.resolve(__dirname, "dist"),
// 所有输出文件的目标路径,必须是绝对路径(使用nodejs的path模块)
filename: "[name].js", // string(default)
// entry chunk的文件名模板
publicPath: "/assets/", // string
// 输出解析文件的目录,url相对于html页面
library: {
type: "umd", // 通用模块定义
// 导出库的类型
name: "MyLibrary", // string | string[]
// 导出库的名称
},
uniqueName: "my-application",
// 此生成的唯一名称,以避免与同一HTML中的其他生成冲突
name: "my-config",
// 配置的名称,显示在输出中
},
module: {
// 模块配置相关
rules: [
// 模块规则(配置loader、解析器选项)
{
// 匹配的条件Conditions:
test: /\.jsx?$/,
include: [
path.resolve(__dirname, "app")
],
exclude: [
path.resolve(__dirname, "app/demo-files")
]
// 这些是匹配条件,每个条件接受一个正则表达式或字符串
//测试和包含具有相同的行为,两者必须匹配
//排除不能匹配(优先于测试和包含)
//最佳做法:
//-仅在测试和文件名匹配中使用RegExp
//-在include和exclude中使用绝对路径数组来匹配完整路径
//-尽量避免排除,而更喜欢包含
//每个条件还可以接收具有“and”、“or”或“not”属性的对象
//这是一系列条件。
issuer: /\.css$/,
issuer: path.resolve(__dirname, "app"),
issuer: { and: [ /\.css$/, path.resolve(__dirname, "app") ] },
issuer: { or: [ /\.css$/, path.resolve(__dirname, "app") ] },
issuer: { not: [ /\.css$/ ] },
issuer: [ /\.css$/, path.resolve(__dirname, "app") ], // like "or"
// 执行Actions:
// 单个loader:
loader: "babel-loader",
// 应用的loader,相对于上下文解析
// loader的配置选项
options: {
presets: ["es2015"]
},
// 应用多个复合loader和loader对应的options
use: [
"htmllint-loader",
{
loader: "htmllint-loader",
options: {
//...
}
}
],
type: "javascript/auto",
// 指定模块的类型
},
{
oneOf: [
// ...(rules)
]
// 只使用这一套规则
},
{
// ... (conditions)
rules: [
// ...(rules)
]
// 使用所有这些嵌套规则(结合使用条件)
}
]
},
resolve: {
// 用于解析模块请求的选项
// 不适用于loader的模块解析
modules: ["node_modules",path.resolve(__dirname, "app")],
// 查找模块的目录(按数组顺序)
extensions: [".js",".json",".jsx",".css"],
// 使用的扩展名
alias: {
// 模块名称别名的列表
// 别名是相对于当前上下文导入的
"module": "new-module",
// 别名:"module" -> "new-module" 和 "module/path/file" -> "new-module/path/file"
"only-module$": "new-module",
// 别名 "only-module" -> "new-module",但不匹配 "only-module/path/file" -> "new-module/path/file"
"module": path.resolve(__dirname, "app/third/module.js"),
// 别名“module”->“/app/third/module.js”和“module/file”导致错误
"module": path.resolve(__dirname, "app/third"),
// 别名为“module”->”/app/third“和“module/file”->”/app/third/file”
[path.resolve(__dirname, "app/module.js")]: path.resolve(__dirname, "app/alternative-module.js"),
// 别名“/app/module.js”->”/app/alternative module.js”
}
},
performance: {}, // 用不上
devtool: "source-map",
// 通过为浏览器调试工具提供极其详细的源映射的元信息来增强调试能力
// 但会牺牲构建速度!
context: __dirname, // string(绝对路径!)
// webpack的主目录
// entry和module.rules.loader选项都相对于此目录解析
target: 'web',
// 由于 JavaScript 既可以编写服务端代码也可以编写浏览器代码,所以 webpack 提供了多种部署 target,web代表浏览器端,默认是browserlist
devServer: {
proxy: {}, // 代理
static: path.join(__dirname, 'public'), //boolean | string | array | object, static file location
compress: true, // 是否开启 gzip压缩
historyApiFallback: true, // 不晓得咋用
hot: true, // 热更新
https: false,
},
plugins: [], //插件
optimization: {
// 优化选项
chunkIds: "size",
// 为模块生成ID的方法,告知 webpack 当选择模块 id 时需要使用哪种算法
moduleIds: "size",
// 模块ID的生成方法,告知 webpack 当选择模块 id 时需要使用哪种算法
mangleExports: "size",
// 将导出名称重命名为较短的名称
minimize: true,
// 最小化输出文件
minimizer: [new CssMinimizer(), "..."],
// 用于输出文件的最小值
splitChunks: {
cacheGroups: {
"my-name": {
// 定义具有特定属性的模块组
test: /\.sass$/,
type: "css/mini-extract"
}
}
}
}
}
二、导出方式
除了导出单个配置外,还有一些能满足更多需求的导出配置的方式。
a.导出为函数
区分开发环境和生产环境的情况,也可以由导出为函数来处理,这个函数包含两个参数:
- 参数1是环境
- 参数2是传递给webpack的配置项
module.exports = funtion(env, argv) {
return {
mode: env.production ? 'production':'development',
devtool: env.production ? 'source-map' : 'eval',
plugins: [
new TerserPlugin({
terserOptions: {
compress: argv.mode === 'production'
}
})
]
}
}
b.导出为Promise
当需要异步加载配置变量时,webpack将执行函数并导出一个配置文件,同时返回一个Promise。webpack支持使用Promise.all([your promise]) 导出多个promise:
module.exports = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
entry: './app.js',
// ...
})
}, 5000)
})
}
c.导出多种配置
除了导出单个配置对象/或函数,你可能也会需要导出多种配置(webpack@3以上)。当webpack运行时,所有配置项都会构建,比如对于多targets(如AMD和CommonJS)构建library时会非常有用:
module.exports = [
{
output: {
filename: './dist-amd.js',
libraryTarget: 'amd'
},
name: 'amd',
entry: './app.js',
mode: 'production'
},
{
output: {
filename: './dist-commonjs.js',
libraryTarget: 'commonjs',
},
name: 'commonjs',
entry: './app.js',
mode: 'production'
}
]
备注:如果你只传了一个--config-name
名字标识,webpack将只会构建指定的配置项。
d.dependencies(基于c的基础上)
以防你的某个配置依赖于另一个配置的输出,你可以使用一个 dependencies 列表指定一个依赖列表:
module.exports = [
{
name: 'client',
target: 'web',
// …
},
{
name: 'server',
target: 'node',
dependencies: ['client'],
},
]
e.parallelism(基于c的基础上)
如果你导出了多个配置,你可以在配置中使用 parallelism 选项来指定编译的最大并发数。(webpack@5.22以上)
module.exports = [
{
//config-1
},
{
//config-2
},
];
module.exports.parallelism = 1;
三、使用其他语言
Webpack支持使用多种编程语言和数据描述格式来编写配置文件。在node-interpret中你可以找到当前所支持的文件类型列表,通过node-interpret,webpack能够处理这些类型的配置文件。
a.TypeScript
npm install --save-dev typescript ts-node @types/node @types/webpack
// 如果使用webpack-dev-server,还需要安装以下依赖
npm install --save-dev @types/webpack-dev-server
安装完成知乎便可以开始编写配置文件,示例如下:
// webpack.config.ts
import * as path from 'path';
import * as webpack from 'webpack';
// 如果需要配置devServer,需要引入以下内容
import 'webpack-dev-server'
const config: webpack.Configuration = {
mode: 'production',
entry: './foo.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'foo.bundle.js'
}
}
export default config;
该示例需要typescript版本在2.7及以上,并在tsconfig.json文件的compilerOptions中添加esModuleInterop和allowSyntheticDefaultImports两个配置项。
值得注意的是你需要确保tsconfig.json的compilerOptions中module选项的值为commonjs,否则webpack的运行会失败报错,因为ts-node不支持commonjs以外的其他模块规范。
你可以通过三个途径来完成module的设置:
- 直接tsconfig.json文件
- 修改tsconfig.json并且添加ts-node的设置
- tsconfig-paths
第一种方法就是打开你的tsconfig.json文件,找到compilerOptions的配置,然后设置target和module的选项分别为“ES5”和“CommonJs”(在target设置为ES5时你也可以不显示编写module的配置)。
// tsconfig.json
{
"compilerOptions": {
"target": "ES5",
"module": "ESNext"
}
}
第二种方法就是添加ts-node设置:
你可以为tsc保持“module”:“ESNext”配置,如果你是用webpack或者其他构建工具的话,为ts-node设置一个重载(override):
// tsconfig.json
{
"compilerOptions": {
"module": "ESNext",
},
"ts-node": {
"compilerOptions": {
"module": "CommonJS"
}
}
}
第三种方法需要先安装tsconfig-paths这个npm包,如下所示:
npm install --save-dev tsconfig-paths
安装后你可以为webpack配置创建一个单独的TypeScript配置文件,示例如下:
// tsconfig-for-webpack-config.json
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"esModuleInterop": true
}
}
ts-node可以根据tsconfig-paths提供的环境变量process.env.TS_NODE_PROJECT来找到tsconfig.json文件路径。
process.env.TS_NODE_PROJECT变量的设置如下所示:
// package:json
{
"scripts": {
"build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack-config.json\"webpack"
}
}
之所以要添加cross-env,是因为我们在直接使用TS_NODE_PROJECT时遇到过“TS_NODE_PROJECT”unrecognized command报错的反馈,添加cross-env之后该问题也得到了解决。
b.CoffeeScript
与Typescript类似,在使用CoffeeScript前需要先安装其依赖,如下所示:
npm install --save-dev coffeescript
然后编写配置文件:
// webpack.config.coffee
HtmlWebpackPlugin = require('html-webpack-plugin')
webpack = require('webpack')
path = require('path')
config =
mode: 'production'
entry: './path/to/my/entry/file.js'
output:
path: path.resolve(__dirname, 'dist')
filename: 'my-first-bundle.js'
module: rules: [{
test: /\.(js|jsx)$/
use: 'babel-loader'
}]
plugins: [
new HtmlWebpackPlugin(template: './src/index.html')
]
module.exports = config
c.Babel and JSX
首先,跟上面两个方式一样,需要安装一些必要的依赖,如下所示:
npm install --save-dev babel-register jsxobj babel-preset-es2015
编写.babelrc配置文件:
{
"presets": ["es2015"]
}
编写webpack配置文件:
// webpack.config.babel.js
import jsxobj from 'jsxobj'
// 插件引入示例
const CustomPlugin = (config) => ({
...config,
name: 'custom-plugin'
})
export default (
<webpack target="web" watch mode="production">
<entry path="src/index.js" />
<resolve>
<alias
{...{
react: 'preact-compat',
'react-dom': 'preact-compat'
}}
/>
</resolve>
<plugins>
<CustomPlugin foo="bar" />
</plugins>
</webpack>
)
如果你在其他地方也使用了Babel并且modules的值设为false,则必须维护两份.babelrc的文件,或者你也可以将上述示例中的import jsxobj from 'jsxobj’替换为const jsxobj = require(‘jsxobj’)。并将新的export语法替换为module.exports,因为node还未支持ES6的模块语法。
四、注意事项
- webpack完全遵循CommonJS模块规范
- 可以通过require()引入其他文件
- 可以通过require()使用npm下载的工具函数
- 可以使用三元运算符来编写
- 可以对value使用常量或变量赋值
- 编写并执行函数,生成部分配置
- 配置文件不可过长,如有则需拆分多个,例如区分环境
本文就到此结束啦,感兴趣的小伙伴们就点点关注点点赞把~