最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

vitejs預(yù)構(gòu)建理解及流程解析

 更新時(shí)間:2022年07月06日 10:29:51   作者:前端論道  
這篇文章主要為大家介紹了vitejs預(yù)構(gòu)建理解及流程解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

vite在官網(wǎng)介紹中,第一條就提到的特性就是自己的本地冷啟動(dòng)極快。這主要是得益于它在本地服務(wù)啟動(dòng)的時(shí)候做了預(yù)構(gòu)建。出于好奇,抽時(shí)間了解了下vite在預(yù)構(gòu)建部分的主要實(shí)現(xiàn)思路,分享出來供大家參考。

為啥要預(yù)構(gòu)建

簡單來講就是為了提高本地開發(fā)服務(wù)器的冷啟動(dòng)速度。按照vite的說法,當(dāng)冷啟動(dòng)開發(fā)服務(wù)器時(shí),基于打包器的方式啟動(dòng)必須優(yōu)先抓取并構(gòu)建你的整個(gè)應(yīng)用,然后才能提供服務(wù)。隨著應(yīng)用規(guī)模的增大,打包速度顯著下降,本地服務(wù)器的啟動(dòng)速度也跟著變慢。

為了加快本地開發(fā)服務(wù)器的啟動(dòng)速度,vite 引入了預(yù)構(gòu)建機(jī)制。在預(yù)構(gòu)建工具的選擇上,vite選擇了 esbuild 。esbuild 使用 Go 編寫,比以 JavaScript 編寫的打包器構(gòu)建速度快 10-100 倍,有了預(yù)構(gòu)建,再利用瀏覽器的esm方式按需加載業(yè)務(wù)代碼,動(dòng)態(tài)實(shí)時(shí)進(jìn)行構(gòu)建,結(jié)合緩存機(jī)制,大大提升了服務(wù)器的啟動(dòng)速度。

預(yù)構(gòu)建的流程

1. 查找依賴

如果是首次啟動(dòng)本地服務(wù),那么vite會(huì)自動(dòng)抓取源代碼,從代碼中找到需要預(yù)構(gòu)建的依賴,最終對外返回類似下面的一個(gè)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'
}

具體實(shí)現(xiàn)就是,調(diào)用esbuildbuild api,以index.html作為查找入口(entryPoints),將所有的來自node_modules以及在配置文件的optimizeDeps.include選項(xiàng)中指定的模塊找出來。

//...省略其他代碼
  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 {
    // 重點(diǎn)看這里:使用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)) {
                // 重點(diǎn)看這里:將符合預(yù)構(gòu)建條件的依賴記錄下來,depImports就是對外導(dǎo)出的需要預(yù)構(gòu)建的依賴對象
                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默認(rèn)支持的入口文件類型有js、ts、jsx、css、jsonbase64、dataurlbinary、file(.png等),并不包括html。

vite是如何做到將index.html作為打包入口的呢?原因是vite自己實(shí)現(xiàn)了一個(gè)esbuild插件esbuildScanPlugin,來處理.vue.html這種類型的文件。

具體做法是讀取html的內(nèi)容,然后將里面的script提取到一個(gè)esm格式的js模塊。

      // 對于html類型(.VUE/.HTML/.svelte等)的文件,提取文件里的script內(nèi)容。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,作為一個(gè)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 的內(nèi)容被處理成了一個(gè)虛擬模塊
          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
            }
            // 默認(rèn)的js文件的loader是js,其他對于ts、tsx jsx有對應(yīng)的同名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,將它轉(zhuǎn)換為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中的模塊依賴是需要預(yù)構(gòu)建的。

例如import ElementPlus from 'element-plus'。

因?yàn)樵跒g覽器環(huán)境下,是不支持這種裸模塊引用的(bare import)。

另一方面,如果不進(jìn)行構(gòu)建,瀏覽器面對由成百上千的子模塊組成的依賴,依靠原生esm的加載機(jī)制,每個(gè)的依賴的import都將產(chǎn)生一次http請求。面對大量的請求,瀏覽器是吃不消的。

因此客觀上需要對裸模塊引入進(jìn)行打包,并處理成瀏覽器環(huán)境下支持的相對路徑或路徑的導(dǎo)入方式。

