前言
这一篇文章主要是续着上篇的 ——「webpack4 从零搭建 vue 项目开发环境」。强烈推荐先看上一篇文章 。
文章主要说一下不同环境的区分和一些可以优化的点
文章 demo 已经提交到 github , 请自取 ———— 「webpack 4 demo」
区分「开发环境」和「生产环境」
- 开发环境:
热更新、多线程、sourceMap等等 - 生产环境:
js压缩、提取css、css压缩、多线程、sourceMap、目录清理等等
由于「开发环境」和「生产环境」的需求不同,所以我们要区分不同环境来构建我们的项目。
配置 webpack.common.js 、webpack.dev.js 和 webpack.prod.js
新建 build 目录,并新建下面三个文件
webpack.common.js公用配置webpack.dev.js开发环境配置webpack.prod.js生成环境配置
安装 webpack-merge
用于合并 webpack 公用配置到指定环境
npm i webpack-merge@5.8.0 -D
配置 webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
entry: path.join(__dirname, "../src/main.js"),
output: {
path: path.join(__dirname, "../dist"),
filename: "bundle.js"
},
module: {
rules: [
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192, //小于 8K ,用 url-loader 转成 base64 ,否则使用 file-loader 来处理文件
fallback: {
loader: 'file-loader',
options: {
name: '[name].[hash:8].[ext]',
outputPath: '../dist/images/', //打包之后文件存放的路径, dist/images
}
},
}
}
]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
fallback: {
loader: 'file-loader',
options: {
name: '[name].[hash:8].[ext]',
outputPath: '../dist/media/',
}
},
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 1,
fallback: {
loader: 'file-loader',
options: {
name: '[name].[hash:8].[ext]',
outputPath: '../dist/fonts/',
}
},
}
}
]
},
{
test: /\.(css)$/,
use: [
"style-loader",
"css-loader",
"postcss-loader"
]
},
{
test: /\.(less)$/,
use: [
"style-loader",
"css-loader",
"postcss-loader",
"less-loader",
]
},
{
test: /\.vue$/,
use: ["vue-loader"]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, '../src/index.html')
}),
new VueLoaderPlugin()
]
};
配置 webpack.dev.js
const webpack = require("webpack");
const { merge } = require('webpack-merge')
const webpackCommon = require('./webpack.common.js')
module.exports = merge(webpackCommon, {
mode: "development",
devServer: {
contentBase: path.join(__dirname, "../dist"),
compress: true,
open: true,
quiet: false,
hot: true, //开启热更新
port: 3000,
clientLogLevel: 'none', //关闭浏览器控制台输出的热更新信息
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
});
配置 webpack.prod.js
const { merge } = require('webpack-merge')
const webpackCommon = require('./webpack.common.js')
module.exports = merge(webpackCommon, {
mode: "production"
})
修改 package.json npm 脚本
指定 webpack 不同环境执行不同的 配置文件。
"scripts": {
"serve": "webpack-dev-server --config build/webpack.dev.js", //运行环境
"build": "webpack --config build/webpack.prod.js",//开发环境
},
其实也可以通过 --mode 来设置 process.env.NODE_ENV ,并判断是「开发环境」还是「生产环境」,执行不同的配置文件内容。这里就不演示了。网上也很多例子,感兴趣自己去搜一下啊
CleanWebpackPlugin 自动删除 dist 上次打包的内容
npm i clean-webpack-plugin -D
// webpack.prod.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [
new CleanWebpackPlugin()
],
DefinePlugin 设置环境全局变量
DefinePlugin 是 webpack 内置的插件,不需要安装
配置 webpack.dev.js 和 webpack.prod.js
这里只演示 webpack.dev.js ,webpack.prod.js 设置也是一样的。
plugins: [
new webpack.DefinePlugin({
'process.env': {
//设置全局变量可以在整个项目中调用
BASE_URL: JSON.stringify('http://localhost:3000/dev')
}
})
]
调用全局变量
console.log('BASE_URL:', process.env.BASE_URL)
设置 sourceMap 便于调试
webpack 打包过的 bundle.js 都是压缩过的。没法正常调试。而sourceMap就是用来帮助我们调试。如果你之前用过 vue-cli ,打包的时候 ,会在 dist 目录下生存后缀为 .map 的文件,这个就是源码的映射文件。
我们可以通过设置devtool来生成不同类型的 sourceMap 。不同模式生成的代码体积、构建速度、安全性等等都有差异,可以参考「webpack官网」。
个人推荐,开发环境推荐使用带eval字样的生成模式,速度会比较快。至于是cheap和module根据自己的需要选择就好。生产环境,为了安全性,建议使用把sourceMap放在本地或者内网,可使用SourceMapDevToolPlugin插件来配置。
- 开发环境
devtool: "cheap-module-eval-source-map", - 生产环境
个人测试了一下,发现SourceMapDevToolPlugin 和devtool是存在冲突的,使用SourceMapDevToolPlugin来配置时需要设置 devtool: false。
webpack 默认情况下会在bundle.js 最后面追加一行 //# sourceMappingURL=源代码对应的 sourcemaps 文件的路径的注释,使用浏览器调试的时候(F12),就会自动去查找这个路径的 sourceMap 。我们可以在 append里自定义这个路径,支持 url 和 本地地址
// webpack.prod.js
devtool: false,
plugins: [
// 默认情况下生成的模式是 `source-map`
new webpack.SourceMapDevToolPlugin({
//sourcemap 内网地址
append: '\n//# sourceMappingURL=http://localhost:1888/sourcemaps/[name].[hash:8].map',
// 生成 sourcemap 的存放路径和名称
filename: 'sourcemaps/[name].[hash:8].map',
// module:true // 相当于 devtool 的 module
// columns:false //相当于 devtool 的 cheap
// noSources:true //相当于 devtool 的 nosources
})
],
这里顺便提一下,vue-cli「生产环境」默认生成为:devtool:source-map 也就是生成的map文件包含了未压缩的源代码,个人觉得不太安全!如果项目比较打的时候构建速度也会特别慢
如果你是第一次接触 sourceMap相信看完也是懵逼的,可以看一下下面的文章,应该对你有帮助
sourceMap 推荐阅读文章:
优化 (前言)
一个项目是否需要优化,要根据自己的项目情况来决定的。比如某些优化只在大项目时候才会体现出它的优势,小项目使用反而会适得其反。如果你感觉自己的项目构建速度和项目体积都可以接受,那也没有必要去优化。
下面是一些可以优化的点,但不要都一股脑的加上。根据自己项目情况来尝试和选择,查看效果
优化前准备工作
speed-measure-webpack-plugin 用于查看 loader 和 插件 的处理时间
webpack-bundle-analyzer 用于查看打包后包的体积大小。
减少文件查找范围
指定 loader 处理范围 ———— exclude/include
exclude 是排除不在某个目录下查找
include 是限定在哪个目录下查找
同时配置的话,exclude 的优先级高于 include,推荐只使用include。
// webpack.commom.js
{
test: /\.js$/,
// exclude: /node_modules/,
include: path.join(__dirname, "../src"),
use: ["babel-loader"]
},
resolve.alias 别名配置
用别名来代替,编写更简洁,也可以起到减少查找范围的作用
// webpack.commom.js
resolve: {
alias: {
"@": path.join(__dirname, "../src")
}
},
resolve.extensions 指定文件后缀匹配规则
webpack 默认配置为 extensions: ['.js', '.json'],假设文件导入的时候没用写后缀名,会根据这个规则来匹配。
例如:import router from "@/router/index", 先找js,再找 json 。
extensions 可以修改匹配的顺序,可以跳转它的优先级,不常用的文件不建议写进入
// webpack.commom.js
resolve: {
extensions: [".js", ".vue",".json"]
// extensions:[".ts",".js", ".vue",".json"] 如果你的项目中有使用到 `typescript` 推荐把 `.ts` 放在前面
}
缓存
cache-loader
在一些开销比较大的loader前面使用cache-loader,开销比较小的 loader 不需要使用,用了反而会慢。它会把结果缓存在磁盘上,node_modueles/.cache/cache-loader 默认存放路径。第一次构建的时间时间会比较长,后面再次构建就会比较快了
npm install cache-loader -D
// webpack.commom.js
{
test: /\.js$/,
include: path.join(__dirname, "../src"),
use: ["cache-loader", "babel-loader"]
}
babel-loader
其实 babel-loader 本身也具备缓存的功能。 我听说 cache-loader 会更快一点。我自己试了一下,差距不大,cache-loader 稍微快一点点。
// webpack.commom.js
{
test: /\.js$/,
include: path.join(__dirname, "../src"),
use: [{
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}]
},
hard-source-webpack-plugin
HardSourceWebpackPlugin 为模块提供中间缓存, 第一次构建,花费正常时间。第二个构建速度快60% ~ 90%。不推荐 DllPlugin 和 DllReferencePlugin 一起使用
注意:开发环境,使用 HardSourceWebpackPlugin 时,不要使用 speed-measure-webpack-plugin 会报错
npm install hard-source-webpack-plugin -D
// webpack.commom.js
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
plugins: [
// 为模块提供中间缓存,不能和 SpeedMeasurePlugin 插件同时使用
new HardSourceWebpackPlugin(),
// 排除缓存 MiniCssExtractPlugin 插件 ,不然会报错
new HardSourceWebpackPlugin.ExcludeModulePlugin([
{
test: /mini-css-extract-plugin[\\/]dist[\\/]loader/,
},
]),
]
多线程
大家知道 JavaScript 是单线程的,我们可以使用 web worker来现实多线程执行JavaScript 。如果你没听过 web worker 推荐看一下这篇文章——「Web Worker 使用教程」
而在 webpack 打包构建项目的时候,我们也可以使用 worker 来实现。有两个基于worker实现的插件 thread-loader 和 happypack ,happypack 貌似被作者抛弃了,推荐使用thread-loader 来现实。
请只在耗时比较长的 loader 上使用!
npm install thread-loader -D
// webpack.commom.js
{
test: /\.js$/,
include: path.join(__dirname, "../src"),
use: ["thread-loader", "cache-loader", "babel-loader"]
// 项目大,loader 花费时间长时用
}
压缩
JS压缩,删除注释和 console —— terser-webpack-plugin
webpack 4 需要安装,webpack 5 默认支持
npm i terser-webpack-plugin@4 -D
// webpack.prod.js
const TerserPlugin = require('terser-webpack-plugin');
optimization: {
minimizer: [
// 压缩 JS
new TerserPlugin({
parallel: true, //开启多线程
// include: path.join(__dirname, "../src"),
sourceMap: true, // 如果使用了 SourceMapDevToolPlugin ,需要设置为 true
extractComments: false, // 默认打包会生成 LICENSE.txt 文件,这里设置禁止生成
terserOptions: {
output: {
comments: false, //删除注释
},
compress: {
drop_console: true //删除 console
// drop_debugger: false //默认为 true, 会删除 debugger
},
},
}),
],
},
css 提取
如果打包生成的JS太大,提取css有利于减少JS的体积以及页面的快速渲染(link 引入样式不会堵塞DOM渲染和JS渲染),
npm i mini-css-extract-plugin@1.6.2 -D
// webpack.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module: {
rules: [
{
test: /\.(css)$/,
include: path.join(__dirname, "../src"),
use: [
MiniCssExtractPlugin.loader,
// "thread-loader", // 项目大,loader 花费时间长时用
"css-loader",
"postcss-loader"
]
},
{
test: /\.(less)$/,
include: path.join(__dirname, "../src"),
use: [
MiniCssExtractPlugin.loader,
// "thread-loader", // 项目大,loader 花费时间长时用
"css-loader",
"postcss-loader",
"less-loader",
]
},
]
},
// 提取 css
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[hash:8].css'
}),
]
css 压缩
目的就是减少项目体积
npm i optimize-css-assets-webpack-plugin -D
// webpack.prod.js
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
plugins: [
// 压缩 css
new OptimizeCssAssetsPlugin()
]
减少打包文件
Tree Shaking
Tree Shaking 用于剔除一些引入了却没有使用的代码。
//package.json
// 需要排除 「.css」和「.vue」文件,样式才能生效
"sideEffects": [
"*.css",
"*.vue"
],
externals
打包的时候把这些 externals 配置的包剔除掉,减少体积。然后使用 CDN 的方式引入这些包。
// webpack.prod.js
externals: {
"vue": "Vue",
"vue-router":"VueRouter"
}
<!-- index.html -->
<!-- cdn 引入 externals 排除的依赖 -->
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.runtime.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/vue-router/3.5.2/vue-router.min.js"></script>
依赖版本
由于最新版本是 webpack5 部分插件也会同步更新,插件的最新版本在webpack 4上不适用,可以参考下面依赖版本进行安装!
"devDependencies": {
"@babel/core": "^7.4.4",
"@babel/preset-env": "^7.14.8",
"autoprefixer": "^10.3.1",
"babel-loader": "^8.2.2",
"cache-loader": "^4.1.0",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^5.2.7",
"file-loader": "^6.2.0",
"hard-source-webpack-plugin": "^0.13.1",
"html-webpack-plugin": "^4.5.2",
"less": "^3.5.0",
"less-loader": "^7.3.0",
"mini-css-extract-plugin": "^1.6.2",
"optimize-css-assets-webpack-plugin": "^6.0.1",
"postcss-loader": "^4.3.0",
"speed-measure-webpack-plugin": "^1.5.0",
"style-loader": "^2.0.0",
"terser-webpack-plugin": "^4.2.3",
"thread-loader": "^3.0.4",
"url-loader": "^4.1.1",
"vue-loader": "^15.9.6",
"vue-template-compiler": "^2.6.14",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.8.0"
},
文章 demo 已经提交到 github , 请自取 ———— 「webpack 4 demo」
觉得还不错记得 「点个赞」!谢谢