vite开发环境源码分析

what vite

新一代的前端构建工具, 能提升前端开发体验,主要包括两部分

  1. 一个开发服务器,基于ES原生 Module, 提供了丰富的内置功能,比如对非js模块的转换处理,热更新等;
  2. 一套构建指令,基于Rollup打包代码;

使用层面

在开发环境采用不打包, 基于ES Module提供源码,在浏览器请求源码时进行转换提供代码,其中涉及的转换有(jsx, css, vue文件转为js可以解析的文件),还包括将一些大的依赖采用esbuild进行预构建;

生产环境基于Rollup打包代码, 因为生产环境需要有最佳的性能,多次加载未打包ESM, 效率底下,所以在生产环境还是将代码进行tree-shaking, chunk分割等优化打包处理

how vite bundless

这里我们主要结合vite源码来看一下,vite在开发环境,采用不打包的内部实现

开发服务器

  • 基于node:http模块创建本地服务器, 然后启动本地服务器

配置处理

在resoveConfig中, 对配置进行了各种处理,包括vite.cofig.json中的配置,插件配置,各个打包环境的配置,还有做了一系列的格式化检查,对配置的兼容性检查,对日志统一输出检查,对文件名的有效性检测等;

文件转换

对于css, vue等其他类型的资源文件,vite需要进行资源转换,浏览器才能解析,下面将结合css这个类型资源来简要说一下文件转换, vite中会将css资源文件转成js文件输出;

css 复制代码
.dir-import {
  color: grey;
}
js 复制代码
import {createHotContext as __vite__createHotContext} from "/@vite/client";
import.meta.hot = __vite__createHotContext("/src/style.css");
import {updateStyle as __vite__updateStyle, removeStyle as __vite__removeStyle} from "/@vite/client"
const __vite__id = "vite-project/src/style.css"
const __vite__css = ":root {\n color: grey; }\n"
__vite__updateStyle(__vite__id, __vite__css)
import.meta.hot.accept()
import.meta.hot.prune( () => __vite__removeStyle(__vite__id))

主要是通过vite:css插件进行转换

在vite源码中,通过middleware拦截到请求后,通过loadAndTransform来加载文件内容,转化等

js 复制代码
async function loadAndTransform(environment, id, url, options, timestamp, mod, resolved) {
  // 第一步:加载源文件内容
  const loadResult = await pluginContainer.load(id);
  // 第二步: 判断是否有缓存
  mod ??= await moduleGraph._ensureEntryFromUrl(url, void 0, resolved);
  // 第三步: 通过插件提供的统一出口,转化文件内容
  const transformResult = await pluginContainer.transform(code, id, {
    inMap: map
  });
}

transform中的源码, 在vite内置plugin中,对css的code进行转换

