使用 webpack 搭建 vue 开发环境(一)

9/26/2020 webpack

只要一直在路上,一切都不算远

# 使用 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
1
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
1
2
3
4
5
6
7
8
9
10

# 开始写配置

  • package.json
{
  "scripts": {
    "serve": "webpack --config build/webpack.base.js --mode development"
  }
}
1
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打包文件
    })
  ]
}
1
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)
})
1
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>
1
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-loadervue-style-loader

还有 vue-template-compiler 也是要用到的,解析 template 标签用

npm i vue-loader vue-template-compiler css-loader -D
1
  • 修改 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()
  ]
}
1
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
1

修改一下 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"
  }
}
1
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 } }]
      }
    ]
  }
  // ...
}
1
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>
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

换来只有无情的报错!

还记得那句吗?webpack 只认识 js。我们添加了css-loader。所以 webpack 多认识了 css,依旧不认识 文件类型

不认识文件类型,那就多来一个 file-loaderurl-loader

# 添加文件的识别

  • 因为 url-loader 基于 file-loader,所以都要安装。
  • url-loaderfile-loader 上进行扩展,当文件小于多少 KB 时进行 base64 转换
npm install file-loader url-loader -D
1

时间关系,直接贴出多数文件类型的一个 ruleslimit 的功能就是说小于 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
          }
        }
      }
    ]
  }
  // ...
}
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

重新运行 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'
          }
        }
      }
    ]
  }
  // ...
}
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

重新运行 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')
  }
}
1
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
1
  • webpack.base.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
  // ...
  plugins: [
    // ...
    new CleanWebpackPlugin()
  ]
}
1
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
1
  • webpack.dev.js
const baseConfig = require('./webpack.base')
const { merge } = require('webpack-merge')

module.exports = merge(baseConfig, {
  mode: 'development'
})
1
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()]
})
1
2
3
4
5
6
7
8
  • webpack.base.js
// 删除 CleanWebpackPlugin 相关的内容即可
1
  • package.json
{
  "scripts": {
    "serve": "webpack-dev-server --config build/webpack.dev.js --mode development",
    "build": "webpack --config build/webpack.prod.js --mode production"
  }
}
1
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

安排一下~

Last Updated: 5/9/2021, 10:45:03 PM