0%

Webpack4.x实践

Webpack 作为前端最火的构建工具,是前端自动化工具链最重要的部分。本文主要分为两部分,第一部分:比较 Webpack3 版本和 Webpack4 版本区别;第二部分:Webpack4版本基础配置。该文是笔者自己的学习记录和知识总结,比较基础。也希望文章能够对Webpack使用不太熟悉的同学有所帮助。

Part1: Webpack3 与 Webpack4 版本区别

environment

首先在环境上,Webpack4 版本不再支持node.js4了,这也就意味着如果项目中使用了nodejs4版本的话,并且想要升级至 Webpack4, 需要先升级当前项目node版本。

mode构建模式

Webpack4版本增加mode配置,提供了三种构建模式可供选择 development,production和none,默认为 production, 不同模式下默认的配置也有所不同。

  • development默认配置:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    devtool: eval //调试
    cache:true //缓存模块, 避免在未更改时重建它们。
    module.unsafeCache:true //缓存已解决的依赖项, 避免重新解析它们。
    output.pathinfo:true //在 bundle 中引入「所包含模块信息」的相关注释
    optimization.providedExports:true //在可能的情况下确定每个模块的导出,被用于其他优化或代码生成。
    optimization.splitChunks:true //找到chunk中共享的模块,取出来生成单独的chunk
    optimization.runtimeChunk:true //为 webpack 运行时代码创建单独的chunk
    optimization.namedModules:true //给模块有意义的名称代替ids
    optimization.namedChunks:true //给chunk有意义的名称代替ids
  • production默认配置:
    1
    2
    3
    4
    5
    6
    7
    optimization.flagIncludedChunks:true //某些chunk的子chunk已一种方式被确定和标记,这些子chunks在加载更大的块时不必加载
    optimization.occurrenceOrder:true //给经常使用的ids更短的值
    optimization.usedExports:true //确定每个模块下被使用的导出
    optimization.sideEffects:true //识别package.json or rules sideEffects 标志
    optimization.noEmitOnErrors:true //编译错误时不写入到输出
    optimization.concatenateModules:true //尝试查找模块图中可以安全连接到单个模块中的段。- -
    optimization.minimize:true //使用uglify-js压缩代码

当mode为none时,之前 webpack 的默认配置会全部失效。(最好不要设置mode为none)

optimization 配置项

前面我们看到不同mode模式下,webpack 会有不同的默认配置项,这样我们就可以减少之前版本一些插件的使用了。

NamedModulesPlugin插件 –> optimization.namedModules(默认只在development模式)

ModuleConcatenationPlugin 插件 –> optimization.concatenateModules(默认只在 production 模式)

NoEmitOnErrorsPlugin 插件 –> optimizat on.noEmitOnErrors(默认只在 production 模式)

UglifyJsPlugin –> optimization.minimize(默认只在 production 模式)

CommonsChunkPlugin 插件–> optimization.splitChunks和optimization.runtimeChunk

具体修改配置可见:

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
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
optimization: {
runtimeChunk: {
name: 'manifest'
},
splitChunks: {
chunks: "all",
cacheGroups: {
commons: {
name: "commons",
test: /src[\/]/,
chunks: "initial",
priority: 2,
minChunks: 2
},
vendors: {
name: "vendors",
test: /node_modules/,
chunks: "initial",
priority: 10,
minChunks: 2
}
}
},
minimize: true,
noEmitOnErrors: true,
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: false
}),
new OptimizeCSSAssetsPlugin({})
]

并且 webpack 在运行的时候会根据mode设置一个process.env.NODE_ENV全局变量,可以实现不同的环境执行不同的代码,这样也就减少了DefinePlugin的使用。

Plugins

在 webpack4 版本中推荐使用 mini-css-extract-plugin 插件来替代 extract-text-webpack-plugin 抽取css到单独文件中,使用 optimize-css-assets-webpack-plugin 对 css 进行压缩处理

无需定义入口点和输出文件

Webpack 会查找./src/index.js作为默认入口点。 并且也无需定义输出文件,缺省为./dist/main.js。(这种特性的重要意义在于处理小型项目的时候,不需要配置文件,只需要webpack)。

支持多种模块类型

Webpack4 版本现在支持以下五种模块类型:

JavaScript/auto: Webpack3.0 在多种模块系统中支持了这种模块类型,比如 CommonJS, AMD, ESM

JavaScript/esm: 只支持 ECMAScript 模块

