认识前端构建工具 Vite

认识前端构建工具 Vite_第1张图片

是什么?

Vite(法语意为 "快速的",发音 /vit/)。由两部分组成:

  • 基于原生 ES 模块的开发服务器,,包括模块热更新(HMR)等功能。vite 解决了webpack中存在的 HMR 速度与随着应用越大而越慢的问题。
  • 基于 Rollup 的配置化的打包器(构建指令),可输出用于生产环境的高度优化过的静态资源。同时使用 esbuild 替代 Rollup 进行预打包(非生产环境),速度很快。

Vite 是 Opinionated 的,通过约定保证了易用性——前端开发者常用的功能都是开箱即用,又提供了配置以保证不会丧失灵活性。和 webpack 是 index.js 不同,vite 中入口文件是 index.html。

vite具有这些优点:

  • 去掉打包步骤,快速的冷启动
  • 及时的模块热更新,不会随着模块变多而使得热更新变慢
  • 真正的按需编译
  • 框架无关的

Bundle vs Bundleless

认识前端构建工具 Vite_第2张图片 

Bundle,通常使用Webpack 打包构建工具来打包构建我们的代码,因为:

  • HTTP/1.1 各浏览器有并行连接限制
  • 浏览器不支持模块系统(如 CommonJS 包不能直接在浏览器运行)
  • 代码依赖关系与顺序管理

Bundleless 出现的原因:

  • 项目不断变大,启动和热更新所等待的时间越来越长
  • HTTP/2.0 多路并用
  • 各大浏览器逐一支持 ESM
  • 原生的解决了代码依赖和复用的问题
  • 越来越多的 npm 包拥抱 ESM

Vite 原理

第三方依赖:这类代码大部分都是不常变化纯JavaScript,Vite2 使用 esbulid 来以 pre-bundle 的方式构建这部分代码,处理速度快。

业务代码:这部分代码通常是常被修改的非纯的 JavaScript(例如:JSX,Vue 等),而且也不需要一次性全部加载(可以根据路由,做代码分割加载)。

因此,Vite 本身只需要启动静态服务器,按需编译浏览器请求的模块代码,动态加载到当前页面中。

Vite 分为开发模式和生产模式。

开发模式:基于原生 ES 模块的开发服务器

当代码中出现 import 的时候,发送一个资源请求,Vite开发服务器拦截请求,根据不同文件类型,在服务端完成模块的改写(比如单文件的解析编译等)和请求处理,实现真正的按需编译,然后返回给浏览器。请求的资源在服务器端按需编译返回,不需要生成bundle。服务器随起随用,所以开发环境下的初次启动是非常快的。而且热更新的速度不会随着模块增多而变慢,因为代码改动后,并不会有bundle的过程。

Vite Server 所有逻辑基本都依赖中间件实现。中间件拦截请求之后进行以下处理:

  • 处理 ESM 语法,比如将业务代码中的 import 第三方依赖路径转为浏览器可识别的依赖路径;

  • 对 .ts、.vue 等文件进行即时编译;

  • 对 Sass/Less 的需要预编译的模块进行编译;

  • 和浏览器端建立 socket 连接,实现 HMR。

生产模式: 利用 Rollup 来打包构建源代码。因为嵌套 import 会导致发送大量的网络请求,即使使用HTTP2,在生产中使用未捆绑的 ESM 仍然效率低下。而且esbuild关于一些应用程序绑定所需的重要特性仍在开发中——特别是代码拆分和CSS处理。目前,Rollup 在这些方面更加成熟和灵活。

Vite 特点

NPM Dependency Resolving 

原生的 ESM 只支持通过一个相对或者绝对路径来引用资源,不支持通过一个包名来引用资源,这和 webpack 相同,也是由 vite 做了 NPM Dependency Resolving 的一层转换。

/** 通过一个包名来引用资源 */
import React from 'react'

Dependency pre-bundling(预打包)

开发阶段,只有在依赖变动时,才会去更新query id使之前的的缓存失效,预打包才需要执行,Vite2 利用 esbuild 替代 Rollup加快构建速度,而且给第三方依赖的包都设置了http 缓存,存放在node_modules/.vite下。

