本文使用 Webpack 从零开始搭建一个开发环境的脚手架配置,在此做个记录,也方便以后使用。
前言
我的上一篇文章简单介绍了一下 Webpack 的一些核心概念和基本配置,需要了解的朋友可以先参考一下Webpack 的简单介绍
从 webpack v4.0.0 开始,可以不用引入一个配置文件。直接使用 webpack 命令就可进行打包。但是,一般我们需要进行更灵活的配置功能,所以本文我也创建一个 webpack 的配置文件,对webpack 的一些属性进行配置。
环境搭建
项目结构
首先我们创建一个目录,初始化 npm,然后 在本地安装 webpack,接着安装 webpack-cli(此工具用于在命令行中运行 webpack):
1 | $ mkdir webpack-dev-demo && cd webpack-dev-demo |
project
1 | webpack-dev-demo |
src/index.js1
2
3
4
5
6
7
8
9function component() {
var element = document.createElement('div');
element.innerHTML = 'Hello World';
return element;
}
document.body.appendChild(component());
public/index.js1
2
3
4
5
6
7
8
9
10
11
12
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Webpack 开发环境配置</title>
</head>
<body>
</body>
</html>
package.json1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17{
"name": "webpack-dev-demo",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config webpack.config.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.29.0",
"webpack-cli": "^3.2.1"
}
}
创建配置文件
在项目根目录下创建 webpack.config.js 配置文件
project
1 | webpack-dev-demo |
配置入口和输出
webpack.config.js1
2
3
4
5
6
7
8
9const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: '[name]-[hash:8].js',
path: path.resolve(__dirname, 'dist')
}
}
运行 webpack
1 | $ npm run build |
控制台的打印结果
可以看到打印日志,打包成功了,但是此时在浏览器打开我们的 index.html 文件,却发现界面上什么都不显示,这个也好理解,因为 index.html 此时还没有引入任何的 js 文件。所以这个时候就要将打包后的文件引入到 index.html 文件中,但是可以看到 dist 文件夹下的 js 文件名有很多的 hash 值,而且每次编译都可能不同,怎么办呢?这时候就要引入 html-webpack-plugin 插件了
html-webpack-plugin 插件的使用
安装插件:1
$ npm install html-webpack-plugin --save-dev
使用插件:
webpack.config.js1
2
3
4
5
6
7
8
9
10
11
12
13
14...
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
plugins: [
new HTMLWebpack({
// 用于生成的HTML文档的标题
title: 'Webpack 开发环境配置',
// webpack 生成模板的路径
template: './public/index.html'
})
]
}
关于 html-webpack-plugin 插件更多配置请参考:插件文档。
再次运行 webpack1
$ npm run build
可以看到 dist 文件夹下生成了一个 index.html 文件,在浏览器中打开这个 index.html 文件,可以看到,’Hello World’ 已经能够正常显示了
至此,项目能够正常打包了,但是还不够,此时可以看到 dist 文件夹下有两个 js 文件,但是明明只打了一个包啊。是因为另一个包使我们上一次操作打出来的,并没有删除掉。所以,为了避免 dist 文件夹中的文件变得杂乱,我们还需要引入 clean-webpack-plugin 插件帮助我们清理 dist 文件夹
clean-webpack-plugin 插件的使用
安装插件:1
$ npm install clean-webpack-plugin --save-dev
用法:new CleanWebpackPlugin(paths [, {options}])
webpack.config.js1
2
3
4
5
6
7
8
9
10
11...
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
...
plugins: [
...,
// 用法:new CleanWebpackPlugin(paths [, {options}])
new CleanWebpackPlugin(['dist'])
]
}
再次运行 webpack1
$ npm run build
此时 dist 文件夹下只有一个 js 和 html 文件了。说明插件配置成功了,关于 clean-webpack-plugin 更多配置请参考:插件文档。
配置 Http 服务并进行实时预览
安装 webpack-dev-server 包:1
$ npm install --save-dev webpack-dev-server
使用:
webpack.config.js
1 | ... |
启用热加载功能:(上面已经添加了)
1、在 devServer 配置中添加hot: true
属性
2、在 plugins 中添加new webpack.NamedModulesPlugin()
和new webpack.HotModuleReplacementPlugin()
在 package.json 中添加一个执行命令
package.json1
2
3
4
5
6
7...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config webpack.config.js",
+ "start": "webpack-dev-server"
}
...
启动 Http 服务
执行命令:1
$ npm run start
可以看到控制台打印输出:
打开浏览器,输入:http://localhost:8000/
,可以看到浏览器中可以正常显示 Hello World。
模式配置
webpack 配置中有一个 mode 属性的配置,三个可选属性:
production 会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin。
development 会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。
none 不选用任何默认优化选项
这里我们配置的是开发环境,所以需要将 mode 设置为 development
webpack.config.js1
2
3
4
5...
module.exports = {
+ mode: 'development',
...
}
启用调试工具 Source Map
此时项目能够正常运行,所以没有什么问题,但是现在我们修改一下,将 index.js 中的 return element
改成错误的 return ele
。我们 F12 打开开发工具,可以看到控制台的错误提示,点进去却发现跟我们写的代码不一致,难以对错误的代码进行调试,此时 Source Map 就派上用场了。
在 webpack.config.js 中添加 devtool 属性
webpack.config.js1
2
3
4
5
6...
module.exports = {
mode: 'development',
+ devtool: inline-source-map
...
}
devtool 的多个属性之间的差异
devtool | 构建速度 | 重新构建速度 | 生产环境 | 品质(quality) |
---|---|---|---|---|
(none) | +++ | +++ | yes | 打包后的代码 |
eval | +++ | +++ | no | 生成后的代码 |
cheap-eval-source-map | + | ++ | no | 转换过的代码(仅限行) |
cheap-module-eval-source-map | o | ++ | no | 原始源代码(仅限行) |
eval-source-map | – | + | no | 原始源代码 |
cheap-source-map | + | o | yes | 转换过的代码(仅限行) |
cheap-module-source-map | o | - | yes | 原始源代码(仅限行) |
inline-cheap-source-map | + | o | no | 转换过的代码(仅限行) |
inline-cheap-module-source-map | o | - | no | 原始源代码(仅限行) |
source-map | – | – | yes | 原始源代码 |
inline-source-map | – | – | no | 原始源代码 |
hidden-source-map | – | – | yes | 原始源代码 |
nosources-source-map | – | – | yes | 无源代码内容 |
再次运行项目:1
$ npm run start
可以看到报错依旧,但是在开发工具的控制台上,查看错误提示,可以根据提示清楚的找到我们写的代码所在位置.
测试之后请将错误的
return ele
改为正确的return element
为项目添加模块解析规则
此时,开发环境已经配置的差不多了,但是我现在想给 div 加一个样式,想让文字编程蓝色,居中显示,那么此时就需要用的 loader 了,因为 webpack 默认无法解析 css,所以就需要我们自己配置了
配置 css 模块解析
安装所需插件:1
$ npm install css-loader style-loader --save-dev
css-loader 用来解析 css 文件,而 style-loader 则用来将解析好的 css 内容注入到 JavaScript 里。由于 loader 执行顺序是从下到上,所以要将 css-loader 写在下面。
使用:
webpack.config.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23...
module.exports = {
...
plugins: [...],
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
// 还可以给 loader 添加一些配置
{
loader: 'css-loader',
options: {
// 开启 sourceMop
sourceMap: true
}
}
]
}
]
}
}
在 src 目录下新建一个 css 文件
src/index.css:1
2
3
4div {
color: blue;
text-align: center;
}
src/index.js1
2
3require('./index.css');
...
重新运行项目:1
$ npm run start
可以看到浏览器上此时文字颜色已经变蓝,并且居中显示。
配置其他模块解析
除了 css 之外,其他文件在 webpack 也都被认为是一个模块,也都需要对应的 loader 进行解析。
下面就不一一演示了,先把代码贴出来看一看。
下载所有需要的插件:1
$ npm install file-loader csv-loader xml-loader html-loader markdown-loader --save-dev
webpack.config.js1
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...
module.exports = {
...
plugins: [...],
module: {
rules: [
...
// 解析图片资源
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
// 解析 字体
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
},
// 解析数据资源
{
test: /\.(csv|tsv)$/,
use: [
'csv-loader'
]
},
// 解析数据资源
{
test: /\.xml$/,
use: [
'xml-loader'
]
},
// 解析 MakeDown 文件
{
test: /\.md$/,
use: [
"html-loader",
"markdown-loader"
]
}
]
}
}
这些配置基本上可以满足常规开发中使用到的各种模块资源,不过在开发过程中可能还会需要用到 less、scss 等 css 预编译语言,还需要使用 less-loader,sass-loader 进行配置。更多配置用法这里也无法一一详述,等大家用到的时候再去查阅对应文档即可。
使用 babel 进行配置
目前项目可以正常运行,但是现在 ES6、7 语法已经出来了,但是浏览器中还不能完全识别,所以我们需要 babel 讲 js 文件转换成浏览器可以识别的 ES5 语法。
安装 bable-loader 插件1
$ npm install babel-core babel-loader --save-dev
babel 还能进行配置,可以像上面那样直接在 loader 中进行配置,也可以在根目录下创建 .babelrc 文件配置,项目运行会自动从此文件中读取
使用 babel-loader:
webpack.config.js1
2
3
4
5
6
7
8
9
10
11
12
13
14...
module.exports = {
...
plugins: [...],
module: {
rules: [
{
test: /\.js/,
include: path.resolve(__dirname, 'src'),
loader: 'babel-loader?cacheDirectory',
},
]
}
}
babel 的 cacheDirectory 属性
默认值为 false。当有设置时,指定的目录将用来缓存 loader 的执行结果。之后的 webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程。如果设置了一个空值 (loader: 'babel-loader?cacheDirectory'
) 或者 true (loader: babel-loader?cacheDirectory=true)
,loader 将使用默认的缓存目录 node_modules/.cache/babel-loader
,如果在任何根目录下都没有找到 node_modules
目录,将会降级回退到操作系统默认的临时文件目录。
使用 cacheDirectory 选项,将 babel-loader 提速至少两倍。
babel 的 Presets 配置
presets 属性告诉 Babel 要转换的源码使用了哪些新的语法特性,一个 Presets 对一组新语法特性提供支持,多个 Presets 可以叠加。 Presets 其实是一组 Plugins 的集合,每一个 Plugin 完成一个新语法的转换工作。Presets 是按照 ECMAScript 草案来组织的,通常可以分为以下三大类:
已经被写入 ECMAScript 标准里的特性,由于之前每年都有新特性被加入到标准里,所以又可细分为:
- es2015 包含在2015里加入的新特性;
- es2016 包含在2016里加入的新特性;
- es2017 包含在2017里加入的新特性;
- env 包含当前所有 ECMAScript 标准里的最新特性。
被社区提出来的但还未被写入 ECMAScript 标准里特性,这其中又分为以下四种:
- stage0 只是一个美好激进的想法,有 Babel 插件实现了对这些特性的支持,但是不确定是否会被定为标准;
- stage1 值得被纳入标准的特性;
- stage2 该特性规范已经被起草,将会被纳入标准里;
- stage3 该特性规范已经定稿,各大浏览器厂商和 Node.js 社区开始着手实现;
- stage4 在接下来的一年将会加入到标准里去。
为了支持一些特定应用场景下的语法,和 ECMAScript 标准没有关系,例如 babel-preset-react 是为了支持 React 开发中的 JSX 语法。
babel 的 Plugins 配置
plugins 属性告诉 Babel 要使用哪些插件,插件可以控制如何转换代码。
安装项目中需要使用的 Presets 插件1
$ npm install babel-preset-env babel-preset-stage-0 --save-dev
安装项目中需要的 babel Plugin1
$ npm install babel-plugin-transform-class-properties babel-plugin-transform-runtime babel-runtime --save-dev
babel-plugin-transform-class-properties 可以在项目中使用新增的 class 属性用法
babel-plugin-transform-runtime 由于 babel 转换文件时会在每个文件中都写入辅助代码,使用此插件可以直接使用 babel-runtime 中的代码进行转换,避免代码冗余。所以 babel-plugin-transform-runtime 和 babel-runtime 成对使用
.babelrc1
2
3
4
5
6
7{
"presets": ["env", "stage-0"],
"plugins": [
"transform-runtime",
"transform-class-properties"
]
}
配置完成,此时就可以在项目中自由的使用 ES6 等新增 js 语法了。
使用 friendly-errors-webpack-plugin
有时候项目提示错误,可能是编译错误,可能是 ESLint 提示错误等等,我们希望错误提示能够友好一些,就可以使用这个插件
插件安装:1
npm install friendly-errors-webpack-plugin --save-dev
使用:
webpack.config.js1
2
3
4
5
6
7
8
9
10...
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
module.exports = {
...,
plugins: [
...
new FriendlyErrorsWebpackPlugin()
]
}
更多配置请参考插件文档
配置模块如何解析 resolve
开发的时候我们经常会需要引入自己写的文件模块,可能会需要按照路径一级一级的找,这个时候我们就可以配置 resolve,为一些常用的路径设置别名
配置:
webpack.config.js1
2
3
4
5
6
7
8
9
10
11...
module.exports = {
...
plugins: [...],
modules: {...},
resolve: {
alias: {
src: path.resolve(__dirname, 'src')
}
}
}
使用:
无论在任何文件里,引入 src 目录下的 index.css 文件时,路径都可以按照下面的这个引入路径来写
index.js1
2- require('./index.css');
+ import 'src/index.css';
重新运行项目,发现项目正常启动,index.css 中的样式也正常生效
至此,一个简单的开发环境的 Webpack 脚手架搭建完成了。
最终的项目结构以及文件代码
project1
2
3
4
5
6
7webpack-dev-demo
|- package.json
|- /public
|- index.html
|- /src
|- index.js
|- index.css
public/index.html1
2
3
4
5
6
7
8
9
10
11
12
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Webpack 开发环境配置</title>
</head>
<body>
</body>
</html>
src/index.js1
2
3
4
5
6
7
8
9
10
11import 'src/index.css';
function component() {
var element = document.createElement('div');
element.innerHTML = 'Hello World';
return element;
}
document.body.appendChild(component());
src/index.css1
2
3
4div {
color: blue;
text-align: center;
}
.babelrc1
2
3
4
5
6
7{
"presets": ["env", "stage-0"],
"plugins": [
"transform-runtime",
"transform-class-properties"
]
}
webpack.config.js1
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
module.exports = {
mode: 'development',
devtool: 'inline-source-map',
entry: './src/index.js',
output: {
filename: '[name]-[hash:8].js',
path: path.resolve(__dirname, 'dist')
},
devServer: {
// 必须配置的选项,服务启动的目录,默认为跟目录
contentBase: './dist',
// 使用热加载时需要设置为 true
hot: true,
/**
* 下面为可选配置
*/
// 指定使用一个 host。默认是 localhost
host: 'localhost',
// 端口号
port: 8000,
// 当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html。通过设置为 true 进行启用
historyApiFallback: {
disableDotRule: true
},
// 出现错误时是否在浏览器上出现遮罩层提示
overlay: true,
/**
* 在 dev-server 的两种不同模式之间切换
* 默认情况下,应用程序启用内联模式 inline
* 设置为 false,使用 iframe 模式,它在通知栏下面使用 <iframe> 标签,包含了关于构建的消息
*/
inline: true,
/**
* 统计信息,枚举类型,可供选项:
* "errors-only": 只在发生错误时输出
* "minimal": 只在发生错误或有新的编译时输出
* "none": 没有输出
* "normal": 标准输出
* "verbose": 全部输出
*/
stats: "errors-only",
// 设置接口请求代理,更多 proxy 配置请参考 https://github.com/chimurai/http-proxy-middleware#options
proxy: {
'/api/': {
changeOrigin: true,
// 目标地址
target: 'http://localhost:3000',
// 重写路径
pathRewrite: {
'^/api/': '/'
}
}
}
},
plugins: [
new HTMLWebpackPlugin({
// 用于生成的HTML文档的标题
title: 'Webpack 开发环境配置',
// webpack 生成模板的路径
template: './public/index.html'
}),
// 用法:new CleanWebpackPlugin(paths [, {options}])
new CleanWebpackPlugin(['dist']),
// 添加 NamedModulesPlugin,以便更容易查看要修补(patch)的依赖,由于设置了 mode: 'development',所以这个插件可以省略
// new webpack.NamedModulesPlugin(),
// 进行模块热替换
new webpack.HotModuleReplacementPlugin()
],
module: {
rules: [
{
test: /\.js/,
include: path.resolve(__dirname, 'src'),
loader: 'babel-loader?cacheDirectory'
},
// 解析 css
{
test: /\.css$/,
include: path.resolve(__dirname, 'src'),
use: [
'style-loader',
// 还可以给 loader 添加一些配置
{
loader: 'css-loader',
options: {
// 开启 sourceMop
sourceMap: true
}
}
]
},
// 解析图片资源
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
// 解析 字体
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
},
// 解析数据资源
{
test: /\.(csv|tsv)$/,
use: [
'csv-loader'
]
},
// 解析数据资源
{
test: /\.xml$/,
use: [
'xml-loader'
]
},
// 解析 MakeDown 文件
{
test: /\.md$/,
use: [
"html-loader",
"markdown-loader"
]
}
]
},
resolve: {
alias: {
src: path.resolve(__dirname, 'src')
}
}
}
package.json1
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{
"name": "webpack-dev-demo",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config webpack.config.js",
"start": "webpack-dev-server"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.7.0",
"babel-preset-stage-0": "^6.24.1",
"babel-runtime": "^6.26.0",
"clean-webpack-plugin": "^1.0.1",
"css-loader": "^2.1.0",
"html-webpack-plugin": "^3.2.0",
"style-loader": "^0.23.1",
"webpack": "^4.29.0",
"webpack-cli": "^3.2.1",
"webpack-dev-server": "^3.1.14"
}
}
源码地址
总结
本文篇幅较长,感谢各位的耐心阅读。本文主要从 入口、输出、插件(Plugins)、模块处理(Module)、loader、解析(resolve)等 6 个配置项着手配置了一个基本的 webpack 开发环境脚手架。本文主要讲解的内容为:
loader 的作用以及如何配置使用
babel 的作用以及配置项
各个插件的功能以及适用场景
解析能够为开发带来的效率
本文内容对于已经熟练掌握 Webpack 的朋友来说,可能有些浅薄,但是着重讲解了各个配置项的功能以及配置后对项目产生的效果。对于准备入门 webpack 的朋友应该会有一定的帮助。