原來rollup這么簡(jiǎn)單之 rollup.rollup篇

大家好,我是小雨小雨,致力于分享有趣的、實(shí)用的技術(shù)文章。
內(nèi)容分為翻譯和原創(chuàng),如果有問題,歡迎隨時(shí)評(píng)論或私信,希望和大家一起進(jìn)步。
分享不易,希望能夠得到大家的支持和關(guān)注。

計(jì)劃

rollup系列打算一章一章的放出,內(nèi)容更精簡(jiǎn)更專一更易于理解

目前打算分為以下幾章:

TL;DR

在進(jìn)入枯燥的代碼解析之前,先大白話說下整個(gè)過程,rollup.rollup()主要分為以下幾步:

  1. 配置收集、標(biāo)準(zhǔn)化
  2. 文件分析
  3. 源碼編譯,生成ast
  4. 模塊生成
  5. 依賴解析
  6. 過濾凈化
  7. 產(chǎn)出chunks

按照這個(gè)思路來看其實(shí)很簡(jiǎn)單,但是具體的細(xì)節(jié)卻是百般復(fù)雜的。
不過我們也不必糾結(jié)于具體的某些實(shí)現(xiàn),畢竟條條大路通羅馬,我們可以吸納并改進(jìn)或?qū)W習(xí)一些沒見過的代碼技巧或優(yōu)化方法,在我看來,這才是良好的閱讀源碼的方式。:)

注意點(diǎn)

所有的注釋都在這里,可自行閱讀

!!!版本 => 筆者閱讀的rollup版本為: 1.32.0

!!!提示 => 標(biāo)有TODO為具體實(shí)現(xiàn)細(xì)節(jié),會(huì)視情況分析。

!!!注意 => 每一個(gè)子標(biāo)題都是父標(biāo)題(函數(shù))內(nèi)部實(shí)現(xiàn)

!!!強(qiáng)調(diào) => rollup中模塊(文件)的id就是文件地址,所以類似resolveID這種就是解析文件地址的意思,我們可以返回我們想返回的文件id(也就是地址,相對(duì)路徑、決定路徑)來讓rollup加載

rollup是一個(gè)核心,只做最基礎(chǔ)的事情,比如提供默認(rèn)模塊(文件)加載機(jī)制, 比如打包成不同風(fēng)格的內(nèi)容,我們的插件中提供了加載文件路徑,解析文件內(nèi)容(處理ts,sass等)等操作,是一種插拔式的設(shè)計(jì),和webpack類似
插拔式是一種非常靈活且可長(zhǎng)期迭代更新的設(shè)計(jì),這也是一個(gè)中大型框架的核心,人多力量大嘛~

主要通用模塊以及含義

  1. Graph: 全局唯一的圖,包含入口以及各種依賴的相互關(guān)系,操作方法,緩存等。是rollup的核心
  2. PathTracker: 無副作用模塊依賴路徑追蹤
  3. PluginDriver: 插件驅(qū)動(dòng)器,調(diào)用插件和提供插件環(huán)境上下文等
  4. FileEmitter: 資源操作器
  5. GlobalScope: 全局作用局,相對(duì)的還有局部的
  6. ModuleLoader: 模塊加載器
  7. NodeBase: ast各語法(ArrayExpression、AwaitExpression等)的構(gòu)造基類

