Webpack4 新特性 及 Vue-cli项目升级

webpack 于2018年2月25日正式发布 v4.0.0 版本,代号legato,这将会让 webpack 的配置更加简单,构建速度更快

Webpack4 中文文档

[TOC]

Nodejs版本

Node.js >= 8.9.4

当使用 webpack4 时,必须保证 Node.js 版本 >= 8.9.4,因为 webpack4 使用了大量的ES6语法,这些语法在 nodejs新版 v8 中得到了原生支持

安装

npm i webpack webpack-cli -D

webpack4 中 cli 工具分离成了 webpack 核心库 与 webpack-cli 命令行工具两个模块,需要使用 CLI,必安装 webpack-cli 至项目中

零配置 0CJS

webpack4 设置了默认值,以便无配置启动项目

  • entry 默认值是 ./src/
  • output.path 默认值是 ./dist
  • mode 默认值是 production

模式

mode: development / production / none

开发模式 development

  • 浏览器调试工具
  • 注释、开发阶段的详细错误日志和提示
  • 快速和优化的增量构建机制
  • 开启 output.pathinfobundle 中显示模块信息
  • 开启 NamedModulesPlugin
  • 开启 NoEmitOnErrorsPlugin

生产模式 production

  • 启用所有优化代码的功能
  • 更小的bundle大小
  • 去除只在开发阶段运行的代码
  • 关闭内存缓存
  • Scope hoistingTree-shaking
  • 开启 NoEmitOnErrorsPlugin
  • 开启 ModuleConcatenationPlugin
  • 开启 optimization.minimize

none 会禁用所有的默认设置,可以使用 optimization.* 的方式去设定更详细的配置(搭建你的自定义模式)

插件优化

新增 optimization.splitChunksoptimization.runtimeChunk 来替代 CommonsChunkPlugin插件,

新增 optimization.noEmitOnErrors 来替代 NoEmitOnErrorsPlugin 插件

新增 optimization.namedModules 来替代 NamedModulesPlugin 插件

内置 optimization.minimize 来压缩代码

loader

默认已支持加载 json 模块,不再需要 json-loader

允许通过ESM语法导入JSON,JSON模块中未使用的部分会被消除

详细升级日志请查看 webpack4.0 升级日志中文版

Webpack配置 基本结构

module.exports = {
// 定义模块引用的绝对路径前缀
context: path.resolve(__dirname, '../'),

// 输入配置
entry: {
// 入口文件,如果是多页项目,可配置多个
app: './src/main.js'
},

// 输出配置
output:{
// 输出目录
path: path.resolve(__dirname, '../dist'),
// 输出文件名 name 为 entry 的 key 值,也可以加上 hash 值, 如:[name].[hash:8].js
filename: '[name].js',
// 构建生成的 js 在html中引用时的路径
publicPath: '/'
},

// 模块引用配置
resolve: {
// 定义模块查找的后缀,方便在代码引用时可省略后缀
extensions: ['.js', '.vue', '.json'],
// 定义引用路径别名 配置别名可以加快webpack查找模块的速度
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
},

// 模块加载配置
module:{
// 指定 不同的模块使用不同的加载器处理
// 以 .css 结尾的文件,使用 css-loader 解析css模块,使用 style-loader 将生成的 css 内容以标签的形式添加到 HTML 文档中
rules:[
{
// 文件匹配正则
test:/\.css$/,
// 加载器,从后向前倒序使用
loader:['style-loader','css-loader']
}
]
},

// 插件
plugins:{
// 使用 HtmlWebpackPlugin 将构建好的 js/css 嵌入到模板 index.html 中
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
title: '首页',
hash: true,
})
},

// Web服务器配置
devServer:{
contentBase:'../dist',
host:'localhost',
port:'8080',
}
}

使用 HtmlWebpackPlugin 插件,需要npm安装相应模块

// 安装
npm install html-webpack-plugin -D

// 引用
const HtmlWebpackPlugin = require('html-webpack-plugin')

使用 devServer 同样需要安装相关模块

// 安装
npm install webpack-dev-server -D

// 在 package.json 中配置启动脚本
"script": {
"dev": "webpack-dev-server --open --mode development"
}

// 启动 web服务,默认会查找目录下 webpack.config.js 读取其中 devServer 的配置以启动服务
npm run dev

常用 Loader 配置

