Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vite笔记 #50

Open
lihongxun945 opened this issue Dec 20, 2021 · 0 comments
Open

Vite笔记 #50

lihongxun945 opened this issue Dec 20, 2021 · 0 comments

Comments

@lihongxun945
Copy link
Owner

lihongxun945 commented Dec 20, 2021

认识javascript modules

Vite 主要是用了 js modules,在认识 vite 之前,我们先简单学习一下 javascript modules。 MDN 的 javascript modules 文档 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#aside_%E2%80%94_.mjs_versus_.js

简单的说,支持javascript modules的浏览器,可以通过script标签引入一个 esm 模块,并且支持其中的importexport语法,在解析到对应的语法后会在浏览器中动态加载对应的js文件。

我们写一个简单的测试应用来理解什么是 javascript modules,代码都在这里 https://github.com/lihongxun945/javascript-modules-test

入口文件 index.html,通过script标签加载一个模块:

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

index.js引用了另一个模块,并初始化:

import Person from './person.js';
const person = new Person();

person.js文件直接导出一个 Person类:

export default function Person () {
  console.log('person');
}

启动一个静态服务器,直接访问 index.html,可以发现上述代码无需任何编译,在浏览器中可以直接运行。且浏览器会通过网络请求分别加载 index.jsperson.js

认识vite

为什么需要vite

浏览器已经原生支持了 javascript modules,为什么还要vite呢?有以下几个原因:

  1. 对非JS文件,比如 css、图片等的支持
  2. 对非 esm 语法的支持
  3. 通过 bundle 优化性能,可以把多个小文件合并成大文件避免浏览器加载成百上千个文件
    当然还有一些其他原因,比如生产环境打包、HMR、chunks等

vite 目录结构

modules
vite 默认把index.html放在了项目的根目录,这和我们的webpack项目放在public中有挺大区别。vite 官网上对这个设计做了解释,总结一下主要原因是对于使用 javascript modules的项目来说,index.html 本来就是编译入口文件也是Server的根路径,也就是说既应该放在 src也应该放在 public 中,所以干脆直接放在根目录,这样也不用写 PUBLIC_URL 之类的代码,既符合已有规范还方便,所以就这么写了。

webpack 为啥不这样做? 因为 webpack 的编译入口文件其实是 index.js而不是index.html,而Server的根路径其实是编译后的 index.html,所以就没这么设计。

加载js和CSS

示例中的 index.html通过如下代码加载 main.tsx:

<script type="module" src="/src/main.tsx"></script>

这直接用了 javascript modules 能力
加载的 main.tsx源码是这样的:

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
)

TS显然无法被浏览器识别,而在浏览器中加载的文件已经经过了 vite的编译,编译后的代码如下:

var _jsxFileName = "/Users/hongxun.lhx/github/my-vue-app/src/main.tsx";
import __vite__cjsImport0_react from "/node_modules/.vite/react.js?v=8ca9e3e0"; const React = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react;
import __vite__cjsImport1_reactDom from "/node_modules/.vite/react-dom.js?v=8ca9e3e0"; const ReactDOM = __vite__cjsImport1_reactDom.__esModule ? __vite__cjsImport1_reactDom.default : __vite__cjsImport1_reactDom;
import "/src/index.css";
import App from "/src/App.tsx";
import __vite__cjsImport4_react_jsxDevRuntime from "/node_modules/.vite/react_jsx-dev-runtime.js?v=8ca9e3e0"; const _jsxDEV = __vite__cjsImport4_react_jsxDevRuntime["jsxDEV"];
ReactDOM.render(/* @__PURE__ */ _jsxDEV(React.StrictMode, {
  children: /* @__PURE__ */ _jsxDEV(App, {}, void 0, false, {
    fileName: _jsxFileName,
    lineNumber: 8,
    columnNumber: 5
  }, this)
}, void 0, false, {
  fileName: _jsxFileName,
  lineNumber: 7,
  columnNumber: 3
}, this), document.getElementById("root"));

