vite 与 react 的结合

6,863 阅读7分钟

vite, 法语词, 快的意思. 读音为 /vit/

先来看看之前的先驱: parcel, rollup, webpack.这些工具的目的便是为了打包, 为了前端能有模块化. 为了前端能承担更复杂的逻辑, 不被后端瞧不起.为了证明js是宇宙第一的语言, 为了.... 简单总结下:

  1. 配置项
    • parcel最方便, 几乎不需要任何配置, install之后build即可.
    • webpack和rollup都需要指定entry, output,loaders, plugins, transformations等 -是rollup基于的是node的模块系统, 可以直接使用import/output, 这也是为什么使用rollup需要在版本10以上的原因. 而且webpack不能
    • webpack必须使用绝对路径, 这也是为什么使用path.resolve的原因 rollup可以使用相对路径
  2. tree-shaking
    • parcel同时支持对commonjs以及es6的树摇. 这是很大的优势, 因为npm上还有很多是commonjs的库, 而且parcel在树摇时是平行摇的, 可以充分利用好多核的优势
    • rollup静态解析导入的代码, 自动排除未引入部分, 配置较少
    • webpack配置较为复杂, 必须使用es6的语法, webpack4在生产环境默认启用树摇了, 否则还需要配置uglifyPlugin插件
  3. 代码分段 code-split
    • webpack更优, 一整套配置, 可以较为精细化的控制, 参见官方示例
    • rollup使用浏览器内建的ES模块系统, 由浏览器来进行控制
    • parcel支持零配置的代码分段, 是通过动态导入语法import()来实现的, 该函数返回一个promise
    • 后两者issue和bug的报告比较多, 所以安全起见, webpack更为稳妥, 在compile的时候也是webpack最快.
  4. live reload 实时重载
    • 不用多说, 这是前端开发必备的功能 如果一个code的变化还需要我去按刷新的话, 估计我的command + R键会被按爆.
    • parcel自带有开发服务器, 但是在使用HTTP log hooks 和中间件的时候容易出问题
    • rollup需要安装rollup-plugin-serverrollup-plugin-livereload
    • webpack只需要安装webpack-dev-server即可, 就自定义性来说(配置钩子, 中间件), webpack碾压其余两个
  5. HMR
    • 这也是开发不可少的, 特别是前端业务越来越重, 每次都要回首页重新点击简直痛苦(论首屏时间的重要性), 特别是在改css的时候(改一个字体大小你给我把整个页面都刷了??)
    • webpack简单配置一下, 在需要热更新的地方加上module.accept即可, 相比于其他两者, 更成熟, 自由度更好
    • parcel再一次内建了热更新机制
    • rollup需要rollup-plugin-hotreload插件
  6. 模块转换
    • 将非js格式转化为js格式(bundler只认js)
    • webpack就是一堆loader的配置
    • parcel再一次对常见的格式提供了内建的转换和转译, 无需配置和安装插件

总结来说 如果是一个demo或者功能是可预期的简单(不是产品说的那种), 使用parcel 如果是最小化的第三方导入的一个库, 使用rollup 其余的使用webpack

说了一堆没用的

前端总是有更多更大的轮子被创造出来, 挺好, 百花齐放, 百家争鸣, 百舸争流, 百依百顺. 大佬总是很闲, 无法理解底层搬砖的辛苦, 尤大在vue3.0时着重提了一把, 既然轮子都到眼前了,还是抡一抡.

首先看看vite的描述 开发环境下使用原生esm,生产环境使用Rollup打包 的一个网页开发构建工具 很自然继承了二者的优点:

  1. esm的优点

    • 即时热模块替换(HMR)
  2. Rollup的优点

    • 按需编译, 更多特性参照前文

esm是基于浏览器的实现的,当然该标准在ES6中已经提出,兼容性请参考 mdn, 这里仅仅简单的提几点:

  1. 原生模块的导出和引用

test.mjs

export const test = () => {
    console.log('test')
}

index.js