babel-loader

将ES6的代码使用babel转码为浏览器兼容的ES5

{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src')]
}

babel 的配置在项目根目录下 .babelrc 文件中,如果没有则新建,根据不同的项目要求配置

npm i babel-core babel-loader babel-preset-env babel-preset-stage-2 --save-dev

/.babelrc
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": ["transform-runtime"],
"env": {
"test": {
"presets": ["env", "stage-2"],
"plugins": ["istanbul"]
}
}
}

url-loader

将 图片 转成 data:base64,以减少页面中的图片请求

{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000, // 只转码 1M以下的图片
name: 'img/[name].[hash:7].[ext]' // 发布到 dist/img 目录下,名称中添加 hash 值,避免缓存
}
},

less-loader

将less文件编译为css

{
test: /\.less$/,
use: ['style-loader', 'css-loader','less-loader']
}

这里先将 less-loader 转 css, 再经过 css-loader 将css模块化并解析其中的 @importurl(),再通过 style-loader 将css嵌入html

常用插件

配置全局变量

webpack.DefinePlugin

用于定义在编译过程中使用的全局变量,常用来定义 process.env 用来区分开发环境和生产环境

const isProduction = process.env.NODE_ENV === 'production'

模块热替换

webpack.HotModuleReplacementPlugin 内置

模块热替换(HMR - Hot Module Replacement)功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面

  • 保留在完全重新加载页面时丢失的应用程序状态。
  • 只更新变更内容,以节省宝贵的开发时间。
  • 调整样式更加快速 - 几乎相当于在浏览器调试器中更改样式。

保证 chunkhash 的稳定

webpack.HashedModuleIdsPlugin 内置

使用 hash 做为模块ID, 避免缓存那些没有变化的模块内容,从而实现更优的缓存策略

用 webpack 实现持久化缓存

提取css为文件

extract-text-webpack-plugin NPM

将所有入口 chunk 中引用的 *.css,提取合并为独立的css,在index.html 中使用 link:src 来引用css文件,一般用于生产模式,提取公共的css

拷贝静态文件

copy-webpack-plugin NPM

应用:将模板 index.html 中引用的静态资源,在构建时复制到 dist 指定目录下

CSS优化

optimize-css-assets-webpack-plugin NPM

压缩css, 同时去除重复的样式,减少CSS打包后的体积

vue-cli 项目开发环境的 plugins 配置

plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),

new webpack.HotModuleReplacementPlugin(),

// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
favicon: resolve('favicon.ico'),
inject: true
}),

new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}
])
]

vue-cli 项目生产环境的 plugins 配置

plugins: [
new webpack.DefinePlugin({
'process.env': env
}),
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].css'),
allChunks: true,
}),

new OptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap
? { safe: true, map: { inline: false } }
: { safe: true }
}),

// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
favicon: resolve('favicon.ico'),
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},

chunksSortMode: 'dependency'
}),

new webpack.HashedModuleIdsPlugin(),

new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]

配置服务代理

在开发过程中和后台连调时,一般需要解决跨域问题,webpack 提供了 proxy 配置用于,代理 api 请求,屏蔽浏览器跨域限制

proxy: {
'/api': {
target: 'http://192.168.1.12:5000',
pathRewrite: {'^/api' : '/api'},
changeOrigin: true
}
}

当用户访问 /api/getUser 时,代理到 http://192.168.1.12:5000/api/getUser 去请求数据

Vue-cli 升级 Webpack4.x

升级模块

建议在 webpack 构建流程中使用到的 loadersplugins 都升级到最新版本

安装

如果 pakage.json 中有相应的模块配置,可删除之后重新安装

npm i webpack webpack-cli webpack-dev-server --save-dev

还有以下模块

"copy-webpack-plugin": "^4.0.1",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"html-webpack-plugin": "^3.1.0",
"optimize-css-assets-webpack-plugin": "^4.0.0",
"webpack-bundle-analyzer": "^2.11.1",
"webpack-dev-middleware": "^3.1.2",
"webpack-dev-server": "^3.1.3",
"webpack-merge": "^4.1.0"

修改开发环境

build/webpack.dev.conf.js 中添加 mode 配置

注释掉 webpack.NamedModulesPluginwebpack.NoEmitOnErrorsPlugin 插件,因为 webpack4 开发模式已经内置

