Vite bug 504 (Outdated Optimize Dep) 解決

背景


還是 之前 那個(gè)用 vue-element-admin 搭建的項(xiàng)目,最近剛遷移到了 Vite 。一個(gè)比較大的問題就是雖然項(xiàng)目是秒啟動(dòng),但首次打開頁(yè)面會(huì)有幾秒的 白屏 ,非常難受。于是就去嘗試了各種緩存方案,測(cè)試效果的時(shí)候就用到了 vite --force 這個(gè)命令來強(qiáng)制 Vite 重新構(gòu)建依賴項(xiàng),接著本文的問題就出現(xiàn)了:

GET http://localhost:5173/node_modules/.vite/deps/element-ui_lib_button.js?v=bc3e4ba5 504 (Outdated Optimize Dep)

vite --force 命令啟動(dòng)項(xiàng)目后,打開頁(yè)面有幾率會(huì)顯示白屏,在控制臺(tái)可以看到 504 (Outdated Optimize Dep) 錯(cuò)誤,刷新頁(yè)面是沒用的,不過重新啟動(dòng)項(xiàng)目基本上能解決,這就意味著經(jīng)常需要啟動(dòng)兩遍項(xiàng)目并且時(shí)不時(shí)會(huì)看到這個(gè)錯(cuò)誤,還是比較煩人的。

原因分析


根據(jù)關(guān)鍵字 Outdated Optimize Dep 加斷點(diǎn)定位到了報(bào)錯(cuò)代碼的 位置

try {
  return await fsp.readFile(file, 'utf-8')
} catch (e) {
  // Outdated non-entry points (CHUNK), loaded after a rerun
  throwOutdatedRequest(id)
}

node_modules/.vite/deps 目錄下查看后發(fā)現(xiàn) element-ui_lib_button.js 這個(gè)文件確實(shí)不存在,但應(yīng)該報(bào) 404 錯(cuò)誤而不是過時(shí)的,這點(diǎn)就很讓人迷惑,先不管了。

根本原因就是: optimizer 實(shí)際上并沒有把第三方庫(kù)預(yù)構(gòu)建為對(duì)應(yīng)的緩存文件,但 resolvePlugin 這個(gè)插件在執(zhí)行 tryOptimizedResolve 的時(shí)候卻把需要預(yù)構(gòu)建的第三方依賴的 id 改成預(yù)構(gòu)建后的路徑,而不管是否預(yù)構(gòu)建成功,二者匹配不上所以就出錯(cuò)了。

關(guān)于這點(diǎn)順帶再提一下,預(yù)構(gòu)建和解析兩個(gè)步驟分別寫在兩個(gè)插件里非常割裂,隨著每個(gè)插件各自不斷迭代就難免會(huì)出現(xiàn)各種不一致的行為,從而導(dǎo)致一些莫名其妙的問題。

比如把項(xiàng)目文件添加到 optimizeDeps.include 中會(huì)發(fā)現(xiàn) .vite/deps 中已經(jīng)預(yù)構(gòu)建了,但實(shí)際訪問的時(shí)候還是讀取的源文件,估計(jì)是故意這么設(shè)計(jì)的,但還是可以通過 preAliasPlugin 中的 alias 來繞過這個(gè)限制。

至于為什么沒預(yù)構(gòu)建上,打下 debug 日志看看:

cross-env DEBUG=vite:deps vite --force

反復(fù)啟動(dòng)項(xiàng)目然后對(duì)比啟動(dòng)日志發(fā)現(xiàn)了端倪,報(bào)錯(cuò)的時(shí)候包含下面的日志:

vite:deps ? using post-scan optimizer result, the scanner found every used dependency +74ms

而正常的時(shí)候包含下面的日志:

vite:deps ? new dependencies were found while crawling that weren't detected by the scanner +1ms
vite:deps ? re-running optimizer +0ms
vite:deps new dependencies found: element-ui/lib/button +1ms

根據(jù)日志內(nèi)容定位到 源碼位置

const crawlDeps = Object.keys(metadata.discovered)


// Await for the scan+optimize step running in the background
// It normally should be over by the time crawling of user code ended
await depsOptimizer.scanProcessing