JavaScript/dynamic: 只支持 CommonJS 和AMD.

Json: 支持JSON数据,可以通过require和import使用

Webassembly/experimental: 只支持 WebAssembly


Part2: Webpack4 版本基础配置

先看下面这份 Webpack 的配置文件,如果面前的你已经能够完全读懂每一项,那么你可以快速浏览或者直接略过以下内容,如果你对其中还有一些疑惑,可以花一些时间慢慢阅读下。

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
const path = require('path');
const webpack = require('webpack'); // 需要引入webpack模块

const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入HtmlWebpackPlugin插件
const ExtractTextPlugin = require('extract-text-webpack-plugin') //引入分离插件
module.exports = {
devtool: "eval-source-map",
entry: {
index: path.join(__dirname,'src/index.js'),
child: path.join(__dirname,'src/child.js')
},
output: {
path: path.join(__dirname, './dist'),
filename: '[name].js'
},
// 提取公共代码
/* optimization: {
splitChunks: {
cacheGroups: {
vendor: { // 抽离第三方插件
test: /node_modules/, // 指定是node_modules下的第三方包
chunks: 'initial',
name: 'vendor', // 打包后的文件名,任意命名
// 设置优先级,防止和自定义的公共代码提取时被覆盖,不进行打包
priority: 10
},
common: { // 抽离自己写的公共代码,utils这个名字可以随意起
chunks: 'initial',
name: 'common', // 任意命名
minSize: 0 // 只要超出0字节就生成一个新包
}
}
}
}, */
module: {
rules: [
{
test: /(\.jsx|\.js)$/, //匹配jsx和js结尾的文件
use: {
loader: 'babel-loader',
// 当loader传入的参数过多时,可以添加options对象
options: {
cacheDirectory: true //cacheDirectory用于缓存babel的编译结果,加快重新编译的速度
}
},
include: path.join(__dirname,'src') //字面意思,src下面的所有jsx和js文件都会被babel-loader解析
},
{
test: /.css$/,
// use: ['style-loader','css-loader'] //这里注意调用loader时是从右到左编译的。
use: ExtractTextPlugin.extract({ // 调用分离插件内的extract方法
fallback: 'style-loader',
use: ['css-loader']
})
},
{
test: /.(scss|sass)$/,
// use:['style-loader','css-loader', 'sass-loader']
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'sass-loader']
})
},
{
test: /.less$/,
// use:['style-loader','css-loader', 'less-loader']
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'less-loader','postcss-loader']
})
},
{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'url-loader',
options: {
limit: 8192,
outputPath: 'images' // 设置打包后图片存放的文件夹名称
}
}]
},
{
test: /\.tsx?$/,
use: {
loader: 'ts-loader'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname,'./src/index.tmpl.html') //模板,webpack默认会生成index.html文件
}),
new webpack.HotModuleReplacementPlugin(), // 热更新插件
new ExtractTextPlugin('css/index.css') // 将css统一分离到/dist文件夹下的css文件夹中的index.css
],
devServer: {
contentBase: path.join(__dirname, './dist'), //服务器读取文件目录
port: 9001, //运行的端口号
inline: true, // 文件修改后实时刷新
historyApiFallback: true, //设为true,所有的页面都将跳转到index.html
hot: true, // 热更新
proxy: {
// "/api": "http://localhost:9000/api/"
} // 代理到后端服务接口,当我们需要后端联调的时候可以配置
}
}

1.基础构建

创建并进入文件夹,npm init 初始化项目,安装 webpack 和 webpack-cli

1
2
3
mkdir react-webpack && cd react-webpack && npm init
npm install -g webpack
npm install webpack webpack-cli --save-dev

按以下目录结构创建文件。

然后编写index.html和index.js基础代码,并在 webpack.config.js 配置文件中配置入口和输出文件

1
2
3
4
5
6
7
8
9
10
11
12
13
const path = require('path');
module.exports = {
entry: path.join(__dirname,'src/index.js'),
output: {
path: path.join(__dirname, './dist'),
filename: 'bundle.js'
}
}

修改package.json
"script": {
"start": "webpack --config webpack.config.js"
}

终端运行npm start,浏览器查看运行结果。

这样我们的 webpack 就可以正常使用了。

2. entry

entry顾名思义它是 webpack 通往天堂/地狱的入口。我们也从这里开始 webpack 的旅途。

entry有以下几种配置:

1
2
3
4
5
6
7
8
9
10
11
12
// 单文件入口
entry: path.join(__dirname,'src/index.js'),

// 多文件入口
数组写法:webpack会将数组里面的文件一起打包到bundle.js
entry: ['./src/index.js','./src/test.js']

对象写法:打包之后会生成不同的文件存放
entry: {
index: './src/index.js',
test: './src/test.js'
}

3. output

output 可以控制 webpack 如何向硬盘写入编译文件。

  • filename: 输出的文件名,与entry相匹配,js/[name].js 中name指的就是entry中的index, test
  • path: 目标输出目录路径
  • publicPath: 文件输出公共路径
1
2
3
4
output: {
path: path.join(__dirname, './dist'),
filename: '[name].js'
}

注意:即使我们有多个entry 入口起点,但只指定一个输出配置

4. mode

mode配置为 webpack4 新增,只需在配置文件中配置或者cli参数中传递。

1
2
3
4
5
6
// 方法一
module.exports = {
mode: 'production'
}
// 方法二
webpack --mode=production

更多mode详细介绍可以参考Part1

5. 构建本地服务器(webpack-dev-server)

从上图运行结果的路径我们可以看到页面是从本地文件打开的,在项目开发中为了提高开发效率,我们需要搭建本地服务器。

安装 webpack-dev-server

1
npm install webpack-dev-server --save-dev

简单配置下devServer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
devServer: {
contentBase: path.join(__dirname, './dist'), //服务器读取文件目录
port: 9001, //运行的端口号
inline: true, // 文件修改后实时刷新
historyApiFallback: true, //设为true,所有的页面都将跳转到index.html
proxy: {
// "/api": "http://localhost:9000/api/"
} // 代理到后端服务接口,当我们需要后端联调的时候可以配置
}
修改package.json
"script": {
"start": "webpack --config webpack.config.js",
"dev": "webpack-dev-server --config webpack.config.js --color --progress --hot --open"
}


从浏览器中可以看到我们项目已经跑在本地了。

6. 支持 es6, react.js, TypeScript.js 等

babel是一个编译 JavaScript 的平台,它可以把我们编写的ES6,ES7代码以及react的JSX等编译成浏览器可识别的js文件。

安装babel依赖包, react, typescript

1
2
3
npm install babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0 --save-dev
npm install react react-dom typescript ts-loader --save
npm install typescript ts-loader --save-dev

新建.babelrc 配置文件

1
2
3
4
5
6
7
8
9
{
"presets": [
"es2015",
"react",
"stage-0"
],
"plugins": [
]
}

webpack.config.js 使用babel-loader解析js文件, ts-loader 解析ts语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module: {
rules: [
{
test: /(\.jsx|\.js)$/, //匹配jsx和js结尾的文件
use: {
loader: 'babel-loader',
// 当loader传入的参数过多时,可以添加options对象
options: {
cacheDirectory: true //cacheDirectory用于缓存babel的编译结果,加快重新编译的速度
}
},
include: path.join(__dirname,'src') //字面意思,src下面的所有jsx和js文件都会被babel-loader解析
},
{
test: /\.tsx?$/,
use: {
loader: 'ts-loader'
}
}
]
},

命令行输入:tsc–init

生成 tsconfig.json 文件,修改配置

1
2
3
4
5
6
7
8
9
{
"compilerOptions": {
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs", // 包含es6 es7 和commonjs
"target": "es5", // 目标运行环境
"jsx": "react"
}
}

修改index.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Hello extends Component {
render() {
return (
<div>
hello, webpack,i'm react
</div>
)
}
}
ReactDOM.render(
<Hello/>,
document.getElementById('root')
)

运行报错

原来是 babel-core 和 babel-loader 的版本冲突了,我们安装7版本的 babel-loader 就可以了。

1
2
3
npm install babel-loader@7.1.4 --save-dev

// 终端输入 npm run dev

恭喜 react 和 typescript 环境配置成功。

7. 编译css文件(less,sass)

1
2
3
npm install css-loader style-loader --save-dev  // css 文件
npm install sass-loader node-sass --save-dev // sass文件
npm install less less-loader --save-dev // less文件

webpack.config.js 添加配置

1
2
3
4
5
6
7
8
9
10
11
12
{
test: /.css$/,
use: ['style-loader','css-loader'] //这里注意调用loader时是从右到左编译的。
},
{
test: /.(scss|sass)$/,
use:['style-loader','css-loader', 'sass-loader']
},
{
test: /.less$/,
use:['style-loader','css-loader', 'less-loader']
}