module.exports = {
// ...
mode: 'development',
// ...
plugins: {
// new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
// new webpack.NoEmitOnErrorsPlugin(),
}
}

修改生产环境

build/webpack.production.conf.js 中添加 modeoptimization 配置

同时注释掉 webpack.optimize.CommonsChunkPluginuglifyjs-webpack-pluginwebpack.optimize.ModuleConcatenationPlugin 相关配置及引用

const webpackConfig = merge(baseWebpackConfig, {
// ...
mode: 'production',
// webpack4 内置了 optimization.splitChunks、optimization.runtimeChunk 用来抽取共公代码,优化了缓存策略
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
chunks: 'initial',
name: 'vendors',
},
'async-vendors': {
test: /[\\/]node_modules[\\/]/,
minChunks: 2,
chunks: 'async',
name: 'async-vendors'
}
}
},
runtimeChunk: { name: 'runtime' }
},
// ...
}

经过上面三步,vue-cli 项目升级 webpack4.x 就完成了

需要注意的是当前项目一定要是较新的 webpack 模板生成的项目,是不是新模板,可以查看 package.jsonscripts.dev 是否是使用 webpack-dev-server 启动的,如果是,则为新的模板

性能优化

使用happypack

HappyPack就能让Webpack把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程

npm i happypack@5.0.0-beta.3 --save-dev

const HappyPack = require('happypack')
const os = require('os')
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })

{
test: /\.js$/,
// loader: 'babel-loader',
loader: 'happypack/loader?id=happy-babel-js', // 增加新的HappyPack构建loader
include: [resolve('src')],
exclude: /node_modules/,
}

plugins: [
new HappyPack({
id: 'happy-babel-js',
loaders: ['babel-loader?cacheDirectory=true'],
threadPool: happyThreadPool
})
]

配置noparse

忽略对已知文件的解析: 一个模块中没有其它新的依赖 就可以配置这项,webpack 将不再扫描这个文件中的依赖

resolve: {
alias: {
moment: "moment/min/moment-with-locales.min.js"
}
},
module: {
noParse: [/moment-with-locales/]
}
  • webpack 检查到 entry.js 文件对 moment 的请求
  • 请求被 alias 重定向,转而请求 moment/min/moment-with-locales.min.js
  • noParse 规则中的 /moment-with-locales/ 一条生效,所以 webpack 就直接把依赖打包进了 bundle.js

生产环境不产生source-map

eval: 生成代码 每个模块都被eval执行,并且存在@sourceURL

cheap-eval-source-map: 转换代码(行内) 每个模块被eval执行,并且sourcemap作为eval的一个dataurl

cheap-module-eval-source-map: 原始代码(只有行内) 同样道理,但是更高的质量和更低的性能

eval-source-map: 原始代码 同样道理,但是最高的质量和最低的性能

cheap-source-map: 转换代码(行内) 生成的sourcemap没有列映射,从loaders生成的sourcemap没有被使用

cheap-module-source-map: 原始代码(只有行内) 与上面一样除了每行特点的从loader中进行映射

source-map: 原始代码 最好的sourcemap质量有完整的结果,但是会很慢

当我们不需要调试时,可以关掉 sourcemap 或降低 sourcemap 的级别来加快打包的速度

使用CDN资源或静态资源

使用CDN

webpack.config.js 中配置模块变量为外部依赖

externals: {
moment: true
}

index.html 中添加资源引用

<script src="//apps.bdimg.com/libs/moment/2.8.3/moment-with-locales.min.js"></script>

使用静态资源

有时候,由于网络限制,不允许使用CDN资源,又不想经过Webpack打包,则可以将资源直接引入 index.html ,在 webpack build 配合 copy-webpack-plugin 插件,将资源复制到 dist 目录下,例 vue-cli 生成的项目根目录下的 static 目录就是用来放这类静态资源的

为babel-loader设置缓存

cacheDirectory: 指定的目录将用来缓存 loader 的执行结果。之后的 webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程

{
test: /\.js$/,
loader: 'babel-loader?cacheDirectory',
exclude: /node_modules/,
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
},

官方文档中表示设置 cacheDirectory 可将 babel-loader 提速至少两倍

参考阅读

深入浅出Webpack
vue cli 平稳升级webapck4
Webpack4 那点儿东西
30分钟快速了解webpack