例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。

2. 對查找到的依賴進(jìn)行構(gòu)建

在上一步,已經(jīng)得到了需要預(yù)構(gòu)建的依賴列表?,F(xiàn)在需要把他們作為esbuildentryPoints打包就行了。

//使用esbuild打包,入口文件即為第一步中抓取到的需要預(yù)構(gòu)建的依賴
    import { build } from 'esbuild'
   // ...省略其他代碼
    const result = await build({
      absWorkingDir: process.cwd(),
     // flatIdDeps即為第一步中所得到的需要預(yù)構(gòu)建的依賴對象
      entryPoints: Object.keys(flatIdDeps),
      bundle: true,
      format: 'esm',
      target: config.build.target || undefined,
      external: config.optimizeDeps?.exclude,
      logLevel: 'error',
      splitting: true,
      sourcemap: true,
// outdir指定打包產(chǎn)物輸出目錄,processingCacheDir這里并不是.vite,而是存放構(gòu)建產(chǎn)物的臨時(shí)目錄
      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(構(gòu)建產(chǎn)物的輸出目錄)直接配置為.vite目錄,而是先將構(gòu)建產(chǎn)物存放到了一個(gè)臨時(shí)目錄。當(dāng)構(gòu)建完成后,才將原來舊的.vite(如果有的話)刪除。然后再將臨時(shí)目錄重命名為.vite。這樣做主要是為了避免在程序運(yùn)行過程中發(fā)生了錯(cuò)誤,導(dǎo)致緩存不可用。

  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)
  }
}

以上就是預(yù)構(gòu)建的主要處理流程。

緩存與預(yù)構(gòu)建

vite冷啟動(dòng)之所以快,除了esbuild本身構(gòu)建速度夠快外,也與vite做了必要的緩存機(jī)制密不可分。

vite在預(yù)構(gòu)建時(shí),除了生成預(yù)構(gòu)建的js文件外,還會(huì)創(chuàng)建一個(gè)_metadata.json文件,其結(jié)構(gòu)大致如下:

{
  "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 是緩存的主要標(biāo)識(shí),由vite的配置文件和項(xiàng)目依賴決定(依賴的信息取自package-lock.json、yarn.lock、pnpm-lock.yaml)。 所以如果用戶修改了vite.config.js或依賴發(fā)生了變化(依賴的添加刪除更新會(huì)導(dǎo)致lock文件變化)都會(huì)令hash發(fā)生變化,緩存也就失效了。這時(shí),vite需要重新進(jìn)行預(yù)構(gòu)建。當(dāng)然如果手動(dòng)刪除了.vite緩存目錄,也會(huì)重新構(gòu)建。

// 基于配置文件+依賴信息生成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啟動(dòng)時(shí)首先檢查hash的值,如果當(dāng)前的hash值與_metadata.json中的hash值相同,說明項(xiàng)目的依賴沒有變化,無需重復(fù)構(gòu)建了,直接使用緩存即可。

// 計(jì)算當(dāng)前的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與當(dāng)前hash
    if (prevData && prevData.hash === metadata.hash) {
      log('Hash is consistent. Skipping. Use --force to override.')
      return {
        metadata: prevData,
        run: () => (processing.resolve(), processing.promise)
      }
    }

總結(jié)

以上就是vite預(yù)構(gòu)建的主要處理邏輯,總結(jié)起來就是先查找需要預(yù)構(gòu)建的依賴,然后將這些依賴作為entryPoints進(jìn)行構(gòu)建,構(gòu)建完成后更新緩存。vite在啟動(dòng)時(shí)為提升速度,會(huì)檢查緩存是否有效,有效的話就可以跳過預(yù)構(gòu)建環(huán)節(jié),緩存是否有效的判定是對比緩存中的hash值與當(dāng)前的hash值是否相同。由于hash的生成算法是基于vite配置文件和項(xiàng)目依賴的,所以配置文件和依賴的的變化都會(huì)導(dǎo)致hash發(fā)生變化,從而重新進(jìn)行預(yù)構(gòu)建。

更多關(guān)于vitejs預(yù)構(gòu)建流程的資料請關(guān)注腳本之家其它相關(guān)文章!,希望大家以后多多支持腳本之家!

相關(guān)文章

  • VUE3中的函數(shù)的聲明和使用

    VUE3中的函數(shù)的聲明和使用

    這篇文章主要介紹了VUE3中的函數(shù)的聲明和使用方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • vitejs預(yù)構(gòu)建理解及流程解析

    vitejs預(yù)構(gòu)建理解及流程解析

    這篇文章主要為大家介紹了vitejs預(yù)構(gòu)建理解及流程解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • 講解vue-router之什么是動(dòng)態(tài)路由

    講解vue-router之什么是動(dòng)態(tài)路由

    這篇文章主要介紹了講解vue-router之什么是動(dòng)態(tài)路由,詳細(xì)的介紹了什么是動(dòng)態(tài)路由,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-05-05
  • Vue常見報(bào)錯(cuò)整理大全(從此報(bào)錯(cuò)不害怕)

    Vue常見報(bào)錯(cuò)整理大全(從此報(bào)錯(cuò)不害怕)

    寫代碼的過程中一定會(huì)遇到報(bào)錯(cuò),遇到報(bào)錯(cuò)不要擔(dān)心,認(rèn)真分析就可以解決報(bào)錯(cuò),同時(shí)積累經(jīng)驗(yàn),早日成為大牛,這篇文章主要給大家介紹了關(guān)于Vue常見報(bào)錯(cuò)整理的相關(guān)資料,需要的朋友可以參考下
    2022-08-08
  • vue cli安裝使用less的教程詳解

    vue cli安裝使用less的教程詳解

    這篇文章主要介紹了vue-cli安裝使用less的教程,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-07-07
  • vue項(xiàng)目中按鈕防抖處理實(shí)現(xiàn)過程

    vue項(xiàng)目中按鈕防抖處理實(shí)現(xiàn)過程

    這篇文章主要給大家介紹了關(guān)于vue項(xiàng)目中按鈕防抖處理實(shí)現(xiàn)的相關(guān)資料,在項(xiàng)目開發(fā)中相必大家時(shí)常會(huì)遇到按鈕重復(fù)點(diǎn)擊后引起事件重復(fù)提交的問題,需要的朋友可以參考下
    2023-08-08
  • vue使用map代替Aarry數(shù)組循環(huán)遍歷的方法

    vue使用map代替Aarry數(shù)組循環(huán)遍歷的方法

    這篇文章主要介紹了vue使用map代替Aarry數(shù)組循環(huán)遍歷的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • Vue+Element UI實(shí)現(xiàn)下拉菜單的封裝

    Vue+Element UI實(shí)現(xiàn)下拉菜單的封裝

    這篇文章主要為大家詳細(xì)介紹了Vue+Element UI實(shí)現(xiàn)下拉菜單的封裝代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Vue?事件處理函數(shù)的綁定示例詳解

    Vue?事件處理函數(shù)的綁定示例詳解

    這篇文章主要為大家介紹了Vue?事件處理函數(shù)的綁定示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • Vue?uni-app框架實(shí)現(xiàn)上拉加載下拉刷新功能

    Vue?uni-app框架實(shí)現(xiàn)上拉加載下拉刷新功能

    uni-app是一個(gè)使用Vue.js(opens?new?window)開發(fā)所有前端應(yīng)用的框架,開發(fā)者編寫一套代碼,可發(fā)布到iOS、Android、Web(響應(yīng)式)、以及各種小程序(微信/支付寶/百度/頭條/飛書/QQ/快手/釘釘/淘寶)、快應(yīng)用等多個(gè)平臺(tái)
    2022-09-09

最新評論

山西省| 色达县| 商城县| 杭锦后旗| 滦南县| 上高县| 邓州市| 仲巴县| 安仁县| 同德县| 泽库县| 平陆县| 丁青县| 嵊州市| 西贡区| 勐海县| 闽侯县| 康定县| 张家界市| 新津县| 永泰县| 石屏县| 资阳市| 鞍山市| 丹江口市| 安福县| 米林县| 藁城市| 礼泉县| 葵青区| 天镇县| 温宿县| 长寿区| 威信县| 万山特区| 临高县| 康乐县| 海晏县| 奈曼旗| 行唐县| 临沧市|