注意:调用loader时是从右到左编译的。

8. 支持图片

1
npm install --save-dev url-loader file-loader
  • file-loader 解决css等文件中引入图片路径的问题
  • url-loader 当图片较小的时候会把图片BASE64编码,大于limit参数的时候还是使用file-loader 进行拷贝

webpack.config.js 添加配置

1
2
3
4
5
6
7
8
9
{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'url-loader',
options: {
limit: 8192
}
}]
}

9. devtool(开发工具)

控制是否生成,以及如何生成 source map文件,开发环境下更有利于定位问题,默认 false,

开启之后会影响到编译速度,生产环境记得关闭这个功能。

devtool的值有许多,常见 cheap-eval-source-map、eval-source-map、cheap-module-eval-source-map、inline-cheap-module-source-map等等,

这里主要介绍几种:

1
2
3
4
source-map 把映射文件生成到单独的文件,最完整最慢
cheap-module-source-map 在一个单独的文件中产生一个不带列映射的Map
eval-source-map 使用eval打包源文件模块,在同一个文件中生成完整sourcemap
cheap-module-eval-source-map sourcemap和打包后的JS同行显示,没有映射列

更详细可见官网Concepts
webpack 配置文件中增加

1
devtool: "eval-source-map",

10. 从js中分离css

安装 mini-css-extract-plugin插件

1
npm install --save-dev mini-css-extract-plugin

改写 webpack 配置文件

1
2
3
4
5
6
7
8
9
10
const MiniCssExtractPlugin = require("mini-css-extract-plugin") 

{
test: /\.(scss|sass)$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader"
]
}

11. 增加css前缀

1
npm install postcss-loader autoprefixer --save-dev

新建 postcss.config.js

1
2
3
4
5
6
7
module.exports = {
plugins: [
require('autoprefixer') // 引用autoprefixer模块
]
}

// 在css-loader中再引入postcss-loader即可

12. 简化HTML文件创建

1
npm install html-webpack-plugin --save-dev

webpack 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const path = require('path');
const webpack = require('webpack'); // 需要引入webpack模块
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入HtmlWebpackPlugin插件
module.exports = {
entry: path.join(__dirname,'src/index.js'),
output: {
path: path.join(__dirname, './dist'),
filename: 'bundle.js'
},
module: {
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname,'./src/index.tmpl.html')
})
],
devServer: {
}
}

// package.json文件里添加
"build": "webpack --mode production --config webpack.config.js"

之后我们npm run build会发现自动生成dist文件夹,这是因为我们在output出口配置了dist/bundls.js,这样HtmlWebpackPlugin会自动帮我们在index.html中引用bundle.js,不需要手动引用了。

13. 启用热替换模块 (HMR)

1
2
npm install --save-dev react-hot-loader
因为这个插件是依赖于webpack-dev-server的,所有要在devServer配置项中开启热更新 hot: true

webpack 配置文件

1
2
3
4
5
6
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname,'./src/index.tmpl.html')
})
new webpack.HotModuleReplacementPlugin(), // 热更新插件
]

注意:不要在生产环境(production)下启用 HMR

14. optimization(webpack4新增)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//   提取公共代码
optimization: {
splitChunks: {
cacheGroups: {
vendor: { // 抽离第三方插件
test: /node_modules/, // 指定是node_modules下的第三方包
chunks: 'initial',
name: 'vendor', // 打包后的文件名,任意命名
// 设置优先级,防止和自定义的公共代码提取时被覆盖,不进行打包
priority: 10
},
common: { // 抽离自己写的公共代码,common这个名字可以随意起
chunks: 'initial',
name: 'common', // 任意命名
minSize: 0 // 只要超出0字节就生成一个新包
}
}
}
},

这一部分Part1有详细介绍。

还有很多 plugins 以及优化的知识本文没有介绍到,更多 webpack 知识可去官网了解。

就到这里吧

关于 Webpack4 的一些常规配置就说到这里了,其实在工作当中更多时候,可能并不需要我们去配置这些内容,但是掌握这些技能是必要的。本文对 webpack 的讲述仍有许多不完善的地方,更多知识我们一起学习。文章如有错误的地方,也欢迎指教。

-------------本文结束, 感谢您的阅读-------------