一,概述

本文主要是使用webpack5+vue3+vw适配+axios+vue-router+vuex+vant搭建一个基础的h5项目结构。其中每一节都是独立的配置,想要webpack搭配其他的也可以参考下。至于项目的代码,放在本文最后面,不想看的可以直接下载使用。

为了自己后续搭建h5项目能有个干净的项目框架直接使用,而不是每次都得重新搭建。每一个步骤都是我实践过的,其中存在的一些注意点和原理,我也会简单提及,算是学习实践的一篇笔记吧。

二,初始化项目

第一步:初始化package.json

npm init -y

第二步:安装webpack

npm install webpack webpack-cli webpack-dev-server -D
-D 等价于 --save-dev; 开发环境时所需依赖
-S 等价于 --save; 生产环境时所需依赖

第三步:新建src和config文件夹

新建src文件夹,并写上代码

./src/main.jsconsole.log("hello webpack");

新建config文件夹,并新建webpack.config.js文件

./config/webpack.config.jsconst 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中添加命令

第三步中,我们的运行命令是手动打的,不方便,于是可以在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文件。

第六步:创建 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的项目配置',//这个就对应上文的titleinject: "body" //打包出来的那个js文件,放置在生成的body标签内})],
}

这个HtmlWebpackPlugin的作用,就是以你在public/index.html为初始的模板,配置生成html文件。当我们运行npm run dev的时候。就会生成对应的index.html文件,并且自动引入我们打包出来的js文件,这时候再npm run dev就不会报404了。至此,webpack的初始化工作完成。

三,分环境打包配置

在实际开发中,会有开发环境,测试环境生产环境等。而不同环境我们打出来的包应该是不同的,有些配置是开发环境所特有的,而又有一些配置是生产环境所特有的,为了便维护配置文件,我们可以分环境对webpack进行配置。

第一步,先安装cross-env

它的作用是设置node环境变量, 因为他可以跨终端进行设置.所以项目中基本上都是使用它。

npm install cross-env -D

第二步,修改package.json的scipt

"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等文件

.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配置文件最终是导出一个对象给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的项目配置", //这个就对应上文的titleinject: "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中的打包代码配置了,达到的效果和之前一样:

四,配置babel,es6转es5

有些浏览器或者浏览器版本还不支持es6的语法,所以需要babel把es6转化为es5。

第一步:安装babel相关的依赖

npm install  babel-loader @babel/core @babel/preset-env -D

第二步:安装处理async和await的依赖

npm install  @babel/runtime @babel/plugin-transform-runtime -D

第三步:配置webpack

因为这属于生产环境和开发环境都需要用到的配置,所以在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.jsbabelrc.js 文件(优先级更高)。

这样配置之后,遇到js文件,就会使用babel进行编译处理。

五,引入vue框架

第一步,安装识别解析vue文件的依赖

npm i vue-loader @vue/compiler-sfc -D
npm i vue -S

第二步,创建vue文件,并且在入口文件创建vue实例

入口文件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可以识别vue文件

同样是通用配置,在webpack.base.js中配置:

const { VueLoaderPlugin } = require("vue-loader/dist/index");
module.exports = {...//其他配置module: {rules: [...//其他配置{test: /\.vue$/,loader: "vue-loader"}]},plugins: [...//其他配置new VueLoaderPlugin()]
};

配置完成后可以在浏览器上看到:

六,解析scss文件

第一步,安装依赖

npm install css-loader sass sass-loader mini-css-extract-plugin -D

第二步,配置webpack

还是在通用文件中进行配置:

//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了。

第三步,使用postcss插件给css添加兼容性前缀

一些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资源打包后的路径和名字},

第二步,在modele.rules中进行配置

因为asset类型可以设置文件大小,所以对于图片的处理我们通常使用它。(大于多少kb的使用单独文件的url,小图片则转为dataurl,即base64的形式。)

{test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, type: 'asset', // asset 资源类型可以根据指定的图片大小来判断是否需要将图片转化为 base64generator: {filename: 'assets/images/[hash][ext][query]' // 局部指定输出位置,这里配置的文件输出路径优先级比第一步配置的高},parser: {dataUrlCondition: {maxSize: 30 * 1024 // 限制于 30kb}}
},

值得注意的是,这里配置的文件输出路径优先级更高。

第三步,分别引入大图片和小图片作为测试