主流程解析

  • 1.調(diào)用getInputOptions標(biāo)準(zhǔn)化input配置參數(shù)

    const inputOptions = getInputOptions(rawInputOptions);
    
    • 1.1. 調(diào)用mergeOptions,設(shè)置默認(rèn)的input和output配置,并返回input配置 和 使用非法配置屬性的錯(cuò)誤信息
      let { inputOptions, optionError } = mergeOptions({
        config: rawInputOptions
      });
      
    • 1.2. 調(diào)用options鉤子函數(shù),以在input配合完全標(biāo)準(zhǔn)化之前進(jìn)行自定義修改
      inputOptions = inputOptions.plugins!.reduce(applyOptionHook, inputOptions);
      
    • 1.3. 標(biāo)準(zhǔn)化插件操作:為返回對(duì)象中沒有name屬性的插件設(shè)置默認(rèn)的插件名 => at position 當(dāng)前插件在所有插件中索引值
      inputOptions.plugins = normalizePlugins(inputOptions.plugins!, ANONYMOUS_PLUGIN_PREFIX);
      
    • 1.4. 對(duì)不兼容內(nèi)嵌動(dòng)態(tài)引入模塊或保留模塊兩種情況的配置,進(jìn)行警告報(bào)錯(cuò)
      // 將動(dòng)態(tài)導(dǎo)入的依賴(import | require.ensure() | other)內(nèi)嵌到一個(gè)chunk而不創(chuàng)建獨(dú)立的包,相關(guān)的代碼邏輯如下
      if (inputOptions.inlineDynamicImports) {
        // preserveModules: 盡可能的保留模塊,而不是混合起來,創(chuàng)建更少的chunks,默認(rèn)為false,不開啟
        if (inputOptions.preserveModules) // 如果開啟了,就與內(nèi)嵌沖突了
          return error({
            code: 'INVALID_OPTION',
            message: `"preserveModules" does not support the "inlineDynamicImports" option.`
          });
        // 其他判斷,具體參考代碼倉庫:index.ts
      } else if (inputOptions.preserveModules) {
        // 又對(duì) 以原始文件命名,不綜合打包 的功能進(jìn)行排異處理
        if (inputOptions.manualChunks)
          return error({
            code: 'INVALID_OPTION',
            message: '"preserveModules" does not support the "manualChunks" option.'
          });
        // 其他判斷,具體參考代碼倉庫:index.ts
      }
      
    • 1.5. 返回處理后的input配置
      return inputOptions;
      
  • 2.是否開啟性能檢測(cè),檢測(cè)inputOptions.perf屬性,如果未設(shè)置沒那么檢測(cè)函數(shù)為空

    initialiseTimers(inputOptions);
    
  • 3.創(chuàng)建圖,參數(shù)為input配置和watch,watch當(dāng)前不考慮

    const graph = new Graph(inputOptions, curWatcher);
    
    • 3.1. 初始化警告函數(shù),對(duì)已經(jīng)提示過得警告進(jìn)行緩存

      this.onwarn = (options.onwarn as WarningHandler) || makeOnwarn();
      
    • 3.2. 給當(dāng)前圖掛載路徑追蹤系統(tǒng),無構(gòu)造函數(shù),只有屬性和更改屬性的方法

      this.deoptimizationTracker = new PathTracker();
      
    • 3.3. 初始化當(dāng)前圖的唯一模塊緩存容器,可以將上個(gè)打包結(jié)果的cache屬性賦給下一次打包,提升打包速度 =>

        this.cachedModules = new Map();
      
    • 3.4. 讀取傳遞的上次build結(jié)果中的模塊和插件。插件緩存參考 =>,下文中解釋。

      if (options.cache) {
        if (options.cache.modules)
          for (const module of options.cache.modules) this.cachedModules.set(module.id, module);
      }
      
      if (options.cache !== false) {
        this.pluginCache = (options.cache && options.cache.plugins) || Object.create(null);
      
        for (const name in this.pluginCache) {
          const cache = this.pluginCache[name];
          for (const key of Object.keys(cache)) cache[key][0]++;
        }
      }
      
    • 3.5. treeshake信息掛載。

      if (options.treeshake !== false) {
        this.treeshakingOptions =
          options.treeshake && options.treeshake !== true
            ? {
                annotations: options.treeshake.annotations !== false,
                moduleSideEffects: options.treeshake.moduleSideEffects,
                propertyReadSideEffects: options.treeshake.propertyReadSideEffects !== false,
                pureExternalModules: options.treeshake.pureExternalModules,
                tryCatchDeoptimization: options.treeshake.tryCatchDeoptimization !== false,
                unknownGlobalSideEffects: options.treeshake.unknownGlobalSideEffects !== false
              }
            : {
                annotations: true,
                moduleSideEffects: true,
                propertyReadSideEffects: true,
                tryCatchDeoptimization: true,
                unknownGlobalSideEffects: true
              };
        if (typeof this.treeshakingOptions.pureExternalModules !== 'undefined') {
          this.warnDeprecation(
            `The "treeshake.pureExternalModules" option is deprecated. The "treeshake.moduleSideEffects" option should be used instead. "treeshake.pureExternalModules: true" is equivalent to "treeshake.moduleSideEffects: 'no-external'"`,
            false
          );
        }
      }
      
    • 3.6. 初始化代碼解析器,具體參數(shù)和插件參考Graph.ts

      this.contextParse = (code: string, options: acorn.Options = {}) =>
        this.acornParser.parse(code, {
          ...defaultAcornOptions,
          ...options,
          ...this.acornOptions
        }) as any;
      
    • 3.7. 插件驅(qū)動(dòng)器

      this.pluginDriver = new PluginDriver(
        this,
        options.plugins!,
        this.pluginCache,
        // 處理軟連文件的時(shí)候,是否以為軟連所在地址作為上下文,false為是,true為不是。
        options.preserveSymlinks === true,
        watcher
      );
      
      • 3.7.1. 棄用api警告,參數(shù)掛載

      • 3.7.2. 實(shí)例化FileEmitter并且將實(shí)例所攜帶方法設(shè)置到插件驅(qū)動(dòng)器上

        // basePluginDriver為PluginDriver的第六個(gè)參數(shù),代表graph的'根'插件驅(qū)動(dòng)器
        this.fileEmitter = new FileEmitter(graph, basePluginDriver && basePluginDriver.fileEmitter);
        this.emitFile = this.fileEmitter.emitFile;
        this.getFileName = this.fileEmitter.getFileName;
        this.finaliseAssets = this.fileEmitter.assertAssetsFinalized;
        this.setOutputBundle = this.fileEmitter.setOutputBundle;
        
      • 3.7.3. 插件拼接

        this.plugins = userPlugins.concat(
          basePluginDriver ? basePluginDriver.plugins : [getRollupDefaultPlugin(preserveSymlinks)] 
        );
        
      • 3.7.4. 緩存插件們的上下文環(huán)境,之后執(zhí)行插件的的時(shí)候會(huì)通過index獲取并注入到插件內(nèi)

        // 利用map給每個(gè)插件注入plugin特有的context,并緩存
        this.pluginContexts = this.plugins.map(
          getPluginContexts(pluginCache, graph, this.fileEmitter, watcher)
        );
        
      • 3.7.5. input和output設(shè)置的插件沖突的時(shí)候,報(bào)錯(cuò)

        if (basePluginDriver) {
          for (const plugin of userPlugins) {
            for (const hook of basePluginDriver.previousHooks) {
              if (hook in plugin) {
                graph.warn(errInputHookInOutputPlugin(plugin.name, hook));
              }
            }
          }
        }
        
    • 3.8. 監(jiān)聽模式的設(shè)定

          if (watcher) {
              const handleChange = (id: string) => this.pluginDriver.hookSeqSync('watchChange', [id]);
              watcher.on('change', handleChange);
              watcher.once('restart', () => {
                  watcher.removeListener('change', handleChange);
              });
          }
      
    • 3.9. 全局上下文

      this.scope = new GlobalScope();
      
    • 3.10. 設(shè)置模塊的全局上下文,默認(rèn)為false

      this.context = String(options.context);
      
          // 用戶是否自定義了上下文環(huán)境
          const optionsModuleContext = options.moduleContext;
          if (typeof optionsModuleContext === 'function') {
              this.getModuleContext = id => optionsModuleContext(id) || this.context;
          } else if (typeof optionsModuleContext === 'object') {
              const moduleContext = new Map();
              for (const key in optionsModuleContext) {
                  moduleContext.set(resolve(key), optionsModuleContext[key]);
              }
              this.getModuleContext = id => moduleContext.get(id) || this.context;
          } else {
              this.getModuleContext = () => this.context;
          }
      
    • 3.11. 初始化moduleLoader,用于模塊(文件)的解析和加載

      // 模塊(文件)解析加載,內(nèi)部調(diào)用的resolveID和load等鉤子,讓使用者擁有更多的操作能力
      this.moduleLoader = new ModuleLoader(
              this,
              this.moduleById,
              this.pluginDriver,
              options.external!,
              (typeof options.manualChunks === 'function' && options.manualChunks) as GetManualChunk | null,
              (this.treeshakingOptions ? this.treeshakingOptions.moduleSideEffects : null)!,
              (this.treeshakingOptions ? this.treeshakingOptions.pureExternalModules : false)!
          );
      
  • 4.執(zhí)行buildStart鉤子函數(shù),打包獲取chunks,以供后續(xù)生成和寫入使用

    try {
          // buildStart鉤子函數(shù)觸發(fā)
          await graph.pluginDriver.hookParallel('buildStart', [inputOptions]);
          // 這一步通過id,深度分析拓?fù)潢P(guān)系,去除無用塊,進(jìn)而生成我們的chunks
      
      // build的邏輯詳見下文
          chunks = await graph.build( // 這個(gè)chunks是閉包,所以generate和write可以用到
              inputOptions.input as string | string[] | Record<string, string>,
              inputOptions.manualChunks,
              inputOptions.inlineDynamicImports!
          );
      } catch (err) {
          const watchFiles = Object.keys(graph.watchFiles);
          if (watchFiles.length > 0) {
              err.watchFiles = watchFiles;
          }
          await graph.pluginDriver.hookParallel('buildEnd', [err]);
          throw err;
      }
    
  • 5.返回一個(gè)對(duì)象,包括緩存,監(jiān)聽文件和generate、write兩個(gè)方法

    return {
      cache,
      watchFiles,
      generate,
      write
    }
    