相比 Snowpack, Vite支持更多的特性:

  • 支持多页面
  • 自动分割CSS
  • 支持动态引入polyfill
  • Library Mode
  • 优化的异步chunk加载
  • 官方的 legacy mode plugin 插件,可以生成支持现代/老旧浏览器的双重包,并根据浏览器自动交付正确的包。

Vite + React + TS

1. 创建一个 vite 项目,选择 react-ts 作为预设模板

yarn create @vitejs/app

认识前端构建工具 Vite_第3张图片

认识前端构建工具 Vite_第4张图片 

2. 依次执行安装依赖和启动开发环境命令

yarn          // 安装依赖
yarn dev      // 启动开发环境

3. 目录约定

├── dist/                          // 默认的 build 输出目录
└── src/                           // 源码目录
    ├── assets/                    // 静态资源目录
    ├── config                     
        ├── config.js              // 项目内部业务相关基础配置
    ├── components/                // 公共组件目录
    ├── service/                   // 业务请求管理
    ├── store/                     // 共享 store 管理目录
    ├── until/                     // 工具函数目录
    ├── pages/                     // 页面目录
    ├── router/                    // 路由配置目录
├── .main.tsx                      // Vite 依赖主入口
├── .env                           // 环境变量配置
├── vite.config.ts                 // vite 配置选型,具体可以查看官网 api
└── package.json

4. 配置路由 router/index.ts(还包括重定向(redirect)、懒加载等)

import BlogsList from '@/pages/blogs/index'
import BlogsDetail from '@/pages/blogs/detail'

export default {
  routes: [
    { exact: true, path: '/', component: BlogsList },
    { exact: true, path: '/blogs/detail/:article_id', component: BlogsDetail },
  ],
}

5. 改造 main.tsx

import React from 'react'
import ReactDOM from 'react-dom'
import { HashRouter, Route, Switch } from 'react-router-dom'
import routerConfig from './router/index'
import './base.less'

ReactDOM.render(
  
    
      
        {
          routerConfig.routes.map((route) => {
            return (
              
            )
          })
        }
      
    
  ,
  document.getElementById('root')
)

6. service 管理

所有项目请求都放入 service,建议每个模块都有对应的文件管理:

import * as information from './information'
import * as base from './base'

export {
  information,
  base
}

base.ts 作为业务请求类:

import { request } from '../util/request'

const prefix = '/api'

export const getAllInfoGzip = () => {
  return request({
    url: `${prefix}/apis/random`,
    method: 'GET'
  })
}

util/request 作为统一引入的请求方法,可以自定义替换成 fetch、axios 等请求库,同时可以在此方法内封装通用拦截逻辑。 

在具体业务开发使用的时候可以按照模块名引入,容易查找对应的接口模块:

import { information } from "@/service/index";

const { data } = await information.getAllInfoGzip({ id });

7. vite.config.ts 配置

import { defineConfig } from 'vite'
import reactRefresh from '@vitejs/plugin-react-refresh'
import vitePluginImp from 'vite-plugin-imp'

export default defineConfig({
  plugins: [
    reactRefresh(),
    /** 将 antd-mobile 进行按需加载 */
    vitePluginImp({
      libList: [
        {
          libName: 'antd-mobile',
          style(name) {
            return `antd-mobile/lib/${name}/style/index.css`
          },
        },
      ]
    })
  ],
  resolve: {
    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'],
    /** 配置别名,如果需要 vscode 正常识别的话,也需要配置 tsconfig.json */
    alias: {
      '@': '/src'
    }
  },
  server: {
    /** 配置项目代理 */
    proxy: {
      // 选项写法
      '/api': {
        target: 'https://www.xxx.xxx',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      },
    }
  },
  css: {
    postcss: {
      plugins: [
        /** 配置移动端 px 转换的插件 */
        require('postcss-pxtorem')({ // 把px单位换算成rem单位
          rootValue: 32, // 换算基数,默认100,这样的话把根标签的字体规定为1rem为50px,这样就可以从设计稿上量出多少个px直接在代码中写多上px了。
          propList: ['*'], //属性的选择器,*表示通用
          unitPrecision: 5, // 允许REM单位增长到的十进制数字,小数点后保留的位数。
          exclude: /(node_module)/,  // 默认false,可以(reg)利用正则表达式排除某些文件夹的方法
        })
      ]
    }
  }
})

tsconfig.json:

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
}

你可能感兴趣的:(前端,Vite)