9 Vue 框架生态

构建工具


Webpack

Webpack 特性要点

本质上,webpack 是一个现代 JavaScript 应用程序的_静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个_依赖关系图(dependency graph) ,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle

webpack 用于编译 JavaScript 模块;一旦完成安装,就可以通过 webpack 的 CLIAPI 与其配合交互

Webpack是一个打包模块化javascript的工具,在Webpack里一切文件皆模块,因为 webpack 只识别 js和json 文件,所以需要通过 loader 转换文件,通过 plugin 注入钩子,最后输出由多个模块组合成的文件,Webpack 专注构建模块化项目,Webpack 可以看做是模块打包机;它做的事情是,分析项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss, TypeScript等),并将其打包为合适的格式以供浏览器使用

Webpack 优点
  1. 专注于处理模块化的项目,能做到开箱即用,一步到位
  2. 通过 plugin 扩展,完整好用又不失灵活
  3. 使用场景不局限于 web 开发
  4. 社区庞大活跃,经常引入紧跟时代发展的新特性,能为大多数场景找到已有的开源扩展
  5. 提供了更好的开发体验
Webpack 执行机制

Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程

  1. 初始化参数:从配置文件和Shel1语句中读取与合并参数,得出最终的参数
  2. 开始编译:用上一步得到的参数初始化Compiler对象,加载所有配置的插件,执行对象的run方法开始执行编译
  3. 确定入口:根据配置中的entry找出所有的入口文件
  4. 编译模块:从入口文件出发,调用所有配置的Loader对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
  5. 完成模块编译:在经过第4步使用Loader翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统,在以上过程中, Webpack会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用Webpack提供的API改变Webpack的运行结果
Webpack 热更新原理
1. 基本定义

Webpack的热更新又称热替换(Hot Module Replacement),缩写为HMR。这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。

2. 核心定义
  • HMR 的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff(chunk需要更新的部分),实际上WDS与浏览器之间维护了一个websocket,当本地资源发生变化时,WDS会向浏览器推送更新,并带上构建时的hash,让客户端与上一次资源进行对比
  • 客户端对比出差异后会向WDS发起Ajax请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向WDS发起jsonp请求获取该chunk的增量更新
  • 后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由HotModulePlugin来完成,提供了相关API以供开发者针对自身场景进行处理,像react-hot-loader和vue-loader都是借助这些API实现HMR
Loader 和 Plugin 的不同
1. 不同的作用
  • Loader 直译为"加载器"。Webpack将一切文件视为模块,但是Webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。所以Loader的作用是让Webpack拥有了加载和解析非JavaScript文件的能力。
  • Plugin 直译为"插件", Plugin 可以扩展Webpack的功能,让Webpack具有更多的灵活性。在Webpack运行的生命周期中会广播出许多事件, Plugin可以监听这些事件,在合适的时机通过Webpack提供的API改变输出结果
2. 不同的用法
  • Loader 在module.rules中配置,也就是说他作为模块的解析规则而存在。类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件(test),使用什么加载(1oader)和使用的参数(options)
  • Plugin 在plugins中单独配置。类型为数组,每一项是一个plugin的实例,参数都通过构造函数传入

webpack 核心概念

entry (入口)

**入口起点(entry point)_指示 webpack 应该使用哪个模块,来作为构建其内部_依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的

output (输出)

output 属性告诉 webpack 在哪里输出它所创建的 bundles ,以及如何命名这些文件,默认值为 ./dist。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。你可以通过在配置中指定一个 output 字段,来配置这些处理过程

loader (加载器)

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理

本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块

plugins (插件)

loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务

mode (构建模式)

通过选择 developmentproduction 之中的一个,来设置 mode 参数,启用相应模式下的 webpack 内置的优化

  • development (开发环境)

开发模式顾名思义就是我们开发代码时使用的模式。这个模式下我们主要做两件事:

  1. 编译代码,使浏览器能识别运行
  • 开发时我们有样式资源、字体图标、图片资源、html 资源等,webpack 默认都不能处理这些资源,所以我们要加载配置来编译这些资源
  1. 代码质量检查,树立代码规范
  • 提前检查代码的一些隐患,让代码运行时能更加健壮。
  • 提前检查代码规范和格式,统一团队编码风格,让代码更优雅美观。

不会对打包生成的文件进行代码压缩和性能优化,打包速度快,适合在开发阶段使用

会将 process.env.NODE_ENV 的值设为 development。启用 NameChunksPlugin 和 NameModulesPlugin。特点是能让代码本地调试运行的环境

  • production (生产环境)

生产模式是开发完成代码后,我们需要得到代码将来部署上线。这个模式下我们主要对代码进行优化,让其运行性能更好。优化主要从两个角度出发:

  1. 提升开发体验
  2. 提升打包构建速度
  3. 优化代码体积
  4. 优化代码运行性能

会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin。特点是能让代码优化上线运行的环境

webpack 基本配置

webpack 生态安装
shell 复制代码
npm install webpack webpack-cli -D
webpack 操作
shell 复制代码
npx webpack ./src/main.js --mode=development // 开发模式打包
npx webpack ./src/main.js --mode=production // 生产模式打包
npx webpack // 如有配置文件,按配置文件进行打包操作
// 后续在 package.json script 节点中配置命令可简化命令操作
webpack 配置文件
javascript 复制代码
// Node.js的核心模块,专门用来处理文件路径
const path = require("path");

module.exports = {
  // 入口
  // 相对路径和绝对路径都行
  entry: "./src/main.js",
  // 输出
  output: {
    // path: 文件输出目录,必须是绝对路径
    // path.resolve()方法返回一个绝对路径
    // __dirname 当前文件的文件夹绝对路径
    path: path.resolve(__dirname, "dist"),
    filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
    clean: true, // 自动将上次打包目录资源清空(开发服务器下没有输出文件,生成环境则不需要)
  },
  // 加载器
  module: {
    rules: [],
  },
  // 插件
  plugins: [],
  // 模式
  mode: "development", // 开发模式
};

webpack 开发环境配置

处理样式文件
  1. 下载包
shell 复制代码
npm i css-loader style-loader less-loader less sass-loader sass stylus-loader -D
  1. 功能介绍
    • css-loader:负责将 Css 文件编译成 Webpack 能识别的模块
    • style-loader:会动态创建一个 Style 标签,里面放置 Webpack 中 Css 模块内容
    • less-loader:负责将 Less 文件编译成 Css 文件
    • sass-loader:负责将 Sass 文件编译成 css 文件
    • sasssass-loader 依赖 sass 进行编译
    • stylus-loader:负责将 Styl 文件编译成 Css 文件
  2. 配置
javascript 复制代码
module: {
  rules: [
    {
      // 用来匹配 .css 结尾的文件
      test: /\.css$/,
      // use 数组里面 Loader 执行顺序是从右到左
      use: ["style-loader", "css-loader"],
    },
    {
      test: /\.less$/,
      use: ["style-loader", "css-loader", "less-loader"],
    },
    {
      test: /\.s[ac]ss$/,
      use: ["style-loader", "css-loader", "sass-loader"],
    },
    {
      test: /\.styl$/,
      use: ["style-loader", "css-loader", "stylus-loader"],
    },
  ],
}
处理图片文件