js 复制代码
    async transform(css, id) {
      if (
        !isCSSRequest(id) ||
        commonjsProxyRE.test(id) ||
        SPECIAL_QUERY_RE.test(id)
      ) {
        return
      }

      css = stripBomTag(css)

      // cache css compile result to map
      // and then use the cache replace inline-style-flag
      // when `generateBundle` in vite:build-html plugin and devHtmlHook
      const inlineCSS = inlineCSSRE.test(id)
      const isHTMLProxy = htmlProxyRE.test(id)
      if (inlineCSS && isHTMLProxy) {
        if (styleAttrRE.test(id)) {
          css = css.replace(/"/g, '"')
        }
        const index = htmlProxyIndexRE.exec(id)?.[1]
        if (index == null) {
          throw new Error(`HTML proxy index in "${id}" not found`)
        }
        addToHTMLProxyTransformResult(
          `${getHash(cleanUrl(id))}_${Number.parseInt(index)}`,
          css,
        )
        return `export default ''`
      }

      const inlined = inlineRE.test(id)
      const modules = cssModulesCache.get(config)!.get(id)

      const modulesCode =
        modules &&
        !inlined &&
        dataToEsm(modules, { namedExports: true, preferConst: true })

      if (config.command === 'serve') {
        const getContentWithSourcemap = async (content: string) => {
          if (config.css?.devSourcemap) {
            const sourcemap = this.getCombinedSourcemap()
            if (sourcemap.mappings) {
              await injectSourcesContent(sourcemap, cleanUrl(id), config.logger)
            }
            return getCodeWithSourcemap('css', content, sourcemap)
          }
          return content
        }

        if (isDirectCSSRequest(id)) {
          return null
        }
        // server only
        if (this.environment.config.consumer !== 'client') {
          return modulesCode || `export default ${JSON.stringify(css)}`
        }
        if (inlined) {
          return `export default ${JSON.stringify(css)}`
        }

        const cssContent = await getContentWithSourcemap(css)
        const code = [
          `import { updateStyle as __vite__updateStyle, removeStyle as __vite__removeStyle } from ${JSON.stringify(
            path.posix.join(config.base, CLIENT_PUBLIC_PATH),
          )}`,
          `const __vite__id = ${JSON.stringify(id)}`,
          `const __vite__css = ${JSON.stringify(cssContent)}`,
          `__vite__updateStyle(__vite__id, __vite__css)`,
          // css modules exports change on edit so it can't self accept
          `${modulesCode || 'import.meta.hot.accept()'}`,
          `import.meta.hot.prune(() => __vite__removeStyle(__vite__id))`,
        ].join('\n')
        return { code, map: { mappings: '' } }
      }

      // record css
      if (!inlined) {
        styles.set(id, css)
      }

      let code: string
      if (modulesCode) {
        code = modulesCode
      } else if (inlined) {
        let content = css
        if (config.build.cssMinify) {
          content = await minifyCSS(content, config, true)
        }
        code = `export default ${JSON.stringify(content)}`
      } else {
        // empty module when it's not a CSS module nor `?inline`
        code = ''
      }

      return {
        code,
        map: { mappings: '' },
        // avoid the css module from being tree-shaken so that we can retrieve
        // it in renderChunk()
        moduleSideEffects: modulesCode || inlined ? false : 'no-treeshake',
      }
    },

热更新

在文件转换阶段,css转换code的transform中,可以看到,有注入一些热更新的代码, 在服务端代码更新时,更新新的代码,做到动态替换;

js 复制代码
import {createHotContext as __vite__createHotContext} from "/@vite/client";
import.meta.hot = __vite__createHotContext("/src/style.css");
__vite__updateStyle(__vite__id, __vite__css)
import.meta.hot.accept()
import.meta.hot.prune( () => __vite__removeStyle(__vite__id))

服务端,利用websockot建立双端通信长连接,并且通过chokidar来监听文件的修改,通知到客户端,在不刷新页面的同时,做到内容的更新

js 复制代码
// 服务启动的时候,同时也创建了websocketServer
const ws = createWebSocketServer(httpServer, config, httpsOptions);

在onHMRUpdate中主要做三层维度的更新

依赖预构建

1、CommonJS和UMD兼容性,vite将CommonJS和UMD依赖项转为ESM;

2、性能:有些模块有数百个子模块,比如lodash-es,预构建可以将内部多个模块转换成单个模块,提升页面后续性能;

next

vite存在的问题

vite开发不打包,生产基于rollup打包,开发生产构建模式的不同,很难保证排查两边问题时,代码的一致性,为此vite团队,提出了RollDown, Rolldown使用Rust编写,目前还是开发阶段。

相关推荐
whisperrr.1 小时前
【JavaWeb12】数据交换与异步请求:JSON与Ajax的绝妙搭配是否塑造了Web的交互革命?
前端·ajax·json
烂蜻蜓2 小时前
前端已死?什么是前端
开发语言·前端·javascript·vue.js·uni-app
Rowrey3 小时前
react+typescript,初始化与项目配置
javascript·react.js·typescript
谢尔登3 小时前
Vue 和 React 的异同点
前端·vue.js·react.js
祈澈菇凉8 小时前
Webpack的基本功能有哪些
前端·javascript·vue.js
小纯洁w8 小时前
Webpack 的 require.context 和 Vite 的 import.meta.glob 的详细介绍和使用
前端·webpack·node.js
想睡好8 小时前
css文本属性
前端·css
qianmoQ8 小时前
第三章:组件开发实战 - 第五节 - Tailwind CSS 响应式导航栏实现
前端·css
记得早睡~9 小时前
leetcode150-逆波兰表达式求值
javascript·算法·leetcode
zhoupenghui1689 小时前
golang时间相关函数总结
服务器·前端·golang·time