返回顶部
首页 > 资讯 > 精选 >vitejs预构建的流程是什么
  • 813
分享到

vitejs预构建的流程是什么

2023-07-02 16:07:22 813人浏览 薄情痞子
摘要

本文小编为大家详细介绍“vitejs预构建的流程是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“vitejs预构建的流程是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。为啥要预构建简单来讲就是为了提高本

本文小编为大家详细介绍“vitejs预构建的流程是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“vitejs预构建的流程是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

    为啥要预构建

    简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。

    为了加快本地开发服务器的启动速度,vite 引入了预构建机制。在预构建工具的选择上,vite选择了 esbuildesbuild 使用 Go 编写,比以 javascript 编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。

    预构建的流程

    1. 查找依赖

    如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:

    {  Vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js',  'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs',  'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}

    具体实现就是,调用esbuildbuild api,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。

    //...省略其他代码  if (explicitEntryPatterns) {    entries = await globEntries(explicitEntryPatterns, config)  } else if (buildInput) {    const resolvePath = (p: string) => path.resolve(config.root, p)    if (typeof buildInput === 'string') {      entries = [resolvePath(buildInput)]    } else if (Array.isArray(buildInput)) {      entries = buildInput.map(resolvePath)    } else if (isObject(buildInput)) {      entries = Object.values(buildInput).map(resolvePath)    } else {      throw new Error('invalid rollupOptions.input value.')    }  } else {    // 重点看这里:使用html文件作为查找入口    entries = await globEntries('**/*.html', config)  }//...省略其他代码build.onResolve(        {          // avoid matching windows volume          filter: /^[\w@][^:]/        },        async ({ path: id, importer }) => {          const resolved = await resolve(id, importer)          if (resolved) {            // 来自node_modules和在include中指定的模块            if (resolved.includes('node_modules') || include?.includes(id)) {              // dependency or forced included, externalize and stop crawling              if (isOptimizable(resolved)) {                // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象                depImports[id] = resolved              }              return externalUnlessEntry({ path: id })            } else if (isScannable(resolved)) {              const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined              // linked package, keep crawling              return {                path: path.resolve(resolved),                namespace              }            } else {              return externalUnlessEntry({ path: id })            }          } else {            missing[id] = nORMalizePath(importer)          }        }      )

    但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有jstsjsxCSSJSONbase64dataurlbinaryfile(.png等),并不包括html

    vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue.html这种类型的文件。

    具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。

          // 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents -----------------------------------      build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => {        const resolved = await resolve(path, importer)        if (!resolved) return        // It is possible for the scanner to scan html types in node_modules.        // If we can optimize this html type, skip it so it's handled by the        // bare import resolve, and recorded as optimization dep.        if (resolved.includes('node_modules') && isOptimizable(resolved)) return        return {          path: resolved,          namespace: 'html'        }      })      // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module      build.onLoad(        { filter: htmlTypesRE, namespace: 'html' },        async ({ path }) => {          let raw = fs.readFileSync(path, 'utf-8')          // Avoid matching the content of the comment          raw = raw.replace(commentRE, '<!---->')          const isHtml = path.endsWith('.html')          const regex = isHtml ? scriptModuleRE : scriptRE          regex.lastIndex = 0          // js 的内容被处理成了一个虚拟模块          let js = ''          let scriptId = 0          let match: RegExpExecArray | null          while ((match = regex.exec(raw))) {            const [, openTag, content] = match            const typeMatch = openTag.match(typeRE)            const type =              typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3])            const langMatch = openTag.match(langRE)            const lang =              langMatch && (langMatch[1] || langMatch[2] || langMatch[3])            // skip type="application/ld+json" and other non-JS types            if (              type &&              !(                type.includes('javascript') ||                type.includes('ecmascript') ||                type === 'module'              )            ) {              continue            }            // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader            let loader: Loader = 'js'            if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') {              loader = lang            }            const srcMatch = openTag.match(srcRE)            // 对于<script src='path/to/some.js'>引入的js,将它转换为import 'path/to/some.js'的代码            if (srcMatch) {              const src = srcMatch[1] || srcMatch[2] || srcMatch[3]              js += `import ${JSON.stringify(src)}\n`            } else if (content.trim()) {              // The reason why virtual modules are needed:              // 1. There can be module scripts (`<script context="module">` in Svelte and `<script>` in Vue)              // or local scripts (`<script>` in Svelte and `<script setup>` in Vue)              // 2. There can be multiple module scripts in html              // We need to handle these separately in case variable names are reused between them              // append imports in TS to prevent esbuild from removing them              // since they may be used in the template              const contents =                content +                (loader.startsWith('ts') ? extractImportPaths(content) : '')                // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'}              const key = `${path}?id=${scriptId++}`              if (contents.includes('import.meta.glob')) {                scripts[key] = {                  // transformGlob already transforms to js                  loader: 'js',                  contents: await transformGlob(                    contents,                    path,                    config.root,                    loader,                    resolve,                    config.logger                  )                }              } else {                scripts[key] = {                  loader,                  contents                }              }              const virtualModulePath = JSON.stringify(                virtualModulePrefix + key              )              const contextMatch = openTag.match(contextRE)              const context =                contextMatch &&                (contextMatch[1] || contextMatch[2] || contextMatch[3])              // Especially for Svelte files, exports in <script context="module"> means module exports,              // exports in <script> means component props. To avoid having two same export name from the              // star exports, we need to ignore exports in <script>              if (path.endsWith('.svelte') && context !== 'module') {                js += `import ${virtualModulePath}\n`              } else {                // e.g. export * from 'virtual-module:xx.vue?id=1'                js += `export * from ${virtualModulePath}\n`              }            }          }          // This will trigger incorrectly if `export default` is contained          // anywhere in a string. Svelte and Astro files can't have          // `export default` as code so we know if it's encountered it's a          // false positive (e.g. contained in a string)          if (!path.endsWith('.vue') || !js.includes('export default')) {            js += '\nexport default {}'          }          return {            loader: 'js',            contents: js          }        }      )

    由上文我们可知,来自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缓存目录,也会重新构建。

    // 基于配置文件+依赖信息生成hashconst 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值相同,说明项目的依赖没有变化,无需重复构建了,直接使用缓存即可。

    // 计算当前的hashconst 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)      }    }

    读到这里,这篇“vitejs预构建的流程是什么”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网精选频道。

    --结束END--

    本文标题: vitejs预构建的流程是什么

    本文链接: https://lsjlt.com/news/342808.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    猜你喜欢
    • vitejs预构建的流程是什么
      本文小编为大家详细介绍“vitejs预构建的流程是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“vitejs预构建的流程是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。为啥要预构建简单来讲就是为了提高本...
      99+
      2023-07-02
    • vitejs预构建理解及流程解析
      目录引言为啥要预构建预构建的流程1. 查找依赖2. 对查找到的依赖进行构建缓存与预构建总结引言 vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本...
      99+
      2024-04-02
    • C++的程序流程结构是什么
      这篇文章主要介绍了C++的程序流程结构是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇C++的程序流程结构是什么文章都会有所收获,下面我们一起来看看吧。前言C/C++支持最基本的三种程序运行结构:顺序结构、...
      99+
      2023-06-29
    • 建企业网站的流程是什么
      1.确定网站目的和目标:明确企业网站的目的和目标,如宣传企业形象、展示产品或服务、提供在线销售等。2.制定网站策略:根据目的和目标制...
      99+
      2023-06-14
      建企业网站
    • python中的Pytorch建模流程是什么
      小编给大家分享一下python中的Pytorch建模流程是什么,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!一般我们训练神经网络有以下步骤:导入库设置训练参数的初...
      99+
      2023-06-29
    • asp网站建设的流程是什么
      ASP网站建设的流程一般包括以下几个步骤:1.需求分析:与客户沟通,了解客户的需求和要求,确定网站的基本功能、页面布局、交互方式等。...
      99+
      2023-06-05
      asp网站建设 asp
    • springboot创建项目的流程是什么
      Spring Boot创建项目的流程如下: 在官方网站下载并安装Spring Boot CLI(Command Line Int...
      99+
      2024-03-07
      springboot
    • asp网站建设流程是什么
      1. 需求分析:了解客户需求,明确网站目标、功能和设计要求。2. 网站规划:根据需求分析结果,制定网站构架、页面设计、交互方式等方案...
      99+
      2023-06-14
      asp网站建设 网站建设
    • pycharm搭建项目流程是什么
      搭建项目流程如下:1. 下载和安装PyCharm:首先,你需要从PyCharm官方网站下载适合你操作系统的PyCharm版本,并按照...
      99+
      2023-09-23
      pycharm
    • springCloud项目搭建流程是什么
      本篇内容主要讲解“springCloud项目搭建流程是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“springCloud项目搭建流程是什么”吧!实现跨服务的远程调用(RestTemplat...
      99+
      2023-06-30
    • 柳州网站建设的流程是什么
      柳州网站建设的流程可以分为以下几个步骤:1. 确定需求和目标:与客户沟通,了解网站的目标、功能和需求,制定网站建设计划。2. 网站设...
      99+
      2023-06-12
      柳州网站建设 网站建设
    • 合川网站建设的流程是什么
      合川网站建设的流程一般包括以下步骤:1. 确定需求和目标:与客户沟通,了解网站的需求和目标,包括网站的类型、功能、设计等。2. 策划...
      99+
      2023-06-11
      合川网站建设 网站建设
    • 北京网站建设的流程是什么
      1. 确定需求和目标:确定网站的目标、功能和需求,包括网站类型、目标用户、内容和功能等。2. 制定计划和预算:制定网站建设的计划和预...
      99+
      2023-06-12
      北京网站建设 网站建设
    • 网页建设的基本流程是什么
      网页建设的基本流程如下:1. 确定目标和需求:确定网页的目标和需求,包括网站的类型、目标受众、功能需求、网站风格等。2. 策划网站结...
      99+
      2023-06-05
      网页建设
    • 长沙网站建设的流程是什么
      长沙网站建设的流程一般包括以下步骤:1. 需求分析:了解客户的需求,确定网站的目标、功能、内容等。2. 网站规划:确定网站的结构、布...
      99+
      2023-06-13
      长沙网站建设 网站建设
    • 长沙建立网站的流程是什么
      长沙建立网站的流程包括以下步骤:1. 确定网站目标和需求:确定网站用途、目标受众、功能需求等,制定网站策略。2. 网站设计:根据网站...
      99+
      2023-06-13
      长沙建立网站
    • 成都建设网站的流程是什么
      1. 确定网站需求:首先需要明确网站的目的、功能和受众,以确定网站的需求和规划。2. 网站规划:根据需求确定网站的结构、页面数量、布...
      99+
      2023-06-13
      成都建设网站
    • 无锡网站建设的流程是什么
      无锡网站建设的流程通常包括以下几个步骤:1. 确定需求:与客户沟通,了解客户的需求和目标,包括网站类型、功能、设计风格、目标用户等。...
      99+
      2023-06-14
      无锡网站建设 网站建设
    • 武汉网站建设的流程是什么
      武汉网站建设的流程一般分为以下几个步骤:1.需求分析:与客户进行沟通,明确网站建设的目的、定位、功能、设计要求等。2.网站策划:根据...
      99+
      2023-06-13
      武汉网站建设 网站建设
    • 建设大型网站的流程是什么
      1.需求分析:确定网站的目标、功能和用户需求。2.规划设计:制定网站的结构、内容、布局、色彩等设计方案。3.技术选型:选择适合网站需...
      99+
      2023-06-07
      建设大型网站
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作