webpack4 笔记

简介

本文基于 webpack4 , 用于查询常用配置,算是学习官方文档过后的一个笔记,如果你想全面了解 webpack 的配置,还请参考官方文档

约定

webpack4 号称零配置,其约定了一些基本的编译配置。

  1. 约定入口文件:./src/index.js 作为入口文件,然后将结果输出到 ./dist/main.js
  2. 增加 mode 字段:production 或者 development,在 mode=production 时会执行压缩,mode=development 则不会压缩文件。
  3. 大量的配置根据 mode 做对应的改变,在 production 时对文件编译做到最优。
1
2
# 指定development模式、入口文件、导出文件
webpack --mode development ./src/main.js --output ./dist/bundle.js

webpack4 在默认情况下做了大量的约定配置,实际上在一些简单的项目当中,通过增加各种命令行参数,如 mode、module 等就能代替配置文件的作用。
但我们要做一些精细、直观的配置,还是要使用配置文件,可以在命令行中指定使用的配置文件,这样我们可以区分 development 和 production 环境。
例如:

1
webpack --config webpack.dev.js

babel

安装 babel 以支持 ES6

1
npm i babel-core babel-loader babel-preset-env --save-dev

然后建立.babelrc

1
2
3
{
"presets": ["env"]
}

entry

1
2
3
4
5
6
7
// 只有一个入口的情况
entry: './src/index.js'
// 可以配置多个入口,适合于多页应用
entry: {
app: './src/index.js',
main: './src/main.js'
}

module

一个 module 由多个 rule 组成,一个 rule 根据正则对应一类文件,然后对这类文件配置相应的 loader,官方对于所有 loader 的介绍
在 webpack4 中如果你不需要一些复杂的判断,可以在命令行中加入 –module-bind js=babel-loader 来替换配置文件中的 module 中的对应 rule。
一个 module 的常见属性介绍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
module.exports = {
module: {
// 对各类文件进行相应的loader配置
rules: [
{
// 通过正则匹配文件
test: /\.css$/,
// loaders,loader可以是字符串,可以是对象,是对象的时候又可以对具体的loader进行详细的配置
// 注意:loader的执行顺序是从后往前
use: [],
// 只命中的文件目录
include: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'tests')
],
// 排除的文件目录
exclude: [
path.resolve(__dirname, 'node_modules'),
path.resolve(__dirname, 'bower_modules')
],
// parser 属性可以更细粒度的配置哪些模块语法要解析哪些不解析
// 和 noParse 配置项的区别在于 parser 可以精确到语法层面, 而 noParse 只能控制哪些文件不被解析。
parser: {
amd: false, // disable AMD
commonjs: false, // disable CommonJS
system: false, // disable SystemJS
harmony: false, // disable ES2015 Harmony import/export
requireInclude: false, // disable require.include
requireEnsure: false, // disable require.ensure
requireContext: false, // disable require.context
browserify: false, // disable special handling of Browserify bundles
requireJs: false, // disable requirejs.*
node: false // disable __dirname, __filename, module, require.extensions, require.main, etc.
// node: {...} // reconfigure node layer on module level
}
}
],
// noParse忽略对没有采用模块化的文件进行递归解析处理,包括import/require/define中的
// 可以使用正则表达式
// noParse: /jquery|chartjs/
// 可以使用函数,从 Webpack 3.0.0 开始支持
noParse: content => {
// content 代表一个模块的文件路径
return /jquery|chartjs/.test(content)
}
}
}

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.s?css$/,
use: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.html$/,
use: [
{
loader: 'html-loader',
options: { minimize: true }
}
]
}
]
}
}

resolve

resolve 主要是对导入导出、文件名等做一些配置。
官方 resolve 介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
modules.exports = {
resolve: {
alias: {
// 导入语句中的别名替换,import 'components/button' 就表示 import './src/components/button'
components: './src/components/',
// 只命中以react结尾的
react$: '/path/to/react.min.js'
},
// 在提供的lib中,优先采用es6代码
mainFields: ['jsnext:main', 'browser', 'main'],
// 在导入时不带后缀的情况下,优先访问的文件后缀
extensions: ['.ts', '.js', '.json'],
// 去哪些目录寻找模块,默认是node_modules
modules: ['./src/components', 'node_modules'],
// 描述第三方模块的文件,默认package.json
descriptionFiles: ['package.json'],
// 强制所有导入语句带后缀,默认false
enforceExtension: true,
// 对node_modules中的模块不带后缀
enforceModuleExtension: false
}
}

plugins

每个 plugin 的配置都不同,其用于对整体流程进行操作。

官方 plugins 介绍

extract-text-webpack-plugin 不能用于 webpack4, 应使用 mini-css-extract-plugin 对 CSS 进行处理。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'index.html'),
filename: 'index.html'
}),
new MiniCssExtractPlugin({
filename: '[name].[chunkhash:8].css',
chunkFilename: '[id].css'
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new webpack.HashedModuleIdsPlugin()
]

optimization

optimization 是 webpack4 新增的配置项,用于做一些优化配置,此前版本的多个 plugin 都合并在这个配置项中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module.exports = {
// ...
optimization: {
// 当mode为production时,此项默认为true,会调用UglifyjsWebpackPlugin对结果进行压缩
minimize: false,

// minimizer覆盖默认的压缩plugin
minimizer: [new UglifyJsPlugin({ sourceMap: true })],

// splitChunks代替之前的SplitChunksPlugin,对代码进行分割
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
},
// 编译发生错误时不会停止
noEmitOnErrors: true,
// 设置为true可以显示原始模块标识符,便于调试。mode为development时为true,production为false
namedModules: true,
// 使用DefinePlugin设置process.env.NODE_ENV,默认是mode对应的值,如果未设置mode则为production
nodeEnv: 'production'
}
}