过去在 Webpack4 时,我们处理图片资源通过 file-loaderurl-loader 进行处理

现在 Webpack5 已经将两个 Loader 功能内置到 Webpack 里了,我们只需要简单配置即可处理图片资源

  1. 配置
javascript 复制代码
module: {
  rules: [
    {
      test: /\.(png|jpe?g|gif|webp)$/,
      type: "asset",
      parser: {
        dataUrlCondition: {
          maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
          // 优点:减少请求数量
          // 缺点:体积变得更大
        }
      },
      generator: {
        // 将图片文件输出到 static/imgs 目录中
        // 将图片文件命名 [hash:8][ext][query]
        // [hash:8]: hash值取8位
        // [ext]: 使用之前的文件扩展名
        // [query]: 添加之前的query参数
        filename: "static/imgs/[hash:8][ext][query]",
      },
    },
  ],
}
处理jS文件

Webpack 对 js 处理是有限的,只能编译 js 中 ES 模块化语法,不能编译其他语法,导致 js 不能在 IE 等浏览器运行,所以我们希望做一些兼容性处理。

其次开发中,团队对代码格式是有严格要求的,我们不能由肉眼去检测代码格式,需要使用专业的工具来检测。

  • 针对 js 兼容性处理,我们使用 Babel 来完成
  • 针对代码格式,我们使用 Eslint 来完成

我们先完成 Eslint,检测代码格式无误后,在由 Babel 做代码兼容性处理

ESlint
  1. 配置文件

配置文件由很多种写法:

  • .eslintrc.* :新建文件,位于项目根目录
  • .eslintrc
    • .eslintrc.js
  • .eslintrc.json
    • 区别在于配置格式不一样
  • package.jsoneslintConfig:不需要创建文件,在原有文件基础上写

ESLint 会查找和自动读取它们,所以以上配置文件只需要存在一个即可

  1. 下载包
shell 复制代码
npm i eslint-webpack-plugin eslint -D
  1. 功能介绍
    • eslint-webpack-plugin :eslint 插件,主要用于使用 eslint 进行语法检查
    • eslint : eslint-webpack-plugin 依赖 eslint 进行编译
  2. 具体配置
javascript 复制代码
// .eslintrc.js || .eslintignore :eslint 忽略文件
module.exports = {
  // 解析选项
  parserOptions: {
    ecmaVersion: 6, // ES 语法版本
    sourceType: "module", // ES 模块化
    ecmaFeatures: { // ES 其他特性
        jsx: true  // 如果是 React 项目,就需要开启 jsx 语法
    }
  },
    
  // 具体检查规则
  // "off" 或 0 - 关闭规则
  // "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
  // "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
  rules: {},
  
  // 继承其他规则(vue官方,eslint官方)
  extends: ['plugin:vue/recommended', 'eslint:recommended'],
    
  // 启用环境配置
  "env": {
    "browser": true, // 浏览器环境中的全局变量
    "node": true, // Node.js 全局变量和 Node.js 作用域
    "es6": true // 启用除了 modules 以外的所有 ECMAScript 6 特性(该选项会自动设置 ecmaVersion 解析器选项为 6)。
  },
  
  // 使用插件
  "plugins": ["example"],
  // 其他规则详见:https://eslint.bootcss.com/docs/user-guide/configuring
};
  1. 在 webpack 中使用
javascript 复制代码
const ESLintWebpackPlugin = require("eslint-webpack-plugin");

plugins: [
  new ESLintWebpackPlugin({
    // 指定检查文件的根目录
    context: path.resolve(__dirname, "src"),
  }),
],
  1. ESLint 注释规则
shell 复制代码
// eslint-disable-next-line
此行无规则

此处无规则 // eslint-disable-line

/* eslint-disable */
此块都无规则
/* eslint-enable */
Babel
  1. 配置文件

配置文件由很多种写法:

  • babel.config.* :新建文件,位于项目根目录
    • babel.config.js
    • babel.config.json
  • .babelrc.* :新建文件,位于项目根目录
    • .babelrc
    • .babelrc.js
    • .babelrc.json
  • package.jsonbabel:不需要创建文件,在原有文件基础上写

Babel 会查找和自动读取它们,所以以上配置文件只需要存在一个即可

  1. 下载包
shell 复制代码
npm i babel-loader @babel/core @babel/preset-env -D
  1. 功能介绍
    • babel-loader :负责将 js 文件交给 babel 进行处理
    • @babel/core :如果某些代码需要调用 Babel 的 API 进行转码,就要使用@babel/core模块
    • @babel/preset-env :babel官方预设
  2. 具体配置
javascript 复制代码
// babel.config.js
module.exports = {
  // 预设
  presets: [],
  // 插件
  plugins: []
};
  1. 在 webpack 中使用
javascript 复制代码
module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/, // 排除node_modules代码不编译
      loader: "babel-loader",
    }
  ],
}
  1. 预设参数设置
javascript 复制代码
# .browserslistrc || package.json

# .browserslistrc 配置
> 1% # 市场份额大于 1% 的浏览器
last 2 versions # 适配最新的两个版本
not dead # 确保浏览器未被遗弃

# package.json 添加节点
"browserslist": [
  "> 1%",
  "last 2 ve	rsions"
],
处理HTML文件
  1. 下载包
shell 复制代码
npm i html-webpack-plugin -D
  1. 功能介绍
    • html-webpack-plugin:html 插件,主要用于对html文件的处理
  2. 在 webpack 中使用
javascript 复制代码
const HtmlWebpackPlugin = require("html-webpack-plugin");

plugins: [
  new HtmlWebpackPlugin({
    // 以 public/index.html 为模板创建文件
    // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
    template: path.resolve(__dirname, "public/index.html"),
  }),
],
其他资源

type: "asset/resource"type: "asset"的区别:

  1. type: "asset/resource" 相当于file-loader, 将文件转化成 Webpack 能识别的资源,其他不做处理
  2. type: "asset" 相当于url-loader, 将文件转化成 Webpack 能识别的资源,同时小于某个大小的资源会处理成 data URI 形式
  1. 配置
javascript 复制代码
module: {
  rules: [
    {
      test: /\.(ttf|woff2?|map4|map3|avi)$/,
      type: "asset/resource", // 
      generator: {
        filename: "static/media/[hash:8][ext][query]",
      },
    },
  ],
}
开发服务器配置
  1. 下载包
shell 复制代码
npm i webpack-dev-server -D
  1. 功能介绍
    • webpack-dev-server:开发服务器,能够让所有代码都会在内存中编译打包
  2. 在 webpack 中使用
javascript 复制代码
module.exports = {
  // 开发服务器
  devServer: {
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
  }
}
  1. 使用命令
shell 复制代码
npx webpack serve

webpack 生产环境配置

考虑生产环境打包就需要对原有配置文件进行重新梳理,划分两个文件进行管理

文件目录
├─webpack-test (项目根目录)
  ├── node_modules (下载包存放目录)
  ├── src (项目源码目录,除了html其他都在src里面)
  │    └── 略
  ├── public (项目html文件)
  │    └── index.html
  ├── .browserslistrc(babel预设配置文件)
  ├── .eslintrc.js(Eslint配置文件)
  ├── babel.config.js(Babel配置文件)
  ├── webpack.dev.js(开发模式配置文件)
  ├── webpack.prod.js(生产模式配置文件)
  └── package.json (包的依赖管理配置文件)
设置 webpack.dev.js
javascript 复制代码
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/main.js",
  output: {
    path: undefined, // 开发模式没有输出,不需要指定输出目录
    filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
    // clean: true, // 开发模式没有输出,不需要清空输出结果
  },
  module: {
    rules: [
      {
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.styl$/,
        use: ["style-loader", "css-loader", "stylus-loader"],
      },
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
          },
        },
        generator: {
          // 将图片文件输出到 static/imgs 目录中
          // 将图片文件命名 [hash:8][ext][query]
          // [hash:8]: hash值取8位
          // [ext]: 使用之前的文件扩展名
          // [query]: 添加之前的query参数
          filename: "static/imgs/[hash:8][ext][query]",
        },
      },
      {
        test: /\.(ttf|woff2?)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      },
      {
        test: /\.js$/,
        exclude: /node_modules/, // 排除node_modules代码不编译
        loader: "babel-loader",
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "./src"),
    }),
    new HtmlWebpackPlugin({
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "./src/public/index.html"),
    }),
  ],
  // 其他省略
  devServer: {
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
  },
  mode: "development",
};
设置 webpack.prod.js
javascript 复制代码
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "./dist"), // 生产模式需要输出
    filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
    clean: true,
  },
  module: {
    rules: [
      {
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.styl$/,
        use: ["style-loader", "css-loader", "stylus-loader"],
      },
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
          },
        },
        generator: {
          // 将图片文件输出到 static/imgs 目录中
          // 将图片文件命名 [hash:8][ext][query]
          // [hash:8]: hash值取8位
          // [ext]: 使用之前的文件扩展名
          // [query]: 添加之前的query参数
          filename: "static/imgs/[hash:8][ext][query]",
        },
      },
      {
        test: /\.(ttf|woff2?)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      },
      {
        test: /\.js$/,
        exclude: /node_modules/, // 排除node_modules代码不编译
        loader: "babel-loader",
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "./src"),
    }),
    new HtmlWebpackPlugin({
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "./src/public/index.html"),
    }),
  ],
  // devServer: {
  //   host: "localhost", // 启动服务器域名
  //   port: "3000", // 启动服务器端口号
  //   open: true, // 是否自动打开浏览器
  // },
  mode: "production",
};
配置运行命令
json 复制代码
// package.json
{
  // 其他省略
  "scripts": {
    "start": "npm run dev",
    "dev": "npx webpack serve --config ./config/webpack.dev.js",
    "build": "npx webpack --config ./config/webpack.prod.js"
  }
}
HTML 处理

webpack 自带HTML压缩处理

CSS 处理

Css 文件目前被打包到 js 文件中,当 js 文件加载时,会创建一个 style 标签来生成样式,这样对于网站来说,会出现闪屏现象,用户体验不好,我们应该是单独的 Css 文件,通过 link 标签加载性能才好

提取 CSS 为单独文件
  1. 下载包
shell 复制代码
npm i mini-css-extract-plugin -D
  1. 功能介绍
    • mini-css-extract-plugin :为每个包含 CSS 的 JS 文件都创建一个 CSS 文件,支持按需加载
  2. 在 webpack 中使用
javascript 复制代码
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module: {
  rules: [
    use: [MiniCssExtractPlugin.loader, "css-loader"],
  ]
}

plugins: [
  // 提取css成单独文件
  new MiniCssExtractPlugin({
    // 定义输出文件名和目录
    filename: "static/css/main.css",
  }),
]
CSS 兼容性处理
  1. 下载包
shell 复制代码
npm i postcss-loader postcss postcss-preset-env -D
  1. 功能介绍
    • postcss-loader :使用 PostCSS 处理 CSS 的 loader
    • postcss :一种对 CSS 编译的工具,类似 babel 对 JS 的处理
    • postcss-preset-env :为 CSS 代码自动添加上对应浏览器前缀做兼容性处理
  2. 在 webpack 中使用
javascript 复制代码
module: {
  rules: [
    {
      // 用来匹配 .css 结尾的文件
      test: /\.css$/,
      // use 数组里面 Loader 执行顺序是从右到左
      use: [
        MiniCssExtractPlugin.loader,
        "css-loader",
        {
          loader: "postcss-loader",
          options: {
            postcssOptions: {
              plugins: [
                "postcss-preset-env", // 能解决大多数样式兼容性问题
              ],
            },
          },
        },
      ],
    },
  ]
}
  1. 控制兼容性
json 复制代码
# package.json
"browserslist": ["ie >= 8"]

# .browserslistrc
ie >= 8
  1. 合并配置
javascript 复制代码
// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
  return [
    MiniCssExtractPlugin.loader,
    "css-loader",
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            "postcss-preset-env", // 能解决大多数样式兼容性问题
          ],
        },
      },
    },
    preProcessor,
  ].filter(Boolean); // 过滤掉 undefined
};

module: {
  rules: [
    use: getStyleLoaders(), // 默认处理 CSS
    use: getStyleLoaders("less-loader"), // 如需处理 less 传入对应 loader 即可 
  ]
}
CSS 压缩
  1. 下载包
shell 复制代码
npm i css-minimizer-webpack-plugin -D
  1. 功能介绍
    • css-minimizer-webpack-plugin :压缩 CSS 代码
  2. 在 webpack 中使用
javascript 复制代码
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

plugins: [
    // css压缩
  new CssMinimizerPlugin()
]
JavaScript 处理

webpack 自带JS压缩处理

webpack 前端优化

1. 提升开发体验
SourceMap

是什么

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

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

为什么

代码经过 webpack 处理后,css 和 js 合并成了一个文件,并且多了其他代码。此时如果代码运行出错那么提示代码错误位置我们是看不懂的。

一旦将来开发代码文件很多,那么很难去发现错误出现在哪里。所以我们需要更加准确的错误提示,来帮助我们更好的开发代码

怎么用

通过查看Webpack DevTool 文档open in new window可知,SourceMap 的值有很多种情况.

但实际开发时我们只需要关注两种情况即可:

  • 开发模式:cheap-module-source-map
    • 优点:打包编译速度快,只包含行映射
    • 缺点:没有列映射
javascript 复制代码
module.exports = {
  // 其他省略
  mode: "development",
  devtool: "cheap-module-source-map",
};
  • 生产模式:source-map
    • 优点:包含行/列映射
    • 缺点:打包编译速度更慢,在浏览器中会暴露源代码(需谨慎使用
javascript 复制代码
module.exports = {
  // 其他省略
  mode: "production",
  devtool: "source-map",
};
2. 提升打包构建速度
HotModuleReplacement

是什么

HotModuleReplacement(HMR/热模块替换):在程序运行中,替换、添加或删除模块,而无需重新加载整个页面。

为什么

开发时我们修改了其中一个模块代码,Webpack 默认会将所有模块全部重新打包编译,速度很慢。

所以我们需要做到修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变,这样打包速度就能很快。

怎么用

  • 基本配置
javascript 复制代码
module.exports = {
  // 其他省略
  devServer: {
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
    hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)
  },
};
  • CSS 处理

css 样式经过 style-loader 处理,已经具备 HMR 功能了。 但是 js 还不行

  • JS 处理
javascript 复制代码
// main.js

// 判断是否支持HMR功能
if (module.hot) {
  module.hot.accept("./js/count.js", function (count) {
    const result1 = count(2, 1);
    console.log(result1);
  });

  module.hot.accept("./js/sum.js", function (sum) {
    const result2 = sum(1, 2, 3, 4);
    console.log(result2);
  });
}

上面这样写会很麻烦,所以实际开发我们会使用其他 loader 来解决比如:vue-loaderopen in new window, react-hot-loaderopen in new window

OneOf

是什么

顾名思义就是只能匹配上一个 loader, 剩下的就不匹配了。

为什么

打包时每个文件都会经过所有 loader 处理,虽然因为 test 正则原因实际没有处理上,但是都要过一遍。比较慢。

怎么用

javascript 复制代码
module: {
  rules: [
    {
      oneOf: [
        {
          ...
        }
      ]
    }
  ]
}
Include/Exclude

是什么

  • include

包含,只处理 xxx 文件

  • exclude

排除,除了 xxx 文件以外其他文件都处理

为什么

开发时我们需要使用第三方的库或插件,所有文件都下载到 node_modules 中了。而这些文件是不需要编译可以直接使用的。

所以我们在对 js 文件处理时,要排除 node_modules 下面的文件。

怎么用

javascript 复制代码
module: {
  rules: [
    {
      oneOf: [
        {
          test: /\.js$/,
          // exclude: /node_modules/, // 排除node_modules代码不编译
          include: path.resolve(__dirname, "../src"), // 也可以用包含
          loader: "babel-loader",
        },
      ]
    }
  ]
}

plugins: [
    new ESLintWebpackPlugin({
    // 指定检查文件的根目录
    context: path.resolve(__dirname, "../src"),
    exclude: "node_modules", // 默认值
  }), 
]
Cache

是什么

对 Eslint 检查 和 Babel 编译结果进行缓存。

为什么

每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。

我们可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了。

怎么用

javascript 复制代码
module: {
  rules: [
    {
      oneOf: [
        {
          test: /\.js$/,
          // exclude: /node_modules/, // 排除node_modules代码不编译
          include: path.resolve(__dirname, "../src"), // 也可以用包含
          loader: "babel-loader",
          cache: true, // 开启缓存
          // 缓存目录
          cacheLocation: path.resolve( 
            __dirname,
            "../node_modules/.cache/.eslintcache"
          ),
        },
      ]
    }
  ]
}

plugins: [
    new ESLintWebpackPlugin({
    // 指定检查文件的根目录
    context: path.resolve(__dirname, "../src"),
    exclude: "node_modules", // 默认值
    cache: true, // 开启缓存
    // 缓存目录
    cacheLocation: path.resolve(
      __dirname,
      "../node_modules/.cache/.eslintcache"
    ),
  }), 
]
Thead

是什么

多进程打包:开启电脑的多个进程同时干一件事,速度更快。
需要注意:请仅在特别耗时的操作中使用,因为每个进程启动就有大约为 600ms 左右开销。

为什么

当项目越来越庞大时,打包速度越来越慢,甚至于需要一个下午才能打包出来代码。这个速度是比较慢的。

我们想要继续提升打包速度,其实就是要提升 js 的打包速度,因为其他文件都比较少。

而对 js 文件处理主要就是 eslint 、babel、Terser 三个工具,所以我们要提升它们的运行速度。

我们可以开启多进程同时处理 js 文件,这样速度就比之前的单进程打包更快了。

怎么用

  1. 获取 CPU 核数
javascript 复制代码
// nodejs核心模块,直接使用
const os = require("os");
// cpu核数
const threads = os.cpus().length;
  1. 下载包
shell 复制代码
npm i thread-loader -D
  1. 功能介绍
    • thread-loader :
  2. 在 webpack 中使用
javascript 复制代码
const os = require("os");
const TerserPlugin = require("terser-webpack-plugin");

// cpu核数
const threads = os.cpus().length;

module: {
  rules: [
    {
      oneOf: [
        {
          test: /\.js$/,
          // exclude: /node_modules/, // 排除node_modules代码不编译
          include: path.resolve(__dirname, "../src"), // 也可以用包含
          use: [
            {
              loader: "thread-loader", // 开启多进程
              options: {
                workers: threads, // 数量
              },
            },
            {
              loader: "babel-loader",
              options: {
                cacheDirectory: true, // 开启babel编译缓存
                cacheCompression: false // 缓存文件不要压缩
              },
            },
          ],
        },
      ]
    }
  ]
}

plugins: [
    new ESLintWebpackPlugin({
    // 指定检查文件的根目录
    context: path.resolve(__dirname, "../src"),
    exclude: "node_modules", // 默认值
    cache: true, // 开启缓存
    // 缓存目录
    cacheLocation: path.resolve(
      __dirname,
      "../node_modules/.cache/.eslintcache"
    ),
    threads, // 开启多进程
  }), 
]

// 新增节点,主要用于配置压缩相关的代码
optimization: {
  minimizer: [
    // css压缩也可以写到optimization.minimizer里面,效果一样的
    new CssMinimizerPlugin(),
    // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
    new TerserPlugin({
      parallel: threads // 开启多进程
    })
  ],
},
3. 减少代码体积
Tree Shaking

是什么

Tree Shaking 是一个术语,通常用于描述移除 JavaScript 中的没有使用上的代码。
注意:它依赖 ES Module

为什么

开发时我们定义了一些工具函数库,或者引用第三方工具函数库或组件库。

如果没有特殊处理的话我们打包时会引入整个库,但是实际上可能我们可能只用上极小部分的功能。

这样将整个库都打包进来,体积就太大了。

怎么用

Webpack 已经默认开启了这个功能,无需其他配置。

Babel

是什么

@babel/plugin-transform-runtime: 禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用。

为什么

Babel 为编译的每个文件都插入了辅助代码,使代码体积过大!

Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下会被添加到每一个需要它的文件中。

你可以将这些辅助代码作为一个独立模块,来避免重复引入。

怎么用

  1. 下载包
shell 复制代码
npm i @babel/plugin-transform-runtime -D
  1. 功能介绍
    • @babel/plugin-transform-runtime :禁用了 Babel 自动对每个文件的 runtime 注入,让复制代码从此引用
  2. 在 webpack 中使用
javascript 复制代码
module: {
  rules: [
    {
      oneOf: [
        {
          test: /\.js$/,
          // exclude: /node_modules/, // 排除node_modules代码不编译
          include: path.resolve(__dirname, "../src"), // 也可以用包含
          use: [
            {
              loader: "thread-loader", // 开启多进程
              options: {
                workers: threads, // 数量
              },
            },
            {
              loader: "babel-loader",
              options: {
                cacheDirectory: true, // 开启babel编译缓存
                cacheCompression: false, // 缓存文件不要压缩
                plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
              },
            },
          ],
        },
      ]
    }
  ]
}
Image Minimizer