graph.build邏輯解析

build方法通過id,深度分析拓?fù)潢P(guān)系,去除無用塊,進(jìn)而生成我們的chunks
接受三個(gè)參數(shù):入口、提取公共塊規(guī)則(manualChunks)、是否內(nèi)嵌動(dòng)態(tài)導(dǎo)入模塊

  • build是很單一的方法,就是產(chǎn)出我們的chunks。他返回一個(gè)promise對(duì)象供之后的使用。
      return Promise.all([
        入口模塊, // 代碼為: this.moduleLoader.addEntryModules(normalizeEntryModules(entryModules), true)
        用戶定義公共模塊 // 這塊沒有返回值,只是將公共模塊緩存到模塊加載器上,處理結(jié)果由入口模塊代理返回。巧妙的處理方式,一舉兩得
      ]).then((入口模塊的返回) => {
        // 模塊的依賴關(guān)系處理
        return chunks;
      });
    
  • 入口模塊: this.moduleLoader.addEntryModules(normalizeEntryModules(entryModules), true)
    • normalizeEntryModules對(duì)入口進(jìn)行標(biāo)準(zhǔn)化處理,返回統(tǒng)一的格式:
        UnresolvedModule {
            fileName: string | null;
            id: string;
            name: string | null;
        }
      
    • addEntryModules對(duì)模塊進(jìn)行加載、去重,再排序操作,最后返回模塊,公共chunks。其中,在加載過程中會(huì)將處理過的模塊緩存到ModuleLoaders的modulesById(Map對(duì)象)上。部分代碼如下:
        // 模塊加載部分
        private fetchModule(
          id: string,
          importer: string,
          moduleSideEffects: boolean,
          syntheticNamedExports: boolean,
          isEntry: boolean
        ): Promise<Module> {
          // 主流程如下:
          
          // 獲取緩存,提升效率:
          const existingModule = this.modulesById.get(id);
          if (existingModule instanceof Module) {
            existingModule.isEntryPoint = existingModule.isEntryPoint || isEntry;
            return Promise.resolve(existingModule);
          }
          
          // 新建模塊:
          const module: Module = new Module(
            this.graph,
            id,
            moduleSideEffects,
            syntheticNamedExports,
            isEntry
          );
          
          // 緩存,以備優(yōu)化
          this.modulesById.set(id, module);
          
          // 為每一個(gè)入庫模塊設(shè)置已監(jiān)聽
          this.graph.watchFiles[id] = true;
          
          // 調(diào)用用戶定義的manualChunk方法,獲取公共chunks別名,比如:
          // 比如 manualChunkAlias(id){
          //  if (xxx) {
          //      return 'vendor';
          //  }
          // }
          const manualChunkAlias = this.getManualChunk(id);
          
          // 緩存到 manualChunkModules
          if (typeof manualChunkAlias === 'string') {
            this.addModuleToManualChunk(manualChunkAlias, module);
          }
          
          // 調(diào)用load鉤子函數(shù)并返回處理結(jié)果,其中第二個(gè)數(shù)組參數(shù)為傳到鉤子函數(shù)的的參數(shù)
          return Promise.resolve(this.pluginDriver.hookFirst('load', [id]))
            .cache()
            .then(source => {
              // 統(tǒng)一格式: sourceDescription
              return {
                code: souce,
                // ...
              }
            })
            .then(sourceDescription => {
              // 返回鉤子函數(shù)transform處理后的代碼,比如jsx解析結(jié)果,ts解析結(jié)果
              // 參考: https://github.com/rollup/plugins/blob/e7a9e4a516d398cbbd1fa2b605610517d9161525/packages/wasm/src/index.js
              return transform(this.graph, sourceDescription, module);
            })
            .then(source => {
              // 代碼編譯結(jié)果掛在到當(dāng)前解析的入口模塊上
              module.setSource(source);
              // 模塊id與模塊綁定
              this.modulesById.set(id, module);
              // 處理模塊的依賴們,將導(dǎo)出的模塊也掛載到module上
              // !!! 注意: fetchAllDependencies中創(chuàng)建的模塊是通過ExternalModule類創(chuàng)建的,有別的入口模塊的
              return this.fetchAllDependencies(module).then(() => {
                for (const name in module.exports) {
                  if (name !== 'default') {
                    module.exportsAll[name] = module.id;
                  }
                }
                for (const source of module.exportAllSources) {
                  const id = module.resolvedIds[source].id;
                  const exportAllModule = this.modulesById.get(id);
                  if (exportAllModule instanceof ExternalModule) continue;
      
                  for (const name in exportAllModule!.exportsAll) {
                    if (name in module.exportsAll) {
                      this.graph.warn(errNamespaceConflict(name, module, exportAllModule!));
                    } else {
                      module.exportsAll[name] = exportAllModule!.exportsAll[name];
                    }
                  }
                }
              // 返回這些處理后的module對(duì)象,從id(文件路徑) 轉(zhuǎn)換到 一個(gè)近乎具有文件完整信息的對(duì)象。
              return module;
            })
          
        }
      
        // 去重
        let moduleIndex = firstEntryModuleIndex;
              for (const entryModule of entryModules) {
                  // 是否為用戶定義,默認(rèn)是
                  entryModule.isUserDefinedEntryPoint = entryModule.isUserDefinedEntryPoint || isUserDefined;
                  const existingIndexModule = this.indexedEntryModules.find(
                      indexedModule => indexedModule.module.id === entryModule.id
                  );
                  // 根據(jù)moduleIndex進(jìn)行入口去重
                  if (!existingIndexModule) {
                      this.indexedEntryModules.push({ module: entryModule, index: moduleIndex });
                  } else {
                      existingIndexModule.index = Math.min(existingIndexModule.index, moduleIndex);
                  }
                  moduleIndex++;
              }
        // 排序
        this.indexedEntryModules.sort(({ index: indexA }, { index: indexB }) =>
                  indexA > indexB ? 1 : -1
              );
      
  • 模塊的依賴關(guān)系處理 部分
    • 已經(jīng)加載處理過的模塊會(huì)緩存到moduleById上,所以直接遍歷之,再根據(jù)所屬模塊類進(jìn)行分類

        // moduleById是 id => module 的存儲(chǔ), 是所有合法的入口模塊
              for (const module of this.moduleById.values()) {
                  if (module instanceof Module) {
                      this.modules.push(module);
                  } else {
                      this.externalModules.push(module);
                  }
              }
      
    • 獲取所有入口,找到正確的、移除無用的依賴,并過濾出真正作為入口的模塊

        // this.link(entryModules)方法的內(nèi)部
        
        // 找到所有的依賴
        for (const module of this.modules) {
          module.linkDependencies();
        }
        
        // 返回所有的入口啟動(dòng)模塊(也就是非外部模塊),和那些依賴了一圈結(jié)果成死循環(huán)的模塊相對(duì)路徑
        const { orderedModules, cyclePaths } = analyseModuleExecution(entryModules);
        
        // 對(duì)那些死循環(huán)路徑進(jìn)行警告
        for (const cyclePath of cyclePaths) {
          this.warn({
            code: 'CIRCULAR_DEPENDENCY',
            cycle: cyclePath,
            importer: cyclePath[0],
            message: `Circular dependency: ${cyclePath.join(' -> ')}`
          });
        }
        
        // 過濾出真正的入口啟動(dòng)模塊,賦值給modules
        this.modules = orderedModules;
        
        // ast語法的進(jìn)一步解析
        // TODO: 視情況詳細(xì)補(bǔ)充
        for (const module of this.modules) {
          module.bindReferences();
        }
        
      
    • 剩余部分

        // 引入所有的導(dǎo)出,設(shè)定相關(guān)關(guān)系
        // TODO: 視情況詳細(xì)補(bǔ)充
          for (const module of entryModules) {
                  module.includeAllExports();
              }
        
        // 根據(jù)用戶的treeshaking配置,給引入的環(huán)境設(shè)置上下文環(huán)境
              this.includeMarked(this.modules);
        
              // 檢查所有沒使用的模塊,進(jìn)行提示警告
              for (const externalModule of this.externalModules) externalModule.warnUnusedImports();
        
        // 給每個(gè)入口模塊添加hash,以備后續(xù)整合到一個(gè)chunk里
        if (!this.preserveModules && !inlineDynamicImports) {
                  assignChunkColouringHashes(entryModules, manualChunkModulesByAlias);
              }
        
        let chunks: Chunk[] = [];
        
        // 為每個(gè)模塊都創(chuàng)建chunk
              if (this.preserveModules) {
                  // 遍歷入口模塊
                  for (const module of this.modules) {
                      // 新建chunk實(shí)例對(duì)象
                      const chunk = new Chunk(this, [module]);
                      // 是入口模塊,并且非空
                      if (module.isEntryPoint || !chunk.isEmpty) {
                          chunk.entryModules = [module];
                      }
                      chunks.push(chunk);
                  }
              } else {
                  // 創(chuàng)建盡可能少的chunk
                  const chunkModules: { [entryHashSum: string]: Module[] } = {};
                  for (const module of this.modules) {
                      // 將之前設(shè)置的hash值轉(zhuǎn)換為string
                      const entryPointsHashStr = Uint8ArrayToHexString(module.entryPointsHash);
                      const curChunk = chunkModules[entryPointsHashStr];
                      // 有的話,添加module,沒有的話創(chuàng)建并添加,相同的hash值會(huì)添加到一起
                      if (curChunk) {
                          curChunk.push(module);
                      } else {
                          chunkModules[entryPointsHashStr] = [module];
                      }
                  }
      
                  // 將同一hash值的chunks們排序后,添加到chunks中
                  for (const entryHashSum in chunkModules) {
                      const chunkModulesOrdered = chunkModules[entryHashSum];
                      // 根據(jù)之前的設(shè)定的index排序,這個(gè)應(yīng)該代表引入的順序,或者執(zhí)行的先后順序
                      sortByExecutionOrder(chunkModulesOrdered);
                      // 用排序后的chunkModulesOrdered新建chunk
                      const chunk = new Chunk(this, chunkModulesOrdered);
                      chunks.push(chunk);
                  }
              }
        
        // 將依賴掛載到每個(gè)chunk上
              for (const chunk of chunks) {
                  chunk.link();
              }
      

以上就是rollup.rollup的主流程分析,具體細(xì)節(jié)參考代碼庫注釋

部分功能的具體解析

  • 插件緩存能力解析,為開發(fā)者們提供了插件上的緩存能力,利用cacheKey可以共享相同插件的不同實(shí)例間的數(shù)據(jù)
function createPluginCache(cache: SerializablePluginCache): PluginCache {
    // 利用閉包將cache緩存
    return {
        has(id: string) {
            const item = cache[id];
            if (!item) return false;
            item[0] = 0; // 如果訪問了,那么重置訪問過期次數(shù),猜測(cè):就是說明用戶有意向主動(dòng)去使用
            return true;
        },
        get(id: string) {
            const item = cache[id];
            if (!item) return undefined;
            item[0] = 0; // 如果訪問了,那么重置訪問過期次數(shù)
            return item[1];
        },
        set(id: string, value: any) {
            cache[id] = [0, value];
        },
        delete(id: string) {
            return delete cache[id];
        }
    };
}

可以看到rollup利用對(duì)象加數(shù)組的結(jié)構(gòu)來為插件提供緩存能力,即:

{
  test: [0, '內(nèi)容']
}

數(shù)組的第一項(xiàng)是當(dāng)前訪問的計(jì)數(shù)器,和緩存的過期次數(shù)掛鉤,再加上js的閉包能力簡(jiǎn)單實(shí)用的提供了插件上的緩存能力

總結(jié)

到目前為止,再一次加深了職能單一和依賴注入重要性,比如模塊加載器,插件驅(qū)動(dòng)器,還有Graph。還有rollup的(數(shù)據(jù))模塊化,webpack也類似,vue也類似,都是將具象的內(nèi)容轉(zhuǎn)換為抽象的數(shù)據(jù),再不斷掛載相關(guān)的依賴的其他抽象數(shù)據(jù),當(dāng)然這其中需要符合某些規(guī)范,比如estree規(guī)范。

鄙人一直對(duì)構(gòu)建很感興趣,我的github有接近一半都是和構(gòu)建有關(guān)的,所以這次從rollup入口,開始揭開構(gòu)建世界的那一層層霧霾,還我們一個(gè)清晰地世界。:)

rollup系列不會(huì)參考別人的分享(目前也沒找到有人分析rollup。。),完全自食其力一行一行的閱讀,所以難免會(huì)有些地方不是很正確。
沒辦法,閱讀別人的代碼,有些地方就像猜女人的心思,太tm難了,所以有不對(duì)的地方希望大佬們多多指點(diǎn),互相學(xué)習(xí)。

image

還是那句話,創(chuàng)作不易,希望得到大家的支持,與君共勉,咱們下期見!

image
最后編輯于
?著作權(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)容