谈谈对vitejs预构建的理解(下)

简介: vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。

由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'


2. 对查找到的依赖进行构建


在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuildentryPoints打包就行了。


//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖
    import { build } from 'esbuild'
   // ...省略其他代码
    const result = await build({
      absWorkingDir: process.cwd(),
     // flatIdDeps即为第一步中所得到的需要预构建的依赖对象
      entryPoints: Object.keys(flatIdDeps),
      bundle: true,
      format: 'esm',
      target: config.build.target || undefined,
      external: config.optimizeDeps?.exclude,
      logLevel: 'error',
      splitting: true,
      sourcemap: true,
// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录
      outdir: processingCacheDir,
      ignoreAnnotations: true,
      metafile: true,
      define,
      plugins: [
        ...plugins,
        esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr)
      ],
      ...esbuildOptions
    })
    // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync
    commitProcessingDepsCacheSync()


vite并没有将esbuildoutdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。


function commitProcessingDepsCacheSync() {
    // Rewire the file paths from the temporal processing dir to the final deps cache dir
    const dataPath = path.join(processingCacheDir, '_metadata.json')
    writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata))
    // Processing is done, we can now replace the depsCacheDir with processingCacheDir
    // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录
    if (fs.existsSync(depsCacheDir)) {
      const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped
      rmSync(depsCacheDir, { recursive: true })
    }
    fs.renameSync(processingCacheDir, depsCacheDir)
  }
}


以上就是预构建的主要处理流程。


缓存与预构建


vite冷启动之所以快,除了esbuild本身构建速度够快外,也与vite做了必要的缓存机制密不可分。vite在预构建时,除了生成预构建的js文件外,还会创建一个_metadata.json文件,其结构大致如下:


{
  "hash": "22135fca",
  "browserHash": "632454bc",
  "optimized": {
    "vue": {
      "file": "/path/to/your/project/node_modules/.vite/vue.js",
      "src": "/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js",
      "needsInterop": false
    },
    "element-plus": {
      "file": "/path/to/your/project/node_modules/.vite/element-plus.js",
      "src": "/path/to/your/project/node_modules/element-plus/es/index.mjs",
      "needsInterop": false
    },
    "vue-router": {
      "file": "/path/to/your/project/node_modules/.vite/vue-router.js",
      "src": "/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js",
      "needsInterop": false
    }
  }
}


hash 是缓存的主要标识,由vite的配置文件和项目依赖决定(依赖的信息取自package-lock.jsonyarn.lockpnpm-lock.yaml)。 所以如果用户修改了vite.config.js或依赖发生了变化(依赖的添加删除更新会导致lock文件变化)都会令hash发生变化,缓存也就失效了。这时,vite需要重新进行预构建。当然如果手动删除了.vite缓存目录,也会重新构建。


// 基于配置文件+依赖信息生成hash
const lockfileFormats = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml']
function getDepHash(root: string, config: ResolvedConfig): string {
  let content = lookupFile(root, lockfileFormats) || ''
  // also take config into account
  // only a subset of config options that can affect dep optimization
  content += JSON.stringify(
    {
      mode: config.mode,
      root: config.root,
      define: config.define,
      resolve: config.resolve,
      buildTarget: config.build.target,
      assetsInclude: config.assetsInclude,
      plugins: config.plugins.map((p) => p.name),
      optimizeDeps: {
        include: config.optimizeDeps?.include,
        exclude: config.optimizeDeps?.exclude,
        esbuildOptions: {
          ...config.optimizeDeps?.esbuildOptions,
          plugins: config.optimizeDeps?.esbuildOptions?.plugins?.map(
            (p) => p.name
          )
        }
      }
    },
    (_, value) => {
      if (typeof value === 'function' || value instanceof RegExp) {
        return value.toString()
      }
      return value
    }
  )
  return createHash('sha256').update(content).digest('hex').substring(0, 8)
}


vite启动时首先检查hash的值,如果当前的hash值与_metadata.json中的hash值相同,说明项目的依赖没有变化,无需重复构建了,直接使用缓存即可。