是什么

压缩本地静态图片的插件功能

为什么

开发如果项目中引用了较多图片,那么图片体积会比较大,将来请求速度比较慢。

我们可以对图片进行压缩,减少图片体积。
注意:如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。

怎么用

  1. 下载包
shell 复制代码
npm i image-minimizer-webpack-plugin imagemin -D

还有剩下包需要下载,有两种模式:

shell 复制代码
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D
shell 复制代码
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D
  • 无损压缩(是对文件本身的压缩,和其它数据文件的压缩一样,是对文件的数据存储方式进行优化,采用某种算法表示重复的数据信息,文件可以完全还原,不会影响文件内容,对于数码图像而言,也就不会使图像细节有任何损失)
  • 有损压缩(是对图像本身的改变,在保存图像时保留了较多的亮度信息,而将色相和色纯度的信息和周围的像素进行合并,合并的比例不同,压缩的比例也不同,由于信息量减少了,所以压缩比可以很高,图像质量也会相应的下降)
  1. 功能介绍
    • image-minimizer-webpack-plugin imagemin :用来压缩图片的插件
  2. 在 webpack 中使用
javascript 复制代码
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

optimization: {
  minimizer: [
    // 压缩图片
    new ImageMinimizerPlugin({
      minimizer: {
        implementation: ImageMinimizerPlugin.imageminGenerate,
        options: {
          plugins: [
            ["gifsicle", { interlaced: true }],
            ["jpegtran", { progressive: true }],
            ["optipng", { optimizationLevel: 5 }],
            [
              "svgo",
              {
                plugins: [
                  "preset-default",
                  "prefixIds",
                  {
                    name: "sortAttrs",
                    params: {
                      xmlnsOrder: "alphabetical",
                    },
                  },
                ],
              },
            ],
          ],
        },
      },
    }),
  ]
}
  1. 打包出现问题我们需要安装两个文件到 node_modules 中才能解决,

需要复制到 node_modules\jpegtran-bin\vendor 下面

jpegtran 官网地址open in new window

需要复制到 node_modules\optipng-bin\vendor 下面

OptiPNG 官网地址

  • jpegtran.exe
  • optipng.exe
4. 优化代码运行性能
Code Split

是什么

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

  1. 分割文件:将打包生成的文件进行分割,生成多个 js 文件。
  2. 按需加载:需要哪个文件就加载哪个文件。

为什么

打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。

所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。

怎么用

Code Split

是什么
为什么
怎么用

Code Split

是什么
为什么
怎么用

Code Split

是什么
为什么
怎么用

Code Split

是什么
为什么
怎么用

总结

我们从 4 个角度对 webpack 和代码进行了优化:

  1. 提升开发体验
  • 使用 Source Map 让开发或上线时代码报错能有更加准确的错误提示。
  1. 提升 webpack 提升打包构建速度
  • 使用 HotModuleReplacement 让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。
  • 使用 OneOf 让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。
  • 使用 Include/Exclude 排除或只检测某些文件,处理的文件更少,速度更快。
  • 使用 Cache 对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。
  • 使用 Thead 多进程处理 eslint 和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效果)
  1. 减少代码体积
  • 使用 Tree Shaking 剔除了没有使用的多余代码,让代码体积更小。
  • 使用 @babel/plugin-transform-runtime 插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。
  • 使用 Image Minimizer 对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)
  1. 优化代码运行性能
  • 使用 Code Split 对代码进行分割成多个 js 文件,从而使单个文件体积更小,并行加载 js 速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。
  • 使用 Preload / Prefetch 对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。
  • 使用 Network Cache 能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。
  • 使用 Core-js 对 js 进行兼容性处理,让我们代码能运行在低版本浏览器。
  • 使用 PWA 能让代码离线也能访问,从而提升用户体验。

Vite

Vite 概述

Vite(法语意为 "快速的",发音 /vit/,发音同 "veet")是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:

Vite 安装

shell 复制代码
npm create vite@latest

路由管理


Vue-Router

路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理

前端路由: key是路径,value是组件
Vue-Router 是 Vue.js 官方的路由插件,它和 Vue.js 是深度集成的,适合用于构建单页面应用。Vue 的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。

在 Vue-Router 单页面应用中,则是路径之间的切换,也就是组件的切换。路由模块的本质就是建立起url和页面之间的映射关系。

"更新视图但不重新请求页面"是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有两种方式

  • 利用URL中的hash ("#")
  • 利用History interface 在HTML5中新增的方法

1. Router 配置

javascript 复制代码
// router/index.js

//引入VueRouter
import VueRouter from 'vue-router'
//引入Luyou 组件
import About from '../components/About'
import Home from '../components/Home'

//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
    routes:[
        {
            path:'/about',
            component:About
        },
        {
            path:'/home',
            component:Home
        }
    ]
})

//暴露router
export default router

2. Router 组件

sass 复制代码
<router-link active-class="active" to="/about">About</router-link> // 实现切换(解析为a标签),active-class属性为路由激活时触发
<router-view></router-view> // 指定路由展示位置

3. Router 注意事项

  1. 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹
  2. 通过切换,"隐藏"了的路由组件,默认是被销毁掉的,需要的时候再去挂载
  3. 每个组件都有自己的$route属性,里面存储着自己的路由信息
  4. 整个应用只有一个router,可以通过组件的$router属性获取到

4. 嵌套路由

4.1 配置路由规则
javascript 复制代码
routes:[
    {
        path:'/about',
        component:About,
    },
    {
        path:'/home', // 跳转路径
        component:Home, // 路径相对与组件
        children:[ //通过children配置子级路由
            {
                path:'news', //此处一定不要写: /news
                component:News // 路径相对于组价
            },
            {
                path:'message',//此处一定不要写: /message
                component:Message
            }
        ]
    }
]
4.2 跳转(要写完整路径)
sass 复制代码
<router-link to="/home/news">News</router-link>

5. Router query参数

5.1 传递参数
html 复制代码
<!-- 跳转并携带query参数,to的字符串写法 -->
<router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
                
<!-- 跳转并携带query参数,to的对象写法 -->
<router-link 
    :to="{
        path:'/home/message/detail',
        query:{
           id:666,
       title:'你好'
        }
    }"
>跳转</router-link>
5.2 接收参数
javascript 复制代码
$route.query.id
$route.query.title

6. Router name配置

作用: 可以简化路由的跳转

6.1 配置name
javascript 复制代码
{
    path:'/demo',
    component:Demo,
    children:[
        {
            path:'test',
            component:Test,
            children:[
                {
          name:'hello' //给路由命名
                    path:'welcome',
                    component:Hello,
                }
            ]
        }
    ]
}
6.2 简化跳转
html 复制代码
<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>

<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>

<!--简化写法配合传递参数 -->
<router-link 
    :to="{
        name:'hello',
        query:{
           id:666,
       title:'你好'
        }
    }"
