# 从零开始一步一步搭建 Vue3 + Webpack5 项目脚手架指南

Sam9029 的掘金主页 (opens new window)

**🐱‍🐉🐱‍🐉若此文你认为写的不错,不要吝啬你的赞扬,求收藏,求评论,求一个大大的赞!👍**

由于文章有一万字左右的,故而内容中部分不免冗余,或许有写了些废话进去,但整体也无伤大雅

**🐱‍🐉🐱‍🐉文章是初次实践总结, 若有错误或某个内容有更优的解决方案,还望指正!👍**

附录中有脚手架文件夹目录大纲

脚手架项目源码: https://github.com/sam9029/s9_webpack_guide.git(Vue3 + Webpack5)

Webpack入门基础笔记: Sam9029博客_Webpack (opens new window) (从零开始学习)

目前有一个环境变量文件注入的可优化问题

没有找到一个方案,一次注入.env.XXX环境文件,然后webpack.config.js项目文件(src)都能访问,现在使用另一个组合方案cross-env + dotenv + dotenv-webpack(参考内容位置:注入环境变量

若有同志知道望告知

# 使用本搭建指南前提

  • 了解安装 Node
  • 了解 webpack 基础配置使用(入口 entry、输出 output、loader、plugin、devServer、resolve、mode)
  • 了解前端项目文件项目结构,将会按照(类似 npm create vite@lastest 创建的脚手架文件目录)

# 基础构建配置

# 初始化 package.json 依赖

npm init -y
1

# 安装 webpack 工具

# 安装依赖

npm i webpack webpack-cli -D
1

本文使用 "webpack": "^5.93.0", "webpack-cli": "^5.1.4",

# 配置webpack.config.js

# 创建 src 文件夹

同时创建 webpack.config.js文件

- src
    - main.js
- webpack.config.js
1
2
3
  • main.js
// // dev-log >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
console.log(`[Dev_Log][${"HI"}_]_>>>`);
1
2
  • webpack.config.js

    构建时 webpack 会自动加载该文件的配置

/** node模块 文件路径path */
const path = require("path");

/** 配置项 */
module.exports = {
  /** 入口文件 */
  entry: "./src/main.js",
  /** 构建打包输出文件 */
  output: {
    /** 输出路径 */
    path: path.resolve(__dirname, "dist"), // __dirname:当前文件夹(webpack.config.js所在文件夹)
    /** 文件输出名称 */
    filename: "static/js/[name].[contenthash:6].js", // [name] 表示取文件本身名称,是一种webpack内置的快捷语法,参考https://www.webpackjs.com/configuration/output/#template-strings
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 运行构建命令

npx webpack
1

查看文件目录会输出 dist 文件夹

恭喜你,初入 webpack

但是你可能会看到以下报错,提取关键信息 mode' option has not been set

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
1
2
3
4

解决方案:在webpack.config.js追加mode环境配置

/** 配置项 */
module.exports = {
  // ...其他配置
  mode: "development", // 'development' or 'production'
};
1
2
3
4
5

# webpack.config.js环境设置

开发中一般使用 development-开发模式 构建打包项目文件 上线项目时一般使用 production-生产模式 构建打包项目文件

# HTML 模板文件处理

因为 webpack 只能处理 JS 资源,所以其他资源,如:html, css, jpg, tff(文字)等是需要安装对应的 plugi 插件或者 loader 处理的

# 安装依赖

npm i html-webpack-plugin -D
1

# 创建 public 文件夹

  • 和 src 同级创建 public 文件夹,创建一个 index.html 文件
- public
    - index.html
- src
1
2
3
  • index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <p>webpack html configuration</p>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11

# webpack 配置 html 资源处理 plugin

/** 配置项 */
module.exports = {
  // ... 其他配置
  plugins: [
    /** 模板 html 处理 */
    new HtmlWebpackPlugin({
      /**
       * 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js、css等资源
       * 以 public/index.html 为模板创建文件
       * */
      template: path.resolve(__dirname, "public/index.html"),
    }),
  ],
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 运行构建命令

npx webpack
1
  • 或者
npm run build // 需要在 package.json--script 中配置
1

查看文件目录会输出 dist 文件夹,并且多一个 index.html 文件,还自动压缩了 index.html 代码

# 动态网页标题(可选)

修改HtmlWebpackPlugin

plugins: [
  new HtmlWebpackPlugin({
    title: "Custom template",
    template: "index.html",
  }),
];
1
2
3
4
5
6

index.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body></body>
</html>
1
2
3
4
5
6
7
8

# CSS 样式文件处理

关于 CSS 的处理整个流程配置较为多, 主要流程如下:

  • 处理CSS文件代码,将CSS 样式代码转化为JS 操作样式('css-loader')
  • 转换JS 操作样式提取 CSS 样式代码,使用 style 标签引入到 html 文件('style-loader')
  • 预处理样式(sscss、less、stylus 预处理)的转化 ( 'sass sass-loader' || 'less-loader' || 'stylus-loader')
  • 提取 CSS 文件 (mini-css-extract-plugin)(后面讲解)
  • 处理 CSS 代码兼容性(postcss-loader postcss postcss-preset-env)(后面讲解)
  • 压缩 CSS 文件 (css-minimizer-webpack-plugin)(后面讲解)

# 安装依赖编译 css 文件代码

npm i css-loader style-loader -D
1
  • css-loader:负责将 css 文件编译成 Webpack 能识别的模块
  • style-loader:会动态创建一个 Style 标签,里面放置 Webpack 中 css 模块内容

此时样式就会以 Style 标签的形式在页面上生效

# 添加 css 资源

  • src/assets/style/index.css
.text {
  color: #999;
}
1
2
3
  • src/main.js
// 引入 CSS 资源,Webpack才会对其打包
import "./assets/style/index.css";

// // dev-log >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
console.log(`[Dev_Log][${"HI"}_]_>>>`);
1
2
3
4
5
  • pubilc/index.html

p 标签哪里加一个 class 类 `class="text"

<!-- // ...其他  -->
<p class="text">webpack html configuration</p>
<!-- // ...其他  -->
1
2
3

# webpack 配置 css 资源处理 loader

/** 配置项 */
module.exports = {
  // ... 其他配置

  module: {
    rules: [
      /** 处理 CSS 文件 */
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },

  // ... 其他配置
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# scss、less、stylus 预处理

这些预处理样式的 loader 配置类似于 css 的配置,不赘述了,配置参考下方即可

  • 记得先(npm i xxx-loader -D)安装对应的 loader
  • sass 对应 npm i sass-loader -D
  • less 对应 npm i less-loader -D
  • styl 对应 npm i stylus-loader -D
module: {
    rules: [
      /** 处理 CSS 文件 ...  */

      /** 处理 SCSS 文件 */
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },

      /** 处理 less 文件 */
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },

      /** 处理 stylus 文件 */
      {
        test: /\.styl$/,
        use: ["style-loader", "css-loader", "stylus-loader"],
      },
    ],
  },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# vue 文件资源处理(重要)vue-loader

# 安装依赖

  • 安装使用 Vue 框架的最新版本
npm i vue@latest
1
  • 安装编译.Vue 文件的相关依赖
npm i vue-loader @vue/compiler-sfc -D
1

了解:使用@符号的依赖通常指的是作用域包(Scoped Packages),比如 @vue/compiler-sfc 就表示,它是 vue 生态的依赖包。

  • vue-loader:允许你在 webpack 模块打包器中使用 Vue.js 单文件组件(.vue 文件)
  • @vue/compiler-sfc:这个包是 Vue.js 的一个核心库,用于将 Vue 的单文件组件转换成 JavaScript 模块

# 创建/修改对应文件

  • 修改public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- 预备给Vue实例的挂载DOM -->
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
  • 新建src/app.vue
<template>
  <div class="container__wrapper">vue3-page-template</div>
</template>

<script setup lang="js">
  import { ref, computed, onMounted } from 'vue';

  onMounted(()=>{
    // // dev-log >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    console.log(`[Dev_Log][${'onMounted生命周期'}_]_>>>`)
  })
</script>

<style lang="scss" scoped>
  .container__wrapper {
    color: #999;
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • 修改src/main.js
/* 引入Vue框架 */
import { createApp } from "vue/dist/vue.esm-bundler";
import App from "./app.vue"; //导入

/* 引入 CSS 资源,Webpack才会对其打包 */
import "./assets/style/index.css";
import "./assets/style/index.scss";

// // dev-log >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
console.log(`[Dev_Log][${"Main.js 入口"}_]_>>>`);

/* 创建Vue实例并挂载(mount)到id为app的DOM元素上*/
createApp(App).mount("#app");
1
2
3
4
5
6
7
8
9
10
11
12
13

# 配置 webpack.config.js

import { VueLoaderPlugin } from "vue-loader/dist/index";
/** ...其他引入 */

/** 配置项 */
module.exports = {
  /** ...其他配置 */

  /** loader */
  module: {
    rules: [
      /** 处理 Vue 文件, vue-loader 不支持 oneOf */
      {
        test: /\.vue$/,
        use: ["vue-loader"],
      },

      /** ...其他配置 */
    ],
  },

  /** 插件 */
  plugins: [
    /** vue文件处理 */
    new VueLoaderPlugin(),

    /** ...其他配置 */
  ],

  /** ...其他配置 */
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 测试效果

使用 npx webpack 构建生产文件

在 dist 的文件中,查看编译文件

右键 index.html 使用 Open with Live Server,查看网页效果

没有这个选项就,使用 VScode 安装 Live Server 插件之后尝试

# 网页媒体资源拷贝处理

# 安装依赖

npm i copy-webpack-plugin -D
1

copy-webpack-plugin 是一个复制资源文件的插件,这里用于复制 ico 图标资源

在根目录 public 文件夹中新建 favicon.ico

但是 webpack 处理不会自行对 favicon.ico 处理

当你在 index.html 引入时, <link rel="icon" href="./favicon.ico">

就会因为构建文件中没有 favicon.ico 资源而不展示或控制台提示报错

所以需要配置依赖手动拷贝

# webpack.config.js 配置

/** node模块 文件路径path */
const path = require("path");
/** 拷贝资源Plugin*/
const CopyPlugin = require("copy-webpack-plugin");

/** 配置项 */
module.exports = {
  /** ...其他配置 */

  /** 插件 */
  plugins: [
    /** ...其他配置 */

    //复制public资源到index里面
    new CopyPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, "./public"), //将根文件夹下 public文件夹复制到dist目录下
          to: path.resolve(__dirname, "./dist"),
          globOptions: {
            // 忽略index.html文件
            ignore: ["**/index.html"],
          },
        },
      ],
    }),
  ],
  /** ...其他配置 */
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 图片资源处理(可选)

同理类似于 CSS-loader 的配置

由于 Webpack 5 已经内置了 file-loader 和 url-loader 所以图片资源处理,无需安装依赖

配置参如下:

module.exports = {
  mudule: {
    /** 构建打包输出文件 */
    output: {
      // ...其他配置

      /** 图片、字体等资源输出命名方式(注意用hash) */
      assetModuleFilename: "static/media/[name].[hash:6][ext]",
    },
    rules: [
      /** ...其他配置 */

      /** 处理图片文件 */
      {
        test: /\.(jpe?g|png|gif|webp)$/,
        type: "asset", // 资源类型为 asset,Webpack 会根据文件类型选择合适的加载器进行处理, 比如 url-loader
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
          },
        },
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

当然这里只是最简单的图片资源处理:仅对大小小于 10KB 的小图进行 Base64 URL 转换

其余的方式还有

  • **使用 CDN:**配置 对应的 Plugin,上传所有的图片到 OSS 服务,使用图片 CDN 链接替换项目中的所有图片链接
  • **本地压缩:**对所有的图片进行压缩处理,或者格式处理(如转 webp 格式)--- 参考 ImageMinimizer 图片压缩__Sam9029博客 (opens new window)

在此不做讲解,自行搜索实现方式

# 其他资源处理(可选)

# 开发环境:热更新

注意:仅在开发环境下生效

# 安装依赖

npm i webpack-dev-server -D
1

DevServer 官网指南 https://www.webpackjs.com/configuration/dev-server/ Webpack Dev Server 是一个小型 Node.js Express 服务器,用于提供热模块替换(HMR)功能,使开发者在开发过程中可以即时看到更改的效果。 并且运行时是没有dist输出的

# webpack 配置

// ... 其他依赖引入
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  // ... 其他配置

  /* 配置开发服务器 */
  devServer: {
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
    hot: true, // 热更新
    compress: true, // 压缩编译后文件
  },
  mode: "development",
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 在 package.json 配置 快捷指令

  "scripts": {
	  "dev": "npx webpack serve",
  },
1
2
3

# resolve 配置快捷路径

# webpack.config.js 配置

module.exports = {
  /** 其他配置 */

  /**  resolve 配置  */
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"), // 把 src 这个常用目录修改为 @
    },
    extensions: [".js", ".vue"], //配置了这些我们就不写对应文件后缀名啦
  },
};
1
2
3
4
5
6
7
8
9
10
11

# 使用

  • main.js 的样式引入为案例
/** Vue入口文件 */
// import App from "./app.vue"; // 原本引入(可删)
import App from "./app";

/* 引入 CSS 资源,Webpack才会对其打包 */
// import "./assets/style/index.css"; // 原本引入(可删)
// import "./assets/style/index.scss"; // 原本引入(可删)
import "@/assets/style/index.css";
import "@/assets/style/index.scss";
1
2
3
4
5
6
7
8
9

# 提取 CSS 文件

由于之前只配置了 webpack 的css-loader,style-loader来处理 CSS 代码, 但是使用 npx webpack构建文件之后, 可以观察到打包文件中是没有 CSS 文件

这是由于上诉的 loader 仅仅是将 CSS 被打包到 js 文件中,当 js 文件加载时,会创建一个 style 标签来生成样式, 这样对于网站来说,会出现闪屏现象,用户体验不好

项目中原则上需要采取样式分离的原则, 应该是单独的 Css 文件,通过 link 标签加载性能才好, 所以我们需要引入对应的 loader 和 plugin 同时处理

来将混合在 JS 中的 CSS 代码,单独提取成 CSS 文件,并引入到 index.html 中

# 安装依赖

npm i mini-css-extract-plugin -D
1

# webpack.config.js 配置

  • 注意使用 MiniCssExtractPlugin.loader 时, 就不用使用 style-loader

由于 loader 的执行时由上到下,由右到左

故而 css 处理的 loader 配置都应该写 vue-loader处理 Vue 文件的配置 之下

/** 其他配置 */
/** css文件提取处理 */
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  /** 其他配置 */

  module: {
    rules: [
      /** 其他配置 */
      /** 处理 Vue 文件, vue-loader 不支持 oneOf */
      {
        test: /\.vue$/,
        use: ["vue-loader"],
      },

      /** 处理 CSS 文件 */
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },

      /** 处理 SCSS 文件 */
      {
        test: /\.s[ac]ss$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
      },

      /** 其他配置 */
    ],
  },

  plugins: [
    // /** css文件提取处理 --- */
    new MiniCssExtractPlugin({
      filename: "static/css/[name].[contenthash:6].css", // 定义输出静态文件名和目录
      chunkFilename: "static/css/[name].[contenthash:6].chunk.css", // 定义输出动态引入文件名和目录
    }),

    /** 其他配置 */
  ],

  /** 其他配置 */
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 处理 CSS 代码兼容性

  • 引入 PostCss 处理 CSS 代码兼容性

项目线上运行的环境容器, 可能是 Chrome, firefox, Safari, 或者开发者的噩梦 IE

由于不同浏览器之中的适配不同,对于 DOM API 和 CSS 属性 API 的支持性不一样

所以在项目开发之中, 需要引入 PostCSS 来为项目自动处理这些差异,保证代码的兼容性强, 鲁棒性高

# 安装依赖

npm i postcss-loader postcss postcss-preset-env -D
1

postcss-loader:这是一个 webpack loader,用于在 webpack 构建过程中处理 CSS 文件,特别是使用 PostCSS 进行处理。

postcss:PostCSS 是一个用 JavaScript 工具和插件生态系统来转换 CSS 代码的工具。它允许你使用最新的 CSS 语法,并且可以通过插件来扩展其功能。

postcss-preset-env:这是一个 PostCSS 的插件,提供了未来 CSS 规范中的特性,使得你可以在现代浏览器中使用它们,同时保持向后兼容性。

# webpack.config.js 配置

由于当前 CSS 文件处理的 loader 过多,我们可以进行公共配置可以,参见 setStyleLoaders

/** 其他配置 */
/** css文件提取处理 */
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

/**
 * @description 获取处理css样式的loaders工具配置函数, 配置了基础的loader,同时接收传参自定义loader
 * @param {Array} preProcessorLoader []
 * @returns
 */
const setStyleLoaders = (preProcessorLoader = []) => {
  return [
    // [生产模式]下单独提取css文件loader并压缩;
    MiniCssExtractPlugin.loader,
    // 基础css-loader
    "css-loader",
    // css代码兼容处理loader配置
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            "postcss-preset-env", // 能解决大多数样式兼容性问题
          ],
        },
      },
    },
    // 外部传入自定义 loader
    ...preProcessorLoader,
  ].filter(Boolean); // 筛选所有存在的loader
};

module.exports = {
  /** 其他配置 */

  module: {
    rules: [
      /** 其他配置 */
      /** 处理 Vue 文件, vue-loader 不支持 oneOf */
      {
        test: /\.vue$/,
        use: ["vue-loader"],
      },

      /** 处理 CSS 文件 */
      {
        test: /\.css$/,
        use: setStyleLoaders(),
      },

      /** 处理 scss or sass 文件 */
      {
        test: /\.s[ac]ss$/,
        use: setStyleLoaders(["sass-loader"]),
      },

      /** 其他配置 */
    ],
  },

  plugins: [
    // /** css文件提取处理 --- */
    new MiniCssExtractPlugin({
      filename: "static/css/[name].[contenthash:6].css", // 定义输出静态文件名和目录
      chunkFilename: "static/css/[name].[contenthash:6].chunk.css", // 定义输出动态引入文件名和目录
    }),

    /** 其他配置 */
  ],

  /** 其他配置 */
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# 配置 CSS 兼容的层级

我们可以在!!! package.json !!!文件中添加 browserslist 来控制样式的兼容性做到什么程度。

{
  // 其他省略
  "browserslist": ["ie >= 8"]
}
1
2
3
4

想要知道更多的 browserslist 配置,查看browserslist 文档 open in new window (opens new window)

以上为了测试兼容性所以设置兼容浏览器 ie8 以上。实际开发中我们一般不考虑旧版本浏览器了,所以我们可以这样设置:

{
  // 其他省略
  "browserslist": ["last 2 version", "> 1%", "not dead"]
}
1
2
3
4

# 压缩 CSS 代码文件

由于 CSS 代码在开发中,占比大, 所有导致 CSS 文件大小也就不小,一般会采用压缩 css 的处理

JS 文件不用单独处理,Webpack 会自行处理压缩 JS 代码

# 安装

npm i css-minimizer-webpack-plugin -D
1

"css-minimizer-webpack-plugin" 是一个用于 Webpack 的插件,它的作用是在 Webpack 构建过程中对 CSS 进行压缩和优化

# 配置 webpack.config.js

//... 其他配置
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  entry: "./src/main.js",
  output: {
    //... 其他配置
  },
  module: {
    rules: [
      //... 其他配置
    ],
  },
  plugins: [
    //... 其他配置
    // css压缩
    new CssMinimizerPlugin(),
  ],
  mode: "production",
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 引入 Eslint 语法检测

Eslint 用来检测 js 和 jsx 语法的工具,可以配置各项功能,我们使用 Eslint,关键是写 Eslint 配置文件,里面写上各种 rules 规则,将来运行 Eslint 时就会以写的规则对代码进行检查

# 安装 Eslint 插件

npm i eslint-webpack-plugin eslint -D
1
  • eslint-webpack-plugin:是一个 webpack 插件,将 ESLint 集成到 webpack 构建流程中,使得在构建时可以执行代码检查。
  • eslint:是一个流行的 JavaScript 代码质量和代码风格检查工具,它可以根据自定义的规则来检查 JavaScript 代码中的问题, 用于静态代码分析。

# 创建.eslintrc.js并配置

打包构建是 ESLint 会查找和自动读取该配置文件,并对项目代码进行检测

// .eslintrc.js
module.exports = {
  // 解析选项
  parserOptions: {
    ecmaVersion: 6, // ES 语法版本
    sourceType: "module", // ES 模块化
    ecmaFeatures: {
      // ES 其他特性
      jsx: true, // 如果是 React 项目,就需要开启 jsx 语法
    },
  },
  // 环境配置
  env: {
    node: true, // 启用node中全局变量
    browser: true, // 启用浏览器中全局变量
  },
  // 继承其他规则--继承 Eslint 规则
  extends: ["eslint:recommended"],
  // ...
  // 其他规则详见:https://eslint.bootcss.com/docs/user-guide/configuring
  // 具体检查规则
  rules: {
    // "no-var": "error", // 不能使用 var 定义变量
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 创建 .eslintignore 忽略文件

  • 设置需要忽略 Eslint 检查的文件个文件夹

.eslintignore

# .eslintignore
# 忽略依赖文件
node_modules
# 忽略dist目录下所有文件
dist
# 忽略build目录下类型为js的文件的语法检查
build/*.js
# 忽略src/assets目录下文件的语法检查
src/assets
# 忽略public目录下文件的语法检查
public


# # 忽略当前目录下为js的文件的语法检查(慎用)
# *.js
# # 忽略当前目录下为vue的文件的语法检查(慎用)
# *.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# webpack.config.js 配置引入 Eslint

const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");

module.exports = {
  //... 其他配置
  plugins: [
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "src"),
    }),
  ],
};
1
2
3
4
5
6
7
8
9
10
11
12

# Eslint 缓存设置(可选)

构建时,可以设置 Eslint 缓存,对于每次构建时的 JS 文件检查文件,未发生变动时就可以使用之前的缓存,可以大幅提升构建速度(例如:node_modules 依赖库文常规开发时就不会去改变)

  • 修改ESLintWebpackPlugin
new ESLintWebpackPlugin({
  // 指定检查文件的根目录
  context: path.resolve(__dirname, "../src"),
  exclude: "node_modules", // 默认值
  cache: true, // 开启缓存
  // 缓存目录
  cacheLocation: path.resolve(
    __dirname,
    "../node_modules/.cache/.eslintcache"
  ),
}),
1
2
3
4
5
6
7
8
9
10
11

# 引入 Babel 处理 JS 代码兼容性

Babel 是 JavaScript 语言语法的编译器: 主要用于将 ES6 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中

npm i babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime -D
1
  • babel-loader:用于在 webpack 构建过程中将 JavaScript 文件从 ES6+转换为 ES5,以便在旧版浏览器中运行。
  • @babel/core:这是 Babel 编译器的核心包,提供转译 JavaScript 代码的功能。
  • @babel/preset-env:这是 Babel 的一个预设,用于将 ES6+代码转换为向后兼容的 JavaScript 版本,以便在当前和旧版浏览器中运行。
  • @babel/plugin-transform-runtime: Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下会被添加到每一个需要它的文件中。这个依赖可以将这些辅助代码作为一个独立模块,来避免重复引入。

# 根更目录创建 babel.config.js

// babel.config.js
module.exports = {
  // 预设
  presets: ["@babel/preset-env"],
  // 插件
  plugins: ["@babel/plugin-transform-runtime"], // 统一引入babel辅助代码-减少代码体积
};
1
2
3
4
5
6
7

# webpack.config.js 配置

追加对 JS 文件处理的 loader

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/, // 排除node_modules代码不编译
        loader: "babel-loader",
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11

# babel 缓存设置(可选)

构建时,可以设置 babel 缓存,对于每次构建时的 JS 文件,未发生变动时就可以使用之前的缓存,可以大幅提升构建速度(例如:node_modules 依赖库文常规开发时就不会去改变)

  • 修改 babel-loader
/** JS 文件的 babel 处理代码语法兼容性 */
{
  test: /\.js$/,
  exclude: /node_modules/, // 排除node_modules代码不编译
  use: [
    {
      loader: "babel-loader",
      options: {
        cacheDirectory: true, // 开启babel编译缓存
        cacheCompression: false, // 缓存文件不要压缩
      },
    },
  ],
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 引入 CoreJS 处理 JS 高版本语法

上面我们使用 babel 对 js 代码进行了兼容性处理,其中使用 @babel/preset-env 智能预设来处理兼容性问题。

它能将 ES6 的一些语法进行编译转换,比如箭头函数、扩展运算符等。

但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,它没办法处理

所以此时我们 js 代码仍然存在兼容性问题,一旦遇到低版本浏览器会直接报错。

所以我们想要将 js 兼容性问题彻底解决 core-js 是专门用来做 ES6 以及以上 API 的 polyfill(polyfill 翻译过来叫做垫片/补丁)。

就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性。

# 安装依赖

  • @babel/eslint-parser 是一个 npm 包,它是 Babel 和 ESLint 的一个插件,允许 ESLint 理解 Babel 编译的代码。
npm i @babel/eslint-parser -D
1
  • core-js:这是要安装的包的名称,core-js 是一个常用的 JavaScript 库,提供了对旧浏览器的兼容性支持,实现了一些新的 ECMAScript 特性。
npm i core-js
1

# 配置 babel.config.js 按需引入对应的语法补丁

module.exports = {
  // ...其他配置
  // 智能预设:能够编译ES6语法
  presets: [
    [
      "@babel/preset-env",
      // 按需加载core-js的polyfill
      { useBuiltIns: "usage", corejs: { version: "3", proposals: true } },
    ],
  ],
};
1
2
3
4
5
6
7
8
9
10
11
// 这是@babel/preset-env 预设的配置对象
{ useBuiltIns: "usage", corejs: { version: "3", proposals: true } }

// 这个选项告诉 Babel 按需加载 core-js 的 polyfill,而不是全部加载。
// 这样,只有当代码中实际使用了某个特性时,Babel 才会引入相应的 polyfill。
useBuiltIns: "usage";

// 这个对象进一步配置了 core-js 的行为。
// version: "3"指定了使用 core-js 的版本 3,
// proposals: true 表示启用对 Stage 3 ECMAScript 提案的 polyfill。
corejs: { version: "3", proposals: true }
1
2
3
4
5
6
7
8
9
10
11

# 配置 .eslintrc.js 解释器


 




 




module.exports = {
  parser: "@babel/eslint-parser", // 支持最新的最终 ECMAScript 标准
  // ...其他配置
  // 环境配置
  env: {
    node: true, // 启用node中全局变量
    browser: true, // 启用浏览器中全局变量
    es6: true, // 启用除模块之外的所有 ECMAScript 6 功能
  },
};
1
2
3
4
5
6
7
8
9
10

必须开启 es6: true, 否则报错(注意这个报错是 Eslint 导致的语法检测错误,并非 babel 不能编译和兼容 Promise)

ERROR in [eslint]
X\test\webpack_practice\testDemo4\src\main.js
  61:17  error  'Promise' is not defined  no-undef

✖ 1 problem (1 error, 0 warnings)
1
2
3
4
5

关于 开启 es6: true 的原因,查看eslint-指定解析器选项 (opens new window)

# 高阶构建实践

# 注入环境变量

一般来说的话。项目总是分为 开发环境, 测试环境, 线上环境

避免过多的环境导致开发效率,本文案列先采用 开发环境 + 线上环境 即可

三个方式注入环境变量:

# cross-env命令行配置构建时的环境变量

主要是 webpack 构建时,指定构建的环境变量,开发和生产环境会导致构建编译后的文件代码不同,生产环境的代码有压缩,Size 更小

  • 安装依赖
npm i cross-env -D
1

cross-env 通过命令行来设置 Node 环境变量

  • 配置package.json
{
  "scripts": {
    "dev": "npx webpack serve",
    "build:dev": "cross-env NODE_ENV=development npx webpack",
    "build:prod": "cross-env NODE_ENV=production npx webpack"
  }
}
1
2
3
4
5
6
7

这样就能手动的在运行命令时,先设定环境变量了,代码中通过 process.env.NODE_ENV 访问

  • 配置webpack.config.js
const isProduction = process.env.NODE_ENV === "production";

module.exports = {
  // ...其他配置

  /** mode: 环境模式由package.json脚本命令手动控制 */
  mode: isProduction ? "production" : "development",

  /** 源码映射--SourceMap */
  devtool: isProduction ? "source-map" : "eval-cheap-module-source-map",
};
1
2
3
4
5
6
7
8
9
10
11

# dotenv&dotenv-webpack配置环境变量.env 文件

命令行操作环境变量不优雅,且设置多个环境变量时会很繁琐

除了命令的环境变量,有些时候我们需要在不同的环境下设置一些变量,比如接口域名,运行端口等

但是这里也有一个问题

由于cross-env、dotenv、dotenv-webpack无法在命令行注入文件(只能单个单个变量声明就不优雅)

出于需要,我想在webpack.config.js项目文件(src中)中使用环境变量时,

就要同时引入dotenv、dotenv-webpack两个依赖库,分别处理这个问题

dotenv注入变量后用于供webpack.config.js使用

dotenv-webpack注入变量后用于供项目文件中使用

目前没有找到一个方案,一次注入.env.XXX环境文件,webpack.config.js项目文件都能访问,若有同志知道望告知

  • 安装依赖
npm i dotenv dotenv-webpack -D
1

dotenv 是一种将环境变量从 .env 文件加载到环境中的零依赖模块。

  • 根目录创建 .env.development.env.production
#.env.development
# 开发环境配置
ENV="development"
# 网络请求路径
VUE_APP_BASE_URL="/dev_api"
1
2
3
4
5
# 开发环境配置
ENV="development"
# 网络请求路径
VUE_APP_BASE_URL="/prod_api"
1
2
3
4
  • 配置 webpack.config.js 引入环境变量文件

拓展:既然有 .env 文件,NODE_ENV 变量为何不放在.env 文件中?

1.ENV 不能改成 NODE_ENV 使用,否者和 Cross-env 命令行设置的 NODE_ENV 冲突,所以所有 .env 文件 ENV 仅作为认知标识,不作为使用变量

2.本教程采用的时 一个 webapack.config.js 走天下的策略,关于生产环境和开发环境中不同的配置堵在改文件中判断后动态配置,(若你就想将 NODE_ENV 放在 .env 文件中,也是有办法的,仅提供思路:拆分你的 webapack.config.js ,分为 webapack.develop.js 和 webapack.prod.js,在 package.json 的 script 命令行配置运行对应的 webpack.XXXX.js 文件即可)

/** node模块 文件路径path */
const path = require("path");
/** 环境变量申明 */
const Dotenv = require("dotenv-webpack");
// ...其他配置

/** 环境判断 */
const envMode = process.env.NODE_ENV;
/**
 * 注入环境变量--仅供webpack.config.js使用,项目文件中无法使用
 *  */
require("dotenv").config({ path: `.env.${envMode}` });


/** 配置项 */
module.exports = {
  // ...其他配置

  /** 插件 */
  plugins: [
    /** 环境变量配置---供项目文件中使用, 该文件webpack.config.js无法使用 */
    new Dotenv({
      path: path.resolve(__dirname, `.env.${envMode}`), // 指定引入的.env文件的路径
    }),

    // ...其他配置
  ],

  // ...其他配置
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# webpack.DefinePlugin (可选)

DefinePlugin 是webpack默认插件

本教程未采用该方案,仅作为了解

和 .env.xxx 引入 作用其实类似,不过是 在 webpack.config.js 中就能配置,且无需安装其他依赖,有 webpck 自带的 *DefinePlugin*

然后在项目代码任何地方中以 process.env.XXXX 访问即可

/** 项目全局变量设置 */
const { DefinePlugin } = require("webpack");
// ...其他配置

/** 配置项 */
module.exports = {
  // ...其他配置

  /** 插件 */
  plugins: [
    /** 全局变量设置 */
    new DefinePlugin({
      GROBAL_VARIABLE_ONE: "true",
      GROBAL_VARIABLE_TWO: "false",
    }),

    // ...其他配置
  ],

  // ...其他配置
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 源码映射 SourceMap 配置

SourceMap(源代码映射)是一个用来生成源代码与构建后代码一一映射的文件的方案。

它会生成一个 xxx.map 文件,里面包含源代码和构建后代码每一行、每一列的映射关系。当构建后代码出错了,会通过 xxx.map 文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源。

module.exports = {
  // ... 其他配置

  /** 源码映射--SourceMap */
  devtool: "source-map",
};
1
2
3
4
5
6

使用打包命令 npm run build 之后,观察dist 文件夹,会发现每个代码文件(JS 和 CSS)多对应有一个 .map 文件

# 构建时剔除注释和 Console

webpack 构建时默认剔除了 注释代码

现在处理剔除 Console

# 安装依赖

npm i terser-webpack-plugin -D
1

# 配置 webpack.config.js

/** 代码处理--剔除冗余代码 */
const TerserPlugin = require("terser-webpack-plugin");
// ...其他配置

/** 配置项 */
module.exports = {

  // ...其他配置

  /** 优化项配置 */
  optimization: [
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            /** true 表示剔除所有 console.*; 亦可以使用: ['log','info'] 表示仅仅剔除 log和info */
            drop_console: ['log','info'],
          },
        },
      }),
    ],

    // ...其他配置
  ]

  // ...其他配置
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 可视化依赖分析

# 安装依赖

npm i webpack-bundle-analyzer -D
1

# 配置 webpack.config.js

const BundleAnalyzerPlugin =
  require("webpack-bundle-analyzer").BundleAnalyzerPlugin;

module.exports = {
  entry: "./src/main.js",
  output: {
    // ...其他配置
  },
  module: {
    rules: [
      // ...其他配置
    ],
  },
  plugins: [
    // ...其他配置
    new BundleAnalyzerPlugin({
      analyzerMode: "server",
      analyzerHost: "127.0.0.1",
      analyzerPort: "auto", // 自动使用未使用的端口 或者 '9888'自行设置
      openAnalyzer: true,
      generateStatsFile: false,
      statsOptions: null,
      logLevel: "info",
    }),
  ],
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 效果

之后运行代码都会自动弹出一个本机域名:随机端口的页面展示可视化的依赖图谱

# import 动态导入

很重要的内容,让代码在项目中支持,调用时才使用网络请求引入,提高页面性能 比如路由懒加载,和组件的动态引入

webpack 还提供了在代码项目文件中手动指明要动态导入文件的方式来,拆分该动态导入文件(以实现代码分离,代码分离 == 分割代码

  • webpack 提供了两个类似的技术实现动态代码分离。
  • 第一种,也是推荐选择的方式,是使用符合 ECMAScript 提案 的 import() 语法 实现动态导入。
  • 第二种则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure。 webpack--动态导入 (opens new window)

此处仅讲解第一种

# 使用

通过 import() 函数的方式来在代码的某个位置引入,示例:

// app.vue

onMounted(() => {
  // // dev-log >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  console.log(`[Dev_Log][${"onMounted生命周期"}_]_>>>`);

  readEnvArguments();

  /** 手动指明 文件动态引入 */
  document.getElementById("dynamicEl").addEventListener("click", () => {
    // webpackChunkName: "dynamic":这是webpack动态导入模块命名的方式

    // "dynamic"将来就会作为[name]的值显示。
    import(/* webpackChunkName: "bundle_dynamic" */ "./utils/dynamic.js")
      .then((module) => {
        // // dev-log >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        console.log(`[Dev_Log][${"按需加载模块加载成功"}_]_>>>`);
        console.log(module);
        console.log(module.dynamicFn(2, 1));
      })
      .catch((err) => {
        console.log(`[Dev_Log][${"按需加载模块加载成功"}_]_>>>`, err);
      });
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  • 注意: 直接使用这样使用, eslint 会对动态导入语法报错,需要修改 eslint 配置文件(plugins: ["import"], // 解决动态导入 import 语法报错问题),
  • 配置 eslint-plugin-import解决

# 配置eslint-plugin-import

  • 安装
npm i eslint-plugin-import -D
1

安装时可能会报错,说 Could not resolve dependency:npm ERR! peer eslint@"^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" from eslint-plugin-import@2.29.1,, 其实就是 eslint-plugin-import版本 和 eslint 版本不兼容 可参考一下版本

npm i eslint@8.57.0 eslint-plugin-import@2.27.5 -D
1
  • 配置 .eslintrc.js or eslint.config.js
// .eslintrc.js
module.exports = {
  // 解析选项
  parserOptions: {
    // ecmaVersion: 6, // ES 语法版本
    ecmaVersion: 2020, // 或更高版本
    sourceType: "module", // ES 模块化
    allowImportExportEverywhere: true, // 不限制eslint对import使用位置
    // ecmaFeatures: {
    //   // ES 其他特性
    //   jsx: true, // 如果是 React 项目,就需要开启 jsx 语法
    // },
  },

  // ... 其他配置
  plugins: ["import"],
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 关于parserOptions配置 ecmaVersion: 2020 的解释

若使用

ecmaVersion: 6, // ES 语法版本
1

可能会报错

error: 'import' and 'export' may only appear at thetop level
1

根据文章 ESLint howto fix parsing error: 'import' and 'export' may only appear at the top level (opens new window) 指示

使用

ecmaVersion: 2020, // ES 语法版本
1

就不会报错了,原因如下

This release adds support for ES2020 syntax, which includes support for Dynamic Imports and BigInt. This can be enabled using ecmaVersion: 2020 in your configuration file.

# 效果

页面只有执行到对应的动态导入代码文件时,才会发出网络请求加载,这里使用的一个绑定事件来模拟,在document.getElementById("dynamicEl"), 点击按钮后,观察网络请求,会有一个bundle_dynamic.js 加载

# CodeSplit 代码分割处理

代码分割(Code Split)主要做了:

分割文件:将打包生成的文件进行分割,生成多个 js 文件

防止重复: 如果多入口文件中都引用了同一份代码,我们不希望这份代码被打包到两个文件中,导致代码重复体积更大

按需加载:需要哪个文件就加载哪个文件。(有点抽象:主要是体现在网络请求上,比如:大概是点击按钮,通过 script 标签加载需要的 JS 文件,按需的 JS 文件应该会被处理成 script 标签以便引入,以上均为猜测)

# 使用

通过内置的 optimization.splitChunks 属性进行配置

SplitChunks: 简单的来说就是 Webpack 中一个提取或分离代码的插件,主要作用是提取公共代码,防止代码被重复打包,拆分过大的 js 文件,合并零散的 js 文件,就是 CodeSplit 的具体配置和实现方式

先了解几个概念 - module、bundle、chunk 都是什么?

module:模块,在 webpack 中任何文件都可以作为一个模块,借用官网的图片,左侧的这些类型文件,都可以认为是一个模块,只是需要配置不同的 loader,将文件转换成 webpack 可以支持打包的文件。 chunk:编译完成准备输出时,webpack 将 module 按特定规则组成的一个个 chunk bundle:webpack 处理好 chunk 文件后,生成运行在浏览器中的代码

# 官方默认配置

module.exports = {
  //...
  optimization: {
    splitChunks: {
      // [initial(初始块)、async(按需加载块)、all(全部块)], 默认只对异步模块分割
      chunks: "async",

      // 公共配置属性--以下是默认值
      minSize: 20000, // 分割代码最小的大小
      minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
      minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
      maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
      maxInitialRequests: 30, // 入口js文件最大并行请求数量
      enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)

      // 单独组,哪些模块要打包到一个组 , 以上的属性都会被单独的 cacheGroups 属性设置继承,但是优先级最低
      cacheGroups: {
        // 组名--chunk名
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
          priority: -10, // 权重(越大越高)
          reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
        },
        default: {
          minChunks: 2, // 这里的minChunks权重更大
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 极简版

  • 可以直接使用,其他配置就是默认的,想定制看下面 默认配置注解版
module.exports = {
  // ...其他配置
  optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: "all", // 对所有模块都进行分割、[initial(初始块)、async(按需加载块)、all(全部块)]
    },
  },
};
1
2
3
4
5
6
7
8
9

# 为何 cacheGroups 缓存组处理

我们自己的其他非异步加载的代码和 node_modules 中三方包的代码仍然混合在一起了,这样显然不利于浏览器缓存,因为业务代码改动是会很频繁的,

但是诸多第三方代码的改动是很少的,所以我们需要进一步将业务代码和 node_modules 代码拆分出来

其他比如:UI 库、依赖库等不常变化的代码都可以使用 cacheGroups 配置成单独的文件,这样这些依赖对应的 JS 文件 名称(主要是哈希值)就不行变动,

部署到线上时,客户端就能尽量使用客户端的缓存 JS 文件,无需重复请求

# 基础应用实践

# Vue-router 路由

# 安装

npm create vue@latest
1

# 配置

  • 根目录创建router/index.js
import { createMemoryHistory, createRouter } from "vue-router";
/** createWebHashHistory Hash 模式 */
/** createMemoryHistory Memory 模式 */
/** createWebHistory HTML5 模式 */

import HomeView from "@/views/layout/home.vue";
import AboutView from "@/views/layout/about.vue";

const routes = [
  { path: "/", redirect: "/home" },
  { path: "/home", component: HomeView },
  { path: "/about", component: AboutView },
];

const router = createRouter({
  history: createMemoryHistory(),
  routes,
});

export default router;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • 根目录创建views/layout/index.vue
<!-- views/layout/index.vue  -->
<template>
  <div class="app-layout__wrapper">
    <div class="nav__wrapper">
      <routerLink class="nav_btn" to="/home">HOME</routerLink>
      <routerLink class="nav_btn" to="/about">ABOUT</routerLink>
    </div>

    <routerView />
  </div>
</template>

<script setup lang="js"></script>

<style lang="scss" scoped>
  .app-layout__wrapper {
    .nav__wrapper {
      :deep {
        .nav_btn {
          color: #000;
          margin-right: 20px;
        }
      }
    }
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  • 根目录创建views/layout/home.vue
<template>
  <div class="container__wrapper">
    <h2>vue3-page-template</h2>
    <h2 class="text">test-text</h2>
    <h2 class="my-color">test-my-color-text</h2>
    <button id="dynamicEl">test-dynamic-import-file</button>
  </div>
</template>

<script setup lang="js">
  import { ref, computed, onMounted } from 'vue';
  import { readEnvArguments } from "@/utils/tools.js";

  onMounted(()=>{
    // // dev-log >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    console.log(`[Dev_Log][${'onMounted生命周期'}_]_>>>`)

    readEnvArguments();

    /** 手动指明 文件动态引入 */
    document.getElementById("dynamicEl").addEventListener("click", () => {
        // webpackChunkName: "dynamic":这是webpack动态导入模块命名的方式

        // "dynamic"将来就会作为[name]的值显示。
        import(/* webpackChunkName: "bundle_dynamic" */ "@/utils/dynamic.js")
          .then((module) => {
            // // dev-log >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
            console.log(`[Dev_Log][${"按需加载模块加载成功"}_]_>>>`);
            console.log(module);
            console.log(module.dynamicFn(2, 1));
          })
          .catch((err) => {
            console.log(`[Dev_Log][${"按需加载模块加载成功"}_]_>>>`, err);
          });
      });
  })
</script>

<style lang="scss" scoped>
  .container__wrapper {
    color: #999;
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  • 根目录创建views/layout/about.vue
<template>
  <div class="container__wrapper">about</div>
</template>
1
2
3
  • 修改app.vue
<template>
  <Layout />
</template>

<script setup lang="js">
  import Layout from '@/views/layout/index.vue'
</script>
1
2
3
4
5
6
7
  • 修改main.js
/* 引入Vue框架 */
import { createApp } from "vue/dist/vue.esm-bundler";
/** Vue入口文件 */
import App from "./app.vue";
/** 路由页面 */
import router from "./router/index.js";

/* 引入 CSS 资源,Webpack才会对其打包 */
import "@/assets/style/index.css";
import "@/assets/style/index.scss";

/** 引入测试函数 */
import "@/utils/testCode.js";

/**
 * 创建Vue实例
 * 使用vue-router
 * 并挂载(mount)到id为app的DOM元素上*/
createApp(App).use(router).mount("#app");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 效果

OK, 这样就完成了 Vue-router 的构建,使用npm run dev, 看看效果

# 解决页面 404 警告

虽然初次能进入,但是刷新页面后显示Cannot GET /XXXX

且控制台报错 ❌ GET http://localhost:3000/XXX 404 (Not Found),

以及警告 ⚠Feature flags __VUE_OPTIONS_API__, __VUE_PROD_DEVTOOLS__, __VUE_PROD_HYDRATION_MISMATCH_DETAILS__ are not explicitly defined.

解决方案:

  • 配置 webpack.config.js
/** 项目全局变量设置 */
const { DefinePlugin } = require("webpack");

module.exports = {
    // ... 其他配置

    plugins:[
    	// ... 其他配置

        // 解决警告
        new DefinePlugin({
            __VUE_OPTIONS_API__: JSON.stringify(true),
            __VUE_PROD_DEVTOOLS__: JSON.stringify(false),
            __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: JSON.stringify(true)
        })
    ]

	devServer: {
     // ... 其他配置

     // history模式下的url会请求到服务器端,但是服务器端并没有这一个资源文件,就会返回404,所以需要配置这一项
     historyApiFallback: true, // 解决前端路由刷新404问题
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# Pinia 全局状态管理

# 安装依赖

npm i pinia
1

# 创建src/store文件夹

  • 创建src/store/app.js
import { defineStore } from "pinia";

// useAppStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useAppStore = defineStore("app", {
  state: () => ({
    counter: 0,
  }),
  getters: {
    doubleCount: (state) => state.counter * 2,
  },
  actions: {
    setCounterPlus() {
      this.counter += 1;
    },
  },
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 配置 main.js

  • 修改main.js--挂载 pinia
/* 引入Vue框架 */
import { createApp } from "vue/dist/vue.esm-bundler";
/** Vue入口文件 */
import App from "./app.vue";
/** 路由页面 */
import router from "./router/index.js";
/** pinia状态管理 */
import { createPinia } from "pinia";

/* 引入 CSS 资源,Webpack才会对其打包 */
import "@/assets/style/index.css";
import "@/assets/style/index.scss";

/** 引入测试函数 */
import "@/utils/testCode.js";

/**
 * 创建Vue实例
 * 使用vue-router
 * 并挂载(mount)到id为app的DOM元素上*/
const pinia = createPinia();
const app = createApp(App);
app.use(router).use(pinia).mount("#app");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  • 修改src/views/layout/about.vue
<template>
  <div class="container__wrapper">
    <h2>about</h2>
    <h3 style="user-select: none;">
      <span> store数据counter: {{ appStore.counter}}</span>
      <span style="cursor: pointer;" @click="handlePlus">(➕)</span>
    </h3>
  </div>
</template>

<script setup lang="js">
  import { useAppStore } from '@/stores/app.js'

  const appStore = useAppStore()

  const handlePlus = ()=> {
    appStore.setCounterPlus();
  }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 效果

在 about 页面点击 👍,看数字变化,切换路由后重新进入 about,查看数据是否保持原样

# Sass 样式预处理

参考上方 CSS 处理

直接在.vue文件中使用以下即可,或者单独创建SCSS文件并引入

<style lang="scss" scoped></style>
1

# ElementPlus:UI 组件

# 安装

npm i element-plus
1

# 配置按需引入

  • 安装
npm install unplugin-vue-components unplugin-auto-import -D
1
  • 修改src/main.js,引入 element 样式文件
/** 引入element-plus样式 */
import "element-plus/dist/index.css";
1
2
  • 修改webpack.config.js,按需引入组件
// webpack.config.js
const AutoImport = require("unplugin-auto-import/webpack").default;
const Components = require("unplugin-vue-components/webpack").default;
const { ElementPlusResolver } = require("unplugin-vue-components/resolvers");

module.exports = {
  // ...
  plugins: [
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

关于引入的时候加了 .default 后缀,请参考 unplugin-vue-components (opens new window)unplugin-auto-import (opens new window)

然后直接在页面使用对应的 element-plus 组件即可

# 配置自定义主题文件

项目开发有时需要自行定义各个组件的主题色

有几种方式

  • (一)通过 CSS 重新在:root{}定义覆盖 Elementplus 基础样式变量
  • (二)利用 Scss 文件重新定义 Elementplus 的样式变量

只讲第二种

  • 新建assets/style/element/theme.scss
// styles/element/index.scss
/* 只需要重写你需要的即可 */
@forward "element-plus/theme-chalk/src/common/var.scss" with (
  $colors: (
    "primary": (
      "base": #00a4e4,
    ),
    "success": (
      "base": #c1d82f,
    ),
    "warning": (
      "base": #ffdd00,
    ),
    "danger": (
      "base": #fe5000,
    ),
    "error": (
      "base": #ff0000,
    ),
    "info": (
      "base": #909399,
    ),
  )
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  • 修改webpack.config.js
// ...其他配置
module.exports = {
  // ...其他配置

  module: {
    // ...其他配置

    rules: [
      // ...其他配置

      /** 处理 SCSS 文件 */
      {
        test: /\.s[ac]ss$/,
        use: setStyleLoaders([
          {
            loader: "sass-loader",
            /** elemenplus-自定义CSS主题配置 */
            options: {
              additionalData: `@use "@/assets/style/element/theme.scss" as *;`,
            },
          },
        ]),
      },
    ],
  },

  plugins: [
    // ...其他配置

    Components({
      resolvers: [
        ElementPlusResolver({
          importStyle: "sass",
        }),
      ],
    }),
  ],
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# Axios 网络请求

# 安装

npm i axios
1

由于 axios 实例,需要进行页面提示,可以把 element-plus 库也安装了先

# 配置全局统一的请求器

  • 新建文件src/utils/request/index.js
import axios from "axios";

import { ElMessage } from "element-plus";
import "element-plus/theme-chalk/el-message.css";
// 由于该文件是JS文件,属于手动导入,需要手动对应样式 (按需引入只处理Vue文件)

// 创建axios实例
const request = axios.create({
  // 读取环境文件的网络请求路径变量-设置根路径
  baseURL: process.env.VUE_APP_BASE_URL,
  // 设置超时时间
  timeout: 5000,
});

// 请求拦截器
request.interceptors.request.use(
  // 请求头
  (config) => {
    // token配置
    config.headers["Authorization"] = "Bearer " + "test_use_token";
    return config;
  },
  // 请求失败
  (error) => {
    return Promise.reject(new Error(error));
  }
);

// 响应拦截器
request.interceptors.response.use(
  // 响应成功
  (response) => {
    const res = response.data;

    if (res.code !== 0) {
      ElMessage.error(res.message || res || "请求成功,接口响应错误!");
    } else {
      return res;
    }
  },

  // 响应失败
  (error) => {
    //网络超时异常处理
    if (error.code === "ECONNABORTED" || error.message.includes("timeout")) {
      ElMessage.error("请求超时,请稍后重试");
    }
    // 网路无连接处理
    else if (error.message === "Network Error") {
      ElMessage.error("无网络,请检查网络链接");
    }
    // 状态码系列
    else {
      let status_code = error.response.status;
      let err_message = error.response.data.message;

      if (status_code.toString().split("")[0] == 4) {
        switch (status_code) {
          case 401:
            ElMessage.error(
              `[ 身份权限错误 [ 状态码:${status_code}] ] ` + err_message
            );
            break;
          case 403:
            ElMessage.error(
              `[ 路由权限错误 [ 状态码:${status_code}] ] ` + err_message
            );
            break;
          case 404:
            ElMessage.error(
              `[ 访问路由不存在 [ 状态码:${status_code}] ] ` + err_message
            );
            break;
        }
      } else if (status_code.toString().split("")[0] == 5) {
        ElMessage.error(
          `[ 服务端错误 [ 状态码:${status_code}] ] ` + (error.message || error)
        );
      } else {
        ElMessage.error(error.message || error || "网络请求报错,未知错误!");
      }
    }
  }
);

/** 自动处理 get-params/post-data 传参字段不一致 */
export default ({ method, url, data }) => {
  return request({
    method,
    url,
    // 若 method为post 则用 data,否则用param
    [method.toLowerCase() === "get" ? "params" : "data"]: data,
  });
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

# 开发环境:接口代理服务 Proxy

本地开发时,请求后端接口时,由于浏览器的同源策略可能会产生跨域问题,所以 webpack 内置了一个本地服务器来代理请求,配置devServer.proxy使用即可 注意:接口代理仅用与开发模式,不建议在生产模式使用

  • 修改webpack.config.js,追加 proxy
// ... 其他配置

module.exports = {
  // ... 其他配置

  devServer: {
    // ... 其他配置

    proxy: [
      {
        context: [process.env.VUE_APP_BASE_URL],
        target: process.env.VUE_APP_API_URL, // 真正请求的接口地址
        // 重写目标路径
        pathRewrite: {
          ["^" + process.env.VUE_APP_BASE_URL]: "",
        },
        // 默认情况下,changeOrigin 设置为 false,这意味着代理请求中的 Host 头将保持与原始请求相同。如果你将其设置为 true,代理服务器会将 Host 头更改为代理目标服务器的主机名
        changeOrigin: true,
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • 修改.env.development
# 开发环境配置
ENV="development"
# 网络请求路径
VUE_APP_BASE_URL="https://loaclhost:9000/dev_api"
VUE_APP_API_URL="https://192.168.11.200:9000/dev_api" # 开发环境请求的后端接口地址(一般是内网环境提供)
1
2
3
4
5
  • 修改.env.production
# 开发环境配置
ENV="development"
# 网络请求路径
VUE_APP_BASE_URL="https://online.com/prod_api"
1
2
3
4

# 其他工具依赖库

# 附录

# vue3 + webpack5 脚手架项目文件目录

- node_modules #依赖文件夹
- dist #构建后的资源文件夹
- public #公共文件夹
  - favicon.ico
  - index.html #html入口
- src
  - api #api接口文件夹
  - assets #资源文件夹
  - router #路由文件夹
  - stores #全局状态文件夹
  - utils #工具类文件夹
  - views #页面资源文件夹
  - app.vue #app.vue Vue实例入口文件
  - main.js #项目入口文件
- .env.development #开发环境变量
- .env.production #生产环境变量
- .eslintignore #eslint检查忽略
- .eslintrc.js #eslint检查文件
- babel.config.js #babeljs
- package-lock.json
- package.json #项目信息&依赖管理
- webpack.config.js #webpack配置文件
- readme.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 参考

John Rivers__(万字) webpack 搭 Vue 项目 (opens new window)

【尚硅谷Webpack5入门到原理(面试开发一条龙)】 https://www.bilibili.com/video/BV14T4y1z7sw/?share_source=copy_web&vd_source=86102b3cd06f9a9d6eb0606b0ebe7a1f