使用 webpack 搭建 vue 开发环境(一)
只要一直在路上,一切都不算远
# 使用 webpack 搭建 vue 开发环境(一)
虽然 vue-cli
已经非常成熟,成熟到可能自己写的 webpack 性能上不一定比得上 vue-cli
。当然只是性能上,在实用性,拓展性,学习性上,自己能写出符合自己工程项目的 webpack 配置才是最好的
所有的代码都已经放在了 码云@Jioho/webpack_config (opens new window)
毕竟 github 有点慢~这边的网络上不去
master 分支的代码是每个版本迭代后最新的代码,所以第一篇文章的最终代码在 v0.0.1 分支 (opens new window)
# 安装环境
# 第一步
npm init -y
# 第二步
npm install --save-dev html-webpack-plugin webpack webpack-cli
# 第三步
npm install vue
2
3
4
5
6
7
8
优雅的三部,最起码的 vue 环境,和 webpack 环境少不了
# 目录结构
接下来看下目录结构。完全没有多余的目录
.
|-- build
| `-- webpack.base.js
|-- package-lock.json
|-- package.json
|-- public
| `-- index.html
`-- src
|-- main.js
`-- App.vue
2
3
4
5
6
7
8
9
10
# 开始写配置
- package.json
{
"scripts": {
"serve": "webpack --config build/webpack.base.js --mode development"
}
}
2
3
4
5
- webpack.base.js
先完成最基础的功能:入口文件
、出口文件
、打包后的文件需要有个html页面承载
为了防止目录混淆,各种../../
造成不必要的 bug,我先直接定义了 ROOT_PATH
。然后接下来都是最基础的配置了,应该没啥难度
const path = require('path')
const HtmlWebpackPlugins = require('html-webpack-plugin')
const ROOT_PATH = path.resolve(__dirname, '../')
module.exports = {
entry: path.resolve(ROOT_PATH, 'src/main.js'),
output: {
filename: '[name].js',
path: path.resolve(ROOT_PATH, 'dist')
},
plugins: [
// 设置html模板生成路径
new HtmlWebpackPlugins({
filename: 'index.html',
template: path.resolve(ROOT_PATH, 'public/index.html'),
chunks: ['main'] // 指定在html自动引入的js打包文件
})
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 看下和 vue 相关的文件:
- main.js
import Vue from 'vue'
import App from './APP.vue'
new Vue({
el: '#app',
render: h => h(App)
})
2
3
4
5
6
7
- App.vue
注意我的 App.vue 的 style 标签中还没用上 less/scss。这个需要配置,后面会讲到
<template>
<div class="text-red">
{{ message }}
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
message: 'hello webpack'
}
}
}
</script>
<style>
.text-red {
color: red;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
检验一下:输入 npm run serve
如果那么简单,都不叫 webpack 了 😃
仔细看了下红色字,大概意思就是,webpack 不认识vue
文件,webpack 只认识js
文件,所以我们入口文件引入的是main.js
。然后 main.js 引入的 vue
我也不转弯抹角拿 JS 文件输出一段话引入到 html 上了,那种 demo 谁都会写,那么接下来直接上 vue-loader
# 使用 vue-loader
由于 vue 里面还有style
标签,所以我们还得顺便处理css
的问题。用的是 css-loader
和 vue-style-loader
还有 vue-template-compiler
也是要用到的,解析 template 标签用
npm i vue-loader vue-template-compiler css-loader -D
- 修改 webpack.base.js
由于用的是 webpack4.x 版本,所以引入loader
改用了 module.rules
字段
当然也要注意用到了 VueLoaderPlugin
插件,用于解析vue
文件的
接下来我将会用 // ...
代表原文件中没修改的地方,只贴出了需要修改的地方
module.rules 中执行顺序也要将就,在同一个 test
中,执行顺序都是 先右后左
。包括如果 loader 里面继续嵌套 loader。会把最里的执行完,再到外面
就好像下面的 先执行: css-loader
把执行完的结果在传递给 vue-style-loader
顺序不要搞乱了
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
// ...
module: {
rules: [
{
test: /\.vue$/,
use: ['vue-loader']
},
// 它会应用到普通的 `.css` 文件以及 `.vue` 文件中的 `<style>` 块,因为 VueLoaderPlugin
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
}
]
},
plugins: [
// ...
new VueLoaderPlugin()
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
再次运行 npm run serve
一切顺利,没有写错别字的话,是已经打包成功了,输出了 index.html 并且引入了 main.js。 不过
webpack 4.x
的坑远不止这些
# 运行开发环境,添加真正的 serve
结束上面的步骤后,可以看到输出的是打包文件,并不是 serve
开发环境,所以下面我们继续添加文件热更新的功能
npm install --save-dev webpack-dev-server
修改一下 package.json
文件
- package.json
修改 serve 使用 webpack-dev-server
启动,添加build
命令
{
"scripts": {
"serve": "webpack-dev-server --config build/webpack.base.js --mode development",
"build": "webpack --config build/webpack.base.js --mode production"
}
}
2
3
4
5
6
运行下 npm run serve
几个重要的问题:
- 由于我 8080 端口占用了,所以 webpack 自动帮我分配了 81 端口
- 接下来修改一下 App.vue 文件(不包括 css 部分),修改 html 和 js,网页都可以立即响应,不用自己刷新
- 好像一切都准备就绪了,但是:css 并没有生效!
# 解决 webpack 中,css 不生效的问题
是 css-loader
写错了?还是 vue-style-loader
写错了?其实都不是,问题在于 2 个 loader 版本的问题。
css-loader 4.0 后默认对 esModule 设置的是 true,而 vue-style-loader 4.1.0 默认接收的是 commonjs 的结果,也就是默认接收的是 “css-loader 中 esModule 设置的是 false 的结果”,导致样式无法加载。
引用自 : 踩坑记录:css-loader 4.x.x 版本导致 vue 中样式不加载 (opens new window)
知道问题,那就好解决了,要么换版本,要么改一下配置。我是本着,能用新版本,就尽量用把,迟早都要升级的了
那么改一下 css-loader 配置:
module.exports = {
// ...
modules: {
rules: [
// ...
{
test: /\.css$/,
use: ['vue-style-loader', { loader: 'css-loader', options: { esModule: false } }]
}
]
}
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
改完后重启 webpack,然后可以看到,样式有输出了,样式热更新也没问题
# 引入文件路径的问题
在项目中准备一张图片
然后我们在 vue 中,分别使用 <img>
标签,和background
的形式,引入这张图片
行云流水般的改了下 demo:
- App.vue
<template>
<div class="text-red">
{{message}}
<div>使用 img 标签</div>
<img src="./assets/image/avatar.jpg" alt="avatar" class="avatar" />
<div>使用background</div>
<div class="avatar bg-avatar"></div>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
message: 'hello webpack'
}
}
}
</script>
<style>
.text-red {
color: red;
}
.avatar {
width: 100px;
height: 100px;
}
.bg-avatar {
background-image: url('./assets/image/avatar.jpg');
background-size: 100% 100%;
background-repeat: no-repeat;
}
</style>
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
换来只有无情的报错!
还记得那句吗?webpack 只认识 js
。我们添加了css-loader
。所以 webpack 多认识了 css,依旧不认识 文件类型
不认识文件类型,那就多来一个 file-loader
和url-loader
# 添加文件的识别
- 因为
url-loader
基于file-loader
,所以都要安装。 url-loader
在file-loader
上进行扩展,当文件小于多少 KB 时进行 base64 转换
npm install file-loader url-loader -D
时间关系,直接贴出多数文件类型的一个 rules
。limit
的功能就是说小于 10000kb
的文件,直接打包转为 base64。减少并发的请求
- webpack.base.js
module.exports = {
// ...
module: {
rules: [
// ...
// 图片文件处理
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000
}
}
},
// 音频文件
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000
}
}
},
// 字体文件
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000
}
}
}
]
}
// ...
}
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
重新运行 webpack
背景样式倒是成功了,img 标签引入的,不太行。问题其实还是css
遇到的那个问题。esModule
默认为 true 了,使得 vue-loader 认不得了
顺便一提的就是 打包输出的资源路径,总不能一堆散乱的 img 图片杂乱无章的堆积一起,所以也定一个输出的文件夹
最后 rules
配置改成如下
- webpack.base.js
module.exports = {
// ...
module: {
rules: [
// ...
// 图片文件处理
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 5000,
esModule: false,
outputPath: 'assets/images'
}
}
},
// 音频文件
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 5000,
esModule: false,
outputPath: 'assets/audio'
}
}
},
// 字体文件
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
esModule: false,
outputPath: 'assets/font'
}
}
}
]
}
// ...
}
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
重新运行 webpack,2 处图片都出来了。
运行下打包命令。看到文件夹也输出了图片了
不过图片是哈希值,我们可以添加name: '[name].[contenthash:4].[ext]'
来表示输出原文件名+哈希值(取 4 位),保留原先的拓展名。
# 配置文件哈希值
既然说道了哈希值,就来普及下哈希值生成的规则,为我们的打包后的 JS 也加上哈希值
webpack 提供了三种 hash 方式,分别是 hash,chunkhash,contenthash。
hash
hash 是项目工程级的,整个工程构建的文件 hash 都是一样的,所以只要工程文件有一个修改了,那么所有打包文件的 hash 都会改变,这明显不利于文件缓存,比如第三方库的 chunk
chunkhash
chunkhash 和 hash 不一样,它根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的 hash 值。我们在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用 chunkhash 的方式生成 hash 值,那么只要我们不改动公共库的代码,就可以保证其 hash 值不会受影响
contenthash
contenthash 表示由文件内容产生的 hash 值,内容不同产生的 contenthash 值也不一样。在项目中,通常做法是把项目中 css 都抽离出对应的 css 文件来加以引用
为了方便日后项目文件缓存,下面我多数会采用 contenthash
只有文件修改了,才改动对应的文件名,这也是 vue-cli 的打包方式
改一下我们的配置文件:
- webpack.base.js
module.exports = {
output: {
filename: '[name].[contenthash].js',
path: path.resolve(ROOT_PATH, 'dist')
}
}
2
3
4
5
6
使用 contenthash 会有一个小小的坑,不过目前还没遇到,可以先透露下,日后会有一个这样的报错:
Cannot use [chunkhash] or [contenthash] for chunk in 'js/[name].[chunkhash].js' (use [hash] instead)
。在下一篇文章就会涉及到了
# 打包前清空文件夹,避免不必要的文件冗余
按照刚才说的,我们把输出的图片还原了他们原先的名称;输出文件也改了哈希值。然后重新打包发现:多了一张图片,这是我们第一次打包遗留的文件,按道理是不要了的,如果文件这样长期堆积,冗余的图片会越来越多,安装clean-webpack-plugin
就可以解决这个问题
npm i clean-webpack-plugin -D
- webpack.base.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
// ...
plugins: [
// ...
new CleanWebpackPlugin()
]
}
2
3
4
5
6
7
8
这个插件特别简单,就这样就算是配置完成了。
# 区分下运行环境
安装完 CleanWebpackPlugin
就万事大吉了吗?现在运行 npm run serve
你会发现dist
目录一样被清空了。这并不是我们想看到的,
加上还有非常多场景需要我们区分开发和生产环境,比如配置 sourceMap
之类的问题。所以我们继续在 build
文件夹下,新建 2 个文件
- webpack.dev.js
- webpack.prod.js
环境是分开了,不过目前还有很多配置其实是相同的,相同的配置继续保留在 webpack.base.js
中,差异性配置我们就写到对应的配置文件中
合并这些配置用到的是 webpack-merge
webpack-merge 的写法也是分版本的,引入的方式不太一样,自己注意下即可~
- 旧版:
const { merge } = require('webpack-merge')
- 新版:
const merge = require('webpack-merge')
npm i webpack-merge -D
- webpack.dev.js
const baseConfig = require('./webpack.base')
const { merge } = require('webpack-merge')
module.exports = merge(baseConfig, {
mode: 'development'
})
2
3
4
5
6
- webpack.prod.js
const baseConfig = require('./webpack.base')
const { merge } = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = merge(baseConfig, {
mode: 'production',
plugins: [new CleanWebpackPlugin()]
})
2
3
4
5
6
7
8
- webpack.base.js
// 删除 CleanWebpackPlugin 相关的内容即可
- package.json
{
"scripts": {
"serve": "webpack-dev-server --config build/webpack.dev.js --mode development",
"build": "webpack --config build/webpack.prod.js --mode production"
}
}
2
3
4
5
6
# 最后
篇幅和精力有限,那么第一篇踩坑记录到这里就结束了,还有很多地方没介绍完,比如在 webpack.dev.js
中,应该还有很多 devServer
配置没写到,这是因为 webpack 4.x
号称可以 0 配置打包~。目前用到的热更新,运行在 8080 端口等用的都是 webpack 4.x 默认配置,感兴趣可以直接看下 开发中 Server(devServer) (opens new window)
第一篇踩坑,只是介绍到了使用 webpack 打包 vue 项目,引入图片,处理 css 的问题,区分开发环境
接下来的文章将会往 多入口的单页面
方向发展,第二篇文章将会开始把我们项目的功能完善一下:
- 使用 less 完善开发体验
- 配置 css 前缀自动补全
- 配置 css 样式分离
- 使用
babel
把 ES6 转 ES5
安排一下~