>跳转</router-link>

7. Router params参数

7.1 配置路由,声明接收params参数
javascript 复制代码
{
    path:'/home',
    component:Home,
    children:[
        {
            path:'news',
            component:News
        },
        {
            component:Message,
            children:[
                {
                    name:'xiangqing',
                    path:'detail/:id/:title', //使用占位符声明接收params参数
                    component:Detail
                }
            ]
        }
    ]
}
7.2 传递参数
html 复制代码
<!-- 跳转并携带params参数,to的字符串写法 -->
<router-link :to="/home/message/detail/666/你好">跳转</router-link>
                
<!-- 跳转并携带params参数,to的对象写法 -->
<router-link 
    :to="{
        name:'xiangqing',
        params:{
           id:666,
       title:'你好'
        }
    }"
>跳转</router-link>

注意: 路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!!!

7.3 接收参数
javascript 复制代码
$route.params.id
$route.params.title

8. Router props配置

作用: 让路由组件更方便的收到参数

javascript 复制代码
{
    name:'xiangqing',
    path:'detail/:id',
    component:Detail,

    //第一种写法: props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
    // props:{a:900}

    //第二种写法: props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
    // props:true
    
    //第三种写法: props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
    props(route){
        return {
            id:route.query.id,
            title:route.query.title
        }
    }
}

9. <router-link>的replace属性

  1. 作用: 控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式: 分别为pushreplace,push是追加历史记录,replace是替换当前记录;路由跳转时候默认为push
  3. 如何开启replace模式: <router-link replace .......>News</router-link>

10. 编程式路由导航

作用: 不借助<router-link>实现路由跳转,让路由跳转更加灵活

javascript 复制代码
// $router的两个API
this.$router.push({
    name:'xiangqing',
        params:{
            id:xxx,
            title:xxx
        }
})

this.$router.replace({
    name:'xiangqing',
        params:{
            id:xxx,
            title:xxx
        }
})
this.$router.forward() // 前进
this.$router.back() // 后退
this.$router.go(n) // 可前进也可后退

// 不留记录
this.$router.replace() // 设置 replace属性的话,当点击时,会调用router.replace())而不是router.push())所以导航后不会留下历史记录。

11. 缓存路由组件

作用: 让不展示的路由组件保持挂载,不被销毁,从而保留响应的数据,以便下次打开时使用

html 复制代码
<keep-alive include="News"> 
    <router-view></router-view>
</keep-alive>

12. 组件特有生命周期函数

作用: 路由组件所独有的两个钩子,用于捕获路由组件的激活状态

  1. activated路由组件第一次渲染时触发(mounted后),之后每次被激活时触发(不再触发 beforeCreate, create, beforeMount, mounted)
  2. deactivated路由组件失活时触发(不触发beforeDestroy, destroyed),组件被缓存起来

13. 路由守卫

作用: 对路由进行权限控制
分类: 全局守卫、独享守卫、组件内守卫

全局前置/后置守卫
javascript 复制代码
// 全局前置守卫: 初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
    console.log('beforeEach',to,from)
    if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
        if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
            next() //放行
        }else{
            alert('暂无权限查看')
            // next({name:'guanyu'})
        }
    }else{
        next() //放行
    }
})

// 全局后置守卫: 初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{
    console.log('afterEach',to,from)
    if(to.meta.title){ 
        document.title = to.meta.title //修改网页的title
    }else{
        document.title = 'vue_test'
    }
})
全局解析守卫
javascript 复制代码
// router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置
router.beforeResolve(async to => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... 处理错误,然后取消导航
        return false
      } else {
        // 意料之外的错误,取消导航并把错误传给全局处理器
        throw error
      }
    }
  }
})
路由独享守卫 (router.js)
javascript 复制代码
// 路由配置项: 值为函数
beforEnter:(to,from) => {}

beforeEnter(to,from){
    console.log('beforeEnter',to,from)
    if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
        if(localStorage.getItem('school') === 'atguigu'){
            next()
        }else{
            alert('暂无权限查看')
            // next({name:'guanyu'})
        }
    }else{
        next()
    }
}
组件内守卫 (.vue)
sass 复制代码
// 进入守卫: 通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被验证前调用
  // 不能获取组件实例 `this` !
  // 因为当守卫执行时,组件实例还没被创建!
},
// 组件数据更新
beforeRouteUpdate (to, from) {
    // 在当前路由改变,但是该组件被复用时调用
  // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
  // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
  // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
}
// 离开守卫: 通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from) {
  // 在导航离开渲染该组件的对应路由时调用
  // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
}

注意: beforeRouteEnter 是支持给next传递回调的唯一守卫。对于 beforeRouteUpdatebeforeRouteLeave 来说,this 已经可用了,所以_不支持_传递回调,因为没有必要了

14. Router 工作模式

14.1 hash 模式

利用 HTML 的锚点功能,利用 # 号将前段路由拼接到真实 URL 后面,当前段路由发生改变时,会触发浏览器的 hashChange 事件,vue-router 通过监听 hashChange 事件来实现跳转

优点: 实现简单,在浏览器中符号"#",#以及#后面的字符称之为hash,兼容性好(兼容到 ie8) 绝大多数前端框架均提供了给予 hash 的路由实现 不需要服务器端进行任何设置和开发 除了资源加载和 ajax 请求以外,不会发起其他请求
缺点: 对于部分需要重定向的操作,后端无法获取 hash 部分内容,导致后台无法取得 url 中的数据,典型的例子就是微信公众号的 oauth 验证 服务器端无法准确跟踪前端路由信息 对于需要锚点功能的需求会与目前路由机制冲突

14.2 history 模式

通过 H5 新增的 history API,通过特定的 pushState 和 replaceState 方法,直接修改历史记录来修改前端路由,不会发送请求到浏览器

优点: 重定向过程中不会丢失 url 中的参数;后端可以拿到这部分数据;绝大多数前段框架均提供了 history 模式的路由实现;后端可以准确跟踪路由信息 可以使用 history.state 来获取当前 url 对应的状态信息
缺点: 兼容性不如 hash 路由 (只兼容到 IE10),浏览器在刷新的时候,会按照路径发送真实的资源请求,如果这个路径是前端通过 history API 设置的 URL,那么在服务端往往不存在这个资源,于是就返回 404 了,所以需要端支持

15. r o u t e 和 route和 route和router 的区别是什么

  • $route 是"路由信息对象",包括path, params, hash, query, fullPath, name,children,redirect等路由信息参数。
  • $router 为 VueRouter 的实例,相当于一个全局的路由器对象,里面含有很多属性和子对象,例如 history 对象,经常用的跳转链接就可以用 this.router.push,往 history 中添加一个新的记录或者 r o u t e r . r e p l a c e 。 router.replace。 router.replace。touer.back 返回上一个 history, $router.go 随意跳转页面

16. active-class属于哪个组件中的属性?该如何使用

<router-link> 中添加
直接在路由js文件中配置1inkActiveClass
javascript 复制代码
linkActiveClass: 'active'
引起的问题

