webpack5.0 搭建 Vue 开发环境
webpack
搭建Vue
环境
注意:由于版本兼容问题 请尝试自己搭建的小伙伴 参考本文的package.json
首先附上本文所用的
package.json
{
"name": "webpack_demo",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "webpack --config build/webpack.config.js",
"dev": "webpack-dev-server --hot --open --config build/webpack.dev.js",
"prod": "webpack --config build/webpack.prod.js",
"dll": "webpack --config build/webpack.dll.config.js"
},
"devDependencies": {
"@babel/core": "^7.17.5",
"@babel/preset-env": "^7.16.11",
"autoprefixer": "^10.4.2",
"babel-loader": "^8.2.3",
"cache-loader": "^4.1.0",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.2.4",
"css-loader": "^6.6.0",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"happypack": "^5.0.1",
"html-webpack-plugin": "^5.5.0",
"less": "^4.1.2",
"less-loader": "^10.2.0",
"mini-css-extract-plugin": "^2.5.3",
"optimize-css-assets-webpack-plugin": "^6.0.1",
"postcss-loader": "^6.2.1",
"style-loader": "^3.3.1",
"uglifyjs-webpack-plugin": "^2.2.0",
"vue-loader": "^15.7.0",
"vue-style-loader": "^4.1.3",
"vue-template-compiler": "^2.5.17",
"webpack": "^5.69.1",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4",
"webpack-merge": "^5.8.0",
"webpack-parallel-uglify-plugin": "^2.0.0"
},
"dependencies": {
"@babel/polyfill": "^7.12.1",
"file-loader": "^6.2.0",
"url-loader": "^4.1.1",
"vue": "^2.5.17"
}
}
vue-template-compiler
和vue
版本要一致,不然会报错,别问我怎么知道的!
1.1 初始化项目
-
新建一个目录,初始化
npm
yarn init
-
webpack
是运行在node环境中的,我们需要安装以下两个npm
包yarn add -D webpack webpack-cli
-
新建一个文件夹
src
,然后新建一个文件main.js
,写一点代码测试一下console.log('测试')
-
配置
package.json
命令"scripts": { "build": "webpack ./src/main.js" }
-
执行
yarn run build
此时如果生成了一个
dist
文件夹,并且内部含有main.js
说明已经打包成功了
1.2 开始配置我们自己的设置
上面一个简单的例子只是
webpack
自己默认的配置,下面我们要实现更加丰富的自定义配置 新建一个build
文件夹,里面新建一个webpack.config.js
// webpack.config.js
const path = require('path');
module.exports = {
// 开发模式
mode:'development',
// 入口文件
entry: path.resolve(__dirname,'../src/main.js'),
output: {
// 打包后的文件名称
filename: 'output.js',
// 打包后的目录
path: path.resolve(__dirname,'../dist')
}
}
更改我们的打包命令
"scripts": {
"build": "webpack --config build/webpack.config.js"
}
执行
yarn run build
会发现生成了以下目录 其中dist
文件夹中的main.js
就是我们需要在浏览器中实际运行的文件
1.3 配置html模板
js
文件打包好了,但是我们不可能每次在html
文件中手动引入打包好的js
这里可能有的朋友会认为我们打包
js
文件名称不是一直是固定的嘛(output.js
)?这样每次就不用改动引入文件名称了呀?实际上我们日常开发中往往会这样配置:
module.exports = {
// 省略其他配置
output: {
filename: '[name].[hash:8].js', // 打包后的文件名称
path: path.resolve(__dirname,'../dist') // 打包后的目录
}
}
为了缓存,你会发现打包好的js文件的名称每次都不一样。webpack
打包出来的js
文件我们需要引入到html
中,但是每次我们都手动修改js
文件名显得很麻烦,因此我们需要一个插件来帮我们完成这件事情
yarn add -D html-webpack-plugin
新建一个build
同级的文件夹public
,里面新建一个index.html
具体配置文件如下
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode:'development', // 开发模式
entry: path.resolve(__dirname,'../src/main.js'), // 入口文件
output: {
filename: '[name].[hash:8].js', // 打包后的文件名称
path: path.resolve(__dirname,'../dist') // 打包后的目录
},
plugins:[
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/index.html')
})
]
}
可以发现打包生成的js文件已经被自动引入html文件中
1.3.1 多入口文件如何开发
生成多个html-webpack-plugin实例来解决这个问题
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode:'development', // 开发模式
entry: {
main:path.resolve(__dirname,'../src/main.js'),
header:path.resolve(__dirname,'../src/header.js')
},
output: {
filename: '[name].[hash:8].js', // 打包后的文件名称
path: path.resolve(__dirname,'../dist') // 打包后的目录
},
plugins:[
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/index.html'),
filename:'index.html',
chunks:['main'] // 与入口文件对应的模块名
}),
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/header.html'),
filename:'header.html',
chunks:['header'] // 与入口文件对应的模块名
}),
]
}
1.3.2 clean-webpack-plugin
每次执行yarn run build 会发现dist文件夹里会残留上次打包的文件,这里我们推荐一个plugin来帮我们在打包输出前清空文件夹
clean-webpack-plugin
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
module.exports = {
// ...省略其他配置
plugins:[new CleanWebpackPlugin()]
}
1.4 引用CSS
我们的入口文件是js,所以我们在入口js中引入我们的css文件
同时我们也需要一些loader来解析我们的css文件
yarn add -D style-loader css-loader
如果我们使用less来构建样式,则需要多安装两个
yarn add -D less less-loader
配置文件如下
// webpack.config.js
module.exports = {
// ...省略其他配置
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader'] // 从右向左解析原则
},
{
test:/\.less$/,
use:['style-loader','css-loader','less-loader'] // 从右向左解析原则
}
]
}
}
1.4.1 为css
添加浏览器前缀
yarn add -D postcss-loader autoprefixer
配置如下
// webpack.config.js
module.exports = {
module:{
rules:[
{
test:/\.less$/,
use:['style-loader','css-loader','postcss-loader','less-loader'] // 从右向左解析原则
}
]
}
}
接下来,我们还需要引入autoprefixer
使其生效,这里有两种方式
1,在项目根目录下创建一个postcss.config.js
文件,配置如下:
module.exports = {
plugins: [require('autoprefixer')] // 引用该插件即可了
}
2,直接在webpack.config.js
里配置
// webpack.config.js
module.exports = {
//...省略其他配置
module:{
rules:[{
test:/\.less$/,
use:['style-loader','css-loader',{
loader:'postcss-loader',
options:{
plugins:[require('autoprefixer')]
}
},'less-loader'] // 从右向左解析原则
}]
}
}
这时候我们发现css通过style标签的方式添加到了html文件中,但是如果样式文件很多,全部添加到html中,难免显得混乱。这时候我们想用把css拆分出来用外链的形式引入css文件怎么做呢?这时候我们就需要借助插件来帮助我们
1.4.2 拆分css
yarn add -D mini-css-extract-plugin
注意:使用
mini-css-extract-plugin
记得不能和style-loader
一起使用
webpack 4.0
以前,我们通过extract-text-webpack-plugin
插件,把css
样式从js
文件中提取到单独的css
文件中。webpack4.0以后,官方推荐使用mini-css-extract-plugin
插件来打包css
文件
配置文件如下
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
//...省略其他配置
module: {
rules: [
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader'
],
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[hash].css",
chunkFilename: "[id].css",
})
]
}
1.4.3 拆分多个css
[未使用]
这里需要说的细一点,上面我们所用到的
mini-css-extract-plugin
会将所有的css
样式合并为一个css
文件。如果你想拆分为一一对应的多个css
文件,我们需要使用到extract-text-webpack-plugin
,而目前mini-css-extract-plugin
还不支持此功能。我们需要安装@next
版本的extract-text-webpack-plugin
yarn add -D extract-text-webpack-plugin@next
// webpack.config.js
const path = require('path');
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
let indexLess = new ExtractTextWebpackPlugin('index.less');
let indexCss = new ExtractTextWebpackPlugin('index.css');
module.exports = {
module:{
rules:[
{
test:/\.css$/,
use: indexCss.extract({
use: ['css-loader']
})
},
{
test:/\.less$/,
use: indexLess.extract({
use: ['css-loader','less-loader']
})
}
]
},
plugins:[
indexLess,
indexCss
]
}
1.5 打包 图片、字体、媒体、等文件
file-loader
就是将文件在进行一些处理后(主要是处理文件名和路径、解析文件url),并将文件移动到输出的目录中
url-loader
一般与file-loader
搭配使用,功能与 file-loader 类似,如果文件小于限制的大小。则会返回 base64 编码,否则使用 file-loader 将文件移动到输出的目录中
yarn add file-loader url-loader
// webpack.config.js
module.exports = {
// 省略其它配置 ...
module: {
rules: [
// ...
{
test: /\.(jpe?g|png|gif)$/i, //图片文件
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}
]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'media/[name].[hash:8].[ext]'
}
}
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]'
}
}
}
}
]
},
]
}
}
1.6 用babel转义js文件
为了使我们的js代码兼容更多的环境我们需要安装依赖
yarn add -D babel-loader @babel/preset-env @babel/core
- 注意
babel-loader
与babel-core
的版本对应关系
babel-loader
8.x 对应babel-core
7.xbabel-loader
7.x 对应babel-core
6.x 配置如下
// webpack.config.js
module.exports = {
// 省略其它配置 ...
module:{
rules:[
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:{
presets:['@babel/preset-env']
}
},
exclude:/node_modules/
},
]
}
}
上面的
babel-loader
只会将ES6/7/8
语法转换为ES5
语法,但是对新api
并不会转换 例如(promise、Generator、Set、Maps、Proxy
等) 此时我们需要借助babel-polyfill
来帮助我们转换
yarn add @babel/polyfill
// webpack.config.js
const path = require('path')
module.exports = {
// 入口文件
entry: ["@babel/polyfill",path.resolve(__dirname,'../src/index.js')],
}
搭建vue开发环境
2.1 解析.vue文件
yarn add -D vue-loader vue-template-compiler vue-style-loader
yarn add -S vue
注意:
vue-loader
为^15.7.0 不会报错,否则可能会报 找不到vue-loader
错误
vue-loader
用于解析.vue
文件
vue-template-compiler
用于编译模板 配置如下
const vueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
module:{
rules:[{
test:/\.vue$/,
use:['vue-loader']
},]
},
resolve:{
alias:{
'vue$':'vue/dist/vue.runtime.esm.js',
' @':path.resolve(__dirname,'../src')
},
extensions:['*','.js','.json','.vue']
},
plugins:[
new vueLoaderPlugin()
]
}
2.2 配置webpack-dev-server进行热更新
yarn add -D webpack-dev-server
配置如下
const Webpack = require('webpack')
module.exports = {
// ...省略其他配置
devServer:{
port:3000,
hot:true,
contentBase:'../dist'
},
plugins:[
new Webpack.HotModuleReplacementPlugin()
]
}
webpack.config
全部配置如下:
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const Webpack = require('webpack');
const {
CleanWebpackPlugin
} = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
mode: "development",
// entry: path.resolve(__dirname, '../src/main.js'),
entry: ["@babel/polyfill", path.resolve(__dirname, '../src/main.js')],
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name].[hash:8].js',
},
devServer: {
// 端口
port: 8080,
// 是否热刷新
hot: true,
// 开放资源目录
static: {
directory: path.join(__dirname, 'dist'),
}
},
plugins: [
new VueLoaderPlugin(),
new Webpack.HotModuleReplacementPlugin(),
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html')
}),
new MiniCssExtractPlugin({
filename: "[name].[hash].css",
chunkFilename: "[id].css",
}),
],
module: {
rules: [{
test: /\.vue$/,
use: ['vue-loader'],
include: [/src/],
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader'
],
include: [/src/],
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
include: [/src/],
},
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
},
exclude: /node_modules/
},
{
test: /\.(jpe?g|png|gif)$/i, //图片文件
use: [{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
use: [{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'media/[name].[hash:8].[ext]'
}
}
}
}]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
use: [{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]'
}
}
}
}]
},
]
}
}
注意
devServer
里面的contentBase
似乎已经被废弃将devServer
改为static
2.3 配置打包命令
"scripts": {
"build": "webpack --config build/webpack.config.js",
"dev": "webpack-dev-server --hot --open --config build/webpack.config.js"
},
打包文件已经配置完毕,接下来让我们测试一下
首先在src
新建一个main.js
import App from './App.vue';
import Vue from 'vue';
new Vue({
render: h => h(App)
}).$mount('#app');
在src
新建一个App.vue
<template>
<div class="test">
{{ test }}
</div>
</template>
<script>
export default {
data() {
return {
test: "测试",
};
},
};
</script>
新建一个public
文件夹,里面新建一个index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
执行yarn run dev
这时候如果浏览器出现 测试 的界面,那么配置成功了
2.4 区分开发环境与生产环境
实际应用到项目中,我们需要区分开发环境与生产环境,我们在原来webpack.config.js
的基础上再新增两个文件
webpack.dev.js
开发环境配置文件
开发环境主要实现的是热更新,不要压缩代码,完整的sourceMap
webpack.prod.js
生产环境配置文件
生产环境主要实现的是压缩代码、提取css文件、合理的sourceMap、分割代码
需要安装以下模块:
yarn add -D webpack-merge copy-webpack-plugin optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin
webpack-merge
合并配置copy-webpack-plugin
拷贝静态资源optimize-css-assets-webpack-plugin
压缩cssuglifyjs-webpack-plugin
压缩js
webpack mode
设置production
的时候会自动压缩js代码。原则上不需要引入uglifyjs-webpack-plugin
进行重复工作。但是optimize-css-assets-webpack-plugin
压缩css
的同时会破坏原有的js压缩,所以这里我们引入uglifyjs
进行压缩
2.4.1 webpack.config.js
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const Webpack = require('webpack');
/**
* 清除打包文件
*/
const {
CleanWebpackPlugin
} = require('clean-webpack-plugin')
/**
* html 模板
*/
const HtmlWebpackPlugin = require('html-webpack-plugin')
/**
* 拆分css
*/
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
/**
* 多线程打包
*/
const Happypack = require('happypack');
/**
* 基本的系统操作函数
*/
const os = require('os')
const happyThreadPool = Happypack.ThreadPool({
size: os.cpus().length
})
// 区分版本
const devMode = process.argv.indexOf('--mode=production') === -1;
module.exports = {
// 区分版本
// mode: "development",
// entry: path.resolve(__dirname, '../src/main.js'),
// 转译 promise、Generator、Set、Maps、Proxy 等新增语法
// entry: ["@babel/polyfill", path.resolve(__dirname, '../src/main.js')],
entry: {
main: ["@babel/polyfill", path.resolve(__dirname, '../src/main.js')]
},
output: {
path: path.resolve(__dirname, '../dist'),
//加上可以运行 浏览器404
// publicPath: '../public/',
// filename: '[name].[hash:8].js',
// 区分版本
filename: 'js/[name].[hash:8].js',
chunkFilename: 'js/[name].[hash:8].js'
},
// devServer: {
// // 端口
// port: 8080,
// // 是否热刷新
// hot: true,
// // 开放资源目录
// static: {
// directory: path.join(__dirname, 'dist'),
// }
// },
plugins: [
new VueLoaderPlugin(),
// 热更新模块
new Webpack.HotModuleReplacementPlugin(),
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/index.html'),
//加上 dev 环境 控制台不报错 浏览器报404 不加上 prod 会报错 相同的文件名
// filename: '[name].[hash:8].html',
// 与入口文件对应的模块名
chunks: ['main']
}),
new MiniCssExtractPlugin({
// filename: "[name].[hash].css",
// chunkFilename: "[id].css",
// 区分版本
filename: devMode ? '[name].css' : '[name].[hash].css',
chunkFilename: devMode ? '[id].css' : '[id].[hash].css'
}),
new Happypack({
//和 loader 对应的 id 标识
id: 'happybabel',
// 用法和 loader 配置一样 注意这里是 loaders
loaders: [{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
},
cacheDirectory: true
}],
// 共享进程池
threadPool: happyThreadPool
})
],
resolve: {
alias: {
'vue$': 'vue/dist/vue.runtime.esm.js',
' @': path.resolve(__dirname, '../src'),
'components': path.resolve(__dirname, 'src/components')
// '@pages': path.join(__dirname, "..", "src", "pages"),
// "@components": path.join(__dirname, "..", "src", "components"),
},
extensions: ['*', '.js', '.json', '.vue']
},
module: {
//不去解析jquery
noParse: "/jquery/",
rules: [{
test: /\.vue$/,
use: ['vue-loader'],
include: [path.resolve(__dirname, '../src')],
exclude: /node_modules/
},
{
test: /\.less$/,
use: [
// 区分版本
{
loader: devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
options: {
publicPath: "../dist/css/",
hmr: devMode
}
},
// MiniCssExtractPlugin.loader,
'css-loader',
'less-loader'
],
include: [path.resolve(__dirname, '../src')],
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
include: [/src/],
},
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
},
exclude: /node_modules/
},
{
test: /\.(jpe?g|png|gif)$/i, //图片文件
use: [{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
use: [{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'media/[name].[hash:8].[ext]'
}
}
}
}]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
use: [{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]'
}
}
}
}]
},
]
}
}
注意
new HtmlWebpackPlugin
的filename
在生产打包的情况下不要写成默认的index.html
否则可能会出现Multiple assets emit different content to the same filename index.html错误,在dev
开发环境下改成默认的index.html
否则会出现 控制台不报错,但是浏览器显示404【我是这样的,有大佬知道为什么,烦请告知】
2.4.2 webpack.dev.js
const Webpack = require('webpack')
const path = require('path')
const webpackConfig = require('./webpack.config.js')
const WebpackMerge = require('webpack-merge')
module.exports = WebpackMerge.merge(webpackConfig, {
mode: 'development',
devServer: {
// 端口
port: 8080,
// 是否热刷新
hot: true,
// 开放资源目录
static: {
directory: path.join(__dirname, 'dist'),
}
},
devtool: 'eval-cheap-module-source-map',
plugins: [
// 热更新模块
new Webpack.HotModuleReplacementPlugin()
]
})
注意 新版本的
WebpackMerge
不是方法了,需要.merge
才可以使用
2.4.3 webpack.prod.js
const path = require('path')
const webpackConfig = require('./webpack.config.js')
/**
* 合并配置
*/
const WebpackMerge = require('webpack-merge')
/**
* 拷贝静态资源
*/
const CopyWebpackPlugin = require('copy-webpack-plugin')
/**
* 压缩css
*/
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
/**
*压缩js
*/
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const {
options
} = require('less')
module.exports = WebpackMerge.merge(webpackConfig, {
mode: 'production',
devtool: 'cheap-module-source-map',
plugins: [
new CopyWebpackPlugin({
patterns: [{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../dist')
}]
}),
],
optimization: {
minimizer: [
//压缩js
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: true
}),
new OptimizeCssAssetsPlugin({})
],
splitChunks: {
chunks: 'all',
cacheGroups: {
libs: {
name: "chunk-libs",
test: /[\\/]node_modules[\\/]/,
priority: 10,
// 只打包初始时依赖的第三方
chunks: "initial"
}
}
}
}
})
注意:unable to locate 'D:/study/webpack/webpack_demo/public/xx/x' glob 因为文件夹里面没有内容。
CopyWebpackPlugin
不能copy空文件夹
2.5 优化webpack配置
2.5.1 优化打包速度
构建速度指的是我们每次修改代码后热更新的速度以及发布前打包文件的速度。
2.5.1.1 合理的配置mode参数与devtool参数
mode
可设置development production
两个参数
如果没有设置,webpack4
会将 mode
的默认值设置为 production
production
模式下会进行tree shaking
(去除无用代码)和uglifyjs
(代码压缩混淆)
2.5.1.2 缩小文件的搜索范围(配置include exclude alias noParse extensions
)
-
alias
: 当我们代码中出现import 'vue'
时,webpack
会采用向上递归搜索的方式去node_modules
目录下找。为了减少搜索范围我们可以直接告诉webpack
去哪个路径下查找。也就是别名(alias
)的配置。 -
include exclude
同样配置include exclude
也可以减少webpack loader
的搜索转换时间。 -
noParse
当我们代码中使用到import jq from 'jquery'
时,webpack
会去解析jq这个库是否有依赖其他的包。但是我们对类似jquery
这类依赖库,一般会认为不会引用其他的包(特殊除外,自行判断)。增加noParse
属性,告诉webpack
不必解析,以此增加打包速度。 -
extensions webpack
会根据extensions
定义的后缀查找文件(频率较高的文件类型优先写在前面//省略其他配置 module:{ //不去解析jquery noParse: "/jquery/", rules: [ { test: /\.vue$/, use: ['vue-loader'], include: [/src/], include: [path.resolve(__dirname, '../src')], exclude: /node_modules/ }, ] }, resolve: { alias: { 'vue$': 'vue/dist/vue.runtime.esm.js', ' @': path.resolve(__dirname, '../src') }, extensions: ['*', '.js', '.json', '.vue'] },
2.5.1.3 使用HappyPack
开启多进程Loader
转换
在
webpack
构建过程中,实际上耗费时间大多数用在loader
解析转换以及代码的压缩中。日常开发中我们需要使用Loader
对js,css
,图片,字体等文件做转换操作,并且转换的文件数据量也是非常大。由于js
单线程的特性使得这些转换操作不能并发处理文件,而是需要一个个文件进行处理。HappyPack
的基本原理是将这部分任务分解到多个子进程中去并行处理,子进程处理完成后把结果发送到主进程中,从而减少总的构建时间
yarn add -D happypack
//省略以上配置。。。
/**
* 多线程打包
*/
const Happypack = require('happypack');
/**
* 基本的系统操作函数
*/
const os = require('os')
const happyThreadPool = Happypack.ThreadPool({
size: os.cpus().length
})
module.exports = {
plugins:[
new Happypack({
//和 loader 对应的 id 标识
id: 'happybabel',
// 用法和 loader 配置一样 注意这里是 loaders
loaders: [{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
},
cacheDirectory: true
}],
// 共享进程池
threadPool: happyThreadPool
})
]
}
注意 不要上来就开启多进程打包,一般遇到性能瓶颈或者明确需要优化打包速度时,可以考虑采用这两种方案。因为多进程也有有开销的,如进程的启动,销毁,通信等。
2.5.1.4 使用webpack-parallel-uglify-plugin
增强代码压缩
上面对于loader转换已经做优化,那么下面还有另一个难点就是优化代码的压缩时间。
yarn add -D webpack-parallel-uglify-plugin
/**
* 多进程压缩js 原理还是 UglifyJsPlugin
*/
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
module.exports = {
plugins:[
new ParallelUglifyPlugin({
cacheDir: './cache',
uglifyJs: {
output: {
//删除所有注释
comments: false,
// 最紧凑的输出
beautify: false
},
compress: {
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true
}
}
}),
]
}
注意 不要上来就开启多进程打包,一般遇到性能瓶颈或者明确需要优化打包速度时,可以考虑采用这两种方案。因为多进程也有有开销的,如进程的启动,销毁,通信等。
2.5.1.5 抽离第三方模块
对于开发项目中不经常会变更的静态依赖文件。类似于我们的
elementUi、vue
全家桶等等。因为很少会变更,所以我们不希望这些依赖要被集成到每一次的构建逻辑中去。 这样做的好处是每次更改我本地代码的文件的时候,webpack
只需要打包我项目本身的文件代码,而不会再去编译第三方库。以后只要我们不升级第三方包的时候,那么webpack
就不会对这些库去打包,这样可以快速的提高打包的速度。
这里我们使用webpack
内置的DllPlugin DllReferencePlugin
进行抽离
在与webpack
配置文件同级目录下新建webpack.dll.config.js
代码如下
// webpack.dll.config.js
const path = require("path");
const webpack = require("webpack");
module.exports = {
// 你想要打包的模块的数组
entry: {
vendor: ['vue']
},
output: {
// 打包后文件输出的位置
path: path.resolve(__dirname, 'static/js'),
filename: '[name].dll.js',
library: '[name]_library'
// 这里需要和webpack.DllPlugin中的`name: '[name]_library',`保持一致。
},
plugins: [
new webpack.DllPlugin({
path: path.resolve(__dirname, '[name]-manifest.json'),
name: '[name]_library',
context: __dirname
})
]
};
在package.json
中配置如下命令
"dll": "webpack --config build/webpack.dll.config.js"
接下来在我们的webpack.config.js
中增加以下代码
module.exports = {
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./vendor-manifest.json')
}),
// 拷贝生成的文件到dist目录 这样每次不必手动去cv
new CopyWebpackPlugin({
patterns: [{
from: path.resolve(__dirname, '../static'),
to: path.resolve(__dirname, '../dist')
}]
}),
]
};
执行
yarn run dll
会发现生成了我们需要的集合第三地方 代码的vendor.dll.js
我们需要在html
文件中手动引入这个js
文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>老yuan</title>
<script src="static/js/vendor.dll.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
这样如果我们没有更新第三方依赖包,就不必npm run dll
。直接执行npm run dev npm run build
的时候会发现我们的打包速度明显有所提升。因为我们已经通过dllPlugin
将第三方依赖包抽离出来了
注意在使用时一直报 vendor_library is not defined 的错误 查阅很久资料未解决,所以就放弃了这个配置,有大佬配置成功烦请踢我
2.5.1.6 配置缓存
我们每次执行构建都会把所有的文件都重复编译一遍,这样的重复工作是否可以被缓存下来呢,答案是可以的,目前大部分
loader
都提供了cache
配置项。比如在babel-loader
中,可以通过设置cacheDirectory
来开启缓存,babel-loader?cacheDirectory=true
就会将每次的编译结果写进硬盘文件(默认是在项目根目录下的node_modules/.cache/babel-loader
目录内,当然你也可以自定义)
但如果 loader
不支持缓存呢?我们也有方法,我们可以通过cache-loader
,它所做的事情很简单,就是 babel-loader
开启 cache
后做的事情,将 loader
的编译结果写入硬盘缓存。再次构建会先比较一下,如果文件较之前的没有发生变化则会直接使用缓存。使用方法如官方demo
所示,在一些性能开销较大的 loader
之前添加此 loader
即可
yarn add -D cache-loader
//省略其他配置
module.exports = {
module:{
rules: [{
test: /\.ext$/,
use: [
'cache-loader'
],
include: [path.resolve(__dirname, '../src')],
}]
}
}
目前各个文件配置情况
webpack.config.js
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const Webpack = require('webpack');
/**
* 清除打包文件
*/
const {
CleanWebpackPlugin
} = require('clean-webpack-plugin')
/**
* html 模板
*/
const HtmlWebpackPlugin = require('html-webpack-plugin')
/**
* 拆分css
*/
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
/**
* 多线程打包
*/
const Happypack = require('happypack');
/**
* 基本的系统操作函数
*/
const os = require('os')
const happyThreadPool = Happypack.ThreadPool({
size: os.cpus().length
})
/**
* 拷贝静态资源
*/
const CopyWebpackPlugin = require('copy-webpack-plugin');
// 区分版本
const devMode = process.argv.indexOf('--mode=production') === -1;
module.exports = {
// 区分版本
// mode: "development",
// entry: path.resolve(__dirname, '../src/main.js'),
// 转译 promise、Generator、Set、Maps、Proxy 等新增语法
// entry: ["@babel/polyfill", path.resolve(__dirname, '../src/main.js')],
entry: {
main: ["@babel/polyfill", path.resolve(__dirname, '../src/main.js')]
},
output: {
path: path.resolve(__dirname, '../dist'),
//加上可以运行 浏览器404
// publicPath: '../public/',
// filename: '[name].[hash:8].js',
// 区分版本
filename: 'js/[name].[hash:8].js',
chunkFilename: 'js/[name].[hash:8].js'
},
// devServer: {
// // 端口
// port: 8080,
// // 是否热刷新
// hot: true,
// // 开放资源目录
// static: {
// directory: path.join(__dirname, 'dist'),
// }
// },
plugins: [
new VueLoaderPlugin(),
// 热更新模块
new Webpack.HotModuleReplacementPlugin(),
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/index.html'),
//加上 dev 环境 控制台不报错 浏览器报404 不加上 prod 会报错 相同的文件名
filename: '[name].[hash:8].html',
// 与入口文件对应的模块名
chunks: ['main']
}),
new MiniCssExtractPlugin({
// filename: "[name].[hash].css",
// chunkFilename: "[id].css",
// 区分版本
filename: devMode ? '[name].css' : '[name].[hash].css',
chunkFilename: devMode ? '[id].css' : '[id].[hash].css'
}),
new Happypack({
//和 loader 对应的 id 标识
id: 'happybabel',
// 用法和 loader 配置一样 注意这里是 loaders
loaders: [{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
},
cacheDirectory: true
}],
// 共享进程池
threadPool: happyThreadPool
}),
// 抽离的 模块引入
// new Webpack.DllReferencePlugin({
// context: __dirname,
// manifest: require('../static/vendor-manifest.json')
// }),
// // 拷贝生成的文件到dist目录 这样每次不必手动去cv
// new CopyWebpackPlugin({
// patterns: [{
// from: path.resolve(__dirname, '../static'),
// to: path.resolve(__dirname, '../dist')
// }]
// }),
],
resolve: {
alias: {
'vue$': 'vue/dist/vue.runtime.esm.js',
' @': path.resolve(__dirname, '../src'),
'components': path.resolve(__dirname, 'src/components')
// '@pages': path.join(__dirname, "..", "src", "pages"),
// "@components": path.join(__dirname, "..", "src", "components"),
},
extensions: ['*', '.js', '.json', '.vue']
},
module: {
//不去解析jquery
noParse: "/jquery/",
rules: [{
test: /\.vue$/,
use: ['vue-loader'],
include: [path.resolve(__dirname, '../src')],
exclude: /node_modules/
},
{
test: /\.less$/,
use: [
// 区分版本
{
loader: devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
options: {
publicPath: "../dist/css/",
hmr: devMode
}
},
// MiniCssExtractPlugin.loader,
'css-loader',
'less-loader'
],
include: [path.resolve(__dirname, '../src')],
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
include: [/src/],
},
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
},
exclude: /node_modules/
},
{
test: /\.(jpe?g|png|gif)$/i, //图片文件
use: [{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
use: [{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'media/[name].[hash:8].[ext]'
}
}
}
}]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
use: [{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]'
}
}
}
}]
},
{
test: /\.ext$/,
use: [
'cache-loader'
],
include: [path.resolve(__dirname, '../src')],
}
]
}
}
webpack.dev.js
const Webpack = require('webpack')
const path = require('path')
const webpackConfig = require('./webpack.config.js')
const WebpackMerge = require('webpack-merge')
module.exports = WebpackMerge.merge(webpackConfig, {
mode: 'development',
devServer: {
// 端口
port: 8080,
// 是否热刷新
hot: true,
// 开放资源目录
static: {
directory: path.join(__dirname, 'dist'),
}
},
devtool: 'eval-cheap-module-source-map',
plugins: [
// 热更新模块
new Webpack.HotModuleReplacementPlugin()
]
})
webpack.prod.js
const path = require('path')
const webpackConfig = require('./webpack.config.js')
/**
* 合并配置
*/
const WebpackMerge = require('webpack-merge')
/**
* 拷贝静态资源
*/
const CopyWebpackPlugin = require('copy-webpack-plugin')
/**
* 压缩css
*/
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
/**
*压缩js
*/
// const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
/**
* 多进程压缩js 原理还是 UglifyJsPlugin 开启了多进程 使用前提要有 Happypack
*/
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
module.exports = WebpackMerge.merge(webpackConfig, {
mode: 'production',
devtool: 'cheap-module-source-map',
plugins: [
new CopyWebpackPlugin({
patterns: [{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../dist')
}]
}),
],
optimization: {
minimizer: [
//压缩js
// new UglifyJsPlugin({
// cache: true,
// parallel: true,
// sourceMap: true
// }),
new ParallelUglifyPlugin({
cacheDir: './cache',
uglifyJs: {
output: {
//删除所有注释
comments: false,
// 最紧凑的输出
beautify: false
},
compress: {
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true
}
}
}),
new OptimizeCssAssetsPlugin({})
],
splitChunks: {
chunks: 'all',
cacheGroups: {
libs: {
name: "chunk-libs",
test: /[\\/]node_modules[\\/]/,
priority: 10,
// 只打包初始时依赖的第三方
chunks: "initial"
}
}
}
}
})
2.5.2 优化打包文件体积
打包的速度我们是进行了优化,但是打包后的文件体积却是十分大,造成了页面加载缓慢,浪费流量等,接下来让我们从文件体积上继续优化
2.5.2.1 引入webpack-bundle-analyzer
分析打包后的文件
webpack-bundle-analyzer
将打包后的内容束展示为方便交互的直观树状图,让我们知道我们所构建包中真正引入的内容
yarn add -D webpack-bundle-analyzer
/**
* 分析打包后的文件
*/
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
//省略其他配置
module.exports = {
plugins:[
{
new BundleAnalyzerPlugin({
//打开的地址
analyzerHost: '127.0.0.1',
// 打开的端口
analyzerPort: 8080
})
},
]
}
本文参考博主黄小虫的文章,如需参考,请点击链接跳转掘金
转载自:https://juejin.cn/post/7067805981274701860