最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • vite预构建源码梳理

    正文概述 掘金(wkstudy)   2021-08-15   2474

    对于“为什么要进行依赖预构建?"这个问题vite文档已经解释的很清楚了,那么预构建大概的流程是什么样的呢?

    启动预构建

    从文档中我们知道在服务启动前会进行预构建,对应源码位置在src/node/server/index.ts,预构建的函数名是optimizeDeps

    ...
    const runOptimize = async () => {
        if (config.cacheDir) {
          server._isRunningOptimizer = true;
          try {
            server._optimizeDepsMetadata = await optimizeDeps(config);
          } finally {
            server._isRunningOptimizer = false;
          }
          server._registerMissingImport = createMissingImporterRegisterFn(server);
        }
      };
      
    ...
    await runOptimize();
    ...
    

    开始预构建

    函数optimizeDeps定义在src/node/optimizer/index.ts,其主要流程可分为以下几步:

    1. 判断是否需要预构建,如果之前预构建的内容还可以用,那么直接return,反之继续往下执行。需要说明的是判断预构建的内容是否可用的依据是package.lock.json和部分vite配置的内容,具体实现在getDepHash函数中。
    
      if (!force) {
        let prevData;
        try {
          prevData = JSON.parse(fs.readFileSync(dataPath, "utf-8"));
        } catch (e) {}
        // hash is consistent, no need to re-bundle
        if (prevData && prevData.hash === data.hash) {
          log("Hash is consistent. Skipping. Use --force to override.");
          return prevData;
        }
      }
    
    1. 使用esbuild解析整个项目,获取本次需要进行预构建的依赖解析出问题的依赖,分别赋值给depsmissing,其主要执行过程在函数scanImports中(这部分的代码实现过程梳理放在本部分最后)。
      let deps: Record<string, string>, missing: Record<string, string>;
      if (!newDeps) {
        ({ deps, missing } = await scanImports(config));
      } else {
        deps = newDeps;
        missing = {};
      }
    
    1. 正式预构建前的一系列的处理
      1. 如果missing有值,则报错,就是我们在控制台看到的The following dependencies are imported but could not be resolved.... Are they installed
      2. 把配置项config.optimizeDeps?.include里的依赖加入到deps中,如果处理失败的话也会在控制台报错
      3. 如果deps为空的话,说明不需要预构建,更新预构建内容的hash值后直接return
      4. 执行到这说明本次需要进行预构建,在控制台提示本次预构建的依赖,如下图所示

    vite预构建源码梳理 4. 进一步处理deps得到flatIdDeps,主要是因为默认esbuild打包的话对于依赖的分析、映射的处理可能比较麻烦,这里主要做了两方面的工作

    vite预构建源码梳理

    // esbuild generates nested directory output with lowest common ancestor base
     // this is unpredictable and makes it difficult to analyze entry / output
     // mapping. So what we do here is:
     // 1. flatten all ids to eliminate slash
     // 2. in the plugin, read the entry ourselves as virtual files to retain the
     //    path.
     const flatIdDeps: Record<string, string> = {};
     const idToExports: Record<string, ExportsData> = {};
     const flatIdToExports: Record<string, ExportsData> = {};
    
     await init;
     for (const id in deps) {
       const flatId = flattenId(id);
       flatIdDeps[flatId] = deps[id];
       const entryContent = fs.readFileSync(deps[id], "utf-8");
       const exportsData = parse(entryContent) as ExportsData;
       for (const { ss, se } of exportsData[0]) {
         const exp = entryContent.slice(ss, se);
         if (/export\s+\*\s+from/.test(exp)) {
           exportsData.hasReExports = true;
         }
       }
       idToExports[id] = exportsData;
       flatIdToExports[flatId] = exportsData;
     }
    
    1. 使用esbuild对deps每个依赖进行构建并默认输出到node_modules/.vite
     const result = await build({
       entryPoints: Object.keys(flatIdDeps),
       bundle: true,
       format: "esm",
       external: config.optimizeDeps?.exclude,
       logLevel: "error",
       splitting: true,
       sourcemap: true,
       outdir: cacheDir,
       treeShaking: "ignore-annotations",
       metafile: true,
       define,
       plugins: [
         ...plugins,
         esbuildDepPlugin(flatIdDeps, flatIdToExports, config),
       ],
       ...esbuildOptions,
     });
    
    1. 把此次预构建的信息更新并写入文件node_modules/.vite/_metadata.json,完成预构建!
    for (const id in deps) {
        const entry = deps[id];
        data.optimized[id] = {
          file: normalizePath(path.resolve(cacheDir, flattenId(id) + ".js")),
          src: entry,
          needsInterop: needsInterop(
            id,
            idToExports[id],
            meta.outputs,
            cacheDirOutputPath
          ),
        };
      }
    
      writeFile(dataPath, JSON.stringify(data, null, 2));
    

    scanImports

    ”具体哪些依赖是需要预构建的?“是函数scanImports处理的,在src/node/optimizer/scan.ts中,其过程比较简单,大概分为两步:

    // step 1
     let entries: string[] = []
     ...
     entries = await globEntries('**/*.html', config)
     ...
     
    // step 2
    const plugin = esbuildScanPlugin(config, container, deps, missing, entries)
    
    const { plugins = [], ...esbuildOptions } =
        config.optimizeDeps?.esbuildOptions ?? {}
      await Promise.all(
        entries.map((entry) =>
          build({
            write: false,
            entryPoints: [entry],
            bundle: true,
            format: 'esm',
            logLevel: 'error',
            plugins: [...plugins, plugin],
            ...esbuildOptions
          })
        )
      )
      
    return {
        deps,
        missing
    }
    
    

    由上可以看出,depsmissing是在esbuild插件esbuildScanPlugin中得到的,那么这个插件是怎么做的呢?

    esbuildScanPlugin

    还是在src/node/optimizer/scan.ts中,该插件主要做了以下两件事:

    1. 处理导入模块(依赖),在build.onResolve中,具体:
    ...
    export const OPTIMIZABLE_ENTRY_RE = /\.(?:m?js|ts)$/
    ...
    const resolved = await resolve(id, importer)
    if (resolved) {
        if (shouldExternalizeDep(resolved, id)) {
          return externalUnlessEntry({ path: id })
        }
        if (resolved.includes('node_modules') || include?.includes(id)) {
          // dependency or forced included, externalize and stop crawling
          if (OPTIMIZABLE_ENTRY_RE.test(resolved)) {
            depImports[id] = resolved
          }
          return externalUnlessEntry({ path: id })
        } else {
          // linked package, keep crawling
          return {
            path: path.resolve(resolved)
          }
        }
    } else {
        missing[id] = normalizePath(importer)
    }
    

    由上可知模块(依赖)是否放在deps、missing里、放的话放在哪一个都是由函数resolve决定的,从代码中可以看到resolve的执行逻辑如下:

    由于我对于这一段的处理逻辑不是很清楚,这里只能简单的理解为:

    这里就把预构建需要的depsmissing收集到了。

    1. 处理文件内容,在build.onLoad中,具体:

    预构建的结果

    预构建的结果都放在了node_modules/.vite/中,一般如下图所示,包括两方面的信息:

    vite预构建源码梳理

    1. _metadata.json,是本次预构建产生的一些“版本”和依赖包的信息,如下图所示:

    vite预构建源码梳理

    1. xx.js, xxx.js.map各个依赖包的打包结果

    END

    预构建部分的代码实现大概就是这样,文章同步放在了vite源码阅读中,关于vite源码相关的学习都会记录在这里,欢迎大家讨论交流,感谢各位?


    下载网 » vite预构建源码梳理

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元