因为 to="/"引起的,active-class选择样式时根据路由中的路径去匹配,然后显示,例如在my页面中,路由为localhost:8081/#/my,那么to="/"和to="/my"都可以匹配到,所有都会激活选中样式

解决方案

1. 在router-link中写入exact

javascript 复制代码
<router-link to="/" class="menu-home" active-class="active" exact>首页</router-link>

2. 在路由中加入重定向

javascript 复制代码
<router-link to="/" class="menu-home" active-class="active" exact>首页</router-link>
path: '/',
redirect: '/home'

17. 路由配置项

sass 复制代码
path: '/posts', // 跳转路径
name: 'user', // 命名路由
component: User, // 路径相对于的组件

redirect: '/user/userinfo', // 重定向路由
children: [{...},{...}], // 子路由的配置参数(路由嵌套)
props: [...], // 路由解耦

meta: { ... } // 自定义数据

状态管理


Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 ;它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化;Vuex 也集成到 Vue 的官方调试工具 devtools extension (opens new window),提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能
Vuex 主要解决了以下两个问题

  1. 多个组件依赖于同一状态时,对于多层嵌套的组件的传参将会非常繁琐,并且对于兄弟组件间的状态传递无能为力
  2. 来自不同组件的行为需要变更同一状态。以往采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码

1. 状态管理模式

单向数据流
  • state,驱动应用的数据源
  • view ,以声明方式将 state 映射到视图
  • actions ,响应在 view 上的用户输入导致的状态变化

但是,当我们的应用遇到多个组件共享状态 时,单向数据流的简洁性很容易被破坏

因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的"视图",不管在树的哪个位置,任何组件都能获取状态或者触发行为!

通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护
数据传递流程

当组件进行数据修改的时候我们需要调用 dispatch() 来触发 actions 里面的方法。actions 里面的每个方法中都会有一个 commit() 方法,当方法执行的时候会通过 commit() 来触发mutations 里面的方法进行数据的修改。mutations 里面的每个函数都会有一个 state 参数,这样就可以在 mutations 里面进行 state 的数据修改,当数据修改完毕后,会传导给页面。页面的数据也会发生改变

2. Vuex 组成

  • state: 状态存储,单一状态数
  • getters: 派生状态(可以认为是 store 的计算属性)
  • mutations: 更改 Vuex 的 store 中的状态,且必须同步执行
  • Actions: 类似于 mutation,不同在于Action 提交的是 mutation,而不是直接变更状态;Action 可以包含任意异步操作
  • modules: Vuex 允许我们将 store 分割成模块(module) ;每个模块拥有自己的 state、mutations、actions、getters、甚至是嵌套子模块------从上至下进行同样方式的分割

3. Vuex 配置

javascript 复制代码
// 引入Vue核心库
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 应用Vuex插件
Vue.use(Vuex)

// 准备actions对象------响应组件中用户的动作
const actions = {
  // 响应组件中加的动作
    jia(context,value){
        context.commit('JIA',value)
    },
}
// 准备mutations对象------修改state中的数据
const mutations = {
  // 执行加操作
    JIA(state,value){
        state.sum += value
    }
}
// 准备state对象------保存具体的数据
const state = {
  sum: 0
}
// 派生状态(store计算属性)
const getters = {
    bigSum(state){
        return state.sum * 10
    }
}

//创建并暴露store
export default new Vuex.Store({
    actions,
    mutations,
    state,
  getters
})

4. map 映射方法

4.1. mapState

用于帮助我们映射state中的数据为计算属性

javascript 复制代码
computed: {
    //借助mapState生成计算属性: sum、school、subject(对象写法)
     ...mapState({sum:'sum',school:'school',subject:'subject'}),
         
    //借助mapState生成计算属性: sum、school、subject(数组写法)
    ...mapState(['sum','school','subject']),
},
4.2. mapGetters

用于帮助我们映射getters中的数据为计算属性

javascript 复制代码
computed: {
    // 借助mapGetters生成计算属性: bigSum(对象写法)
    ...mapGetters({bigSum:'bigSum'}),

    // 借助mapGetters生成计算属性: bigSum(数组写法)
    ...mapGetters(['bigSum'])
},
4.3. mapActions

用于帮助我们生成与actions对话的方法,即: 包含$store.dispatch(xxx)的函数

javascript 复制代码
methods:{
    //靠mapActions生成: incrementOdd、incrementWait(对象形式)
    ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})

    //靠mapActions生成: incrementOdd、incrementWait(数组形式)
    ...mapActions(['jiaOdd','jiaWait'])
}
4.4. mapMutations

用于帮助我们生成与mutations对话的方法,即: 包含$store.commit(xxx)的函数

javascript 复制代码
methods:{
    //靠mapActions生成: increment、decrement(对象形式)
    ...mapMutations({increment:'JIA',decrement:'JIAN'}),
    
    //靠mapMutations生成: JIA、JIAN(对象形式)
    ...mapMutations(['JIA','JIAN']),
}

备注: mapActions 与 mapMutations 使用时,若需要传递参数需要: 在模板中绑定事件时传递好参数,否则参数是事件对象

5. Vuex 模块化+命名空间

目的:让代码更好维护,让多种数据分类更加明确

javascript 复制代码
// store.js
const countAbout = {
  namespaced:true,//开启命名空间
  state:{x:1},
  mutations: { ... },
  actions: { ... },
  getters: {
    bigSum(state){
       return state.sum * 10
    }
  }
}

const personAbout = {
  namespaced:true,//开启命名空间
  state:{ ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    countAbout,
    personAbout
  }
})
5.1. 组件中读取state数据
javascript 复制代码
// 方式一: 自己直接读取
this.$store.state.personAbout.list
// 方式二: 借助mapState读取
...mapState('countAbout',['sum','school','subject']),
5.2. 组件中读取getters数据
javascript 复制代码
// 方式一: 自己直接读取
this.$store.getters['personAbout/firstPersonName']
// 方式二: 借助mapGetters读取: 
...mapGetters('countAbout',['bigSum'])
5.3. 组件中调用dispatch方法
javascript 复制代码
// 方式一: 自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
// 方式二: 借助mapActions: 
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
5.4. 组件中调用commit方法
javascript 复制代码
// 方式一: 自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
// 方式二: 借助mapMutations: 
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),

Pinia

Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。 如果您熟悉 Composition API,您可能会认为您已经可以通过一个简单的 export const state = reactive({}). 这对于单页应用程序来说是正确的,但如果它是服务器端呈现的,会使您的应用程序暴露于安全漏洞。 但即使在小型单页应用程序中,您也可以从使用 Pinia 中获得很多好处:

  • dev-tools 支持
    • 跟踪动作、突变的时间线
    • Store 出现在使用它们的组件中
    • time travel 和 更容易的调试
  • 热模块更换
    • 在不重新加载页面的情况下修改您的 Store
    • 在开发时保持任何现有状态
  • 插件:使用插件扩展 Pinia 功能
  • 为 JS 用户提供适当的 TypeScript 支持或 autocompletion
  • 服务器端渲染支持

核心概念

  • store 状态容器
  • state 状态,需要使用箭头函数声明,返回一个状态对象
  • actions 操作状态的选项,同步异步皆可
  • getters 访问状态的选项,支持缓存,针对原有状态进行处理后返回

