当前位置:网站首页 > Vue.js开发 > 正文

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

一,概述

本文主要是使用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.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文件,完成了基本的打包配置。

image-20220410113414963

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

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

image-20220410142259724

三,分环境打包配置

在实际开发中,会有开发环境,测试环境生产环境等。而不同环境我们打出来的包应该是不同的,有些配置是开发环境所特有的,而又有一些配置是生产环境所特有的,为了便维护配置文件,我们可以分环境对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才有了前端工程化。

image-20220410175528129

第三步:将需要区分环境的变量导入项目中

目前,我们只是通过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中,我们可以在项目中把它打印出来:

image-20220410183940808

同样的,如果我们在package.json中运行的脚本是envMode=test的话,这里获取到的变量就会是.env.env.testing两个文件的并集了。项目代码中就能够使用这些变量来处理不同环境中的逻辑了。

第五步:区分开发环境和生产环境的webpack配置

第三第四步主要是为了项目代码中能够使用环境变量,当我们执行完第二步,webpack能根据我们的命令行区分开发和生产环境时,便可以执行区分开发和生产的配置了。

从上文可以知道。webpack配置文件最终是导出一个对象给webpack使用,而我们实际开发中,开发环境是有自己特有的配置,生产环境有自己特有的配置,还有一部分公用的配置。

于是就可以把开发的配置抽离出来,生产的配置抽离出来,公用的配置抽离出来。如下图所示:

image-20220410190317640

这就有个问题,某个环境的配置如何和公用配置合并呢?理想的情况是取并集,并且是对象深层的并集。

有一个插件提供了这个功能,现在安装下这个插件:

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中的打包代码配置了,达到的效果和之前一样:

image-20220410193209897

四,配置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() ] }; 

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

image-20220410234408018

六,解析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了。

image-20220411000855383

第三步,使用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" ], 

于是运行项目就可以看到增加了前缀了:

image-20220411002220063

七,处理图片这类静态资源

我们在代码中直接写:

<div class="app-box"> <img src="./assets/img/pic.jpeg" alt="" /> </div> 

是会报错的,因为这时候webpack还不能解析图片等静态资源。

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 资源类型可以根据指定的图片大小来判断是否需要将图片转化为 base64 generator: { 
    filename: 'assets/images/[hash][ext][query]' // 局部指定输出位置,这里配置的文件输出路径优先级比第一步配置的高 }, parser: { 
    dataUrlCondition: { 
    maxSize: 30 * 1024 // 限制于 30kb } } }, 

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

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

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

image-20220411221246799

八,配置 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文件了。

image-20220411230130519

十一,配置引入字体文件

和第十步一样,使用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(), ], }, }; 

查看配置之后的对比:

image-20220417111618216

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和生产的:

image-20220417121326621

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区分测试环境和生产环境。

在上文,第三节的第五步,已经区分环境打包了。并且注入了环境变量。

image-20220417135434601

于是,当我们运行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。和这两个文件的配置一样,所以不会冲突:

image-20220417140952871

而这时候,我运行npm run testing的时候,因为执行的还是生产配置的文件,其中的mode已经设置为production了。于是会报这个冲突的错误。

解决办法是在webpack.base.conf.js中添加这一行:

module.exports={ 
    ...//其他配置 optimization: { 
    nodeEnv: false } } 

这样配置之后,process.env.NODE_ENV就不会从配置文件的mode取值了,而是直接用我们自己配置的webpack.DefinePlugin:

image-20220417142041733

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

image-20220417143835050

十三,打包友好提示

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

image-20220417151541770

为了简化,我们在webpack.base.conf.js加入stats: "errors-only",这样只有在报错的时候会输出。

image-20220417151807190

为了更好的查看报错的信息,我们可以继续安装插件:

第一步:安装依赖
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服务器:

image-20220417160233894

它会帮助我们统计出每个文件的三种大小:

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

运行项目,就可以看到:

image-20220417202508411

十六,引入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> 

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

image-20220417204659388

十七,引入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"); 
第六步:在页面中使用

image-20220418002512463

十八,配置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的基准是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 // 横屏时使用的视口宽度 } } }; }; 
第三步:查看效果

image-20220423152521328

十九,引入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已经在上文第三节第四步,在环境变量配置文件中新建并注入项目中了:

image-20220423181901700

接下来,在webpack.dev.conf.js中配置开发服务器代理。因为我们在本地开发环境写代码的时候。请求的后端接口是跨域的,为了解决这个跨域问题,就可以利用devServer给我们提供的一个本服务器代理的方式。

原理大概如下图这般:

image-20220423183433443

因为浏览器有同源策略,直接访问服务端服务器会跨域,而服务器之间不会跨域。所以就在本地建立了一个代理来转发请求。

现在来配置这个代理服务器:

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

页面效果:

222

二十,引入Vconsole

有的时候,我们的h5是需要内嵌在app上的,为了能够查看报错,可以安装一下Vconsole插件:

第一步:安装依赖
npm i vconsole -S 
第二步,项目中配置

在上文的第三节第四步,我们已经分环境配置好变量并注入项目中了。于是就可以在main.js中配置:

image-20220423192239064

//main.js // 开发测试环境显示console if (process.env.VUE_APP_SHOWCONSOLE === "true") { let Vconsole = require("../node_modules/vconsole/dist/vconsole.min"); new Vconsole(); } 
第三步:查看效果:

image-20220423192347758

二十一,代码地址及小结

项目地址(本文内容在master分支上):
https://gitee.com/ling-xu/webapck5 
到此这篇webpack5+vue3搭建h5项目模板-(一)-基础配置的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • webpack5+vue3搭建h5项目模板-(二)-eslint代码规范化2024-11-29 14:18:07
  • vue3的自定义指令2024-11-29 14:18:07
  • 原生js实现dom的获取及操作2024-11-29 14:18:07
  • 简易版实现vue1.02024-11-29 14:18:07
  • 使用vuepress搭建文档站点2024-11-29 14:18:07
  • js正则表达式--个人常用2024-11-29 14:18:07
  • vue2项目中全局引入scss变量2024-11-29 14:18:07
  • inquirer:命令行中与用户进行交互的js库2024-11-29 14:18:07
  • vue2使用脚手架配置prettier报错:‘prettier/prettier‘: context.getPhysicalFilename is not a function2024-11-29 14:18:07
  • webpack5学习与实战-(六)-babel-loader解析js文件2024-11-29 14:18:07
  • 全屏图片