本文主要是使用webpack5+vue3+vw适配+axios+vue-router+vuex+vant搭建一个基础的h5项目结构。其中每一节都是独立的配置,想要webpack搭配其他的也可以参考下。至于项目的代码,放在本文最后面,不想看的可以直接下载使用。
为了自己后续搭建h5项目能有个干净的项目框架直接使用,而不是每次都得重新搭建。每一个步骤都是我实践过的,其中存在的一些注意点和原理,我也会简单提及,算是学习实践的一篇笔记吧。
npm init -y
npm install webpack webpack-cli webpack-dev-server -D
-D 等价于 --save-dev; 开发环境时所需依赖
-S 等价于 --save; 生产环境时所需依赖
新建src文件夹,并写上代码
./src/main.js
console.log("hello webpack");
新建config文件夹,并新建webpack.config.js文件
./config/webpack.config.js
const path = require("path");
module.exports = {
entry: path.resolve(__dirname, "../src/main.js"), // 入口文件,打包从这个文件开始
output: {
path: path.resolve(__dirname, "../dist"),//出口文件,打包生成的文件放置到这个文件夹下
filename: "./js/[name].[chunkhash].js" //打包成的文件名。name取的原始文件名,chunkhash生成哈希值,这样每次打包出来不一样,避免浏览器缓存读取旧文件。
},
mode: "development" //开发模式
};
再在根目录下运行命令行:
npx webpack --config ./config/webpack.config.js
//因为我们把webpack配置文件放置到config文件夹内了,所以要用--config来指定配置文件的路径。
//npx 作用:直接调用项目内部安装的模块,而无需再输入模块路径。npx webpack等价于./node_modules/.bin/webpack --config ./config/webpack.config.js
于是就可以看到根目录生成了dist文件,完成了基本的打包配置。
第三步中,我们的运行命令是手动打的,不方便,于是可以在package.json的script中添加命令:
"scripts": {
"dev": "webpack --config ./config/webpack.config.js --progress --color -w"
},
//--progress: 启用在构建过程中打印编译进度
//--color: 启用控制台颜色
//--watch或-w: 监听文件变化
于是在项目根目录执行npm run dev即可完成第三步的打包工作。
在第二步的时候,我们安装了这个依赖:webpack-dev-server,它的作用是在本地起一个服务器,当我们运行webpack的时候,打包出来的文件会放置在电脑的内存中(不放dist里了),然后在这个服务器上运行。
现在在config/webpack.config.js
中进行配置这个开发服务器:
module.exports = {
//...其他配置
devServer: {
hot: true, //模块的热替换
open: true, // 编译结束后自动打开浏览器
port: 8080, // 设置本地端口号
host: "localhost" //设置本地url
}
};
然后修改package.json
文件:
"dev": "webpack server --config ./config/webpack.config.js --progress --color",
会报404,因为这时候打开的浏览器,是localhost:8080,它默认打开的是该目录下的index.html文件,目前这样写是没有这个文件的,所以会报404,需要引入一个插件来生成index.html文件。
安装依赖:
npm install html-webpack-plugin -D
然后在根目录新建public文件夹,放置index.html和 favicon.ico文件,htmlWebpackPlugin.options.title
是读取html-webpack-plugin插件中配置的值。等运行wenpack打包的时候,就会给它赋值了。
<!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" />
<link rel="icon" href="./favicon.ico" type="image/png" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
</body>
</html>
修改 webpack.config.js
配置HtmlWebpackPlugin:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ...其他配置
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html", //用来做模板的html的文件路径(从项目根目录开始)
filename: "index.html", //生成的html的名字
title:'webpack5的项目配置',//这个就对应上文的title
inject: "body" //打包出来的那个js文件,放置在生成的body标签内
})
],
}
这个HtmlWebpackPlugin的作用,就是以你在public/index.html为初始的模板,配置生成html文件。当我们运行npm run dev
的时候。就会生成对应的index.html文件,并且自动引入我们打包出来的js文件,这时候再npm run dev
就不会报404了。至此,webpack的初始化工作完成。
在实际开发中,会有开发环境,测试环境生产环境等。而不同环境我们打出来的包应该是不同的,有些配置是开发环境所特有的,而又有一些配置是生产环境所特有的,为了便维护配置文件,我们可以分环境对webpack进行配置。
它的作用是设置node环境变量, 因为他可以跨终端进行设置.所以项目中基本上都是使用它。
npm install cross-env -D
"dev": "cross-env envMode=dev webpack server --config ./config/webpack.config.js --progress --color",
主要是加了cross-env envMode=dev
这个的作用就是在本地的node全局变量process.env上增加一个名为envMode,值为dev的变量。
怎么测试是否成功添加呢?可以在webpack.congfig.js中打印一下,然后控制台就会打印出来了,为什么是控制台而不是浏览器?那是因为webpack的程序是在node环境中运行,从而打包出我们想要的bundle.js之类的文件。所以说,是基于node才有了前端工程化。
目前,我们只是通过cross-env在node的全局变量中添加了需要的环境变量,而项目代码(src文件夹下的代码),并不能读取这些变量。但区分环境配置不同的参数又是实际开发中必不可少的。比如测试环境和生产环境的接口地址不同,开发环境需要打开Vconsole,而生产环境需要关闭等。
这就需要我们能够在项目中读取这些变量。就需要另一个库来实现:
Dotenv
是一个零依赖的模块,它能将环境变量中的变量从 .env
文件加载到 process.env
中。也就是项目代码中能够读取到process.env的值。
npm i dotenv -D
然后再在webpack.config.js中进行配置:
const webpack = require("webpack");
const envMode = process.env.envMode;
require("dotenv").config({ path: `.env` });//这个的作用是执行根目录下的.env文件,把里面的变量放到process.env上
require("dotenv").config({ path: `.env.${envMode}` });//这个的作用是执行根目录下的.env.${envMode}文件,把里面的变量放到process.env上,和.env相同的部分会覆盖它
console.log("-------", envMode);
// 正则匹配以 VUE_APP_ 开头的 变量
const prefixRE = /^VUE_APP_/;
let env = {};
// 只有 NODE_ENV,BASE_URL 和以 VUE_APP_ 开头的变量将通过 webpack.DefinePlugin 静态地嵌入到客户端侧的代码中
for (const key in process.env) {
if (key == "NODE_ENV" || key == "BASE_URL" || prefixRE.test(key)) {
env[key] = JSON.stringify(process.env[key]);
}
}
module.exports = {
...//
plugins: [
new webpack.DefinePlugin({
// 定义环境和变量
"process.env": {
...env
},
__VUE_OPTIONS_API__: false,//避免控制台警告信息
__VUE_PROD_DEVTOOLS__: false
}),
...//
]
};
注意到,它是需要手动获取到变量的,主要就是这两行代码:
require("dotenv").config({ path: `.env` });//这个的作用是执行根目录下的.env文件,把里面的变量放到process.env上
require("dotenv").config({ path: `.env.${envMode}` });//这个的作用是执行根目录下的.env.${envMode}文件,把里面的变量放到process.env上,和.env相同的部分会覆盖它
这样配置之后,就可以和vue-cli创建的项目一样,在根目录创建环境变量并使用了,可以参考
.env
# 公共环境配置
VUE_APP_TITLE = "webpack5+vue3"
.env.dev
NODE_ENV = 'development'
#开发环境要代理,这个地方要写
VUE_APP_baseUrl = '/api'
VUE_APP_SHOWCONSOLE=true
.env.testing
NODE_ENV = 'testing'
VUE_APP_baseUrl = '../../'
VUE_APP_SHOWCONSOLE = true
.env.prod
NODE_ENV = 'production'
VUE_APP_baseUrl = '../../'
VUE_APP_SHOWCONSOLE = false
这样配置之后,当我们运行npm run dev
的时候,就会把.env
和.env.dev
文件中的环境变量放置到项目的全局变量process.env中,我们可以在项目中把它打印出来:
同样的,如果我们在package.json中运行的脚本是envMode=test的话,这里获取到的变量就会是.env
和.env.testing
两个文件的并集了。项目代码中就能够使用这些变量来处理不同环境中的逻辑了。
第三第四步主要是为了项目代码中能够使用环境变量,当我们执行完第二步,webpack能根据我们的命令行区分开发和生产环境时,便可以执行区分开发和生产的配置了。
从上文可以知道。webpack配置文件最终是导出一个对象给webpack使用,而我们实际开发中,开发环境是有自己特有的配置,生产环境有自己特有的配置,还有一部分公用的配置。
于是就可以把开发的配置抽离出来,生产的配置抽离出来,公用的配置抽离出来。如下图所示:
这就有个问题,某个环境的配置如何和公用配置合并呢?理想的情况是取并集,并且是对象深层的并集。
有一个插件提供了这个功能,现在安装下这个插件:
npm i webpack-merge -D
然后新建三个文件,把我们之前做的配置按照开发环境、生产环境和公用配置进行拆分,并删除之前的配置文件:
webpack.base.conf.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require("webpack");
const envMode = process.env.envMode;
require("dotenv").config({ path: `.env` });
require("dotenv").config({ path: `.env.${envMode}` });
console.log("-------", envMode);
// 正则匹配以 VUE_APP_ 开头的 变量
const prefixRE = /^VUE_APP_/;
let env = {};
// 只有 NODE_ENV,BASE_URL 和以 VUE_APP_ 开头的变量将通过 webpack.DefinePlugin 静态地嵌入到客户端侧的代码中
for (const key in process.env) {
if (key == "NODE_ENV" || key == "BASE_URL" || prefixRE.test(key)) {
env[key] = JSON.stringify(process.env[key]);
}
}
module.exports = {
entry: path.resolve(__dirname, "../src/main.js"), // 入口
plugins: [
new webpack.DefinePlugin({
// 定义环境和变量
"process.env": {
...env
},
__VUE_OPTIONS_API__: false,//这两个配置是引入vue后,避免控制台警告信息的
__VUE_PROD_DEVTOOLS__: false
}),
new HtmlWebpackPlugin({
template: "./public/index.html", //用来做模板的html的文件路径
filename: "index.html", //生成的html的名字
title: "webpack5的项目配置", //这个就对应上文的title
inject: "body" //打包出来的那个js文件,放置在生成的body标签内
})
]
};
webpack.dev.conf.js
const path = require("path");
const { merge } = require("webpack-merge");
const BaseWebpackConfig = require("./webpack.base.conf");
module.exports = merge(BaseWebpackConfig, {
mode: "development",
output: {
path: path.resolve(__dirname, "../dist"),
filename: "./js/[name].[chunkhash].js"
},
devServer: {
hot: true, //模块的热替换
open: true, // 编译结束后自动打开浏览器
port: 8080, // 设置本地端口号
host: "localhost" //设置本地url
}
});
webpack.prod.conf.js
const path = require("path");
const { merge } = require("webpack-merge");
const BaseWebpackConfig = require("./webpack.base.conf");
module.exports = merge(BaseWebpackConfig, {
mode: "production",
output: {
path: path.resolve(__dirname, "../dist"),
filename: "./js/[name].[chunkhash].js"
}
});
然后修改packgae.json中的script为:
"scripts": {
"dev": "cross-env envMode=dev webpack serve --config ./config/webpack.dev.conf.js --color ",
"testing": "cross-env envMode=testing webpack --config ./config/webpack.prod.conf.js --color",
"build": "cross-env envMode=prod webpack --config ./config/webpack.prod.conf.js --color"
},
然后运行npm run dev
,就可以执行./config/webpack.dev.conf.js
中的打包代码配置了,达到的效果和之前一样:
有些浏览器或者浏览器版本还不支持es6的语法,所以需要babel把es6转化为es5。
npm install babel-loader @babel/core @babel/preset-env -D
npm install @babel/runtime @babel/plugin-transform-runtime -D
因为这属于生产环境和开发环境都需要用到的配置,所以在webpack.base.js
中进行配置。
module.exports = {
...//
module: {
rules: [
{
test: /\.js$/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
plugins: [["@babel/plugin-transform-runtime"]],
//开启缓存
cacheDirectory: true
}
},
exclude: /node_modules/
}
]
}
}
值得注意的是:若不想将配置写在配置文件中,可在项目根目录创建 babel.config.js
或 babelrc.js
文件(优先级更高)。
这样配置之后,遇到js文件,就会使用babel进行编译处理。
npm i vue-loader @vue/compiler-sfc -D
npm i vue -S
入口文件main.js
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
app.mount("#app");
App.vue文件
<template>
<div class="app-box">测试页面</div>
</template>
<script setup></script>
<style></style>
同样是通用配置,在webpack.base.js中配置:
const { VueLoaderPlugin } = require("vue-loader/dist/index");
module.exports = {
...//其他配置
module: {
rules: [
...//其他配置
{
test: /\.vue$/,
loader: "vue-loader"
}
]
},
plugins: [
...//其他配置
new VueLoaderPlugin()
]
};
配置完成后可以在浏览器上看到:
npm install css-loader sass sass-loader mini-css-extract-plugin -D
还是在通用文件中进行配置:
//1,引入
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//2,plugins中使用
plugins: [
new MiniCssExtractPlugin({
filename: "assets/styles/[contenthash].css" //配置css打包之后的存放路径(这个配置contenthash在开发环境会导致热更新css报错,开发环境用name)
})
]
//3,module.rule中配置规则
{
test: /\.(scss|css)$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
"css-loader",
"sass-loader"
]
}
值得注意的是以前我们都是使用style-loader将css挂在dom上,现在的做法是使用MiniCssExtractPlugin插件将css抽离出来成为一个文件,然后再在html中使用link来引入了。
另外,这个use中的数组是有顺序要求的。先执行sass-loader转化为一般的css文件,再执行css-loader转化为webpack能够理解的模块进行打包,最后使用MiniCssExtractPlugin在html文件中引入。
然后运行项目,可以看到已经能识别scss了。
一些css3的代码,一些浏览器不支持。需要添加浏览器前缀。就需要使用到这个插件。
npm i postcss-loader autoprefixer -D
然后修改刚刚设置的css配置,在scss-loader后面增加这个postcss-loader,也就是转化成普通的css后需要增加浏览器前缀。
{
test: /\.(scss|css)$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
"css-loader",
"postcss-loader",
"sass-loader"
]
}
但是增加前缀是需要使用postcss-loader的一个插件。
于是需要再在项目根目录新建一个文件:postcss.config.js
文件
module.exports = () => {
return {
plugins: {
autoprefixer: {}
}
};
};
这里需要配置浏览器的兼容列表,但是更好的做法是在package.json文件中进行配置:
"browserslist": [
"> 0.1%",
"last 2 versions"
],
于是运行项目就可以看到增加了前缀了:
我们在代码中直接写:
<div class="app-box">
<img src="./assets/img/pic.jpeg" alt="" />
</div>
是会报错的,因为这时候webpack还不能解析图片等静态资源。
webpack5 之前我们处理静态资源比如。图片字体之类的资源的时候等,需要用到url-loader,file-loader,raw-loader,webpack5则放弃了这三个loader,这三个loader在github上也停止了更新。 webpack5使用四种新增的内置资源模块(Asset Modules)替代了这些loader的功能。
asset/resource 将资源分割为单独的文件,并导出url,就是之前的 file-loader的功能.
asset/inline 将资源导出为dataURL(url(data:))的形式,之前的 url-loader的功能.
asset/source 将资源导出为源码(source code). 之前的 raw-loader 功能.
asset 自动选择导出为单独文件或者 dataURL形式(默认为8KB). 之前有url-loader设置asset size limit 限制实现。
在公共配置里设置assetModule的文件输出路径:
output: {
assetModuleFilename: "assets/images/[contenthash][ext]" //自定义asset module资源打包后的路径和名字
},
因为asset类型可以设置文件大小,所以对于图片的处理我们通常使用它。(大于多少kb的使用单独文件的url,小图片则转为dataurl,即base64的形式。)
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
type: 'asset', // asset 资源类型可以根据指定的图片大小来判断是否需要将图片转化为 base64
generator: {
filename: 'assets/images/[hash][ext][query]' // 局部指定输出位置,这里配置的文件输出路径优先级比第一步配置的高
},
parser: {
dataUrlCondition: {
maxSize: 30 * 1024 // 限制于 30kb
}
}
},
值得注意的是,这里配置的文件输出路径优先级更高。
如下图,可以看到大图片是打包成文件了,然后依据路径引入,而小图片,则是直接编译成base64:
在我们引入文件或者资源的时候,常常使用相对路径的话,要是文件路径特别深的话。就得写好多好长的路径,这样子显然不方便。于是就可以配置路径别名的方式来简化这一过程。
还是在公用配置文件中写:
function resolve(dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
...//其他配置
resolve: {
alias: {
"@": resolve("src"),
"@components": resolve("src/components"),
"@assets": resolve("src/assets"),
"@img": resolve("src/assets/img"),
"@utils": resolve("src/utils"),
"@api": resolve("src/api"),
"@css": resolve("src/assets/css"),
"@plugins": resolve("src/plugins")
}
}
}
这样配置之后。如上文我们使用图片就可以:
<template>
<div class="app-box">
<div class="img1-box">
<h4>这个是大图片</h4>
<img src="@img/pic.jpeg" alt="" />
</div>
<div class="img2-box">
<h4>这个是小图片</h4>
<img src="@img/test.png" alt="" />
</div>
</div>
</template>
webpack5.20以下版本清除dist文件内容一般使用插件 clean-webpack-plugin, 5.20版本以后output新增特性clean,用于清除dist文件。
因为只有打包出来才会生成dist文件,所以我们把这个配置放在生产的配置文件中:
//webpack.prod.conf.js
module.exports = {
//...
output: {
clean: true, // Clean the output directory before emit.
},
};
一个完整的项目,不仅有开发依赖的各种文件需要打包上传到服务器上,也许还会有各种开发文档,项目中没引用过的图片、设计图、或者会有一些静态页面不需要编译,也要上传到服务器上,以方便预览。
这里就需要用到插件copy-webpack-plugin
npm i copy-webpack-plugin -D
因为之前我的html模板中引入favicon.ico是写死了相对路径,npm run build生成的dist下不会有这个文件,所以会报404。为了解决这一问题,可以正好用这个插件把public文件夹下的favicon.ico复制到dist根目录下:
<link rel="icon" href="./favicon.ico" type="image/png" />
因为这是打包dist文件时才需要执行,所以写在生产的配置文件中:
const copyWebpackPlugin = require("copy-webpack-plugin");
function resolve(dir) {
return path.join(__dirname, "..", dir);
}
module.exports = merge(BaseWebpackConfig, {
...//其他配置
plugins: [
//静态资源输出到根目录
new copyWebpackPlugin({
patterns: [
{
from: resolve("/public"),
to: resolve("/dist"), //放到output文件夹下
globOptions: {
dot: true,
gitignore: false,
ignore: [
// 配置不用copy的文件
"**/index.html"
]
}
}
]
})
]
}
于是运行npm run build,就可以看到dist根目录下会出现favicon.ico文件了。
和第十步一样,使用webpack的asset module内置模块。
{
test: /\.(eot|svg|ttf|woff|woff2|)$/,
type: 'asset/resource',
generator: {
// 输出文件位置以及文件名
filename: 'assets/fonts/[hash:8].[name][ext]'
}
},
之前上文第六步,已经安装了抽离css为单独文件,然后link便签引入html的插件。现在来安装压缩css的插件。
npm install css-minimizer-webpack-plugin -D
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
...//其他配置
optimization: {
minimizer: [
new CssMinimizerPlugin(),
],
},
};
查看配置之后的对比:
html
文件在上文的第二节的第六步,已经安装并配置了html-webpack-plugin,这里就不再次安装,而是直接在生产配置文件webpack.prod.conf.js
中增加配置,因为之前的文件是写在webpack.base.conf.js
中,这里再写,当运行npm run build
的时候,就会有冲突(base中配置了,prod中又配置),所以呢,就需要把第二节的第六步中的配置转移到开发环境配置中去:
于是公用配置:
//webpack.base.conf.js
- const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
plugins: [
...//其他配置
- new HtmlWebpackPlugin({
- template: "./public/index.html", //用来做模板的html的文件路径
- filename: "index.html", //生成的html的名字
- title: "webpack5的项目配置", //这个就对应上文的title
- inject: "body" //打包出来的那个js文件,放置在生成的body标签内
- }),
]
};
开发环境配置:
//webpack.base.conf.js
+ const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
plugins: [
...//其他配置
+ new HtmlWebpackPlugin({
+ template: "./public/index.html", //用来做模板的html的文件路径
+ filename: "index.html", //生成的html的名字
+ title: "webpack5的项目配置", //这个就对应上文的title
+ inject: "body" //打包出来的那个js文件,放置在生成的body标签内
+ }),
]
};
生产环境配置:
//webpack.prod.conf.js
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ...
plugins: [
+ new HtmlWebpackPlugin({
+ template: "./public/index.html", //用来做模板的html的文件路径
+ filename: "index.html", //生成的html的名字
+ title: "webpack5的项目配置", //这个就对应上文的title
+ inject: "body", //打包出来的那个js文件,放置在生成的body标签内
+ minify: {
+ collapseWhitespace: true, // 去掉空格
+ removeComments: true // 去掉注释
+ }
+ })
// ...
]
}
对比开发环境打包出来的html和生产的:
npm install terser-webpack-plugin -D
const TerserWebpackPlugin = require('terser-webpack-plugin');
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
new TerserWebpackPlugin()
]
},
// ...
}
在实际开发过程中,测试环境的配置和生产基本一样,所以共用prod文件进行配置。但是还是有一丢丢区别。比如在压缩js的时候。生产环境我们希望去除console,而测试环境又希望保留。于是就需要区分配置。这个前提是要让webpack区分测试环境和生产环境。
在上文,第三节的第五步,已经区分环境打包了。并且注入了环境变量。
于是,当我们运行npm run testing
的时候,process.env.NODE_ENV=testing
。而当我们运行npm run build
的时候,process.env.NODE_ENV=production
。
于是在webpack.prod.conf.js
中就可以这样配置js的压缩。
const TerserWebpackPlugin = require('terser-webpack-plugin');
//只有生产的包会清除log
const consoleObj = function () {
if (process.env.NODE_ENV === "production") {
return {
// 多进程
parallel: true,
//删除注释
extractComments: false,
terserOptions: {
compress: {
// 生产环境去除console
drop_console: true,
drop_debugger: true
}
}
};
} else {
return {
// 多进程
parallel: true
};
}
};
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
new TerserWebpackPlugin(consoleObj())
]
},
// ...
}
但是我执行npm run testing
的时候,遇到了一个bug。报错了:
WARNING in DefinePlugin
Conflicting values for 'process.env.NODE_ENV'
意思就是process.env.NODE_ENV
已经存在,所以冲突了,查看了官网:
Tells webpack to set process.env.NODE_ENV to a given string value. optimization.nodeEnv uses DefinePlugin unless set to false. optimization.nodeEnv defaults to mode if set, else falls back to 'production'.
是因为optimization.nodeEnv
,如果不设置值的话,默认会取mode中设置的值,而恰好我mode的设置:开发环境是development,生产环境是production。和这两个文件的配置一样,所以不会冲突:
而这时候,我运行npm run testing
的时候,因为执行的还是生产配置的文件,其中的mode已经设置为production了。于是会报这个冲突的错误。
解决办法是在webpack.base.conf.js
中添加这一行:
module.exports={
...//其他配置
optimization: {
nodeEnv: false
}
}
这样配置之后,process.env.NODE_ENV
就不会从配置文件的mode取值了,而是直接用我们自己配置的webpack.DefinePlugin:
到目前为止,我们一运行打包,控制台会输出这么多东西:
为了简化,我们在webpack.base.conf.js
加入stats: "errors-only"
,这样只有在报错的时候会输出。
为了更好的查看报错的信息,我们可以继续安装插件:
npm install friendly-errors-webpack-plugin -D
plugins: [
new FriendlyErrorsWebpackPlugin({
// 成功的时候输出
compilationSuccessInfo: {
messages: [`已经打包成功啦~`]
},
// 是否每次都清空控制台
clearConsole: true
})
],
有的时候,我们需要对项目进行优化。比如说减少首屏加载时间等,就需要直观地查看每个文件打包出来的大小。于是就需要这么一个插件。来查看打包文件的大小。
npm install webpack-bundle-analyzer -D
webpack.prod.conf.js
文件 const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
// ...
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: process.env.analyMode == "true" ? "server" : "disabled", //这个配置后默认是不会启用这个插件
generateStatsFile: false, // 是否生成stats.json文件
statsOptions: { source: false }
})
// ...
],
};
package.json
文件因为查看生产配置才有意义,所以就写在生产配置中了:
{
"scripts": {
// ...
"analyzer": "cross-env envMode=prod analyMode=true webpack --config ./config/webpack.prod.conf.js --color --progress"
},
}
控制台执行 npm run analyzer
系统自动启动打包报告的HTTP服务器:
它会帮助我们统计出每个文件的三种大小:
stat size(统计尺寸)
parsed size(解析大小,webpack打包出来的文件大小)
Gzipped size(压缩大小,在开启了gzip后达到的大小)
npm install vue-router -S
//home页面
<template>
<div class="home-box">
<div class="home-title">首页页面</div>
<button class="home-btn" @click="goToMine">前往我的页面</button>
</div>
</template>
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
function goToMine() {
router.push("/mine");
}
</script>
<style></style>
//mine页面
<template>
<div class="mine-box">
<div class="mine-title">我的页面</div>
<button class="mine-btn" @click="goToHome">前往首页</button>
</div>
</template>
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
function goToHome() {
router.push("/home");
}
</script>
<style></style>
//router.js
import { createRouter, createWebHashHistory } from "vue-router";
import Home from "@/views/home/index.vue";
import Mine from "@/views/mine/index.vue";
const routerHistory = createWebHashHistory();
const router = createRouter({
history: routerHistory,
routes: [
{
path: "/",
redirect: "/home",
component: Home,
children: [
{
path: "/home",
name: "home",
component: Home,
meta: {
keepAlive: false
}
}
]
},
{
path: "/mine",
name: "Mine",
component: Mine,
meta: {
keepAlive: false
}
}
]
});
export default router;
然后App.vue
//App.vue
<template>
<div id="app">
<router-view v-if="!$route.meta.keepAlive" class="router" />
<router-view
v-if="$route.meta.keepAlive"
v-slot="{ Component }"
class="router"
>
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
</div>
</template>
<script setup></script>
<style lang="scss"></style>
最后main.js:
//main.js
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/index.js";
const app = createApp(App);
app.use(router).mount("#app");
运行项目,就可以看到:
虽然vue3的compositionAPI可以替代vuex。但我还是比较倾向于用vuex。感觉用vuex模块化管理页面的状态数据,然后compositionAPI用来封装抽离通用的功能性代码。
npm install vuex -S
src/store/modules/user.js
const state = {
userInfo: {}
};
const mutations = {
setUserInfo(state, infoObj) {
state.userInfo = infoObj;
}
};
const actions = {
async getUserInfo({ commit }) {
//异步接口请求
setTimeout(() => {
let obj = {
userName: "姓名",
age: 18
};
commit("setUserInfo", obj);
}, 0);
}
};
export default {
namespaced: true,
state,
mutations,
actions
};
src/store/modules/index.js
import { createStore } from "vuex";
import user from "./modules/user";
const store = createStore({
state: {},
getters: {},
actions: {},
mutations: {},
modules: {
user
}
});
export default store;
然后在main.js中:
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/index.js";
import store from "./store/index.js";
const app = createApp(App);
app
.use(router)
+ .use(store)
.mount("#app");
最后在home页面使用:
<template>
<div class="home-box">
<div class="home-title">首页页面</div>
<div class="home-content">
<div class="content-title">测试store中的数据</div>
<div class="user-name">{{ userObj.userName }}</div>
</div>
<button class="home-btn" @click="goToMine">前往我的页面</button>
</div>
</template>
<script setup>
import { computed } from "vue";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
const router = useRouter();
const store = useStore();
const userObj = computed(() => store.state.user.userInfo);
store.dispatch("user/getUserInfo");
function goToMine() {
router.push("/mine");
}
</script>
<style></style>
于是就可以看到页面效果:
npm i vant -S
需要再安装一个依赖
npm i babel-plugin-import -D
const presets = [];
const plugins = [
//配置vant的按需引入,babel-plugin-import 是一款 babel 插件
[
'import',
{
libraryName: 'vant',
libraryDirectory: 'es',
style: true
},
'vant'
]
];
module.exports = {
plugins,
presets
};
import { Button } from "vant";
export default (app) => {
app.use(Button);
};
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/index.js";
import store from "./store/index.js";
+ import installVant from "@/plugins/vant.js";
const app = createApp(App);
+ installVant(app);
app.use(router).use(store).mount("#app");
npm i postcss-px-to-viewport -D
module.exports = ({ file }) => {
const designWidth = file.includes(path.join("node_modules", "vant"))
? 375
: 750;//这一步是因为vant的基准是375
return {
plugins: {
autoprefixer: {},
"postcss-px-to-viewport": {
unitToConvert: "px", // 需要转换的单位,默认为"px"
viewportWidth: designWidth, // 设计稿的视口宽度
unitPrecision: 5, // 单位转换后保留的精度
propList: ["*"], // 能转化为vw的属性列表
viewportUnit: "vw", // 希望使用的视口单位
fontViewportUnit: "vw", // 字体使用的视口单位
selectorBlackList: [".ignore", ".hairlines", ".ig-"], // 需要忽略的CSS选择器
minPixelValue: 1, // 最小的转换数值,如果为1的话,只有大于1的值会被转换
mediaQuery: false, // 媒体查询里的单位是否需要转换单位
replace: true, // 是否直接更换属性值,而不添加备用属性
include: undefined, // 如果设置了include,那将只有匹配到的文件才会被转换,例如只转换 'src/mobile' 下的文件 (include: /\/src\/mobile\//)
landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
landscapeUnit: "vw", // 横屏时使用的单位
landscapeWidth: 568 // 横屏时使用的视口宽度
}
}
};
};
npm i axios -S
import axios from "axios";
import { Toast } from "vant";
import qs from "qs";
axios.defaults.headers["Content-Type"] = "application/json; charset=UTF-8";
export default function request(options) {
return new Promise((resolve, reject) => {
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: process.env.VUE_APP_baseUrl, //baseURL 将会被加在 url 前面。
// 超时
timeout: 10000,
withCredentials: true // 是否允许带cookie这些
});
// request拦截器
service.interceptors.request.use(
async (config) => {
switch (config.method) {
case "get":
config.params = config.params;
break;
case "post":
config.headers["Content-Type"] =
"application/x-www-form-urlencoded; charset=UTF-8";
config.data = qs.stringify(config.data);
break;
default:
}
return config;
},
(error) => {
Toast.clear();
console.log(error);
Promise.reject(error);
} //请求拦截器的报错处理
);
// 响应拦截器
service.interceptors.response.use(
(res) => {
const handleErrCode = res.data.code;
if (handleErrCode === "10000") {
resolve(res.data);
} else {
console.log("其他状态码的处理逻辑");
reject(res.data);
}
},
(error) => {
Toast.clear();
console.log("err" + error);
let { message } = error;
if (message == "Network Error") {
message = "网络异常,请检查网络";
} else if (message.includes("timeout")) {
message = "系统接口请求超时,请检查网络";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
Toast({
message: message,
type: "error",
duration: 5 * 1000
});
return reject(error);
}
);
service(options);
});
}
api文件夹下新建http.js
import request from "@/api/request";
export function httpPost(data, url) {
return request({
url: url,
method: "post",
data
});
}
export function httpGet(data, url) {
return request({
url: url,
method: "get",
data
});
}
注意到第二步的axios封装中,有这一行配置:
baseURL: process.env.VUE_APP_baseUrl, //baseURL 将会被加在 url 前面。
而process.env.VUE_APP_baseUrl
已经在上文第三节第四步,在环境变量配置文件中新建并注入项目中了:
接下来,在webpack.dev.conf.js
中配置开发服务器代理。因为我们在本地开发环境写代码的时候。请求的后端接口是跨域的,为了解决这个跨域问题,就可以利用devServer给我们提供的一个本服务器代理的方式。
原理大概如下图这般:
因为浏览器有同源策略,直接访问服务端服务器会跨域,而服务器之间不会跨域。所以就在本地建立了一个代理来转发请求。
现在来配置这个代理服务器:
module.exports = merge(BaseWebpackConfig, {
//..其他配置
devServer: {
hot: true, //模块的热替换
open: true, // 编译结束后自动打开浏览器
port: 8080, // 设置本地端口号
host: "localhost", //设置本地url
// 设置代理,用来解决本地开发跨域问题
proxy: {
"/api": {
secure: false,
changeOrigin: true,
target:
"https://www.fastmock.site/mock/88bbb3bb8d6ea3dc8f09431a61ce2e50/mymock_test",
pathRewrite: { "^/api": "" }
}
}
},
}
开发环境下,我们已经配置了 process.env.VUE_APP_baseUrl=='/api'
<div class="five item">
<div class="content-title">测试axios请求数据</div>
<van-button type="success" class="home-btn" @click="testAxios">
发送请求
</van-button>
<div class="content-request">{{ renderTest }}</div>
</div>
import { httpGet } from "@/api/http.js";
//发送请求
async function testAxios() {
try {
let params = {};
const res = await httpGet(params, "/justtest");
renderTest.value = res.data.content;
} catch (error) {}
}
页面效果:
有的时候,我们的h5是需要内嵌在app上的,为了能够查看报错,可以安装一下Vconsole插件:
npm i vconsole -S
在上文的第三节第四步,我们已经分环境配置好变量并注入项目中了。于是就可以在main.js中配置:
//main.js
// 开发测试环境显示console
if (process.env.VUE_APP_SHOWCONSOLE === "true") {
let Vconsole = require("../node_modules/vconsole/dist/vconsole.min");
new Vconsole();
}
https://gitee.com/ling-xu/webapck5
第一次在掘金写博客,这篇文章算是开个头,后续再加上eslint配置这些。慢慢更新吧。
一个毕业后在工厂拧了两年螺丝的前大国工酱,现前端新手村成员。写博客主要是为了分享和记录,内容比较简单,还望路过的大佬斧正。