如下图,可以看到大图片是打包成文件了,然后依据路径引入,而小图片,则是直接编译成base64:

八,配置 alias 路径别名

在我们引入文件或者资源的时候,常常使用相对路径的话,要是文件路径特别深的话。就得写好多好长的路径,这样子显然不方便。于是就可以配置路径别名的方式来简化这一过程。

还是在公用配置文件中写:

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>

九,打包时清除上次构建dist目录

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.},
};

十,直接复制指定文件到dist下指定路径

一个完整的项目,不仅有开发依赖的各种文件需要打包上传到服务器上,也许还会有各种开发文档,项目中没引用过的图片、设计图、或者会有一些静态页面不需要编译,也要上传到服务器上,以方便预览。

这里就需要用到插件copy-webpack-plugin

第一步:安装copy-webpack-plugin

npm i copy-webpack-plugin -D

第二步:配置将public中favicon.ico文件复制到dist根目录

因为之前我的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]'}
},

十二,配置压缩

1,压缩css

之前上文第六步,已经安装了抽离css为单独文件,然后link便签引入html的插件。现在来安装压缩css的插件。

第一步:安装插件
npm install css-minimizer-webpack-plugin -D
第二步:配置
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");module.exports = {...//其他配置optimization: {minimizer: [new CssMinimizerPlugin(),],},
};

查看配置之后的对比:

2,压缩 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和生产的:

3,压缩js文件

第一步:安装依赖
npm install terser-webpack-plugin -D
第二步:配置依赖
const TerserWebpackPlugin = require('terser-webpack-plugin');module.exports = {// ...optimization: {minimize: true,minimizer: [new TerserWebpackPlugin()]},// ...
}
第三步:配置测试环境和生产环境的js压缩

在实际开发过程中,测试环境的配置和生产基本一样,所以共用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: {// 生产环境去除consoledrop_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:

第四步:对比配置前后的js文件是否压缩

十三,打包友好提示

到目前为止,我们一运行打包,控制台会输出这么多东西:

为了简化,我们在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后达到的大小)

十五,引入vue-router

第一步:安装依赖

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-viewv-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");

运行项目,就可以看到:

十六,引入vuex

虽然vue3的compositionAPI可以替代vuex。但我还是比较倾向于用vuex。感觉用vuex模块化管理页面的状态数据,然后compositionAPI用来封装抽离通用的功能性代码。

第一步:安装vuex

npm install vuex -S

第二步:模块化配置vuex

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>

于是就可以看到页面效果:

十七,引入vant

第一步:安装依赖

npm i vant -S

第二步:按需引入vant

需要再安装一个依赖

npm i babel-plugin-import -D

第三步:新建babel.config.js

const presets = [];
const plugins = [//配置vant的按需引入,babel-plugin-import 是一款 babel 插件['import',{libraryName: 'vant',libraryDirectory: 'es',style: true},'vant']
];
module.exports = {plugins,presets
};

第四步:新建plugin文件夹按需引入vant组件

import { Button } from "vant";
export default (app) => {app.use(Button);
};

第五步:在main中注册

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");

第六步:在页面中使用

十八,配置vw自适应

第一步:安装依赖

npm i postcss-px-to-viewport -D

第二步:添加根目录下postcss.config.js配置

module.exports = ({ file }) => {const designWidth = file.includes(path.join("node_modules", "vant"))? 375: 750;//这一步是因为vant的基准是375return {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 // 横屏时使用的视口宽度}}};
};

第三步:查看效果

十九,引入axios

第一步:安装axios依赖

npm i axios -S

第二步:新建api文件夹,下新建request.js,封装axios

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);});
}

第三步:引出get和post请求

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) {}
}

页面效果:

二十,引入Vconsole

有的时候,我们的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();
}

第三步:查看效果:

二十一,代码地址及小结

项目地址(本文内容在master分支上):

https://gitee.com/ling-xu/webapck5