还有一些优化配置会随 mode 自动做改变,以在 production 时做到最优。

performance

performance 用于控制文件大小,此处指的控制是指在超过指定大小时进行警告或报错提示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
//...
performance: {
// 在单个文件大于250k时提示error还是warning,默认是false,即warning提示
// 也可以直接指定字符串'error'|'warning'
hints: false,
// 首次加载最大大小,超过会报警,单位byte,默认250000
maxEntrypointSize: 250000,
// 单个文件最大大小,超过会报警,单位byte,默认250000
maxAssetSize: 100000,
// 指定哪些文件会用于计算性能,这里是js文件
assetFilter: function(assetFilename) {
return assetFilename.endsWith('.js')
}
}
}

DevServer

官方 DevServer 介绍
只会对启动 webpack-dev-server 起作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
module.exports = {
devServer: {
// 模块热替换
hot: true,
// 自动刷新,实时预览,默认为true
// 如果关闭的话,需要访问http://localhost:8080/webpack-dev-server/实时预览
inline: true,
// 用于单页应用,任何请求均返回index.html
historyApiFallback: true,
// 配置由多个单页组成的应用
historyApiFallback: {
// 使用正则匹配命中路由
rewrites: [
// /user 开头的都返回 user.html
{ from: /^\/user/, to: '/user.html' },
{ from: /^\/game/, to: '/game.html' },
// 其它的都返回 index.html
{ from: /./, to: '/index.html' }
]
},
// 配置文件根目录
// 用来配置暴露本地文件的规则,你可以通过 contentBase:false 来关闭暴露本地文件
contentBase: path.join(__dirname, 'public'),
// 注入响应头
headers: {
'X-foo': 'bar'
},
// 配置监听地址,默认是127.0.0.1,这样只有本地可以访问,设为0.0.0.0可以被其他机器访问
// 相当于命令webpack-dev-server --host 0.0.0.0
host: '0.0.0.0',
// 端口
port: 8081,
// 允许访问的域名
allowedHosts: ['test.com'],
// 关闭host检查,这样可以让局域网其他的机器通过IP访问
disableHostCheck: true,
// https配置,会自动生成一份证书
https: true,
// 自己配置证书
https: {
key: fs.readFileSync('path/to/server.key'),
cert: fs.readFileSync('path/to/server.crt'),
ca: fs.readFileSync('path/to/ca.pem')
},
// 客户端日志等级,可取如下之一的值 none | error | warning | info,默认info
clientLogLevel: 'info',
// gzip压缩,默认false
compress: true,
// 第一次启动在构建完成时打开浏览器
open: true,
// 指定上述情况打开的页面
openPage: '',
// 设置为true不会watch文件的变化,只有重新访问才会编译
lazy: true
}
}

从零搭建 react 配置

为避免某些众所周知的问题,你可以使用 cnpm 代替 npm:

1
2
3
4
5
6
7
8
9
10
# 安装react模块
npm install react react-dom --save
# 安装babel,如果你想使用更多的es特性,你可以安装stage部分
npm install babel-core babel-loader babel-preset-env babel-preset-stage-2 babel-preset-react --save-dev
# webpack
npm install webpack webpack-cli webpack-dev-server webpack-merge --save-dev
# 用于处理css和img的loader
npm install style-loader css-loader node-sass sass-loader file-loader --save-dev
# webpack plugins
npm install clean-webpack-plugin html-webpack-plugin mini-css-extract-plugin uglifyjs-webpack-plugin optimize-css-assets-webpack-plugin --save-dev

.bashrc:

1
2
3
{
"presets": ["env", "stage-2", "react"]
}

下面我们创建三个文件,webpack.common 是一些公共配置,webpack.dev 用于开发环境,会启动 webpack-dev-server,webpack.prod 用于生产环境,会最小化编译代码。

然后通过 webpack –config 指定相关文件,在 package.json 中进行命令配置。

webpack.common.config.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
module.exports = {
entry: {
app: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'index.html'),
filename: 'index.html'
})
],
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.s?css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
// 模块化
modules: true
}
},
'sass-loader'
]
},
{
test: /\.(png|jpg|gif|svg)$/,
use: ['file-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: ['file-loader']
}
]
}
}

webpack.dev.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const merge = require('webpack-merge')
const common = require('./webpack.common')
const webpack = require('webpack')

module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
// 启动模块热重载
hot: true,
// 可以设置proxy与后端联调
proxy: {
'/': 'http://localhost:8083'
}
},
plugins: [new webpack.HotModuleReplacementPlugin()]
})

webpack.prod.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 省略一些引用
module.exports = merge(common, {
mode: 'production',
output: {
filename: '[name].[chunkhash].bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[chunkhash:8].css',
chunkFilename: '[id].css'
}),
new webpack.HashedModuleIdsPlugin()
],
module: {
rules: [
{
test: /\.s?css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
// 模块化
modules: true
}
},
'sass-loader'
]
}
]
},
optimization: {
minimizer: [
new UglifyJsPlugin({
sourceMap: true
}),
new OptimizeCSSAssetsPlugin({})
],
splitChunks: {
chunks: 'all'
}
}
})