代码实现

初始化
typescript 复制代码
// main.ts
import { createPinia } from 'pinia' // 按需引入 pinia 工厂函数
const pinia = createPinia() // 创建 pinia 实例
app.use(pinia) // 挂载 pinia 实例
创建 store
typescript 复制代码
import { defineStore } from 'pinia'
// 根据定义函数创建容器函数
export const useMainStore = defineStore('main', {
  // 状态数据,要求配置项必须为箭头函数,
  state: () => {
    return {
      count: 0
    }
  },

  // 注意: 一下配置不能使用箭头函数(this 会绑定外层对象)
  // 类似于计算属性,可以使用 this 访问当前状态实例
  getters: {
    // 接收一个可选参数 state,访问容器
    // bigCount(state){ return state.count++}

    // 如果不接受 state 参数,直接使用 this 访问容器需要指定返回类型
    bigCount(): number {
      return this.count * 10
    }
  },

  actions: {
    // 操作状态的选项,同步/异步皆可,还可以接受调用时传递的参数,可以使用 this 访问当前状态实例
    updateCount(num: number) {
      this.count++ // 直接修改
      this.$patch({}) // 对象形式修改
      this.$patch(() => {}) // 函数形式修改
    }
  }
})
使用状态数据
typescript 复制代码
<script lang="ts" setup>
import { useMainStore} from '../store' // 引入 store 创建函数
import { storeToRefs} from 'pinia' // 引入响应式函数

const mainStore = useMainStore() // 创建容器实例
const {count} = storeToRefs(mainStore) // 结构容器数据,需要通过 ref 响应式转换

mainStore.updateCount(10) // 直接调用 actions
mainStore.bigCount // 直接访问 getters
mainStore.count++ // 直接修改 state
mainStore.$patch({ count: mainStore.count++}) // 对象写法
mainStore.$patch(()=>{ mainStore.count++}) // 函数写法 
</script>

语法转换


Babel

Babel 概述

Babel 是一个 JavaScript 编译器

Babel 是一个工具链,主要用于在当前和旧的浏览器或环境中,将 ECMAScript 2015+ 代码转换为 JavaScript 向后兼容版本的代码

Babel 安装

sass 复制代码
npm install --save-dev @babel/core @babel/cli @babel/preset-env

Babel 配置

javascript 复制代码
babel.config.js 配置文件

// 发布阶段插件配置
const prodplugins = []
if (process.env.NODE_ENV === 'production') {
  prodplugins.push('transform-remove-console')
}

// 默认导出
module.exports = {
  presets: ['@vue/cli-plugin-babel/preset'], // 使用 Babel 插件预设
  plugins: [ // 配置插件 // 数组形式['pluginsName',{配置项:'',配置项:''}]; 字符串形式: 'pluginsName'
    [ // element 插件配置
      'component',
      {
        libraryName: 'element-ui',
        styleLibraryName: 'theme-chalk'
      }
    ],
    // 发布阶段插件
    ...prodplugins,
    // 需要使用的插件使用放置至此
    'pluginsName'
  ]
}

Babel 插件推荐

  • babel-plugin-transform-remove-console 在项目 build 阶段把移除所有 console
sass 复制代码
npm install babel-plugin-transform-remove-console --save-dev // 安装
'transform-remove-console' // 插件使用名称
  • @babel/plugin-syntax-dynamic-import 实现路由懒加载(Vue3 自带)
sass 复制代码
npm install @babel/plugin-syntax-dynamic-import --save-dev
'@babel/plugin-syntax-dynamic-import' // 插件使用名称
// router => index.js 中实现
const Login = () => import(/* webpackChunkName: "login_home_welcome" */ '@/components/Login.vue')
const Home = () => import(/* webpackChunkName: "login_home_welcome" */ '@/components/Home.vue')
const WelCome = () => import(/* webpackChunkName: "login_home_welcome" */ '@/components/WelCome.vue')

core-js

core-js 概述

core-js是完全模块化的javascript标准库

包含ECMA-262至今为止大部分特性的polyfill,如promises、symbols、collections、iterators、typed arrays、etc,以及一些跨平台的WHATWG / W3C特性的polyfill,如WHATWG URL; 它可以直接全部注入到全局环境里面,帮助开发者模拟一个包含众多新特性的运行环境,这样开发者仅需简单引入core-js,仍然使用最新特性的ES写法编码即可;也可以不直接注入到全局对象里面,这样对全局对象不会造成污染,但是需要开发者单独引入core-js的相关module,并可能还需要通过手工调用module完成编码,没法直接使用最新ES的写法;它是一个完全模块化的库,所有的polyfill实现,都有一个单独的module文件,既可以一劳永逸地把所有polyfill全部引入,也可以根据需要,在自己项目的每个文件,单独引入需要的core-js的modules文件

core-js 安装

sass 复制代码
npm install --save core-js

core-js 配置

sass 复制代码
import 'core-js' // 配置后可以使用所有 ES6 及之后的语法

语法规范


ESLint

ESLint 概述

可组装的JavaScript和JSX检查工具

ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性和避免错误

ESLint 将源代码解析成 AST,然后检测 AST 来判断代码是否符合规则

ESLint 安装

sass 复制代码
npm install --save-dev esling

ESLint 配置

javascript 复制代码
// .eslintrc.js 配置文件
module.exports = { // 默认导出
  root: true, // ESLint 一旦发现配置文件中有 "root": true, 它就会停止在父级目录中寻找
  env: { // 使用 env 关键字指定想启用的环境
    node: true // 开启 node 环境
  },
  extends: ['plugin:vue/essential', '@vue/standard'], // 指定eslint配置文件路径的字符串(配置文件的路径、可共享配置的名称);字符串数组:每个配置继承它前面的配置
  parserOptions: { // 自定义解析器配置,ESLint 默认使用 Espree 作为其解析器
    parser: '@babel/eslint-parser' // 指定一个不同的解析器
  },
  rules: { // 改变规则设置(主要配置)
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 判断是否生产环境 : 警告 : 关闭规则
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 判断是否生产环境 : 警告 : 关闭规则
    'vue/multi-word-component-names': 'off', // 关闭驼峰命名规则
    'space-before-function-paren': 'off', // 关闭函数关键字与左括号之间空白规则
    'vue/no-unused-vars': 'off' // 关闭未使用的变量直接报错,但任然提示
  }
}

规则级别

  • "off" or 0 - 关闭规则
  • "warn" or 1 - 将规则视为一个警告(不会影响退出码)
  • "error" or 2 - 将规则视为一个错误 (退出码为1)
相关推荐
轻口味8 分钟前
命名空间与模块化概述
开发语言·前端·javascript
前端小小王43 分钟前
React Hooks
前端·javascript·react.js
迷途小码农零零发1 小时前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
娃哈哈哈哈呀1 小时前
vue中的css深度选择器v-deep 配合!important
前端·css·vue.js
旭东怪2 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
ekskef_sef3 小时前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6414 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻4 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云4 小时前
npm淘宝镜像
前端·npm·node.js