likes
comments
collection
share

vite的工程化实践

作者站长头像
站长
· 阅读数 25

前言

为什么要使用vite

在浏览器支持ES模块之前,诸如webpack、Rollup等打包工具的出现,确实是极大的改善了前端开发者的开发体验。但是当我们开始构建越来越大型的应用时,需要处理的js代码量也是呈指数级增长,模块和模块之间的依赖往往会使我们的开发工具需要更长的时间(甚至使几分钟)才能启动开发服务器,即使使用HMR,文件的修改后的效果也需要几秒钟才能在浏览器中反映出来。这也会我们要是用vite的原因,提示构建速率来改善开发体验。

vite VS 传统脚手架

下面主要是介绍一下vite和webpack的区别

vitewebpack
启动开发服务器利用浏览器的原生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项目

vite的工程化实践

生成的目录结构如下:

│  .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 // 启动项目

vite的工程化实践

初始化配置

在项目开始之前,引入项目的所需的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。

参考文献

Vite 的好与坏

Vite 开发实践

Vite 下一代的前端工具链