webpack5+vue3搭建h5项目模板-(一)-基础配置相关推荐

  1. electron 项目 第一篇 vite +vue3 搭建electron项目

    vite + vue3 搭建 electron 项目 1 使用 vite 创建 vue3 项目 2 安装 electron 依赖 3 嵌入 electron 3.1 创建 electron 目录 3. ...

  2. Mars3D项目模板:基础项目 原生JS版 (widget方式)介绍

    项目介绍 Mars3D基础项目 是基于Mars3D平台 做的一个应用系统,提供的一个基础项目模版,包含常用基础地图功能,可在该基础项目上快速开发搭建新项目.方便快速搭建三维地图产品,敏捷开发,可复用, ...

  3. 使用webpack5自己搭建react项目脚手架(手把手教,把手伸过来,好软~呸,好手)

    问:目前前端最火的两大框架react和vue官方都有自己的成熟的cli脚手架,为什么还要自己搭建脚手架了? 答: 一.自己搭建脚手架可以根据自己公司的项目特征来决定使用哪些具体的插件或者编译方式,官方 ...

  4. 极简但是很全的vue3+vite+pinia项目模板,开箱即用

    主要功能: 1.layout布局 2.router,pinia,axio, 3.element-ui plus 部分组件二次封装 4.多语言 / icon图标组件封装 / 多环境 / eslint 其 ...

  5. vue 搭建H5项目及适配

    1.创建项目 vue create test (项目名称) vue2完整创建流程 2.进行H5适配         2.1 选择ui框架(以vantui 为例)                  具体 ...

  6. VUE2.0搭建H5项目

    1.搭建VUE2.0项目 vue create '项目名' 2.安装flexible插件(适配移动端插件) npm install lib-flexible -S 2.1 在main.js中引入fle ...

  7. 搭建vmware虚拟化平台的基础配置,以及Hadoop平台的搭建

    需要准备的东西: vmware workstations centos.iso hadoop3.3.0 mobaxterm/xshell/pietty/winscp jdk的tar包 第一步 : 安装 ...

  8. 使用React和Tailwind CSS搭建项目模板

    公众号关注 "太空编程" 设为 "星标",带你了解硬核的编程知识! 众所周知,Tailwind CSS框架越来越流行,所以我决定尝试学习并使用Tailwind ...

  9. VisualStudio2022制作多项目模板及Vsix插件

    一.安装工作负载 在vs2022上安装"visual studio扩展开发 "工作负载 二.制作多项目模板 导出项目模板这个我就不再多说了(项目→导出模板→选择项目模板,选择要导出 ...

  10. vite+vue3+ts项目搭建之集成qiankun让其成为子应用模板(vite+vue3+ts+qiankun项目)—新增主应用在线地址可以进入vue2/vue3子应用

    前言 以下操作,是续接之前 第四步 --即:vite+vue3+ts+pinia+element-plus项目已完成搭建好,可以直接业务开发了 主应用技术栈:vue2+webpack+js 集成qia ...

最新文章

  1. setting.xml的mirror、mirrorOf和pom.xml的repositories、repository的关系关联*
  2. 继鼎晖夹层完成80亿募集后,鼎晖夹层IDC基金首轮关账15亿
  3. python画画零基础_[零基础学python]画圈还不简单吗?
  4. 一篇文章带你搞定Python返回函数
  5. mysql 中default 和NULL
  6. 无人值守的自动 dump(一)
  7. php 类加载其它类,PHP 命名空间 namespace / 类别名 use / 框架自动载入 机理的
  8. Popush项目团队开发章程
  9. nginx配置详解(转)
  10. Windows Azure HandBook (8) Azure性能测试(1)
  11. 地图下载器拼接ArcGIS Server瓦片
  12. Babylon.js 第28章 多面体
  13. DSPE-PEG9-Mal纯度是95%以上的单分散小分子PEG试剂
  14. 怎么画学计算机的男孩形象,怎么一步一步教大班幼儿画自己的画像:小男孩简笔画(步骤图解)...
  15. java企业员工出差报销预算管理系统-SSH-Mysql
  16. python单曲循环播放MP3
  17. 数字信号处理(FIR滤波器的设计与原理及基础知识)
  18. 使用曲面细分渲染毛发
  19. Libgdx粒子效果介绍与使用心得
  20. 在国外期刊发表文章时一定用到的,SCI论文写法攻略

热门文章

  1. 关于Volatile
  2. 【jquery调用ajax老是进error,不进success】 bug命名:小雨
  3. springboot(运行原理参考借鉴)
  4. NPM 上传自己的包
  5. bzoj1048 [HAOI2007]分割矩阵
  6. Algorithm——何为算法?
  7. Java笔记1(2015-8-30)
  8. ASP.NET WEB API简介
  9. 现在被apihook搞郁闷了.....进展很慢...先放上几个必用的api说明吧.
  10. Linux常用到的命令