写在前面

欢迎关注公众号“燕小书”,回复:“技术交流”进微信技术交流群,公众号会陆续发布优质文章。

为什么要使用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三种模式

  1. npm create vite@latest // 使用npm
  2. yarn create vite // 使用yarn
  3. pnpm create vite // 使用pnpm

这里选择yarn,创建了一个基于vite构建的react项目
image.png
生成的目录结构如下:

  1. .gitignore
  2. index.html
  3. package.json
  4. tsconfig.json
  5. tsconfig.node.json
  6. vite.config.ts
  7. ├─public
  8. vite.svg
  9. └─src
  10. App.css
  11. App.tsx
  12. index.css
  13. main.tsx
  14. vite-env.d.ts
  15. └─assets
  16. react.svg

其中index.html为页面的入口文件,main.tsx为系统主入口,vite.config.ts为vite的配置文件。

  1. yarn install // 安装依赖
  2. yarn dev // 启动项目

image.png
这里我们根据脚手架启动的项目就已经运行起来了。

初始化配置

在项目开始之前,引入项目的所需的react-router、ant design、axios、less or sass并使用git作为版本管理工具。

vite的按需加载

Vite需要借助插件vite-plugin-imp来实现按需加载

  1. yarn add vite-plugin-imp -dev
  1. import { defineConfig } from 'vite'
  2. import react from '@vitejs/plugin-react'
  3. import vitePluginImp from '@vitejs/plugin-react'
  4. // https://vitejs.dev/config/
  5. export default defineConfig({
  6. plugins: [
  7. react(),
  8. vitePluginImp({
  9. libName: "antd",
  10. style: (name: string) => `antd/lib/${name}/style/index.less`,
  11. })
  12. ],
  13. })

配置antd主题颜色

  1. import fs from 'fs'
  2. import path from 'path'
  3. import { getThemeVariables } from 'antd/dist/theme'
  4. import lessVarsToJs from 'less-vars-to-js'
  5. // 解析你的配置
  6. const antdThemeConfig = lessVarsToJs(
  7. fs.readFileSync(path.resolve(__dirname, 'src/assets/antdTheme.less'))
  8. )
  9. export default defineConfig({
  10. css: {
  11. preprocessorOptions: {
  12. less: {
  13. // 支持内联 JavaScript
  14. javascriptEnabled: true,
  15. modifyVars: {...getThemeVariables(), ...antdThemeConfig}
  16. }
  17. }
  18. }
  19. })

简写路径配置

  1. resolve: {
  2. alias: {
  3. "@": path.resolve(__dirname, 'src')
  4. }
  5. },

ts的路径解析能力

  1. import tsConfigPaths from 'vite-tsconfig-paths'
  2. export default {
  3. plugins: [
  4. tsConfigPaths(),
  5. ],
  6. }

版本控制

  1. import tsConfigPaths from 'vite-plugin-package-version'
  2. export default {
  3. plugins: [
  4. pluginPackageVersion(),
  5. ],
  6. }

端口配置

默认端口是5173,可以改成你喜欢的3000。

  1. export default {
  2. server: {
  3. port: 3000,
  4. strictPort: true,
  5. },
  6. }

vite.config.ts最终配置

  1. import fs from 'fs'
  2. import path from 'path'
  3. import { getThemeVariables } from 'antd/dist/theme'
  4. import { defineConfig } from 'vite'
  5. import react from '@vitejs/plugin-react'
  6. import vitePluginImp from '@vitejs/plugin-react'
  7. import tsConfigPaths from 'vite-tsconfig-paths'
  8. import pluginPackageVersion from 'vite-plugin-package-version'
  9. import lessVarsToJs from 'less-vars-to-js'
  10. const antdThemeConfig = lessVarsToJs(
  11. fs.readFileSync(path.resolve(__dirname, 'src/assets/antdTheme.less'))
  12. )
  13. // https://vitejs.dev/config/
  14. export default defineConfig({
  15. plugins: [
  16. react(),
  17. tsConfigPaths(),
  18. pluginPackageVersion(),
  19. vitePluginImp({
  20. libName: "antd",
  21. style: (name: string) => `antd/lib/${name}/style/index.less`,
  22. })
  23. ],
  24. css: {
  25. preprocessorOptions: {
  26. less: {
  27. // 支持内联 JavaScript
  28. javascriptEnabled: true,
  29. modifyVars: {...getThemeVariables(), ...antdThemeConfig}
  30. }
  31. }
  32. },
  33. server: {
  34. port: 3000,
  35. strictPort: true,
  36. },
  37. resolve: {
  38. alias: {
  39. "@": path.resolve(__dirname, 'src')
  40. }
  41. },
  42. })
  43. v

