vite的工程化实践
前言
为什么要使用vite
在浏览器支持ES模块之前,诸如webpack、Rollup等打包工具的出现,确实是极大的改善了前端开发者的开发体验。但是当我们开始构建越来越大型的应用时,需要处理的js代码量也是呈指数级增长,模块和模块之间的依赖往往会使我们的开发工具需要更长的时间(甚至使几分钟)才能启动开发服务器,即使使用HMR,文件的修改后的效果也需要几秒钟才能在浏览器中反映出来。这也会我们要是用vite的原因,提示构建速率来改善开发体验。
vite VS 传统脚手架
下面主要是介绍一下vite和webpack的区别
vite | webpack | |
---|---|---|
启动开发服务器 | 利用浏览器的原生ESM加载源码模块,启动时处理一次依赖预构建即可,二次启动秒开,访问时浏览器原生解析依赖 | 递归依赖分析、转换代码、编译、打包输出主流浏览器可直接执行的js文件,浏览器访问直接渲染 |
构建打包器 | 基于ESBuild使用Go编写,构建速度比nodejs快10-100倍 | webpack由javascript编写运行在nodejs环境,运行效率较差 |
热更新 | 精准更新已修改的ESM模块 | 修改文件后需要重新构建文件,且随着应用体积增大而花费更长时间 |
生态 | vite生态只能说勉强够用,某些功能可能需要妥协或者自己实现 | Loader和Plugin生态丰富,方案齐全 |
生产环境 | Esbuild本身存在一些限制,所以生产环境采用的Rollup | 依托丰富的生态,稳定可靠 |
webpack启动之后会做一堆事情,经历一条很长的编译打包链条,从入口开始需要逐步经历语法解析、依赖收集、代码转译、打包合并、代码优化,最终将高版本的、离散的源码编译打包成低版本、高兼容性的产物代码,这可满满都是 CPU、IO 操作啊,在 Node 运行时下性能必然是有问题。而 Vite 运行 Dev 命令后只做了两件事情,一是启动了一个用于承载资源服务的 service;二是使用 Esbuild预构建 npm 依赖包。之后就一直躺着,直到浏览器以 http 方式发来 ESM 规范的模块请求时,Vite 才开始“「按需编译」”被请求的模块
正文
搭建vite项目
这里官网提供了npm、yarn、pnpm三种模式
npm create vite@latest // 使用npm
yarn create vite // 使用yarn
pnpm create vite // 使用pnpm
这里选择yarn,创建了一个基于vite构建的react项目
生成的目录结构如下:
│ .gitignore
│ index.html
│ package.json
│ tsconfig.json
│ tsconfig.node.json
│ vite.config.ts
│
├─public
│ vite.svg
│
└─src
│ App.css
│ App.tsx
│ index.css
│ main.tsx
│ vite-env.d.ts
│
└─assets
react.svg
其中index.html为页面的入口文件,main.tsx为系统主入口,vite.config.ts为vite的配置文件。
yarn install // 安装依赖
yarn dev // 启动项目
初始化配置
在项目开始之前,引入项目的所需的react-router、ant design、axios、less or sass并使用git作为版本管理工具。
vite的按需加载
Vite需要借助插件vite-plugin-imp来实现按需加载
yarn add vite-plugin-imp -dev
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import vitePluginImp from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
vitePluginImp({
libName: "antd",
style: (name: string) => `antd/lib/${name}/style/index.less`,
})
],
})
配置antd主题颜色
可能你需要先添加@types/node
包
yarn add @types/node --save
import fs from 'fs'
import path from 'path'
import { getThemeVariables } from 'antd/dist/theme'
import lessVarsToJs from 'less-vars-to-js'
// 解析你的配置
const antdThemeConfig = lessVarsToJs(
fs.readFileSync(path.resolve(__dirname, 'src/assets/antdTheme.less'))
)
export default defineConfig({
css: {
preprocessorOptions: {
less: {
// 支持内联 JavaScript
javascriptEnabled: true,
modifyVars: {...getThemeVariables(), ...antdThemeConfig}
}
}
}
})
简写路径配置
resolve: {
alias: {
"@": path.resolve(__dirname, 'src')
}
},
ts的路径解析能力
import tsConfigPaths from 'vite-tsconfig-paths'
export default {
plugins: [
tsConfigPaths(),
],
}
版本控制
import tsConfigPaths from 'vite-plugin-package-version'
export default {
plugins: [
pluginPackageVersion(),
],
}
端口配置
默认端口是5173,可以改成你喜欢的3000。
export default {
server: {
port: 3000,
strictPort: true,
},
}
vite.config.ts最终配置
import fs from 'fs'
import path from 'path'
import { getThemeVariables } from 'antd/dist/theme'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import vitePluginImp from '@vitejs/plugin-react'
import tsConfigPaths from 'vite-tsconfig-paths'
import pluginPackageVersion from 'vite-plugin-package-version'
import lessVarsToJs from 'less-vars-to-js'
const antdThemeConfig = lessVarsToJs(
fs.readFileSync(path.resolve(__dirname, 'src/assets/antdTheme.less'))
)
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
tsConfigPaths(),
pluginPackageVersion(),
vitePluginImp({
libName: "antd",
style: (name: string) => `antd/lib/${name}/style/index.less`,
})
],
css: {
preprocessorOptions: {
less: {
// 支持内联 JavaScript
javascriptEnabled: true,
modifyVars: {...getThemeVariables(), ...antdThemeConfig}
}
}
},
server: {
port: 3000,
strictPort: true,
},
resolve: {
alias: {
"@": path.resolve(__dirname, 'src')
}
},
})
v
tsconfig.json
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["vite.config.ts"]
}
添加prettier格式化代码格式
yarn add prettier --save
增加prettier.config.ts文件
module.exports = {
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: false,
singleQuote: true,
quoteProps: 'as-needed',
jsxSingleQuote: false,
trailingComma: 'es5',
bracketSpacing: true,
bracketSameLine: false,
arrowParens: 'always',
htmlWhitespaceSensitivity: 'ignore',
vueIndentScriptAndStyle: true,
endOfLine: 'lf',
}
React Router配置
// src/App.tsx
import { Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { ConfigProvider, Layout, Result } from 'antd';
import zhCN from 'antd/es/locale/zh_CN';
import ROUTES, { IRoute } from './routes';
import ErrorBoundary from './components/ErrorBoundary';
import './App.css';
function FullBack() {
return <div>加载中...</div>;
}
function App() {
return (
<div className="App">
<ConfigProvider locale={zhCN}>
<BrowserRouter>
<Layout>
<ErrorBoundary>
{/* 使用懒加载会导致加载延迟,使用suspense优化体验 */}
<Suspense fallback={<FullBack />}>
<Routes>
{ROUTES?.map((route) => {
return (
<Route
key={route.url}
path={route.url}
element={route.Element}
/>
);
})}
<Route
path="*"
element={
<div style={{ marginTop: 80 }}>
<Result
status="404"
title="404"
subTitle="抱歉,您访问的资源不存在"
/>
</div>
}
/>
</Routes>
</Suspense>
</ErrorBoundary>
</Layout>
</BrowserRouter>
</ConfigProvider>
</div>
);
}
export default App;
// src/routes
import { lazy } from 'react';
export interface IRoute {
path: string;
Element?: React.ReactElement;
}
const Home = lazy(() => import('./../pages/Home'));
const Test = lazy(() => import('./../pages/Test'));
const ROUTES: Array<IRoute | undefined> = [
{
path: 'home',
Element: <Home />,
},
{
path: 'test',
Element: <Test />,
},
];
export default ROUTES;
// src/components/ErrorBoundary
import React, { Component } from 'react';
import { Result } from 'antd';
export default class ErrorBoundary extends Component<{
children: React.ReactNode;
hasError?: boolean;
}> {
static getDerivedStateFromError() {
return {
hasError: true,
};
}
state = { hasError: false };
render() {
const { children } = this.props;
const { hasError } = this.state;
if (hasError) {
return (
<Result
status="500"
title="500"
subTitle="抱歉,服务器出错了,请刷新重试"
/>
);
}
return children;
}
}
请求库函数
yarn add ahooks --save
ahooks
提供了大量的封装好的Hooks使用,其中useRequest
更是一个强大异步数据管理的Hooks,一般情况下的请求场景都是足够用的。
注意事项
遇到的问题
测试中,后续遇到问题会陆续跟大家分享,关注更新。
总结
vite实现了真正的按需加载,我们只需要提供页面加载所需的模块,剩下的就让浏览器去做。对于我们来说,打包
不是目的,能够快速的运行
才是我们的目的。配置简单,学习成本也比较低,不需要向webpack一样去加载各种loader。
参考文献
转载自:https://juejin.cn/post/7209977982949359673