// 计算当前的hash
const mainHash = getDepHash(root, config)
 const metadata: DepOptimizationMetadata = {
    hash: mainHash,
    browserHash: mainHash,
    optimized: {},
    discovered: {},
    processing: processing.promise
  }
 let prevData: DepOptimizationMetadata | undefined
    try {
      const prevDataPath = path.join(depsCacheDir, '_metadata.json')
      prevData = parseOptimizedDepsMetadata(
        fs.readFileSync(prevDataPath, 'utf-8'),
        depsCacheDir,
        processing.promise
      )
    } catch (e) { }
    // hash is consistent, no need to re-bundle
    // 比较缓存的hash与当前hash
    if (prevData && prevData.hash === metadata.hash) {
      log('Hash is consistent. Skipping. Use --force to override.')
      return {
        metadata: prevData,
        run: () => (processing.resolve(), processing.promise)
      }
    }


总结


以上就是vite预构建的主要处理逻辑,总结起来就是先查找需要预构建的依赖,然后将这些依赖作为entryPoints进行构建,构建完成后更新缓存。vite在启动时为提升速度,会检查缓存是否有效,有效的话就可以跳过预构建环节,缓存是否有效的判定是对比缓存中的hash值与当前的hash值是否相同。由于hash的生成算法是基于vite配置文件和项目依赖的,所以配置文件和依赖的的变化都会导致hash发生变化,从而重新进行预构建。


相关文章
|
8月前
|
存储 监控 安全
插件机制详解:原理、设计与最佳实践
插件机制详解:原理、设计与最佳实践
387 0
|
8月前
|
机器学习/深度学习 PyTorch TensorFlow
机器学习PAI的1.6.1开源包依旧不全怎么办
机器学习PAI的1.6.1开源包依旧不全怎么办
162 1
|
敏捷开发 测试技术
推三返一开发稳定版丨推三返一项目系统开发详细指南/方案需求/步骤逻辑/流程功能/案例设计/技术架构/源码程序
推三返一系统开发是一种软件开发模式,也被称为迭代增量开发模式。它是一种敏捷开发方法的一种,通过将整个开发过程分为多个迭代周期,每个周期都会增加新的功能和特性,并在每个迭代周期结束后进行测试、反馈和修改。推三返一系统开发的核心思想是“推进三步,反馈一步”。
|
运维 测试技术 数据库
测试思想-流程规范 关于预发布环境的一些看法
测试思想-流程规范 关于预发布环境的一些看法
538 0
|
数据采集 消息中间件 分布式计算
项目七个阶段总体介绍|学习笔记
快速学习项目七个阶段总体介绍
项目七个阶段总体介绍|学习笔记
分体式测斜探头安装注意要点
分体式测斜探头采用高端 MEMS 技术的双轴高精度倾角仪,主要用于坝体、边坡、路基、基坑、岩体滑坡及大型建筑倾斜、土体内部水平位移变形测量。该仪器可重复使用,并可方便地实现倾斜测量的自动化监测。产品安装方便、使用简单、抗外界电磁干扰、承受振动冲击能力强,是军工装备、工业自动化、测量测绘等行业倾角测量的最佳选择。
分体式测斜探头安装注意要点
|
前端开发 安全 JavaScript
挑战21天手写前端框架 day19 依赖预打包是什么意思
挑战21天手写前端框架 day19 依赖预打包是什么意思
401 0
挑战21天手写前端框架 day19 依赖预打包是什么意思
|
JSON 缓存 JavaScript
谈谈对vitejs预构建的理解(上)
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
|
前端开发 数据库 Java
面向失败的设计-服务能力与依赖调用自我保护
作为一种架构模式,微服务将复杂系统切分为数十乃至上百个小服务,每个服务负责实现一个独立的业务逻辑。这些小服务易于被小型的软件工程师团队所理解和修改,并带来了语言和框架选择灵活性,缩短应用开发上线时间,可根据不同的工作负载和资源要求对服务进行独立缩扩容等优势。
1549 0
|
SQL Oracle Java
工作和学习中遇到的各种报错及解决方案
一. Java项目连接MySQL时报错: "The last packet sent successfully to the server was 0 milliseconds ago." 1. 出错原因 数据库回收了连接, 而系统的缓冲池不知道, 继续使用被回收的连接.