Vite

什么是构建工具

构建工具做了什么

(浏览器他只认识html, css, js)

  1. typescript: 如果遇到ts文件我们需要使用tsc将typescript代码转换为js代码
  2. React/Vue: 安装react-compiler / vue-compiler, 将我们写的jsx文件或者.vue文件转换为render函数
  3. less/sass/postcss/component-style: 我们又需要安装less-loader, sass-loader等一系列编译工具
  4. 语法降级: babel ---> 将es的新语法转换旧版浏览器可以接受的语法
  5. 体积优化: uglifyjs ---> 将我们的代码进行压缩变成体积更小性能更高的文件
  6. .....

能够把tsc, react-compiler, less, babel, uglifyjs全部集成到一起,代码一旦变化,自动将tsc, react-compiler, less, babel, uglifyjs全部走一遍 ---> 最终得到js文件,这个东西就叫做构建工具 打包: 将我们写的浏览器不认识的代码,交给构建工具进行编译处理的过程就叫做打包, 打包完成以后会给一个浏览器可以认识的文件

构建工具功能

  1. 模块化开发支持: 支持直接从node_modules里引入代码 + 多种模块化支持
  2. 处理代码兼容性: 比如babel语法降级, less,ts 语法转换(不是构建工具做的, 构建工具将这些语法对应的处理工具集成进来自动化处理)
  3. 提高项目性能: 压缩文件, 代码分割
  4. 优化开发体验:
    • 构建工具会帮你自动监听文件的变化, 当文件变化以后自动帮你调用对应的集成工具进行重新打包, 然后再浏览器重新运行(整个过程叫做热更新, hot replacement
    • 开发服务器: 跨域的问题, 用react-cli create-react-element vue-cli 解决跨域的问题,

构建工具他让我们可以不用每次都关心代码在浏览器如何运行, 只需要首次给构建工具提供一个配置文件(这个配置文件也不是必须的, 会有默认的处理), 有了这个集成的配置文件以后, 就可以在下次需要更新的时候调用一次对应的命令就好了, 如果再结合热更新, 我们就不需要管任何东西, 这就是构建工具去做的东西。

主流的构建工具

  • webpack
  • vite
  • parcel
  • esbuild
  • rollup
  • grunt
  • gulp

vite相较于webpack的优势

官方文档open in new window 然而,当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。我们开始遇到性能瓶颈 —— 使用 JavaScript 开发的工具通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用 HMR(热更新),文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。 造成的结果: 构建工具需要很长时间才能启动开发服务器 (启动开发服务器 ---> 把项目跑起来)

// index.js
// 这一段代码最终会到浏览器里去运行
const lodash = require("lodash"); // commonjs 规范
import Vue from "vue"; // es6 module
// webpack是允许我们这么写的

webpack的编译原理, AST 抽象语法分析的工具 分析出你写的这个js文件有哪些导入和导出操作

// webpack的一个转换结果
const lodash = webpack_require("lodash");
const Vue = webpack_require("vue");
(function(modules) {
    function webpack_require() {}
    // 入口是index.js
    // 通过webpack的配置文件得来的: webpack.config.js ./src/index.js
    modules[entry](webpack_require);

}({
    "./src/index.js": (webpack_require) => {
        const lodash = webpack_require("lodash");
        const Vue = webpack_require("vue");
    }
}))

因为webpack支持多种模块化, 他一开始必须要统一模块化代码, 所以意味着他需要将所有的依赖全部读一遍 vite不会直接把webpack干翻, vite是基于es modules的, 侧重点不一样, webpack更多的关注兼容性, 而vite关注浏览器端的开发体验 此外,vite的上手难度更低,,webpack的配置是非常多的

vite脚手架和vite

vite官网搭建vite项目文档教程open in new window 比如我们敲了yarn create vite

  1. 帮我们全局安装一个东西: create-vite (vite的脚手架)
  2. 直接运行这个create-vite bin目录的下的一个执行配置

误区: 认为官网中使用对应yarn create构建项目的过程也是vite在做的事情 create-vite和vite的关系是什么呢? ---- create-vite内置了vite,就像使用vue-cli 会内置webpack

预设

我们自己搭建一个项目: 需要下载vite, vue, post-css, less, babel vue-cli/create-react-app(开发商)给我们提供已经精装修的模板: 把react/vue都下好了, 同时还把配置调整到了最佳实践 create-vite(开发商)给一套模板(一套预设): 下载vite, vue, post-css, less, babel好了, 并且做好了最佳实践的配置

vite的预加载

路径补全

import _ from "lodash"; // lodash可能也import了其他的东西

在处理的过程中如果说看到了有非绝对路径或者相对路径的引用, vite则会尝试开启路径补全

import _ from "/node_modules/.vite/lodash"; // lodash可能也import了其他的东西
// 开发者工具中捕捉到如下
import __vite__cjsImport0_lodash from "/node_modules/.vite/deps/lodash.js?v=ebe57916";

vite找寻依赖的过程是自当前目录依次向上查找的过程, 直到搜寻到根目录或者搜寻到对应依赖为止 生产和开发 yarn dev ---> 开发(每次依赖预构建所重新构建的相对路径都是正确的) yarn build ---> vite会全权交给一个叫做rollup的库去完成生产环境的打包

依赖预构建

首先vite会找到对应的依赖, 然后调用esbuild(对js语法进行处理的一个库), 将其他规范的代码转换成esmodule规范, 然后放到当前目录下的node_modules/.vite/deps, 同时对esmodule规范的各个模块进行统一集成 他解决了3个问题:

  1. 不同的第三方包会有不同的导出格式(这个是vite没法约束人家的事情)
  2. 对路径的处理上可以直接使用.vite/deps, 方便路径重写
  3. 叫做网络多包传输的性能问题(也是原生esmodule规范不敢支持node_modules的原因之一), 有了依赖预构建以后无论他有多少的额外export 和import, vite都会尽可能的将他们进行集成最后只生成一个或者几个模块

vite环境变量配置

环境变量: 会根据当前的代码环境产生值的变化的变量就叫做环境变量

代码环境分类

  1. 开发环境
  2. 测试环境
  3. 预发布环境
  4. 灰度环境
  5. 生产环境

.env: 所有环境都需要用到的环境变量 .env.development: 开发环境需要用到的环境变量(默认情况下vite将我们的开发环境取名为development) .env.production: 生产环境需要用到的环境变量(默认情况下vite将我们的生产环境取名为production) yarn dev --mode development 会将mode设置为development传递进来

演示代码

import {defineConfig, loadEnv} from "vite";
import viteBaseConfig from "./vite.base.config";
import viteDevConfig from "./vite.dev.config";
import viteProdConfig from "./vite.prod.config";

// 策略模式
const envResolver = {
    "build": () => {
        console.log("生产环境");
        return ({ ...viteBaseConfig, ...viteProdConfig })
    },
    "serve": () => {
        console.log("开发环境");
        return  ({ ...viteBaseConfig, ...viteProdConfig }) // 新配置里是可能会被配置envDir .envA
    }
}

export default defineConfig(({ command, mode }) => {
    // 是build 还是serve主要取决于我们敲的命令是开启开发环境还是生产环境
    // console.log("process", process.cwd());
    // 当前env文件所在的目录,process.cwd方法: 返回当前node进程的工作目录
    // 第二个参数不是必须要使用process.cwd(),
    const env = loadEnv(mode, process.cwd(), "");
    return envResolver[command]();
})

环境变量处理

在vite中的环境变量处理(服务器端): vite内置了dotenv这个第三方库 dotenv会自动读取.env文件, 并解析这个文件中的对应环境变量,并将其注入到process对象下(但是vite考虑到和其他配置的一些冲突问题, 他不会直接注入到process对象下) 涉及到vite.config.js中的一些配置:

  • root
  • envDir: 用来配置当前环境变量的文件地址

如果,新的配置设置了envDir,那么之前读取的默认路径的.env内容就是无效的,vite给我们提供了一些补偿措施:我们可以调用vite的loadEnv来手动确认env文件 官网原话:注意 Vite 默认是不加载 .env 文件的,因为这些文件需要在执行完 Vite 配置后才能确定加载哪一个

import { defineConfig, loadEnv } from 'vite'

export default defineConfig(({ command, mode }) => {
  // 根据当前工作目录中的 `mode` 加载 .env 文件
  // 设置第三个参数为 '' 来加载所有环境变量,而不管是否有 `VITE_` 前缀。
  const env = loadEnv(mode, process.cwd(), '')
  return {
    // vite 配置
    define: {
      __APP_ENV__: env.APP_ENV,
    },
  }
})

当我们调用loadenv的时候, 他会做如下几件事:

  1. 直接找到.env文件 并解析其中的环境变量 并放进一个对象里
  2. 会将传进来的mode这个变量的值进行拼接: .env.development,  并根据我们提供的目录去取对应的配置文件并进行解析, 并放进一个对象
  3. 我们可以理解为
 const baseEnvConfig = 读取.env的配置
 const modeEnvConfig = 读取.env.mode相关配置
 const lastEnvConfig = { ...baseEnvConfig, ...modeEnvConfig }

客户端使用

如果是客户端, vite会将对应的环境变量注入到import.meta.env里去,不需要导入直接是用就好了 vite做了一个拦截, 他为了防止我们将隐私性的变量直接送进import.meta.env中, 所以他做了一层拦截, 如果你的环境变量不是以VITE_开头的, 他就不会帮你注入到客户端中去, 如果我们想要更改这个前缀, 可以去使用envPrefix配置

补充一个小知识: 为什么vite.config.js可以书写成esmodule的形式, 这是因为vite他在读取这个vite.config.js的时候会率先node去解析文件语法, 如果发现你是esmodule规范会直接将你的esmodule规范进行替换变成commonjs规范

在vite中处理css

原理

vite天生就支持对css文件的直接处理,处理逻辑如下:

  1. vite在读取到main.js中引用到了Index.css
  2. 直接去使用fs模块去读取index.css中文件内容
  3. 直接创建一个style标签, 将index.css中文件内容直接copy进style标签里
  4. 将style标签插入到index.html的head中
  5. 将该css文件中的内容直接替换为js脚本(方便热更新或者css模块化), 同时设置Content-Type为js 从而让浏览器以JS脚本的形式来执行该css后缀的文件

类名重复会导致样式被覆盖,在协同开发的时候很容易出现,cssmodule就是来解决这个问题的,原理如下:

  1. module.css (xxx.module.css命名文件,module是一种约定, 表示需要开启css模块化)
  2. 他会将你的所有类名进行一定规则的替换(将footer 替换成 _footer_i22st_1)
  3. 同时创建一个映射对象{ footer: "_footer_i22st_1" }
  4. 将替换过后的内容塞进style标签里然后放入到head标签中 (能够读到index.html的文件内容)
  5. 将componentA.module.css内容进行全部抹除, 替换成JS脚本
  6. 将创建的映射对象在脚本中进行默认导出

配置

css: { // 对css的行为进行配置
    // modules配置最终会丢给postcss modules
    modules: { // 是对css模块化的默认行为进行覆盖
        localsConvention: "camelCaseOnly", // 修改生成的配置对象的key的展示形式(驼峰还是中划线形式)
        scopeBehaviour: "local", // 配置当前的模块化行为是模块化还是全局化 (有hash就是开启了模块化的一个标志, 因为他可以保证产生不同的hash值来控制我们的样式类名不被覆盖)
        // generateScopedName: "[name]_[local]_[hash:5]" // https://github.com/webpack/loader-utils#interpolatename
        // generateScopedName: (name, filename, css) => {
        //     // name -> 代表的是你此刻css文件中的类名
        //     // filename -> 是你当前css文件的绝对路径
        //     // css -> 给的就是你当前样式
        //     console.log("name", name, "filename", filename, "css", css); // 这一行会输出在哪??? 输出在node
        //     // 配置成函数以后, 返回值就决定了他最终显示的类型
        //     return `${name}_${Math.random().toString(36).substr(3, 8)}`;
        // }
        hashPrefix: "hello", // 生成hash会根据你的类名 + 一些其他的字符串(文件名 + 他内部随机生成一个字符串)去进行生成, 如果你想要你生成hash更加的独特一点, 你可以配置hashPrefix, 你配置的这个字符串会参与到最终的hash生成, (hash: 只要你的字符串有一个字不一样, 那么生成的hash就完全不一样, 但是只要你的字符串完全一样, 生成的hash就会一样)
        globalModulePaths: ["./componentB.module.css"], // 代表你不想参与到css模块化的路径
    },
    preprocessorOptions: { // key + config key代表预处理器的名
        less: { // 整个的配置对象都会最终给到less的执行参数(全局参数)中去
            // 在webpack里就给less-loader去配置就好了
            math: "always",
            globalVars: { // 全局变量
                mainColor: "red",
            }
        },
    },
    devSourcemap: true,
},

postcss

介绍

vite天生对postcss有非常良好的支持 postcss的工作: 保证css在执行起来是万无一失的 less,sass等预处理器并不能够解决这些问题:

  1. 对未来css属性的一些使用降级问题
  2. 前缀补全: Google非常卷 --webkit

都对postcss有一个误区: 他们认为postcss和less sass是差不多级别 我们写的css代码 --> postcss ---> less(去将语法进行编译(嵌套语法,函数,变量)成原生css) --> 再次对未来的高级css语法进行降级 --> 前缀补全 --> 浏览器客户端 目前来说 less和sass等一系列预处理器的postcss插件已经停止维护了 less插件,所以业内就产生了一个新的说法: postcss是后处理器 我们写的js代码 --> babel --> 将最新的ts语法进行转换js语法 --> 做一次语法降级  --> 浏览器客户端去执行 babel --> 帮助我们让js执行起来万无一失

class App {} // es6的写法
function App() {} // es3的语法

使用postcss

  1. 安装依赖
yarn add postcss-cli postcss -D
  1. 书写描述文件postcss.config.js
// 预设环境里面是会包含很多的插件
// 语法降级 --> postcss-low-level
// 编译插件 --> postcss-compiler
// ...
const postcssPresetEnv = require("postcss-preset-env");

// 预设就是帮你一次性的把这些必要的插件都给你装上了
module.exports = {
    plugins: [postcssPresetEnv(/* pluginOptions */)]
}

vite配置文件配置

直接在css.postcss中进行配置, 该属性直接配置的就是postcss的配置(优先级更高),也可以编写postcss.config.js

  • postcss-preset-env: 支持css变量和一些未来css语法 自动补全(autoprefixer)

打包配置

build: {
    rollupOptions: { // 配置rollup的一些构建策略
        output: { // 控制输出
            // 在rollup里面, hash代表将你的文件名和文件内容进行组合计算得来的结果
            assetFileNames: "[hash].[name].[ext]"
        }
    },
    assetsInlineLimit: 4096000, // 4000kb 
    outDir: "dist", // 输出目录
    assetsDir: "static" // 输出目录中的静态资源目录
},

vite插件

插件是什么?

vite会在生命周期的不同阶段中去调用不同的插件以达到不同的目的

vite-aliases

https://github.com/subwaytime/vite-aliasesopen in new window vite-aliases可以帮助我们自动生成别名: 检测你当前目录下包括src在内的所有一级文件夹, 并帮助我们去生成别名,示例如下:

{
  	"@": "/**/src",
    "@assets": "/**/src/assets",
    "@components": "/**/src/components",
}

ViteAliases({
	/**
	 * Relative path to the project directory
	 */
	dir: 'src',

	/**
	 * Prefix symbol for the aliases
	 */
	prefix: '@',

	/**
	 * Allow searching for subdirectories
	 */
	deep: true,

	/**
	 * Search depthlevel for subdirectories
	 */
	depth: 1,

	/**
	 * Creates a Logfile
	 * use `logPath` to change the location
	 */
	createLog: false,

	/**
	 * Path for Logfile
	 */

	logPath: 'src/logs',

	/**
	 * Create global project directory alias
	 */
	createGlobalAlias: true,

	/**
	 * Turns duplicates into camelCased path aliases
	 */
	adjustDuplicates: false,

	/**
	 * Used paths in JS/TS configs will now be relative to baseUrl
	 */
	useAbsolute: false,

	/**
	 * Adds seperate index paths
	 * approach created by @davidohlin
	 */
	useIndexes: false,

	/**
	 * Generates paths in IDE config file
	 * works with JS or TS
	 */
	useConfig: true,

	/**
	 * Will generate Paths in tsconfig
	 * used in combination with `useConfig`
	 * Typescript will be auto detected
	 */
	dts: false,

	/**
	 * Root path of Vite project
	 */
	root: process.cwd()
});

vite-plugin-html

  • HTML 压缩能力
  • EJS 模版能力
  • 多页应用支持
  • 支持自定义entry
  • 支持自定义template

https://github.com/vbenjs/vite-plugin-htmlopen in new window

createHtmlPlugin({
  minify: true,
  /**
   * After writing entry here, you will not need to add script tags in `index.html`, the original tags need to be deleted
   * @default src/main.ts
   */
  entry: 'src/main.ts',
  /**
   * If you want to store `index.html` in the specified folder, you can modify it, otherwise no configuration is required
   * @default index.html
   */
  template: 'public/index.html',

  /**
   * Data that needs to be injected into the index.html ejs template
   */
  inject: {
    data: {
      title: 'index',
      injectScript: `<script src="./inject.js"></script>`,
    },
    tags: [
      {
        injectTo: 'body-prepend',
        tag: 'div',
        attrs: {
          id: 'tag',
        },
      },
    ],
  },
}),

vite与ts

vite-plugin-checkeropen in new window A Vite plugin that can run TypeScript, VLS, vue-tsc, ESLint, Stylelint in worker thread to add type checking and linting support for Vite. 需要安装typescript包

// vite.config.js
import checker from 'vite-plugin-checker'
export default {
  plugins: [
    checker({
      // e.g. use TypeScript check
      typescript: true,
    }),
  ],
}

需要在根目录下创建一个tsconfig.json文件规定ts的一些编译选项

{
    "compilerOptions": {
        "module": "system",
        "noImplicitAny": true,
        "removeComments": true,
        "preserveConstEnums": true,
        "outFile": "../../built/local/tsc.js",
        "sourceMap": true
    },
    "include": [
        "src/**/*"
    ],
    "exclude": [
        "node_modules",
        "**/*.spec.ts"
    ]
}

如果在构建环境中遇到ts报错需要限制打包操作,需要在package.json的命令中如下配置:

"script": {
	"build": "tsc --noEmit && vite build"
}

额外地,针对环境变量(也就是.env文件里面的变量),需要有额外的类型声明需要在根目录下书写**vite-env.d.ts或env.d.ts**文件, 使用 import.meta.env.VITE_NODE_ENV 获取环境变量,但是是没有智能提示的,只有默认的以下几种提示,,要想有自己定义的环境变量提示,需要自己添加:

  • MODE,用来指明现在所处于的模式,一般通过它进行不同环境的区分,比如 dev、test、pre、prd 等等,默认为:“development”
  • BASE_URL,用来请求静态资源初始的 url
  • PROD,用来判断当前环境是否是正式环境
  • DEV,用来与 PROD 相反的环境
  • SSR,用来判断是否是服务端渲染的环境
/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_NODE_ENV: string; //定义提示信息 数据是只读的无法被修改
  //多个变量定义多个...
}

declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}
Last Updated:
Contributors: liushun-ing