tsconfig.json

  1. {
  2. "compilerOptions": {
  3. "composite": true,
  4. "module": "ESNext",
  5. "moduleResolution": "Node",
  6. "allowSyntheticDefaultImports": true,
  7. "baseUrl": ".",
  8. "paths": {
  9. "@/*": ["./src/*"]
  10. }
  11. },
  12. "include": ["vite.config.ts"]
  13. }

添加prettier格式化代码格式

  1. yarn add prettier --save

增加prettier.config.ts文件

  1. module.exports = {
  2. printWidth: 80,
  3. tabWidth: 2,
  4. useTabs: false,
  5. semi: false,
  6. singleQuote: true,
  7. quoteProps: 'as-needed',
  8. jsxSingleQuote: false,
  9. trailingComma: 'es5',
  10. bracketSpacing: true,
  11. bracketSameLine: false,
  12. arrowParens: 'always',
  13. htmlWhitespaceSensitivity: 'ignore',
  14. vueIndentScriptAndStyle: true,
  15. endOfLine: 'lf',
  16. }

React Router配置

  1. React Router配置// src/App.tsx
  2. import { Suspense } from 'react';
  3. import { BrowserRouter, Routes, Route } from 'react-router-dom';
  4. import { ConfigProvider, Layout, Result } from 'antd';
  5. import zhCN from 'antd/es/locale/zh_CN';
  6. import ROUTES, { IRoute } from './routes';
  7. import ErrorBoundary from './components/ErrorBoundary';
  8. import './App.css';
  9. function FullBack() {
  10. return <div>加载中...</div>;
  11. }
  12. function App() {
  13. return (
  14. <div className="App">
  15. <ConfigProvider locale={zhCN}>
  16. <BrowserRouter>
  17. <Layout>
  18. <ErrorBoundary>
  19. {/* 使用懒加载会导致加载延迟,使用suspense优化体验 */}
  20. <Suspense fallback={<FullBack />}>
  21. <Routes>
  22. {ROUTES?.map((route) => {
  23. return (
  24. <Route
  25. key={route.url}
  26. path={route.url}
  27. element={route.Element}
  28. />
  29. );
  30. })}
  31. <Route
  32. path="*"
  33. element={
  34. <div style={{ marginTop: 80 }}>
  35. <Result
  36. status="404"
  37. title="404"
  38. subTitle="抱歉,您访问的资源不存在"
  39. />
  40. </div>
  41. }
  42. />
  43. </Routes>
  44. </Suspense>
  45. </ErrorBoundary>
  46. </Layout>
  47. </BrowserRouter>
  48. </ConfigProvider>
  49. </div>
  50. );
  51. }
  52. export default App;
  1. // src/routes
  2. import { lazy } from 'react';
  3. export interface IRoute {
  4. path: string;
  5. Element?: React.ReactElement;
  6. }
  7. const Home = lazy(() => import('./../pages/Home'));
  8. const Test = lazy(() => import('./../pages/Test'));
  9. const ROUTES: Array<IRoute | undefined> = [
  10. {
  11. path: 'home',
  12. Element: <Home />,
  13. },
  14. {
  15. path: 'test',
  16. Element: <Test />,
  17. },
  18. ];
  19. export default ROUTES;
  1. // src/components/ErrorBoundary
  2. import React, { Component } from 'react';
  3. import { Result } from 'antd';
  4. export default class ErrorBoundary extends Component<{
  5. children: React.ReactNode;
  6. hasError?: boolean;
  7. }> {
  8. static getDerivedStateFromError() {
  9. return {
  10. hasError: true,
  11. };
  12. }
  13. state = { hasError: false };
  14. render() {
  15. const { children } = this.props;
  16. const { hasError } = this.state;
  17. if (hasError) {
  18. return (
  19. <Result
  20. status="500"
  21. title="500"
  22. subTitle="抱歉,服务器出错了,请刷新重试"
  23. />
  24. );
  25. }
  26. return children;
  27. }
  28. }

请求库函数

  1. yarn add ahooks --save

[ahooks](https://ahooks.gitee.io/zh-CN/guide)提供了大量的封装好的Hooks使用,其中useRequest更是一个强大异步数据管理的Hooks,一般情况下的请求场景都是足够用的。

遇到的问题

测试中,后续遇到问题会陆续跟大家分享,关注更新。

总结

vite实现了真正的按需加载,我们只需要提供页面加载所需的模块,剩下的就让浏览器去做。对于我们来说,打包不是目的,能够快速的运行才是我们的目的。配置简单,学习成本也比较低,不需要向webpack一样去加载各种loader。

参考文献

Vite 的好与坏
Vite 开发实践
Vite 下一代的前端工具链