构建工具出现的原因和功能
企业级项目遇到的功能:
- typeScript: 需要使用tsc将ts文件转为js文件 React/Vue:
安装react-compiler/vue-compiler less/sass/postcss/component-style:
安装less-loader/sass-loader 语法降级:Babel-降级 体积优化: uglify.js,压缩文件体积 - 打包: 将我们写的浏览器不认识的代码交给构建工具进行编译处理的过程就叫做打包,打包完成可以给浏览器一个可以认识的文件
构建工具的功能:
1.模块化开发支持 支持直接从node_modules获取包,提供多种模块化支持
2.处理代码兼容性:babel语法降级,less,ts语法转换(构建工具将处理这些语法的工具集成进行自动化处理)
3.提高项目性能: 压缩文件,代码分割
4.优化开发体验: 构建工具自动监听文件变化,变化后重新打包然后在浏览器重新运行(整个过程叫做热更新,hot replacement)
开发服务器: 解决跨域问题
主流的构建工具: webpack,vite,parcel esbuild rollup grunt
Vite相对于webpack的优势
起因: 项目越大,构建工具(webpack)处理的js代码体积越大,和webpack的工作原理有关
结果: 构建工具需要很长时间才能启动开发服务器(启动项目),即使使用热更新依然会占用很大的时间
webpack不能优化这种方法,因为它支持多种模块化(require,import),他会重写require方法,统一模块化代码,所以意味着必须将所有文件读一遍(兼容性)
vite 基于ESMmodule,侧重点不一样,webpack侧重于兼容性,vite侧重于浏览器端的开发体验(按需加载)
Vite脚手架和vite的区别:
执行create-vite
全局新建一个脚手架: create-vite(vite的脚手架)
直接运行这个create-vite bin目录下的一个执行配置
create-vite内置了vite的功能
Vite的使用
默认情况下,ESModule导入资源时,要么是绝对路径要么是相对路径,不能找到第三方模块,而依赖安装在node_modules,不适用构建工具,浏览器并不知道younode_modules这个文件夹,会报错,如果浏览器自行查询node_modules,ESModule导入资源时是通过网络请求的方式查找的。如果loadash中还依赖其他各种库,都依赖网络请求的方式,将会极大地消耗性能
CommonJS是运用require实现的,运行在服务端,读取依赖是从本地文件读取,并不需要网络请求,所以不消耗性能
安装 vite: npm i vite -D
vite的预加载:
在处理的过程中如果看到了有非绝对或者相对路径的引用,则会尝试开启路径补全
eg: import _ from ‘lodash’ 处理之后就成为: import _ from “node_modules/.vite/lodash”
找寻依赖的过程是指从当前目录依次向上查找的过程,直至寻到根目录或者搜寻到对应的依赖为止
开发环境中,每次一来构建所重新构建的相对路径都是正确的,生产环境中,vite会全权交给一个叫rollup的库完成生产环境的打包
有的包是以CommonJS的方式导出的(axios等),为了解决这个问题,采用依赖预构建,首先vite会找到对应的依赖,然后调用esbuild对js语法进行处理的一个库(将其他规范转为ESModule)
然后放到当前目录下的node_modules/.vite/deps目录下,同时对esModule规范的各个模块进行统一集成,依赖预构建解决了以下的三个问题:
1.不同的第三方包会有不同的导出格式,vite无法约束导出
2.对路径的处理上可以直接用.vite/deps,方便对路径进行重写
3.网络多包传输的性能问题(也是原生ESModule不敢支持node_modules的原因之一),有了依赖预构建之后无论有多少额外的import和export,vite都会尽可能的降他们进行集成,最后只生成一个或者几个模块
eg:
export default function a(){}
//导入语句:
export {default as a} from './a.js'
//处理后会变成:
function a(){}
vite的配置文件目录
配置文件的代码提示
/** @type import("vite").userConfig**/
const viteConfig = {
}
//或者:
import { defineConfig } from 'vite'
export default defineConfig({
optimizeDeps:{
exclude:[] // 将数组中的依赖不进行依赖预构建
},
})
关于环境的处理:
使用webpack处理时,
webpack.dev.config/webpack.prod.config/webpack.base.config/webpackmerge
//使用vite
import { defineConfig } from 'vite'
import viteBaseConfig from './vite.base.config'
import viteDevConfig from "./vite.dev.config";
import viteProConfig from "./vite.pro.config";
//策略模式
const envResolve = {
"build":() => {
return {...viteProConfig,...viteBaseConfig} //可能会配置新的envDir
},
"serve":() => {
return {...viteDevConfig,...viteBaseConfig}
}
}
export default defineConfig(({command}) =>{
console.log(envResolve[command])
return envResolve[command]()
})
环境变量的配置
环境变量:会根据当前代码环境产生的变化的变量就叫环境变量
代码环境: 开发环境、测试环境、预发布环境、灰度环境、生产环境
dotenv: 第三方库,启动项目的时候去读当前目录下的env文件并解析文件中对应的环境变量,并将其注入到process对象下,但是vite考虑到和其他配置的冲突问题,不会直接注入
到process对象下,因为读取vite配置文件是在读取process环境变量之后的,会引起冲突
涉及到vite的配置:
root:
envDir: 配置当前环境变量的地址
补偿措施: 调用vite.loadEnv方法,手动确认env文件
vite.config.js可以写成esModule的形式可以运行在node中: 因为vite在读取vite.config.js时会率先node去解析,如果是ESmODULE,会直接将ESmODULE转换为commonjs规范
.vite对静态资源的处理和配置
tree-shaking: 摇树优化:打包工具会自动删除那些没有用到的变量和方法
静态资源:服务端的静态资源指的是除了动态的API以外,百分之九十九的资源都是静态资源,动态生成的例如请求数据等
客户端的静态资源指的就是图片、视频等
vite对静态资源基本上是开箱即用的,除了svg
服务端:回去读取图片文件的内容->Buffer:二进制的字符串
import picture from './test.png?raw' //?raw表示获取Buffer
import picture from './test.png?url' //?url表示获取图片的绝对路径
resolve:{
alias:{
"@assets":path.resolve(__dirname,'./assets') //表示当前目录下的assets文件的路径替换为@assets
}
}
插件理念
插件是什么:
vite会在不同的生命周期的不同阶段调用不同的插件以达到不同的目的
生命周期:钩子函数,vite从开始到执行结束
中间件: 在不同的生命周期的不同阶段调用不同的插件以达到不同的目的
常用的插件:
vite-aliases:
vite-aliases帮助我们自动生成别名,检测当前目录下的所有文件夹(包括src),并帮助我们生成别名
ViteAliases({
"@":"src",
"@assets":"src/assets"
})
vite-alias的原理:
手写的vite-aliases其实就是抢在vite的配置文件执行之前去修改这个配置文件
const fs = require('fs')
const path = require('path')
function getTotalSrcDir(keyName){
// 获取项目文件静态资源目录
const result = fs.readdirSync(path.resolve(__dirname,'../assets'))
const diffResult = diffDirAndFile(result,'../assets')
// console.log({diffResult})
const resolveAliases = {} //存储的就是别名配置
//生成别名-路径键值对
diffResult.dirs.forEach(dirName => {
const key = keyName || `@${dirName}`
const absPath = path.resolve(__dirname,'../assets' + '/'+ dirName)
resolveAliases[key] = absPath
})
return resolveAliases
}
// 区分文件还是文件夹
function diffDirAndFile(dirFileAndArr = [],basePath=""){
const result = {
dirs:[],
files:[]
}
dirFileAndArr.forEach(name => {
const currentFileStat = fs.statSync(path.resolve(__dirname,basePath + "/" + name))
const isDirectory = currentFileStat.isDirectory()
if(isDirectory){
result.dirs.push(name)
}else{
result.files.push(name)
}
})
return result
}
// vite的插件必须返回给vite一个配置对象
export default ({} = {keyName:""}) => {
return {
//config是vite独有的生命周期hook
/*
config:现有配置环境的变量
env: mode:String(development| production),command: String(dev|build)
*/
config(config,env){
// 只是将配置文件传过来,并不会执行配置文件
// 可以返回一个对象,可以是部分的vite-config(将是修改的那一部分),返回的对象
// 将被合并到已有的vite.config文件中
const resolveAliases = getTotalSrcDir(keyName)
// 返回resolve插件
return {
resolve:{
alias:resolveAliases
}
}
}
}
}
vite-plugin-html
vite内置了很多核心插件
vite-plugin-html: 动态控制index.html中的内容
ejs语法在服务端使用的会很频繁,因为需要动态去修改index.html
module.exports = (option) => {
return {
//转换html的
transformIndexHtml:{
transform:(html,ctx) =>{
//ctx: 表示当前请求的上下文
//html: 当前的html文件
return html.replace(/<%= title %>/g,option.inject.data.title)
},
enforce:'pre' //将插件的执行时机提前
/*
* 插件顺序: Alias,带有enforce属性的插件,vite的核心插件,没有带enforce属性的插件,
* 带有 enforce: 'post' 的用户插件,Vite 后置构建插件(最小化,manifest,报告)*/
}
}
}
vite-plugin-mock
用于模拟数据,前后端并行开发时,用于模拟请求返回数据,npm i vite-plugin-mock mock.js
const fs = require('fs')
// const mockJS = require('mock.js')
const path = require('path')
module.exports = (options) => {
// 拦截http请求
// baseUrl:请求地址
//
return {
configureServer(server){
// 服务器的相关配置
/*
* req:请求体
* res: 响应对象
* next: 放行函数,是否交给下一个中间件
* */
const mockStat = fs.statSync("mock")
const isDirectory = mockStat.isDirectory()
let mockResult = []
if(isDirectory){
//process.cwd获取当前执行的根目录
mockResult = require(path.resolve(process.cwd(),'mock/index.js'))
console.log(mockResult)
}
server.middlewares.use((req,res,next) => {
const mockItem = mockResult.find(item => item.url === req.url)
if(mockItem) {
// const mockStat = fs.statSync("mock")
// const isDirectory = mockStat.isDirectory()
const responseData = mockItem.response(req)
res.end(JSON.stringify(responseData)) //调用res.end设置请求头
}else{
next() //next执行先响应,等到异步任务队列执行时res.end在执行就会报错
}
})
}
}
}
插件补充:
插件的生命周期:
config: config(options){} //options是我们写的vite.config的配置,钩子接收原始用户配置(命令行选项指定的会与配置文件合并)和一个描述配置环境的变量
export default defineConfig({
build:{
rollupOptions:{
output:{
assetFileNames:"[name].[hash].[ext]"
}
}
},
plugins:[
{
config(userConfig){
return {
mode:'production'
}
},
configResolved(options){
// 整个配置文件完全解析完成之后会走这个钩子
},
//配置开发服务器,拦截请求处理
configureServer(server){
server.middlewares.use((req,res,next) => {
})
},
//获取html,通过流程处理返回新的html字符串
transformIndexHtml(html){
},
// vite在内部有一个默认的配置
//编译环境预览: npx vite preview
//热更新: 自定义热更新行为
handleHotUpdate(ctx) {
},
//rollup和vite兼容的钩子函数,会执行两次,vite和rollup会各执行一次
options(rollupOptions){
//该钩子在服务启动时会调用
//rollupOptions接受的是和rollup相关的配置
console.log({rollupOptions})
},
buildStart(fullRollupOptions){
//该钩子在服务启动时会调用
console.log({fullRollupOptions})
}
}
]
})
暂时先写到这里,如有失误,还请多多指正。(vite的插件版本要和node版本对应一下,否则会出现安装失败或者无法使用的情况)