if (!isBuild && optimizationResult && !config.optimizeDeps.noDiscovery) {
  const result = await optimizationResult.result
  optimizationResult = undefined
  currentlyProcessing = false


  const scanDeps = Object.keys(result.metadata.optimized)


  if (scanDeps.length === 0 && crawlDeps.length === 0) {
    debug?.(
      colors.green(
        `? no dependencies found by the scanner or crawling static imports`,
      ),
    )
    result.cancel()
    firstRunCalled = true
    return
  }


  const needsInteropMismatch = findInteropMismatches(
    metadata.discovered,
    result.metadata.optimized,
  )
  const scannerMissedDeps = crawlDeps.some((dep) => !scanDeps.includes(dep))
  const outdatedResult =
    needsInteropMismatch.length > 0 || scannerMissedDeps


  if (outdatedResult) {
    // Drop this scan result, and perform a new optimization to avoid a full reload
    result.cancel()


    // Add deps found by the scanner to the discovered deps while crawling
    for (const dep of scanDeps) {
      if (!crawlDeps.includes(dep)) {
        addMissingDep(dep, result.metadata.optimized[dep].src!)
      }
    }
    if (scannerMissedDeps) {
      debug?.(
        colors.yellow(
          `? new dependencies were found while crawling that weren't detected by the scanner`,
        ),
      )
    }
    debug?.(colors.green(`? re-running optimizer`))
    debouncedProcessing(0)
  } else {
    debug?.(
      colors.green(
        `? using post-scan optimizer result, the scanner found every used dependency`,
      ),
    )
    startNextDiscoveredBatch()
    runOptimizer(result)
  }

else 分支打上斷點(diǎn)再?gòu)?fù)現(xiàn)問題,發(fā)現(xiàn) crawlDeps 的值是:

[
  "element-ui/packages/theme-chalk/src/index.scss"
]

并且 scanDeps 的值也是:

[
  "element-ui/packages/theme-chalk/src/index.scss"
]

而手動(dòng)計(jì)算的 Object.keys(metadata.discovered) 的值是:

[
  "element-ui/packages/theme-chalk/src/index.scss",
  "element-ui/lib/button"
]

所以原因找到了: crawlDeps 的值錯(cuò)誤導(dǎo)致本該進(jìn)入 if 分支進(jìn)行增量構(gòu)建的,結(jié)果卻走了 else 分支直接結(jié)束了。

粗看這段代碼好像沒什么問題,但注意到中間有兩段 await ,盲猜和這有關(guān)系,再補(bǔ)充點(diǎn)日志看下。

使用 patch-packagepatches/vite+4.4.9.patch 這個(gè)補(bǔ)丁文件應(yīng)用上

npx patch-package
diff --git a/node_modules/vite/dist/node/chunks/dep-df561101.js b/node_modules/vite/dist/node/chunks/dep-df561101.js
index 1bc8674..2603df8 100644
--- a/node_modules/vite/dist/node/chunks/dep-df561101.js
+++ b/node_modules/vite/dist/node/chunks/dep-df561101.js
@@ -45413,11 +45413,18 @@ async function createDepsOptimizer(config, server) {
            return;
        }
        const crawlDeps = Object.keys(metadata.discovered);
+        const _debug = () => {
+            const discovered = Object.keys(metadata.discovered);
+            console.log(`metadata.discovered ( size: ${discovered.length} ) : ${depsLogString(discovered)}`)
+        }
+        _debug()
        // Await for the scan+optimize step running in the background
        // It normally should be over by the time crawling of user code ended
        await depsOptimizer.scanProcessing;