import { test } from "./test.mjs";
test()

index.html

<script src="./index.js" type="module"></script>

上述代码不需要经过任何的babel或webpack,直接可以被浏览器运行并打印test

  1. 可以导出函数,var,const,let,类
  2. mjsjs均可, 只是mdn官方推荐使用js先, 因为很多服务器的content-type不认识mjs, 会导致解析出错.所以沿用js即可
  3. 动态加载模块, 实现懒加载
btn.addEventListener('click', () => {
    import('./someDynamicModule').then(module => {
        // handleModule
    })
})

与webpack的比较:

  1. Vite没有在开发阶段进行打包的操作, 源代码中的ES导入语法是直接从浏览器中实现的. 浏览器通过之前说的<script type="module">来实例化他们, 每一个的导入都是一个HTTP请求.
  2. 开发环境需要做的事情就是拦截并进行代码转换浏览器无法解析的格式, 比如 '*.vue, *.jsx'等.相当于webpack的loader.但是仅仅只有loader的作用, 其他的并不涉及
  3. 生产环境Vite依然使用了bundle, 但用的是更轻量的rollup而不是webpack, 具体这两者的渊源请参见

这样的好处是什么呢?

  1. 不用打包, 所以冷启动很快
  2. 代码按需编译, 天然的树摇和懒加载
  3. HMR从依赖整个模块的数量中解耦出来所以不会因为app的增大而导致HMR的速度降低,

第三点我想着重说一下, webpack的HMR之所以需要设置module.accept就是希望开发者能在真的需要热更新的地方使用, 因为一旦监听, 就会监听这个模块以及这个模块关联的所有依赖的变化, 这是比较消耗资源的, 而且一旦发现变化会将整个模块以及关联的模块都重新编译一次. 那么你说是不是很耗时间和性能?如果可以对每个单独的模块进行监听和重编译, 是不是美滋滋?

不过也不是没有缺点

  1. 在整个网页重新加载的时候会比webpack慢, 因为是基于原生es, 需要在遍历到<script type="module">的时候发一次网络请求, 并且在那个模块中有可能还有其他的模块, 还得继续发网络请求.但是在首次之后就可以使用缓存了, 也不是不可以接受, 而且也可以选择在开发环境时使用rollup进行打包.

下面来实战一下, 因为vue的独占期, 所以对vue的项目是无缝结合的, 但是我在工作中更多的是应用到了react, 以下代码为与react的结合

  1. 新建项目 yarn init -y
  2. 安装依赖yarn add @pika/react @pika/react-dom vite @pika是esm模块的前缀, 也就是react和react-dom的esm模块形式
  3. 添加react的支持配置 vite.config.js
module.exports = {
    jsx: 'react',
    plugins: [require('vite-plugin-react')]
}
  1. 项目根目录创建index.html, 如下
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <link rel="icon" href="/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vite App</title>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.tsx"></script>
</body>
</html>
  1. 新建src/main.tsx, 如下
import React from 'react'
import style from './index.module.scss'
export default function() {
    const [count, setCount] = React.useState(1)
    return (
        <div>
            hello world
            <button className={style.button} onClick={() => setCount(count + 1)}>
                count: {count}
            </button>
        </div>
    )
}
  1. 关于css的模块化和预处理器 实际上vite建议使用原生的css变量, 因为他的目标是现代浏览器. 如果非要使用预处理器也可以, 有三种方案, 请参考这篇文章, 决定采用生成d.ts的方案, 因为这样可以有代码提示, 更香.于是选择安装typed-scss-modules以及sass, 加个"tsm": "tsm src -w"即可.这里要注意, 使用模块化的引入, 也就是import style from 'xxx.scss'需要遵循vue中的命名规范, 需要加一个module后缀, 也就是必须命名为xxx.module.scss

可以看出整个项目的配置项是相当的少, 很多都黑盒处理了, 让前端可以更关注业务, 之后我会进一步研究其源码, 敬请期待.

附上整个项目地址