Vite
什么是构建工具
构建工具做了什么
(浏览器他只认识html, css, js)
- typescript: 如果遇到ts文件我们需要使用tsc将typescript代码转换为js代码
- React/Vue: 安装react-compiler / vue-compiler, 将我们写的jsx文件或者.vue文件转换为render函数
- less/sass/postcss/component-style: 我们又需要安装less-loader, sass-loader等一系列编译工具
- 语法降级: babel ---> 将es的新语法转换旧版浏览器可以接受的语法
- 体积优化: uglifyjs ---> 将我们的代码进行压缩变成体积更小性能更高的文件
- .....
能够把tsc, react-compiler, less, babel, uglifyjs全部集成到一起,代码一旦变化,自动将tsc, react-compiler, less, babel, uglifyjs全部走一遍 ---> 最终得到js文件,这个东西就叫做构建工具 打包: 将我们写的浏览器不认识的代码,交给构建工具进行编译处理的过程就叫做打包, 打包完成以后会给一个浏览器可以认识的文件
构建工具功能
- 模块化开发支持: 支持直接从node_modules里引入代码 + 多种模块化支持
- 处理代码兼容性: 比如babel语法降级, less,ts 语法转换(不是构建工具做的, 构建工具将这些语法对应的处理工具集成进来自动化处理)
- 提高项目性能: 压缩文件, 代码分割
- 优化开发体验:
- 构建工具会帮你自动监听文件的变化, 当文件变化以后自动帮你调用对应的集成工具进行重新打包, 然后再浏览器重新运行(整个过程叫做热更新, hot replacement
- 开发服务器: 跨域的问题, 用react-cli create-react-element vue-cli 解决跨域的问题,
构建工具他让我们可以不用每次都关心代码在浏览器如何运行, 只需要首次给构建工具提供一个配置文件(这个配置文件也不是必须的, 会有默认的处理), 有了这个集成的配置文件以后, 就可以在下次需要更新的时候调用一次对应的命令就好了, 如果再结合热更新, 我们就不需要管任何东西, 这就是构建工具去做的东西。
主流的构建工具
- webpack
- vite
- parcel
- esbuild
- rollup
- grunt
- gulp
vite相较于webpack的优势
官方文档 然而,当我们开始构建越来越大型的应用时,需要处理的 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项目文档教程 比如我们敲了yarn create vite
- 帮我们全局安装一个东西: create-vite (vite的脚手架)
- 直接运行这个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个问题:
- 不同的第三方包会有不同的导出格式(这个是vite没法约束人家的事情)
- 对路径的处理上可以直接使用.vite/deps, 方便路径重写
- 叫做网络多包传输的性能问题(也是原生esmodule规范不敢支持node_modules的原因之一), 有了依赖预构建以后无论他有多少的额外export 和import, vite都会尽可能的将他们进行集成最后只生成一个或者几个模块
vite环境变量配置
环境变量: 会根据当前的代码环境产生值的变化的变量就叫做环境变量
代码环境分类
- 开发环境
- 测试环境
- 预发布环境
- 灰度环境
- 生产环境
.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的时候, 他会做如下几件事:
- 直接找到.env文件 并解析其中的环境变量 并放进一个对象里
- 会将传进来的mode这个变量的值进行拼接:
.env.development
, 并根据我们提供的目录去取对应的配置文件并进行解析, 并放进一个对象 - 我们可以理解为
const baseEnvConfig = 读取.env的配置
const modeEnvConfig = 读取.env.mode相关配置
const lastEnvConfig = { ...baseEnvConfig, ...modeEnvConfig }
客户端使用
如果是客户端, vite会将对应的环境变量注入到i
里去,不需要导入直接是用就好了 vite做了一个拦截, 他为了防止我们将隐私性的变量直接送进i
中, 所以他做了一层拦截, 如果你的环境变量不是以VITE_
开头的, 他就不会帮你注入到客户端中去, 如果我们想要更改这个前缀, 可以去使用envPrefix配置
补充一个小知识: 为什么vite.config.js可以书写成esmodule的形式, 这是因为vite他在读取这个vite.config.js的时候会率先node去解析文件语法, 如果发现你是esmodule规范会直接将你的esmodule规范进行替换变成commonjs规范
在vite中处理css
原理
vite天生就支持对css文件的直接处理,处理逻辑如下:
- vite在读取到main.js中引用到了Index.css
- 直接去使用fs模块去读取index.css中文件内容
- 直接创建一个style标签, 将index.css中文件内容直接copy进style标签里
- 将style标签插入到index.html的head中
- 将该css文件中的内容直接替换为js脚本(方便热更新或者css模块化), 同时设置Content-Type为js 从而让浏览器以JS脚本的形式来执行该css后缀的文件
类名重复会导致样式被覆盖,在协同开发的时候很容易出现,cssmodule就是来解决这个问题的,原理如下:
- module.css (xxx.module.css命名文件,module是一种约定, 表示需要开启css模块化)
- 他会将你的所有类名进行一定规则的替换(将footer 替换成 _footer_i22st_1)
- 同时创建一个映射对象{ footer: "_footer_i22st_1" }
- 将替换过后的内容塞进style标签里然后放入到head标签中 (能够读到index.html的文件内容)
- 将componentA.module.css内容进行全部抹除, 替换成JS脚本
- 将创建的映射对象在脚本中进行默认导出
配置
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等预处理器并不能够解决这些问题:
- 对未来css属性的一些使用降级问题
- 前缀补全: 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
- 安装依赖
yarn add postcss-cli postcss -D
- 书写描述文件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-aliases 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-html
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-checker 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**
文件, 使用 i
- 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
}