美文网首页
【vite】构建标准化react应用

【vite】构建标准化react应用

作者: sssgoEasy | 来源:发表于2021-09-02 20:32 被阅读0次

    背景

    之前公司项目采用的是umi脚手架一体化构建工具,得益于对webpack与各框架的集成和封装,使得快速上手的能力大大加强,但是随着项目的不断迭代与功能增加,依赖的库也是越来越多,目前最明显的感受就是每次启动与打包构建的时长,往往是好几分钟~,热更新有时也要耗费数秒,对于开发效率与体验影响很大。。。

    之前尤大发布vite1.0时也了解了一点,最明显感受就是一个字“快”,不过一直没仔细研究过,只知道是基于`esbuild`和`rollup`,目前`vite2.0`已经发布,完全作为一个独立的构建工具,对`react`等其他非`vue`框架有着很好的支持。最近也算忙里偷闲,算是稍微研究了一下基本知识。本篇文章记录我以`vite`构建`react`的过程及细节,后续会继续深入研究输出`vite`相关系列文章,敬请期待

    目标

    我对构建项目的要求如下:

    • 支持Typescript
    • 支持ReactJSX语法
    • 支持ES6语法
    • 支持Less module
    • 支持EslintPrettierPre-commit hook
    • 支持HMR快速热更新
    • 支持Antd按需引入与主题样式覆盖
    • 支持Proxy代理、alias别名
    • 兼容传统浏览器
    • 开发启动速度要够快,以秒计算
    • 支持懒加载和chunk分割

    介绍

    前置条件之一

    浏览器原生支持 ES 模块。

    特点

    • 基于原生 ES 模块,即 <script type="module" >,做到快速加载
    • 使用 Esbuild 预构建依赖 (本地开发环境)
    • 使用 Rollup 打包代码(线上生产环境)
    • HMR 是在原生 ESM 上执行的
    • 利用对 HTTP 头信息的控制,优化缓存与重加载,高效率利用浏览器能力。
    • 开箱即用,内置多种支持,如:Typescript支持、JSX支持、CommonJSUMD兼容、css预处理器与css modules

    vite对模块的分类

    • 依赖
      • 在开发时不会变动的纯 JavaScript。(如第三方依赖antdlodash等)
      • 在该场景采用具有优势的 Esbuild 处理。(大量模块的组件库、CommonJS格式的文件等)
    • 源码
      • 通常包含一些并非直接是 JavaScript 的文件,需要转换,时常会被编辑。(JSXCSSVue/React组件等)
      • 会根据路由拆分代码按需加载模块
      • Vite 以 原生 ESM 方式提供源码

    概念

    预构建:将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能。简单来说就是尽量合并与减少请求。

    例如:当我们引入的一个第三方模块依赖了大量其他模块时,在不合并请求的情况下,会请求上百次不等,造成网络拥塞影响性能,而通过预构建合并后只需要一个请求即可。(lodash-es 有超过个内置模块!当我们执行 import { debounce } from 'lodash-es' 时,浏览器同时发出 600 多个 HTTP 请求!通过预构建 lodash-es 成为一个模块,我们就只需要一个 HTTP 请求了!)

    相较于传统的 webpack 构建工具,先打包构建所有的依赖和项目代码,然后再启动开发服务器。Vite 则利用浏览器对 ESM 的支持,先启动开发服务器,然后再根据代码执行按需加载剩下所需的对应模块。

    因为缓存,在我们第二次启动时几乎可以做到秒开!非常的可怕~

    官网图

    官网图很清晰的描绘了区别:

    Bundle based dev server Native ESM based dev server

    步骤

    项目初始化

    官方支持React模板预设有:reactreact-ts,因为我需要Typescript,所以直接用这个模板,省事了~

    # npm 6.x
    npm init @vitejs/app my-react-app --template react-ts
    
    # npm 7+, 需要额外的双横线:
    npm init @vitejs/app my-react-app -- --template react-ts
    
    # yarn
    yarn create @vitejs/app my-react-app --template react-ts
    

    引入react三件套

    这里有兴趣的可以尝试下 pnpm 包管理工具,安装速度很快,不了解的可以查看pnpm官方文档,相较于传统的npmyarn工具都有很好的性能提升与使用体验,这里不做过多介绍,放张图大家体会下~

    注:就目前我的使用情况来看大部分场景几乎都没问题的,不过还是存在一小部分问题。如:安装precommit后Git hooks不生效等。

    安装速度比较

    安装依赖

    # pnpm
    pnpm add react react-dom react-router-dom
    # or npm
    npm i react react-dom react-router-dom
    

    创建页面

    src目录下创建pages目录放置页面组件模块,然后我们简单写两个页面测试下:

    // pages/Home/index.tsx
    import React from 'react';
    
    const Home: React.FC = () => <div> Home </div>;
    
    export default Home;
    
    // pages/About/index.tsx
    import React from 'react';
    
    const About: React.FC = () => <div> About </div>;
    
    export default About;
    

    修改文件App.tsx

    // App.tsx
    import React, { Suspense } from 'react';
    import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
    import Home from './pages/Home'
    import About from './pages/About'
    
    const App = () => {
      return (
        <Suspense fallback={<span>loading</span>}>
          <Router>
            <Switch>
              <Route key="/home" path="/home" component={Home}></Route>
              <Route key="/about" path="/about" component={About}></Route>
            </Switch>
          </Router>
        </Suspense>
      );
    };
    
    export default App;
    
    vite-react-app-1 vite-react-app-2

    配置路由/界面

    新建layouts组件,主要用于区别渲染登录注册页面布局界面:

    layouts/BasicLayout.tsxlayouts/UserLayout.tsx

    这里就不一一做展示了,详细代码见仓库,地址贴在下面了。

    新建路由配置文件router/index.ts:

    import React from 'react';
    
    const Page404 = React.lazy(() => import('../pages/404'));
    const Home = React.lazy(() => import('../pages/Home'));
    const Login = React.lazy(() => import('../pages/User/Login'));
    const Register = React.lazy(() => import('../pages/User/Register'));
    
    const routes: IRoute[] = [
      {
        path: '/user',
        component: React.lazy(() => import('../layouts/UserLayout')),
        meta: {
          title: '用户路由',
        },
        redirect: '/user/login',
        children: [],
      },
      {
        path: '/',
        component: React.lazy(() => import('../layouts/BasicLayout')),
        meta: {
          title: '系统路由',
        },
        redirect: '/home',
        children: [
          {
            path: '/home',
            meta: {
              title: '首页',
              icon: 'home',
            },
            component: <Home />,
          },
          {
            path: '/about',
            meta: {
              title: '关于',
              icon: 'about',
            },
            component: <About />,
          },
        ],
      },
    ]
    
    export default routes;
    

    创建store状态管理文件

    react hooks诞生后,大部分场景使用hooksprops进行状态管理基本可以满足多数需求,少部分全局应用信息与用户信息等需要全局状态管理的,这里我觉得也不需要完整引入一个reduxmobx等这种库。当然还要结合具体场景和公司技术栈确定,较大型和复杂的项目等视情况而定~

    我这里使用了zustand做了简单配置,使用起来也是比较的简单,详情参见官方文档

    // 创建store
    import create from 'zustand'
    
    const useStore = create(set => ({
      bears: 0,
      increasePopulation: () => set(state => ({ bears: state.bears + 1 })),
      removeAllBears: () => set({ bears: 0 })
    }))
    
    // 组件绑定
    function BearCounter() {
      const bears = useStore(state => state.bears)
      return <h1>{bears} around here ...</h1>
    }
    
    function Controls() {
      const increasePopulation = useStore(state => state.increasePopulation)
      return <button onClick={increasePopulation}>one up</button>
    }
    

    引入Antd组件库并配置按需加载

    这里就不废话了,直接展示如何在vite中配置antd的按需加载,首先我们安装一个插件:

    pnpm add vite-plugin-imp -D

    然后在vite.config.ts文件的plugins中添加配置vitePluginImp

    这里我们顺势再引入一个less-vars-to-js包,less-vars-to-js可以将less文件转化为json键值对的形式,当然你也可以直接在modifyVars属性后写json键值对。这样做的好处是可以把全局配置统一放到config文件进行管理,方便维护。

    自定义覆盖主题色

    config/variables.less // @primary-color: '#ff7875';

    import reactRefresh from '@vitejs/plugin-react-refresh';
    import lessToJS from 'less-vars-to-js';
    import path from 'path';
    import { defineConfig } from 'vite';
    // vite-plugin-imp 该插件按需加载存在部分样式丢失的情况
    // import vitePluginImp from 'vite-plugin-imp';
    // 由于 vite 本身已按需导入了组件库,因此仅样式不是按需导入的,因此只需按需导入样式即可。
    import styleImport from 'vite-plugin-style-import';
    
    const themeVariables = lessToJS(
      fs.readFileSync(path.resolve(__dirname, './config/variables.less'), 'utf8'),
    );
    
    export default defineConfig({
      base,
      plugins: [
        reactRefresh(),
        // 配置按需引入antd
        // vitePluginImp({
        //   libList: [
        //     {
        //       libName: 'antd',
        //       style: (name) => `antd/es/${name}/style/index.less`,
        //     },
        //   ],
        // }),
        styleImport({
          libs: [
            {
              libraryName: 'antd',
              esModule: true,
              resolveStyle: (name) => {
                return `antd/es/${name}/style/index`;
              },
            },
          ],
        }),
      ],
      css: {
        preprocessorOptions: {
          less: {
            // 支持内联 JavaScript,支持 less 内联 JS
            javascriptEnabled: true,
            // 重写 less 变量,定制样式
            modifyVars: themeVariables,
          },
        },
      }
    })
    
    vite-img-4

    环境变量

    方案一

    通过 --mode 注入配置参数以匹配测试/开发环境等。

    我们修改下package.json文件:

    scripts: {
      "build:beta": "vite build --mode beta",
      "build:release": "vite build --mode release",
      "build:legacy ": "vite build --mode legacy ",
    }
    

    node环境下直接process.argv即可获取到,我们可以在vite.config.ts中打印信息查看

    // vite.config.ts
    import {defineConfig} from 'vite'
    
    const env = process.argv[process.argv.length - 1];
    console.log('env:', env);
    
    export default defineConfig({})
    

    方案二

    使用函数式写法配置动态获取环境变量等参数

    首先在根目录下创建.env文件

    # port
    VITE_PORT = 3100
    
    # HTTP API
    VITE_HTTP_API = http://127.0.0.1:8000
    
    # title
    VITE_APP_TITLE = Vite React App
    

    然后我们调整下 vite.config.ts 文件

    // 函数式配置
    import { loadEnv } from 'vite';
    import type { ConfigEnv, UserConfig } from 'vite';
    
    export default ({ command, mode }: ConfigEnv): UserConfig => {
      const root = process.cwd();
      const env = loadEnv(mode, root);
    
      console.log('env', env);
      console.log('command', command);
      console.log('mode', mode);
    }
    

    组件内可通过import.meta.env获取,我们可以在Home/index.tsx中打印信息查看

    // Home/index.tsx
    import React from 'react'
    import { Button } from 'antd'
    
    const Home: React.FC = () => {
      console.log('import.meta.env', import.meta.env)
      return <div>
        <Button type='primary'>Home</Button>
      </div>
    }
    export default Home
    

    alias 别名设置

    export default defineConfig({
        ...
      resolve: {
        alias: [
          { find: /^~/, replacement: path.resolve(__dirname, './') },
          { find: '@', replacement: path.resolve(__dirname, 'src') },
        ],
        // alias: {
        //   '~': path.resolve(__dirname, './'), // 根路径
        //   '@': path.resolve(__dirname, 'src') // src 路径
        // }
      }
      ...
    })
    

    proxy代理配置

    export default defineConfig({
        ...
        server: {
        port: 8080, // 开发环境启动的端口
        proxy: {
          '/api': {
            // 当遇到 /api 路径时,将其转换成 target 的值
            target: 'http://127.0.0.1:8080/api/',
            changeOrigin: true,
            rewrite: (path) => path.replace(/^\/api/, ''), // 将 /api 重写为空
          },
        }
      }
    })
    

    兼容传统浏览器

    vite默认只支持现代浏览器,即ES module,对于IE等老版本浏览器,可使用@vitejs/plugin-legacy插件在build时进行polyfill

    yarn add @vitejs/plugin-legacy -D

    // vite.config.js
    import legacy from '@vitejs/plugin-legacy'
    
    export default {
      plugins: [
        legacy({
          targets: ['ie >= 11'],
          additionalLegacyPolyfills: ['regenerator-runtime/runtime']
        })
      ]
    }
    

    Eslint、Prettier、Stylelint 代码检查与格式化

    使用eslint这种代码检查工具是为了更好的规范代码和写法,列如:禁用var,建议使用constlet,以及配合TS使用时对各种类型规范等,对于代码优化与后期维护都很方便。

    而格式化对于多人协同开发时,统一代码风格很有用。

    VSCode 集成

    插件规则遵循就近原则,会优先启用本项目下的配置文件,当前配置会覆盖全局配置,如果没有就采用全局配置。

    source.fixAll.eslint 开启后可使编辑器按照eslint规则 auto fix

    vscode 中安装 EslintPrettier 插件,并开启功能,推荐在全局配置(或当前工作目录下)中添加:

      // file: vscode setting.json
      // onSave
      "editor.formatOnSave": true, //每次保存的时候自动格式化
      // eslint
      "eslint.alwaysShowStatus": true,  // 总是在 VSCode 显示 ESLint 的状态
      "eslint.quiet": true,             // 忽略 warning 的错误
      "editor.codeActionsOnSave": {     // 保存时使用 ESLint 修复可修复错误
          "source.fixAll": true,
          "source.fixAll.eslint": true
      },
      // prettier
      "[javascript]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[javascriptreact]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[typescript]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[typescriptreact]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[json]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[vue]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[html]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[css]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[less]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[scss]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[yaml]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
    

    为了方便配置,可以使用 VsCode插件 setting sync(该插件通过GitHub Gist ID 绑定,实现云同步上传下载),通过GitHub Gist ID云同步配置和插件。

    git commit 集成

    git commit原理:

    git在执行的过程会提供相关钩子函数,如commitpush时,在项目目录下的.git目录下的hooks文件夹下存在相关git生命周期的钩子函数配置,我们可以手动自己配置,只需删除后缀.sample即可,不过因为这是本地文件,对于团队协同开发同步配置不太友好,所以还是建议采用第三方库统一管理。

    hooks文件夹./.git/hooks/

    vite-react-app-2
    1. 我们先在项目根目录下创建.husky文件夹

    2. 然后添加文件pre-commit

      #!/bin/sh
       . "$(dirname "$0")/_/husky.sh"
       npx --no-install lint-staged
      
    3. package.json 中添加如下配置:

      script 配置 postinstall是为了确保在安装依赖完成后,npm可以执行postinstall钩子,使husky安装配置文件到.husky文件下,npm在安装执行的过程提供了一些生命周期钩子,postinstall、prepare等。

      {
        "script": {
          "postinstall": "husky install",
          "lint:fix": "eslint --cache --ext .js,.jsx,.ts,.tsx --no-error-on-unmatched-pattern --quiet --fix ./src",
          "lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
        },
        "lint-staged": {
          "**/*.{js,jsx,tsx,ts,json}": [
            "npm run lint:fix",
            "git add --force"
          ],
          "**/*.{less}": [
            "npm run lint:style",
            "git add --force"
          ]
        },
      }
      
    4. 再安装 huskylint-staged 依赖库,

    在提交代码时,lint git 暂存区的代码,若 lint 不通过则中断提交,保证问题代码不进入代码仓库。

    目录结构

    ├── dist                                // 默认的 build 输出目录
    ├── config                              // 全局配置文件
    └── src                                 // 源码目录
        ├── assets                          // 公共的文件(如image、css、font等)
        ├── components                      // 项目组件
        ├── constants                       // 常量/接口地址等
        ├── layout                          // 全局布局
        ├── routes                          // 路由
        ├── store                           // 状态管理器
        ├── utils                           // 工具库
        ├── pages                           // 页面模块
            ├── Home                        // Home模块,建议组件统一大写开头
            ├── ...
        ├── App.tsx                         // react顶层文件
        ├── main.ts                         // 项目入口文件
        ├── typing.d.ts                     // ts类型文件
    ├── .editorconfig                       // IDE格式规范
    ├── .env                                // 环境变量
    ├── .eslintignore                       // eslint忽略
    ├── .eslintrc                           // eslint配置文件
    ├── .gitignore                          // git忽略
    ├── .npmrc                              // npm配置文件
    ├── .prettierignore                     // prettierc忽略
    ├── .prettierrc                         // prettierc配置文件
    ├── .stylelintignore                    // stylelint忽略
    ├── .stylelintrc                        // stylelint配置文件
    ├── index.html                          // 项目入口文件
    ├── LICENSE.md                          // LICENSE
    ├── package.json                        // package
    ├── pnpm-lock.yaml                      // pnpm-lock
    ├── postcss.config.js                   // postcss
    ├── README.md                           // README
    ├── tsconfig.json                       // typescript配置文件
    └── vite.config.ts                      // vite
    

    项目地址

    项目Github地址

    【vite】构建标准化react应用 | 小帅の技术博客 (ssscode.com)

    参考

    vitejs

    Vite 2.0 + React + Ant Design 4.0 搭建开发环境

    zustand

    如何为你的 Vue 项目添加配置 Stylelint

    从零配置 Eslint + Prettier + husky + lint-staged 构建前端代码工作流

    ESLint 使用指南

    深入浅出eslint——关于我学习eslint的心得

    在 pre-commit 的钩子中运行 npm script

    @umijs/fabric

    相关文章

      网友评论

          本文标题:【vite】构建标准化react应用

          本文链接:https://www.haomeiwen.com/subject/bxvcwltx.html