+        _debug()
        if (!isBuild && optimizationResult && !config.optimizeDeps.noDiscovery) {
            const result = await optimizationResult.result;
+            _debug()
            optimizationResult = undefined;
            currentlyProcessing = false;
            const scanDeps = Object.keys(result.metadata.optimized);

日志也證實(shí)了和 await 確實(shí)有關(guān)系,實(shí)際項(xiàng)目中依賴比較多構(gòu)建比較慢還可以看到三次的值都是不同的。

    vite:deps ? static imports crawl ended +2s
+ metadata.discovered ( size: 1 ) : element-ui/packages/theme-chalk/src/index.scss
+ metadata.discovered ( size: 1 ) : element-ui/packages/theme-chalk/src/index.scss
    vite:deps Dependencies bundled in 1140.67ms +0ms
+ metadata.discovered ( size: 2 ) : element-ui/packages/theme-chalk/src/index.scss, element-ui/lib/button
    vite:deps ? using post-scan optimizer result, the scanner found every used dependency +75ms
    vite:deps ? dependencies optimized +1ms

問題解決


問題找到了,解決辦法也很簡(jiǎn)單:調(diào)整定義 crawlDeps 的位置,等兩段 await 都結(jié)束后再讀取最新的 metadata.discovered 。最便捷的方法就是直接改 npm 包代碼再用 patch-package 生成補(bǔ)丁,之后直接應(yīng)用補(bǔ)丁就行了。( 參考 patch-package 小節(jié)

patches/vite+4.4.9.patch

diff --git a/node_modules/vite/dist/node/chunks/dep-df561101.js b/node_modules/vite/dist/node/chunks/dep-df561101.js
index 1bc8674..092f4e0 100644
--- a/node_modules/vite/dist/node/chunks/dep-df561101.js
+++ b/node_modules/vite/dist/node/chunks/dep-df561101.js
@@ -45412,7 +45412,6 @@ async function createDepsOptimizer(config, server) {
         if (closed) {
             return;
         }
-        const crawlDeps = Object.keys(metadata.discovered);
         // Await for the scan+optimize step running in the background
         // It normally should be over by the time crawling of user code ended
         await depsOptimizer.scanProcessing;
@@ -45420,6 +45419,7 @@ async function createDepsOptimizer(config, server) {
             const result = await optimizationResult.result;
             optimizationResult = undefined;
             currentlyProcessing = false;
+            const crawlDeps = Object.keys(metadata.discovered);
             const scanDeps = Object.keys(result.metadata.optimized);
             if (scanDeps.length === 0 && crawlDeps.length === 0) {
                 debug$8?.(colors$1.green(`? no dependencies found by the scanner or crawling static imports`));
@@ -45452,6 +45452,7 @@ async function createDepsOptimizer(config, server) {
             }
         }
         else {
+            const crawlDeps = Object.keys(metadata.discovered);
             currentlyProcessing = false;
             if (crawlDeps.length === 0) {
                 debug$8?.(colors$1.green(`? no dependencies found while crawling the static imports`));

問題復(fù)現(xiàn)


問題解決了就想著去倉(cāng)庫(kù)提交一個(gè) PR 從根源上進(jìn)行修復(fù),但這就需要提供一個(gè) 最小復(fù)現(xiàn) 來證實(shí)這確實(shí)是個(gè)問題。前面也說了這個(gè)問題是偶現(xiàn)的,甚至可能和項(xiàng)目的復(fù)雜程度有關(guān)系,想要在一個(gè)新建的項(xiàng)目里穩(wěn)定復(fù)現(xiàn)著實(shí)有點(diǎn)困難。花了幾天時(shí)間各種試,終于找到一種特定情況可以穩(wěn)定復(fù)現(xiàn):

  • 啟動(dòng)項(xiàng)目的同時(shí)立即訪問任意 url( 除了 //favicon.ico 以及 public 下的靜態(tài)資源 ),比如 /xxx 。這一點(diǎn)非常重要,因?yàn)檫@會(huì)在 transformMiddleware 中觸發(fā)額外的 transformRequest ,會(huì)因此導(dǎo)致 checkIfCrawlEndAfterTimeout 中的定時(shí)器提前啟動(dòng)。

    const knownIgnoreList = new Set(['/', '/favicon.ico'])
    
    ...
    
    return async function viteTransformMiddleware(req, res, next) {
      if (req.method !== 'GET' || knownIgnoreList.has(req.url!)) {
        return next()
      }
    
    ...
    
    const result = await transformRequest(url, server, {
     html: req.headers.accept?.includes('text/html'),
    })
    

    為了不考驗(yàn)手速,直接寫了一個(gè)插件來模擬請(qǐng)求:

    function requestSimulation() {
      return {
        name: 'request-simulation',
        configureServer(server) {
          const { listen } = server;
          server.listen = async (...args) => {
            await listen.apply(server, args);
            // request as fast as server is ready without manually open browser
            const url = server.resolvedUrls.local[0] + 'not_root';
            axios.get(url, { headers: { Accept: 'text/html' } }).catch((e) => {
              console.error(e);
            });
          };
        },
      };
    }
    
  • 通過插件在解析階段動(dòng)態(tài)增加依賴項(xiàng)( 比如 unplugin-auto-import 插件做的事 ),這類依賴在 自動(dòng)依賴搜尋 階段不會(huì)被識(shí)別到,只有請(qǐng)求源文件時(shí)才會(huì)通過 addMissingDep 追加到預(yù)構(gòu)建依賴中。

    function autoImport() {
      return {
        name: 'auto-import',
        transform(code, id) {
          if (id.includes('/main')) {
            // trigger addMissingDep
            return `import ElButton from 'element-ui/lib/button'\n${code}`;
          }
        },
      };
    }
    
  • 執(zhí)行緩慢的 transformIndexHtml 用來增加請(qǐng)求完 /xxxpreTransformRequest 被調(diào)用前的時(shí)間間隔,會(huì)導(dǎo)致 onCrawlEnd 提前執(zhí)行( 因?yàn)?checkIfCrawlEndAfterTimeout 中設(shè)置的定時(shí)器到時(shí)間了而且沒有被延長(zhǎng) )。

    function slowTransformIndexHtml() {
      return {
        name: 'slow-transform-index-html',
        transformIndexHtml: {
          order: 'pre',
          async handler(html) {
            // manually make it slower
            await new Promise((resolve) => {
              // wait time longer than callCrawlEndIfIdleAfterMs
              setTimeout(() => resolve(), 100);
            });
            return html;
          },
        },
      };
    }
    

    preTransformRequest 就是在請(qǐng)求 html 的時(shí)候就預(yù)先解析依賴文件而無(wú)需等到瀏覽器請(qǐng)求每個(gè)資源文件的時(shí)候請(qǐng)求一個(gè)解析一個(gè),可通過配置關(guān)閉。

    function preTransformRequest(server: ViteDevServer, url: string, base: string) {
     if (!server.config.server.preTransformRequests) return
    
     url = unwrapId(stripBase(url, base))
    
     // transform all url as non-ssr as html includes client-side assets only
     server.transformRequest(url).catch((e) => {
       if (
         e?.code === ERR_OUTDATED_OPTIMIZED_DEP ||
         e?.code === ERR_CLOSED_SERVER
       ) {
         // these are expected errors
         return
       }
       // Unexpected error, log the issue but avoid an unhandled exception
       server.config.logger.error(e.message)
     })
    }
    
  • 預(yù)構(gòu)建一個(gè)比較大的 scss 文件 ( 比如 element-ui ),編譯這個(gè) scss 文件可能會(huì)花費(fèi)數(shù)秒的時(shí)間,就會(huì)導(dǎo)致整個(gè)預(yù)構(gòu)建階段變慢,也就是增加前面提到的兩段 await 前后的間隔時(shí)間。

    function slowOptimize() {
      return {
        name: 'slow-optimize',
        config() {
          return {
            // pre-build scss file to make the optimize step slower
            // ref: https://github.com/vitejs/vite/issues/7719#issuecomment-1098683109
            optimizeDeps: {
              extensions: ['.scss', '.sass'],
              include: ['element-ui/packages/theme-chalk/src/index.scss'],
              esbuildOptions: {
                plugins: [
                  sassPlugin({
                    type: 'style',
                    logger: { warn() {} },
                  }),
                ],
              },
            },
          };
        },
      };
    }
    

復(fù)現(xiàn)步驟


點(diǎn)擊此處查看 demo

vite-bug
pnpm i
pnpm run dev

啟動(dòng)項(xiàng)目后打開控制臺(tái)就能看到 504 (Outdated Optimize Dep) 錯(cuò)誤了。啟動(dòng)日志如下:

  Forced re-optimization of dependencies
    vite:deps scanning for dependencies... +0ms

    VITE v4.4.9  ready in 986 ms

    ?  Local:   http://localhost:5173/
    ?  Network: use --host to expose
    ?  press h to show help
+ call delayDepsOptimizerUntil('/not_root') 0ms after the last
+ call markIdAsDone('/not_root') 0ms after the last
    vite:deps Crawling dependencies using entries:
    vite:deps   /home/projects/vitejs-vite-imqoo8/index.html +0ms
    vite:deps ? static imports crawl ended +871ms
+ metadata.discovered ( size: 1 ) : element-ui/packages/theme-chalk/src/index.scss
    vite:deps Scan completed in 927.22ms: no dependencies found +95ms
+ metadata.discovered ( size: 1 ) : element-ui/packages/theme-chalk/src/index.scss
+ call delayDepsOptimizerUntil('main.js') 177ms after the last
+ call delayDepsOptimizerUntil('node_modules/.vite/deps/element-ui_lib_button.js?v=1d7c7005') 10ms after the last
    vite:deps Dependencies bundled in 1216.69ms +0ms
+ metadata.discovered ( size: 2 ) : element-ui/packages/theme-chalk/src/index.scss, element-ui/lib/button
    vite:deps ? using post-scan optimizer result, the scanner found every used dependency +1s
    vite:deps ? dependencies optimized +1ms
+ call delayDepsOptimizerUntil('node_modules/.pnpm/vite@4.4.9_sass@1.66.1/node_modules/vite/dist/client/client.mjs') 1175ms after the last
+ call delayDepsOptimizerUntil('node_modules/.pnpm/vite@4.4.9_sass@1.66.1/node_modules/vite/dist/client/env.mjs') 3ms after the last
+ call delayDepsOptimizerUntil('node_modules/.vite/deps/element-ui_lib_button.js?v=1d7c7005') 405ms after the last

多次運(yùn)行項(xiàng)目,你可能會(huì)發(fā)現(xiàn)綠色部分的日志出現(xiàn)在不同的位置。

根據(jù)日志可以看出兩點(diǎn):

  • 調(diào)用 delayDepsOptimizerUntil('/not_root') 之后 177ms 才調(diào)用的 delayDepsOptimizerUntil('main.js') 。

    這個(gè)時(shí)間間隔超出了 callCrawlEndIfIdleAfterMs 定義的 50ms ,所以定時(shí)器沒有被延長(zhǎng)反而早于預(yù)期地執(zhí)行了 onCrawlEnd 。

    硬編碼來延長(zhǎng)計(jì)時(shí)器的做法就不太合理,來個(gè)耗時(shí)的異步任務(wù)就打破這個(gè)鏈條導(dǎo)致提前結(jié)束了。

    理想的效果:

         scan             optimize                     │
    ──────────────? ─────────────────────?             │
                                                       │
    ──────────────────────────────────────────────────?│
         crawl of static imports                       │
    

    實(shí)際運(yùn)行的效果:

         scan             optimize             │
    ──────────────? ─────────────────────?     │
                                               │
    ──────────────────────────────────────────?│-------
         crawl of static imports               │
    

    這時(shí)提前結(jié)束很可能導(dǎo)致一部分依賴沒有預(yù)構(gòu)建上,不太好模擬自行想象吧。

  • 如前面分析過的, metadata.discovered 的數(shù)量從 1 變?yōu)榱?2 。

修改 vite.config.js 中的配置:

- process.env.NO_SLOW || slowTransformIndexHtml(),
+ // process.env.NO_SLOW || slowTransformIndexHtml(),

Vite 會(huì)自動(dòng)重啟,再看下啟動(dòng)日志:

  [vite] vite.config.js changed, restarting server...
  Forced re-optimization of dependencies
    vite:deps scanning for dependencies... +25m
  [vite] server restarted.
+ call delayDepsOptimizerUntil('/not_root') 0ms after the last
+ call markIdAsDone('/not_root') 0ms after the last
    vite:deps Crawling dependencies using entries:
    vite:deps   /home/projects/vitejs-vite-imqoo8/index.html +25m
+ call delayDepsOptimizerUntil('main.js') 823ms after the last
    vite:deps Scan completed in 928.80ms: no dependencies found +99ms
+ call delayDepsOptimizerUntil('node_modules/.vite/deps/element-ui_lib_button.js?v=e80a6e76') 100ms after the last
+ call markIdAsDone('/home/projects/vitejs-vite-imqoo8/main.js') 922ms after the last
    vite:deps ? static imports crawl ended +988ms
+ metadata.discovered ( size: 2 ) : element-ui/packages/theme-chalk/src/index.scss, element-ui/lib/button
+ metadata.discovered ( size: 2 ) : element-ui/packages/theme-chalk/src/index.scss, element-ui/lib/button
    vite:deps Dependencies bundled in 1137.07ms +25m
+ metadata.discovered ( size: 2 ) : element-ui/packages/theme-chalk/src/index.scss, element-ui/lib/button
    vite:deps ? new dependencies were found while crawling that weren't detected by the scanner +1s
    vite:deps ? re-running optimizer +0ms
    vite:deps new dependencies found: element-ui/packages/theme-chalk/src/index.scss, element-ui/lib/button +6ms
    vite:deps Dependencies bundled in 1032.49ms +1s
    vite:deps ? dependencies optimized +1s
+ call delayDepsOptimizerUntil('node_modules/.vite/deps/chunk-76J2PTFD.js?v=b0e72c20') 2196ms after the last
+ call delayDepsOptimizerUntil('node_modules/.pnpm/vite@4.4.9_sass@1.66.1/node_modules/vite/dist/client/client.mjs') 4394ms after the last
+ call delayDepsOptimizerUntil('node_modules/.pnpm/vite@4.4.9_sass@1.66.1/node_modules/vite/dist/client/env.mjs') 2ms after the last

會(huì)發(fā)現(xiàn) metadata.discovered 的數(shù)量不再變化,并且 re-running optimizer 這一步按照預(yù)期所想的被執(zhí)行了。

此時(shí)在頁(yè)面中可以看到 Hello,World! 的內(nèi)容,并且控制臺(tái)也不再報(bào)錯(cuò)了。

另外,這里比較有意思的一點(diǎn)就是 delayDepsOptimizerUntil('main.js') 前的時(shí)間間隔變成了 823ms ,說明在此之前執(zhí)行了 scss 文件的預(yù)構(gòu)建,然而再重啟項(xiàng)目會(huì)發(fā)現(xiàn)這個(gè)時(shí)間可能又變成 10ms 了,也就是說預(yù)構(gòu)建 scss 文件的時(shí)機(jī)忽早忽遲的。

后續(xù)思考


async/await 語(yǔ)法可以看作是 Promise 鏈?zhǔn)交卣{(diào)的語(yǔ)法糖,以類似寫同步代碼的順序來寫異步代碼,更便于理解。加上 JS 本就是單線程執(zhí)行的,所以用多了這種語(yǔ)法以后就會(huì)陷入一種 就是在寫同步代碼 的誤區(qū)。await 表達(dá)式會(huì)跳出當(dāng)前函數(shù)而執(zhí)行 隊(duì)列( event loop ) 中的其他代碼,等待異步操作結(jié)束后再跳回到當(dāng)前函數(shù)繼續(xù)執(zhí)行, 這個(gè)中間過程需要多久,又執(zhí)行了哪些不相關(guān)的其他代碼 是無(wú)法預(yù)料的,所以本文出現(xiàn)的這個(gè)問題就是因?yàn)闆]有考慮到 await 前后局部變量的狀態(tài)會(huì)發(fā)生變化而導(dǎo)致的。

而且異步代碼不只是 Promise ,還有各種 事件監(jiān)聽定時(shí)器 、 微任務(wù) 等,綜合下來整個(gè)代碼的運(yùn)行順序和想象中可能就不太一樣了。思考下面這段代碼的執(zhí)行結(jié)果:

const queue = [];

async function sleep(time) {
  await new Promise((resolve) => {
    setTimeout(() => resolve(), time);
  });
}

async function randomSleep() {
  await sleep(Math.round(Math.random() * 100));
}

async function a(n) {
  await randomSleep();
  queue.push(`a${n}`);
  await b(n);
}

async function b(n) {
  await randomSleep();
  queue.push(`b${n}`);
  await c(n);
}

async function c(n) {
  await randomSleep();
  queue.push(`c${n}`);
}

(async () => {
  await Promise.all(
    Array(5)
      .fill(null)
      .map((_, n) => a(n))
  );
  console.log(queue.join(' > '));
})();

a[n] > b[n] > c[n] 的順序是必然的,但是 a[i]b[j] 的順序就說不準(zhǔn)了。

另外,在寫同步代碼的時(shí)候都喜歡用這種方式來計(jì)算耗時(shí):

const start = Date.now();
foo();
console.log(`use ${Date.now() - start}ms`);

針對(duì)異步代碼還用這種方式就不太準(zhǔn)了,還是以上面的代碼為例,運(yùn)行結(jié)果可能是這樣的:

a0 > a1 > b0 > b1 > c0 > c1

如果只是簡(jiǎn)單的以 a0 開始 c0 結(jié)束來計(jì)算時(shí)間差顯然是錯(cuò)誤的,因?yàn)橹虚g還包括了 a1b1 的耗時(shí)。

Vite 就是如此簡(jiǎn)單粗暴地計(jì)算耗時(shí)的。比如為了 自定義 Element 主題 就需要編譯整個(gè) scss 文件,然而打開 debug 日志后可以發(fā)現(xiàn)不僅是這個(gè) scss 文件的耗時(shí)巨慢,其他代碼的耗時(shí)也跟著增加了,這就對(duì)排查問題造成了干擾( 明明慢的只是一個(gè)文件而已,結(jié)果日志卻顯示很多文件都慢 )。

一個(gè)巨慢的同步任務(wù)導(dǎo)致整個(gè)主線程卡住顯然是不好的體驗(yàn),只能期待后續(xù) Vite 對(duì)多線程的應(yīng)用

關(guān)于 StackBlitz


曾經(jīng)在分享 demo 的時(shí)候用到過一些在線編輯器: CodeSandbox 、 CodePen 、 StackBlitz 等,主要原理都是把代碼上傳到云端,然后單獨(dú)啟動(dòng)一個(gè)容器來運(yùn)行項(xiàng)目并提供端口供用戶訪問。限制還是比較多,寫項(xiàng)目不現(xiàn)實(shí),只適合做分享和演示,綜合使用下來還是 CodeSandbox 體驗(yàn)好一點(diǎn)。

然而沒想到幾年過去 StackBlitz 直接彎道超車了, WebContainers 把整個(gè) Node.js 環(huán)境搬到了瀏覽器上,可以像在本地環(huán)境中一樣使用任意的前端框架并且支持各種 Node.js 后端框架,再加上 VSCode 風(fēng)格的編輯器,和本地開發(fā)體驗(yàn)是非常接近的。

這次提 PR 就用到了 StackBlitz Codeflow ( 強(qiáng)烈建議看下官網(wǎng)的演示視頻 ),寫 demo 、提 issue 、寫 PR 整個(gè)流程一條龍服務(wù),不需要 clone 項(xiàng)目到本地,所有操作全在瀏覽器內(nèi)完成。

  • .new 域名 支持創(chuàng)建眾多腳手架生成的模板項(xiàng)目。
  • 打開任意的 GitHub 倉(cāng)庫(kù),在 url 前面加上 pr.new 前綴就能重定向到 Codeflow IDE 一鍵開始項(xiàng)目開發(fā),而無(wú)需專門配置本地開發(fā)環(huán)境。
  • issue 頁(yè)面跳轉(zhuǎn)到 pr.new 后會(huì)自動(dòng)拉取 issue 中提供的 reproduction 項(xiàng)目和主體項(xiàng)目進(jìn)行 關(guān)聯(lián) ,這樣寫 PR 的時(shí)候就能直接驗(yàn)證更改操作是否有效。 這個(gè)功能非常有用!

如何評(píng)價(jià) StackBlitz 可在瀏覽器運(yùn)行 Node.js 程序的 WebContainers?

最后


這是我真正意義上的第一次提 issuePR ,很榮幸成為 Vite 項(xiàng)目 contributors 中的一員,為開源貢獻(xiàn)了一點(diǎn)自己的微薄之力。

不過,在本地定位問題并解決也就只需要幾個(gè)小時(shí),然而梳理原因、分析解釋、翻譯、提供最小復(fù)現(xiàn)、測(cè)試卻花費(fèi)了數(shù)天,挺耽誤時(shí)間的。再加上項(xiàng)目成員看到問題、確認(rèn)、合并 PR 、發(fā)包又是不短的時(shí)間,時(shí)效性太低了,這也是我一直以來不想提 issue 的原因。

仔細(xì)想想,還是自己改代碼然后 patch-package 打補(bǔ)丁比較香,這也是 node_modules 這個(gè) 屎山 為數(shù)不多的優(yōu)點(diǎn)了。


轉(zhuǎn)載請(qǐng)注明出處: https://github.com/anyesu/blog/issues/46

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容