源代码
熟悉 webpack 与 webpack4 配置。
webpack4 相对于 3 的最主要的区别是所谓的零配置
,但是为了满足我们的项目需求还是要自己进行配置,不过我们可以使用一些 webpack 的预设值。同时 webpack 也拆成了两部分,webpack 和 webpack-cli,都需要本地安装。
我们通过实现一个 vue 的开发模板(vue init webpack 模板,其实跟 vue 关系不太大)来进行一次体验。在配置过程中会尽量使用 webpack4 的相关内容。
本文不做 webpack 配置的完整介绍,着重介绍配置过程中需要注意的地方。查看代码注释阅读效果更佳,完整配置与详细注释可见源代码。配置位于 build 文件夹下。
与版本 4 相关的章节会添加符号 ④。
需要注意的一点是,我们的 webpack 代码是运行在node环境下的,这部分代码可以使用 node api,但是我们的业务代码(src下)是无法使用 node api 的。
由于 webpack 配置中的如 context,entry(chunk入口),output(输出)和 module.rules 中 loaders 的配置在开发模式和生产模式基本都是公用的,所以我们提取到 webpack.base.js
文件内,供复用。其中 output 部分如下:
output: {
path: path.resolve(__dirname, '../dist/'), // 资源文件输出时写入的路径
filename: 'static/js/[name].[chunkhash].js', // 使用 chunkhash 加入文件名做文件更新和缓存处理
chunkFilename: 'static/js/[name].[chunkhash].js'
}
需要注意的有:
hash 是用在文件输出的名字中的,如 [name].[hash].js
,总的来说,webpack 提供了三种 hash:
[hash]
:此次打包的所有内容的 hash。[chunkhash]
:每一个 chunk 都根据自身的内容计算而来。[contenthash]
:由 css 提取插件提供,根据自身内容计算得来。三种 hash 的使用,我们在优化部分再讲,先优先使用 [chunkhash]
。
loader 优先级需要注意两点,
同 test 配置内优先级:在同一个 test 下配置多个 loader ,优先处理的 loader 放在配置数组的后面,如对 less 处理,则:
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
}
不同 test 内优先级:如对 js 文件的处理需要两个 test 分别配置,使用 eslint-loader
和 babel-loader
,但是又不能配置在一个配置对象内,可使用 enforce: 'pre' 强调优先级,由 eslint-loader
优先处理。
{
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
},
{
test: /\.js$/,
loader: 'babel-loader'
}
我们以 less 文件的 loader 配置 ['vue-style-loader', 'css-loader', 'postcss-loader', 'less-loader']
,使用 @import url(demo.less)
为例:
vue-style-loader 功能类似 style-loader
但是由于 vue 中的单文件组件,又分为两种情况:
.vue 文件内的 style: vue-loader
会对 .vue 单文件组件进行处理,对 .vue 单文件组件内的各种 lang="type" 我们可以在 vue-loader
的 options 配置不同的 loader,由于 vue-loader
内置了 postcss
对 css 进行处理,所以此处我们不需要再配置 postcss-loader
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
less: ['// xxx-loaders'],
scss: ['// xxx-loaders'],
}
}
}
import 'demo.less'
,这种方式引入的样式文件,在 vue-loader
处理范围置之外,所以仍然需要配置 postcss-loader
。由于这种差异我们将 对 css 预处理器文件的配置封装为函数,由 usePostCss
参数生成对应配置,将文件放入 utils.js
文件内,将 vue-loader
配置放在 vue-loader.js
文件内。
也就是对 css 预处理器的配置我们需要在 vue-loader
内和 webpack
内配置两遍。
写这篇 README.md 期间 vue-loader 发布了 v15 版,需要配合插件使用,不用再进行两遍配置
postcss-loader 是一个强大的 css 处理工具,我们将 postcss 的配置拆分出去,新建 postcss.config.js
配置文件
module.exports = {
plugins: {
// 处理 @import
'postcss-import': {},
// 处理 css 中 url
'postcss-url': {},
// 自动前缀
'autoprefixer': {
"browsers": [
"> 1%",
"last 2 versions"
]
}
}
}
除了注释中列出的需要的功能插件,我们还可能会用到 nextcss
(新的css语法的处理),px2rem/px-to-viewport
移动端适配相关的插件。
我们使用 babel 编译浏览器不能识别的 js、类 js 语法,如转义 ES6+、JSX等。同样将 babel-loader 的配置拆分出去,需要创建 .babelrc
并配置:
{
"presets": [
[
/* *
* babel-preset-env
* 可以根据配置的目标运行环境自动启用需要的 babel 插件。
*/
"env", {
"modules": false, // 关闭 babel 对 es module 的处理
"targets": { // 目标运行环境
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}
]
],
"plugins": [
"syntax-dynamic-import" // 异步加载语法编译插件
]
}
我们还需要对图片、视频、字体等文件进行 loader 配置,以字体文件为例子,主要用到的是 url-loader:
{
/**
* 末尾 \?.* 匹配带 ? 资源路径
* 我们引入的第三方 css 字体样式对字体的引用路径中可能带查询字符串的版本信息
*/
test: /\.(woff2|woff|eot|ttf|otf)(\?.*)?$/,
/**
* url-loader
* 会配合 webpack 对资源引入路径进行复写,如将 css 提取成独立文件,可能出现 404 错误可查看 提取 js 中的 css 部分解决
* 会以 webpack 的输出路径为基本路径,以 name 配置进行具体输出
* limit 单位为 byte,小于这个大小的文件会编译为 base64 写进 js 或 html
*/
loader: 'url-loader',
options: {
limit: 10000,
name: 'static/fonts/[name].[hash:7].[ext]',
}
}
直接引用(绝对路径)和代码执行时确定的资源路径应该是以静态文件存在的,这些资源文件不会经过 webpack 编译处理,所以我们将它们放在独立的文件夹(如 static)中,并在代码打包后拷贝到我们的输出目录,我们使用 copy-webpack-plugin 自动完成这个工作:
const CopyWebpackPlugin = require('copy-webpack-plugin')
// 在开发模式下,会将文件写入内存
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: 'static',
ignore: ['.*']
}
])
此插件在拷贝文件过多时会崩溃,不知道解决了没有。
我们先进行生产模式的配置。
在 package.json 下添加
"scripts": {
"build": "node build/build.js"`
}
那么使用 npm run build
命令就可执行 node build/build.js
,我们不直接使用 webpack webpack.prod.config.js
命令去执行配置文件,而是在 build.js 中,做一些文件删除的处理,再启动 webpack。
主要是两个工作,引入 rimraf
模块删除 webpack 下之前产生的指定文件,启动 webpack,并在不同阶段给出不同的提示信息。
// 在第一行设置当前为 生产环境
process.env.NODE_ENV = 'production'
const webpack = require('webpack')
const rm = require('rimraf')
const webpackConfig = require('./webpack.prod')
// 删除 webpack 输出目录下的内容,也可只删除子文件如 static 等
rm(webpackConfig.output.path, err => {
// webpack 按照生产模式配置启动
webpack(webpackConfig, (err, stats) => {
// 输出一些状态信息
})
}
更多细节见源代码注释。
新建 webpack.prod.js
文件,使用
const merge = require('webpack-merge') // 专用合并 webpack 配置的包
const webpackBaseConfig = require('./webpack.base')
module.exports = merge(webpackBaseConfig, {
// 生产模式配置
})
合并基本配置和生产模式独有配置,然后我们开始进行生产模式下的 webpack 的配置信息的填写。
这是 webpack4 的新 api ,有三个预设值:development
,production
,none
,我们在生产模式选用mode: 'production'
,webpack4在此配置下默认启用了:
插件
所以这些默认启用的内容我们不需要再配置。
最后一点设置 process.env.NODE_ENV 的值设为 production
其实是使用 DefinePlugin 插件:
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("production")
})
从而我们可以在业务代码中通过 process.env.NODE_ENV
,如进行判断,使用开发接口还是线上接口。如果我们需要在 webpack 中判断当前环境,还需要单独的设置 process.env.NODE_ENV = 'production'
,这也是我们在 build.js
中第一行做的事情。
(还可能包括后面提取出来的 css 文件)到 HTML 文件。const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins: [
new HtmlWebpackPlugin({
filename: path.join(__dirname, '../dist/index.html'),// 文件写入路径
template: path.join(__dirname, '../src/index.html'),// 模板文件路径
inject: true // js 等 bundles 插入 html 的位置 head/body等
})
]
如果不对 HtmlWebpackPlugin 进行配置,则其会创建一个 HTML 文件,其中 filename
在开发模式下还是比较重要的。
使用过 webpack3 的同学应该对 extract-text-webpack-plugin 插件(以旧插件代称)比较熟悉,为了尝试webpack4,我并不想使用这个插件的 @next
版本,所以选择了新的替代插件 mini-css-extract-plugin(以新插件代称)。
与旧插件相同,同样需要在 webpack 的 loader 部分和 plugin 部分都进行配置,不同的是新插件提供了单独的 loader,在 loader 部分与旧插件的配置方式不太相同。配置如下:
loader 部分
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
//
[
{
loader: MiniCssExtractPlugin.loader,
options: {
// (segmentfault 这儿的多行注释渲染有点问题