我们来看一看代码是怎么编译的。
main.tsx依赖的 ReactReactDOM 经过了 vite 的编译,且缓存在了 node_modules/.vite 目录中。这样做好处是只需要加载两个文件,如果不处理那么浏览器需要加载很多React的依赖。根据官网的说明,vite启动时会自动分析node_modules依赖并把他们都打包,所以并不会因为这些依赖太多导致浏览器加载大量js。
由于打包是在本地进行的,冷启动显然会受到影响,经过自己本地实际测试,冷启动有打包 React相关依赖,热启动无需打包,启动速度分别是:

  1. 冷启动 435ms
  2. 热启动 236ms
    虽然冷启动确实慢了一些,但是不得不说 esbuild 打包 React只多用了 100ms,相比 webpack 依然有大幅提升。

index.css 显然也会被编译成 JS,否则 import 会报错,我们看看编译后的 index.css

import { createHotContext as __vite__createHotContext } from "/@vite/client";
import.meta.hot = __vite__createHotContext("/src/index.css");
import { updateStyle, removeStyle } from "/@vite/client"
const id = "/Users/hongxun.lhx/github/my-vue-app/src/index.css"
const css = "body {\n  margin: 0;\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n    sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n    monospace;\n}\n"
updateStyle(id, css)
import.meta.hot.accept()
export default css
import.meta.hot.prune(() => removeStyle(id))

编译的结果和我们想象的差不多,这里有一个 updateStyle方法,起作用就是通过style标签把CSS插入到文档中,方法的实现如下:

function updateStyle(id, content) {
    let style = sheetsMap.get(id);
    {
        if (style && !(style instanceof HTMLStyleElement)) {
            removeStyle(id);
            style = undefined;
        }
        if (!style) {
            style = document.createElement('style');
            style.setAttribute('type', 'text/css');
            style.innerHTML = content;
            document.head.appendChild(style);
        }
        else {
            style.innerHTML = content;
        }
    }
    sheetsMap.set(id, style);
}

因为我们是 import index.css 的写法,所以export default css实际 并没啥用,如果是用CSS Module写法就有用了。

回到 main.tsxJSX被编译成了JS是常规操作,对 App.tsx的处理也和上面的逻辑类似。

静态资源

App.tsx中加载了一张图片:

import logo from './logo.svg'

根据用Webpack的经验,显然 logo.svg也会被编译成JS,并返回文件地址,实际也确实是这样:

export default "/src/logo.svg"

Vite快在哪里?

认识了Vite的基本原理之后,就可以明白vite为什么在本地开发那么快了。主要是基于以下几点:

  1. 本地只有冷启动的时候有资源打包,热启动完全无任何打包编译操作,只是启动了一个服务器,所以通常 0.2 秒就能完成启动
  2. 浏览器直接通过esmodule加载src文件,按需编译单个文件。
  3. node_modules依赖被打包成大文件,避免了浏览器加载多个js。
  4. 公共依赖开启缓存缓存,根本不用请求到 server。
  5. 最后也是最重要的:esbuild打包速度无敌快。

那么esbuild 为什么这么快呢?
modules
官方是有解释的,可以看这里:https://esbuild.github.io/faq/#why-is-esbuild-fast
总结一下几个主要原因:

  1. GO语言的优势:esbuild是用go语言写的,是编译型语言,且针对多线程进行优化,而webpack是用JS写的,解释型且主要是单线程的特性注定他的性能比不过GO。
  2. 良好的并行优化:多线程优化,尽可能用到全部的CPU核心,且多线程可以共享内存数据。
  3. 从底层实现:自己实现了底层逻辑,没有依赖三方库。比如TS解析的时候如果用TS官方的编译器,需要检查类型,因此就会变慢;GO的静态类型速度也快于JS中的动态类型。
  4. 更小的内存使用:更小的内存数据,更少次数对JS进行遍历。

esbuild有这么多优点,那么有没有缺点呢?
当然有的,esbuild的快其实来源于两部分:一部分是 GO语言和多线程带来的优势,另一个部分其实是舍弃了一些特性换来的速度提升。比如 esbuild 省略了语法检查,官方文档中明确说明了esbuild没有做TS类型检查,实际我测试发现JS语法检查也没做;没有生产环境需要用到的代码分割等特性(但有计划做)。因为这些不完善的地方,在打包生产环境代码的时候,vite依然用的是 rollup。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant