<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端底層構(gòu)建工具重構(gòu)之路——IMFLOW架構(gòu)升級(jí)文檔

          共 22402字,需瀏覽 45分鐘

           ·

          2021-11-17 01:18

          導(dǎo)語 |?IMFLOW 是 IMWeb 團(tuán)隊(duì)的一款集成了 Webpack 最佳實(shí)踐的工程化工具,涵蓋了初始化、創(chuàng)建模版、開發(fā)規(guī)范、編譯和構(gòu)建整個(gè)工作流程:從個(gè)體角度屏蔽了繁瑣的構(gòu)建配置,幫助新人快速上手;從團(tuán)隊(duì)角度講收攏了整個(gè)研發(fā)團(tuán)隊(duì)的開發(fā)規(guī)范,化零為整,為團(tuán)隊(duì)工作流保駕護(hù)航。?IMFLOW 在經(jīng)歷一次次演進(jìn)后背負(fù)著沉重的歷史包袱,本文重點(diǎn)講述了在這個(gè)關(guān)頭我們是如何設(shè)計(jì)和重構(gòu)這個(gè)關(guān)鍵工具的。無論你是想學(xué)習(xí)如何重構(gòu)一個(gè)大型輪子,如何設(shè)計(jì)一個(gè)前端構(gòu)建工具亦或如何權(quán)衡規(guī)范限制和靈活配置,本文都或多或少能為你提供一點(diǎn)靈感。

          1. 升級(jí)背景

          IMFLOW 這一名詞在 IMWEB 團(tuán)隊(duì)內(nèi)有兩個(gè)概念:一個(gè)是工具向的,指集成了 Webpack 最佳實(shí)踐的工程化工具,功能上講涵蓋了一個(gè) Web 前端項(xiàng)目的全流程:初始化、創(chuàng)建模版、開發(fā)規(guī)范、編譯和構(gòu)建;另一個(gè)是生態(tài)向的,指整個(gè)團(tuán)隊(duì)各方向業(yè)務(wù)(Web、SCF、LIB 等)的底層工程化生態(tài),為各種各樣的使用場景提供統(tǒng)一的工作流。

          以任何一個(gè)身份而言,IMFLOW 從功能和體驗(yàn)上已經(jīng)實(shí)現(xiàn)了它原本的使命,且接入并持續(xù)支持了團(tuán)隊(duì)很多的項(xiàng)目,然而從長遠(yuǎn)角度看,IMFLOW 想要成為一個(gè)家喻戶曉的輪子仍有很多欠缺:

          • 從工具的角度出發(fā),IMFLOW 作為一個(gè)工程化工具承擔(dān)了太多,它是腳手架又不僅僅是腳手架,支持插件系統(tǒng)但是又和業(yè)務(wù)強(qiáng)綁定,整體需要加載的內(nèi)容較多,單單從鍵入一個(gè)?imf create?指令到真正執(zhí)行到?create?邏輯就需要 3s 左右,最近加入了對(duì) Webpack 5 的支持,各個(gè)項(xiàng)目的升級(jí)情況不一,目前是 alpha 版本支持而正式版本不支持,這種版本管理模式也是不科學(xué)的。

          • 從生態(tài)的角度出發(fā),目前已經(jīng)成熟的 IMF、IMFS、IMFL 各自服務(wù)的業(yè)務(wù)類型不同,但實(shí)際上的 CORE 部分是重合的(如下圖),核心維護(hù)者的一些改動(dòng)往往需要同步到多個(gè)工具的同一模塊中,不耦合的部分是重復(fù)工作,如果遇到部分和業(yè)務(wù)強(qiáng)耦合的模塊還要做對(duì)應(yīng)修改,后續(xù)將進(jìn)入排期開發(fā)的 Mini Program、IMServer 等工程化工具需要編寫大量重復(fù)內(nèi)容;用戶的使用邏輯和體驗(yàn)往往是相近的,同樣是通過 create 創(chuàng)建模版,同樣是使用 dev 進(jìn)行本地編譯,用戶需要安裝、學(xué)習(xí)和使用多套指令,書寫多個(gè)配置文件,接入成本和學(xué)習(xí)成本都是不言而喻的。

          2. 升級(jí)目標(biāo)

          1. 解耦解耦再解耦,將流程代碼(上圖的 CORE 部分)和業(yè)務(wù)代碼分離,IMFLOW 重構(gòu)為 IMFLOW-CORE。

          2. 極致插件化,各構(gòu)建工具重構(gòu)為插件式的構(gòu)建套件,通過 CORE 安裝和加載,同時(shí)支持其他插件生態(tài)。

          3. 輕量化,壓縮各構(gòu)建套件的體積,提升啟動(dòng)時(shí)間到 1s 以內(nèi)。

          4. 收歸配置與依賴,各構(gòu)建套件使用統(tǒng)一的配置規(guī)范、配置文件、依賴管理和環(huán)境判斷邏輯。

          5. 極致封裝,規(guī)范化暴露給插件(構(gòu)建套件)的能力,所有可用 API 統(tǒng)一由 CORE 管理,封裝數(shù)據(jù)上報(bào)、配置訪問和指令注冊(cè)等流程。

          6. 擁抱社區(qū),兼容 FEFlow 生態(tài)中的插件,通過 IMFLOW 加載和管理,擁有相同的使用體驗(yàn)。

          7. 向下兼容,通過盡可能小的配置改動(dòng)即可完成版本更新,降低用戶的更新成本;盡可能小的修改既有插件,完成對(duì)新架構(gòu)的支持。

          3. 重構(gòu)分析

          3.1. 重構(gòu)模式

          業(yè)界常用的復(fù)雜系統(tǒng)重構(gòu)方法有三種模式:拆遷者模式、絞殺者模式和修繕者模式三種,首先結(jié)合業(yè)務(wù)分析一下各個(gè)模式的利弊。

          3.1.1. 拆遷者模式

          拆遷者模式字如其名,指完全實(shí)現(xiàn)一個(gè)新系統(tǒng),直接替代舊系統(tǒng),在新系統(tǒng)開發(fā)和替換周期內(nèi),兩套系統(tǒng)并行,直到新系統(tǒng)完全替代舊系統(tǒng)之后再下線舊系統(tǒng)。

          特點(diǎn):完全重構(gòu)一個(gè)新系統(tǒng),用于替代舊系統(tǒng)。

          優(yōu)點(diǎn):

          1. 新系統(tǒng)與舊系統(tǒng)完全分離,徹底解決歷史包袱。

          缺點(diǎn):

          1. 系統(tǒng)替換系統(tǒng)存在風(fēng)險(xiǎn),容易遺漏或不兼容。

          2. 替換期間需要維護(hù)兩套系統(tǒng),人力成本較大。

          3.1.2. 絞殺者模式

          絞殺者模式指新需求出現(xiàn)的時(shí)候,新建一個(gè)模塊替代舊模塊,依此反復(fù)直到重構(gòu)完成所有的模塊。

          特點(diǎn):在遺留系統(tǒng)之外構(gòu)建新服務(wù),并使用新服務(wù),保持原有的遺留系統(tǒng)不變

          優(yōu)點(diǎn):

          1. 能完整還原新需求。

          2. 系統(tǒng)可穩(wěn)定提供價(jià)值。

          缺點(diǎn):

          1. 重構(gòu)由需求推動(dòng),風(fēng)險(xiǎn)不可控。

          2. 迭代成本依耦合程度和需求量決定,容易退化成拆遷者模式。

          3.1.3. 修繕者模式

          修繕者模式是將系統(tǒng)內(nèi)部隔離出部分,通常是需要修繕的部分,對(duì)這部分進(jìn)行抽象、重構(gòu)和替換

          特點(diǎn):修繕者模式改造只會(huì)在系統(tǒng)的內(nèi)部,不改變系統(tǒng)的外在表現(xiàn)

          優(yōu)點(diǎn):

          1. 能完整還原原油需求。

          2. 系統(tǒng)外部無感知。

          3. 重構(gòu)與業(yè)務(wù)需求隨時(shí)可切換支持

          缺點(diǎn):

          1. 重構(gòu)時(shí)間跨度大

          2. 技術(shù)上迭代成本高

          3.1.4. 重構(gòu)模式選型

          在了解三種重構(gòu)模式之后,來看看 IMFLOW 的業(yè)務(wù)特點(diǎn):

          • 重構(gòu)后外部體驗(yàn)必然發(fā)生變化,原本的 IMFLOW-LIB 和 IMFLOW-SCF 都將收攏進(jìn) IMFLOW。

          • 各構(gòu)建工具和業(yè)務(wù)強(qiáng)綁定,很難在服務(wù)內(nèi)部進(jìn)行重構(gòu)。

          • 一些舊業(yè)務(wù)比起新架構(gòu)更需要穩(wěn)定維護(hù),沒有升級(jí)必要。

          • 重構(gòu)后架構(gòu)和舊架構(gòu)不是一一映射關(guān)系,很難抽離模塊進(jìn)行單獨(dú)重構(gòu)。

          • 很多業(yè)務(wù)的 CI/CD 流程已經(jīng)穩(wěn)定,全部重構(gòu)需要大量測(cè)試。

          綜合三種重構(gòu)模式和 IMFLOW 本身業(yè)務(wù)特點(diǎn),比較合適的是拆遷者模式,我們可以完全拋棄歷史包袱專注于新架構(gòu)的設(shè)計(jì)上,且一些既有項(xiàng)目本身并不需要版本迭代,舊架構(gòu)已經(jīng)可以滿足這類項(xiàng)目的需求。但上文也有提到一些項(xiàng)目的 CI/CD 產(chǎn)物是直接影響現(xiàn)網(wǎng)的,對(duì)于這一部分我們希望他保持穩(wěn)定,且從功能本身也沒有重構(gòu)的必要,他們只是新架構(gòu)中的一個(gè)功能模塊,只涉及很少的核心邏輯,因而對(duì)于這一部分我們采用修繕者模式的思想,總結(jié)而言我采取一種底層拆遷上層修繕的方式完成這次架構(gòu)升級(jí)。

          3.2. 重構(gòu)內(nèi)容

          如升級(jí)目標(biāo)所述,我們需要將既有的幾個(gè)構(gòu)建工具 IMFLOW、IMFLOW-SCF 和 IMFLOW-LIB 整合為一套系統(tǒng),將各個(gè)構(gòu)建工具重構(gòu)為構(gòu)建套件,每個(gè)構(gòu)建套件有業(yè)務(wù)強(qiáng)相關(guān)的構(gòu)建相關(guān)指令如?createdev?和?build?等等,各個(gè)構(gòu)建套件以插件的邏輯加載到新架構(gòu) IMFLOW-CORE 中,如下圖:

          這部分內(nèi)容比較好理解,下文我想用逼人空想出的哪吒模型通俗的講解新架構(gòu)重構(gòu):整個(gè)重構(gòu)可以想象成把三個(gè)泥人捏成一個(gè)哪吒,共用一套臟器,其余的部分都是可插拔的,那么這又引出了一個(gè)問題,頭部(構(gòu)建套件)和臟器(CORE 中的各個(gè)模塊)都有了,胳膊腿是什么呢?這里拋出我們的插件系統(tǒng),在各個(gè)構(gòu)建工具設(shè)計(jì)之初就是支持插件的,一般分為兩種插件類型:

          • template 模版類,如?react- template,可以理解為腳手架,主要調(diào)用了?yeoman-generator?和?inquirer,根據(jù)用戶個(gè)性化需求創(chuàng)建模版項(xiàng)目。

          • 編譯加持類,如?postcss,這種插件往往和業(yè)務(wù)(項(xiàng)目)強(qiáng)綁定,不是普適的,可能某個(gè) Web 業(yè)務(wù) A 安裝了,但 Web 業(yè)務(wù) B 沒有。

          在新架構(gòu)中,插件類型顯然沒有這么簡單,但我們也不想把他們搞得過為復(fù)雜,希望在規(guī)范和使用體驗(yàn)之間權(quán)衡,保證插件的開發(fā)者具備足夠的操作空間又能盡可能壓低用戶的學(xué)習(xí)成本,總結(jié)出新構(gòu)架中有如下兩類插件

          • 業(yè)務(wù)強(qiáng)綁定插件,某個(gè)插件的功能固然可以和業(yè)務(wù)強(qiáng)綁定,他們可以根據(jù)自己的功能特點(diǎn)設(shè)置一個(gè)白名單以羅列它支持的所有構(gòu)建套件。

          • 業(yè)務(wù)無關(guān)插件(通用插件),該類插件往往是支持整個(gè)工作流的,如 feflow-codecc 插件,這種插件對(duì)任何一種構(gòu)建套件都適用。

          稍微想一想我們會(huì)發(fā)現(xiàn),對(duì)于以上兩類插件,他們加載時(shí)所使用的上下文是不同的,前者需要使用對(duì)應(yīng)構(gòu)建套件的方法,而后者只需要使用一個(gè)相對(duì) “松” 的上下文。從另一個(gè)角度理解,同樣是胳膊腿,有的是靠某一顆或幾顆腦袋(構(gòu)建套件)控制的,而有的是靠心臟(或者說是心臟創(chuàng)造的一顆假想頭)控制的。

          截止到這我們其實(shí)已經(jīng)明確了將幾個(gè)小泥人重構(gòu)成一個(gè)哪吒所需要的工作(對(duì)應(yīng)重構(gòu)目標(biāo)):

          1. 臟器:一套,新架構(gòu)核心 CORE,提煉公共核心模塊,封裝對(duì)外暴露的各種方法,收攏配置和依賴。

          2. 腦袋:多個(gè),構(gòu)建套件 BuildKit,專注解決自己所要解決的業(yè)務(wù)方向,實(shí)現(xiàn) create、dev、build 等關(guān)鍵指令,并暴露一個(gè)上下文供支持他的胳膊腿消費(fèi)。

          3. 胳膊腿:多個(gè),插件 Plugin,專注實(shí)現(xiàn)自己需要拓展的能力,若為某些個(gè)特定的腦袋服務(wù),注明后即可按照對(duì)應(yīng)上下分編寫邏輯,否則即為全體腦袋服務(wù)。

          4. 名詞解釋

          在正式開啟正文之前,我們有必要對(duì)一些名詞做必要的解釋,防止歧義和迷惑:

          名詞解釋
          CORE新架構(gòu)的核心模塊集群,包含了新架構(gòu)的全部核心功能
          BuildKit構(gòu)建套件,既有的構(gòu)建工具(IMFLOW、IMFLOW-LIB、IMFLOW-SCF)重構(gòu)產(chǎn)物,被 CORE 管理和加載,功能和重構(gòu)前幾乎一致
          Plugin插件,包含通用插件和業(yè)務(wù)綁定插件兩種類型,前者被 CORE 消費(fèi),用于拓展通用能力;后者被 BuildKit 消費(fèi),用于拓展構(gòu)建能力
          IMFLOW結(jié)合上下文理解,一般舊架構(gòu)中的 Web 構(gòu)建最佳實(shí)踐工具,簡稱 IMF,重構(gòu)為 BuildKit 后更名為 BuildKit-Web
          IMFLOW-SCF舊架構(gòu)中的云函數(shù)構(gòu)建最佳實(shí)踐工具,簡稱 IMFS,重構(gòu)為 BuildKit 后更名為 BuildKit-SCF
          IMFLOW-LIB舊架構(gòu)中的基礎(chǔ)公用庫構(gòu)建工具,簡稱 IMFL,重構(gòu)為 BuildKit 后更名為 BuildKit-LIB
          FEFLOW騰訊 OTeam 創(chuàng)建的致力于提升研發(fā)效率和規(guī)范的工程化解決方案,具備一定的社區(qū)規(guī)模
          Commander.jsNode.js 中優(yōu)秀的命令行管理工具,具體的使用方法可以參考往期文章玩轉(zhuǎn) Commander.js— 人人都是命令行工具大師

          如果讀者有發(fā)現(xiàn)我沒闡述清楚的概念可以評(píng)論區(qū)寫一下我增加到上述表格。

          5. 舊架構(gòu)分析(IMF & IMFS & IMFL)

          5.1. 架構(gòu)圖

          IMF、IMFS 和 IMFL 的整體構(gòu)架均如下圖所示,其中 IMFS 和 IMFL 由于業(yè)務(wù)生態(tài)體積較小暫沒有插件生態(tài),但是從代碼架構(gòu)上是存在插件加載的邏輯的,只不過是以本地方式加載的,沒有提供安裝遠(yuǎn)程插件的方法;另外在核心指令的部分也存在部分差異,比如 IMFL 存在?publish?指令,IMFS 存在?deploy?指令,但是不影響核心模塊的公共特性。總體來看,架構(gòu)以 CORE 部分為主要支持,同時(shí)支持腳手架和插件系統(tǒng),可以消費(fèi) CORE 的上下文來拓展功能。

          5.2. 架構(gòu)異同對(duì)比分析

          通過源碼閱讀和比對(duì),總結(jié) CORE 部分有以下幾個(gè)需要特別關(guān)注的點(diǎn):

          • 三者的配置模塊各不相同,IMFLOW 讀取的是 imflow.config.js 而 IMFS 讀取的是 imflow-scf.config.js,且配置的書寫方式不相同;

            // IMFS
            fs.existsSync(
            path.join(this.options.baseDir, "../..", "imflow-scf.config.js")
            )
            // IMFL
            const res = loadConfig.loadSync({
            files: ["imflow-lib.config.js", "package.json"],
            cwd: baseDir,
            packageKey: "imflow-lib"
            });
            // IMF
            const res = loadConfig.loadSync({
            files:
            typeof configFile === "string"
            ? [configFile]
            : ["imflow.config.js", "imtrc.js", "package.json"],
            cwd: baseDir,
            packageKey: "imflow"
            });
          • 三者的指令注冊(cè)方法在底層是相同的,但是只是使用 Commander.js 做了一層最基本的封裝,由各自根據(jù)內(nèi)嵌指令類型封裝了 RegisterXXXCommandType 方法,供插件和內(nèi)嵌指令消費(fèi);

            // 底層注冊(cè)方法,基于 Commander.js
            public registerCommand(command: string): Command {
            returnthis.cli.command(command);
            }
            // 頂層封裝的內(nèi)嵌指令類型數(shù)組
            public registerCreateType(
            type: string,
            description: string,
            action: (options: any) =>void
            ): ImflowSCF {
            this.createCommandTypes.push({ type, description, action });
            returnthis;
            }
          • 三者的上報(bào)策略不盡相同,三者對(duì)于報(bào)錯(cuò)上報(bào)的邏輯基本一致,但 IMFS 直接對(duì)幾個(gè)內(nèi)嵌指令做了執(zhí)行時(shí)上報(bào),而 IMF 沒有這部分上報(bào)邏輯;

            // IMFS 對(duì)內(nèi)嵌指令執(zhí)行時(shí)上報(bào)的邏輯
            program.on("command:create", () => {
            reportCommand("create");
            });
            program.on("command:build", () => {
            reportCommand("build");
            });
            program.on("command:deploy", () => {
            reportCommand("deploy");
            });
          • 三者的插件加載邏輯基本一致,合并內(nèi)嵌指令和配置文件中聲明的插件為統(tǒng)一列表,統(tǒng)一規(guī)范化和加載;

            private applyPlugins() {
            const buildInPlugins = [
            ...
            ];
            const { baseDir } = this.options;
            const globalPlugins = getConfig("plugins") || [];
            try {
            this.initPlugins(
            loadPlugins(globalPlugins, IMFLOW_ROOT_MODULES_PATH),
            "global"
            );
            this.initPlugins(loadPlugins(buildInPlugins, baseDir), "build-in");
            this.initPlugins(loadPlugins(this.config.plugins || [], baseDir), "custom");
            } catch (e) {
            ...
            }
            }
          • 三者的上下文內(nèi)容相對(duì)統(tǒng)一,初始化邏輯也相近,方便后續(xù)做上下文收歸

            export default class ImflowSCF {
            public logger: any = logger;
            public options: ImflowLibOptions;
            public cli = program;
            public funcsPath: string;
            public projectConfigPath: string;
            public plugins: any[] = [];
            public func?: string = undefined;
            private pluginsSet = newSet<string>();
            private createCommandTypes: Array = [];
            private scfCommandTypes: Array = [];
            private apiCommandTypes: Array = [];
            ...
            }

          5.3. 啟動(dòng)時(shí)間分析

          5.3.1. 整體模塊加載

          一次性在文件頭部使用 import 將所有模塊進(jìn)行引入,nodejs 的單線程在 require 時(shí)會(huì)被阻塞,造成不必要的時(shí)間消耗

          // IMF /src/index.ts 頭部
          import loadConfig from"@tencent/imflow-cli-utils/load-config";
          import { logger, spinner } from"@tencent/imflow-common";
          import program, { Command } from"commander";
          import fs from"fs-extra";
          import inquirer from"inquirer";
          import lodash from"lodash";
          import path from"path";
          import resolveFrom from"resolve-from";
          import webpack from"webpack";
          import WebpackChain from"webpack-chain";

          5.3.2. 更新檢查

          更新檢查主要涵蓋三個(gè)步驟:

          1. 獲取 npm /tnpm:使用腳本(command -V?/?npm info)判斷使用 npm /tnpm

          2. 獲取版本信息:使用?npm info @tencent/imflow?得到最新的 IMFLOW 版本號(hào),對(duì)比檢查

          3. 執(zhí)行更新:npm / tnpm i @tencent/imflow

          存在一個(gè)問題是更新檢查目前在每一次輸入命令都會(huì)執(zhí)行,整個(gè)過程持續(xù) 1.5 ~ 2.5s 極大影響了命令執(zhí)行的速度。同時(shí)更新檢查并不是實(shí)時(shí)性要求非常高的需求,imflow 更新也并不頻繁。

          5.3.3. 業(yè)務(wù)模塊 / 插件沒有全量加載

          舊架構(gòu)中的插件是一次性初始化加載的,沒有按需加載,實(shí)際執(zhí)行某一個(gè)特定指令的時(shí)候往往只需要加載部分插件

          private applyPlugins() {
          const buildInPlugins = [
          [require("./plugins/command-create"), []],
          require("./plugins/config-base"),
          require("./plugins/config-dev"),
          require("./plugins/config-html"),
          require("./plugins/config-ssr"),
          require("./plugins/command-build"),
          require("./plugins/command-dev"),
          require("./plugins/command-test"),
          require("./plugins/command-utils"),
          require("./plugins/command-add"),
          require("./plugins/command-upgrade"),
          require("./plugins/command-plugin"),
          require("./plugins/command-config")
          ];
          const { baseDir } = this.options;
          this.initPlugins(loadPlugins(buildInPlugins, baseDir), "build-in");
          ...
          }

          6. 架構(gòu)設(shè)計(jì)

          6.1. 架構(gòu)圖

          整體的構(gòu)架圖如下圖所示,首先將各個(gè)模塊設(shè)置為黑盒,關(guān)注模塊劃分和整體設(shè)計(jì),總體分為四個(gè)部分:

          • CLI 交互層:對(duì)用戶的命令行輸入進(jìn)行處理,作為 CORE 初始化的入?yún)ⅰ?/p>

          • FILE 物理文件層:分為 Global 和 Local 兩個(gè)存儲(chǔ)維度,前者管理用戶的全局配置和依賴,后者管理工程內(nèi)配置和依賴。

          • CORE 核心邏輯層:流程模塊集群,包含 PkgManager 包管理模塊、Commander 指令管理模塊、ConfigSys 配置管理模塊和 PluginSys 插件管理模塊。

          • Market 插件生態(tài):包含業(yè)務(wù)綁定插件、通用插件和構(gòu)建套件三種類型,三者均按照 IMFLOW 插件開發(fā)規(guī)范編寫,由 PluginSys 統(tǒng)一加載和管理。

          再打開黑盒看一下細(xì)致的架構(gòu)拆分,每個(gè)模塊內(nèi)羅列了對(duì)應(yīng)模塊的核心功能 / 子模塊。對(duì)比舊架構(gòu)可以看到我們收攏了 CORE 的能力,泛化了一些具有普適性的能力,并極大豐富了插件 / 套件生態(tài)的內(nèi)容,拓展了多種的插件類型,從整體結(jié)構(gòu)上比較類似 FEFLOW 的架構(gòu),但是整個(gè)底層的實(shí)現(xiàn)邏輯和頂層的插件協(xié)議還是有很大差別的,后面會(huì)專門出一篇文章對(duì)比 IMFLOW,F(xiàn)EFLOW 和 VUE-CLI 架構(gòu)。

          6.2. 目錄結(jié)構(gòu)設(shè)計(jì)

          .
          ├── bin
          │ └── cli.ts ------------------- // 入口文件,實(shí)例化 imflowConfig 和 imflow
          ├── command ----------------------- // 命令行模塊,注冊(cè) imflow 內(nèi)嵌指令
          │ ├── buildKit.ts -------------- // 注冊(cè)構(gòu)建套件的CRUD指令
          │ ├── index.ts
          │ └── plugin.ts ---------------- // 注冊(cè)插件的CRUD指令
          ├── declarations.d.ts ------------- // declare 模塊和命名空間
          ├── index.ts ---------------------- // IMFLOW 類
          ├── interface.d.ts ---------------- // 類型聲明文件
          ├── modules ----------------------- // 核心模塊文件夾
          │ ├── configModule ------------- // 配置加載模塊
          │ │ └── index.ts
          │ ├── packageManager ----------- // 包管理模塊
          │ │ ├── index.ts
          │ │ └── load-pkg.ts
          │ └── pluginModule ------------- // 插件加載器模塊
          │ ├── buildKitInterface.ts - // buildKit 接口
          │ ├── feflowInterface.ts --- // feflow 插件接口
          │ ├── imflowInterface.ts --- // imflow 插件接口
          │ └── index.ts ------------- // 插件實(shí)例化與加載模塊
          └── utils
          ├── check-update.ts ----------- // 更新檢查模塊
          ├── cli-utils.ts -------------- // 環(huán)境工具檢查
          ├── common.ts ----------------- // 通用工具函數(shù)
          ├── executeCommand.ts --------- // 指令執(zhí)行工具
          └── imlog.ts ------------------ // 日志模塊

          7. 核心模塊

          7.1. CLI 命令行模塊

          基本的 Option (--global, --debug, etc.) 注冊(cè),然后初始化一個(gè) IMFLOW-CORE 實(shí)例,執(zhí)行 Run,然后自定義幫助信息并執(zhí)行 Parser。IMFLOW-CORE 目前支持主命令四種 Option:

          program.option("--no-check-latest", "不檢測(cè)最新版本", false);
          program.option("--debug", "輸出調(diào)試信息", false);
          program.option("-k,--build-kit ", "指定BuildKit");
          program.option("-g,--global", "全局使用", false);
          • noCheckLatest?會(huì)跳過 CORE 的版本更新檢查。

          • debug?用于輸出調(diào)試信息,在出現(xiàn) Bug 時(shí)可以通過這一選項(xiàng)給開發(fā)者提供定位信息。

          • buildKit?用于指定要加載的構(gòu)建套件,CORE 內(nèi)置了交互友好的自動(dòng)加載邏輯,這一選項(xiàng)的使用場景一般在工程內(nèi)有多個(gè) buildKit 時(shí)才需要指定按照特定的構(gòu)建套件執(zhí)行命令,比如某個(gè)同時(shí)存在 Web 和 SCF 的倉庫。

          • global?強(qiáng)制按照按照全局環(huán)境執(zhí)行命令,這也是 CORE 唯一一種可以打破環(huán)境隔離的操作,一般用于在工程內(nèi)安裝全局插件 / 套件,插件開發(fā)者書寫使用文檔時(shí)推薦用這種方式安裝全局插件,防止使用者誤將。

          在以上四個(gè)以外還有 Commander.js 內(nèi)置的?-V-h?兩個(gè)選項(xiàng)用于輸出版本號(hào)和幫助信息。

          7.2. PkgManager 包管理模塊

          全局和工程內(nèi)依賴的管理者,對(duì)外暴露一些簡單易用的 API 對(duì)依賴進(jìn)行 CRUD,還包含一個(gè) Version Check 的版本更新模塊,用于定時(shí)異步檢查 IMFLOW-CORE 的版本更新。

          外部在調(diào)用包管理模塊的方法時(shí),所有的邏輯都在模塊中封裝好了,只需要傳包名,不需要關(guān)注用戶使用了哪種包管理工具和執(zhí)行環(huán)境:

          // 安裝
          await context.pkgManager.add(plugin);
          // 刪除
          await context.pkgManager.remove(plugin);

          7.3. ConfigSystem 配置模塊

          該模塊是 IMFLOW-CORE 實(shí)例構(gòu)建器的入?yún)⒅弧?shí)現(xiàn)了以下幾個(gè)核心功能:

          • 實(shí)例化前配置側(cè)的錯(cuò)誤攔截,提早暴露風(fēng)險(xiǎn),確保實(shí)例化的 CORE 是充分可運(yùn)行的。

          • 對(duì)外暴露配置讀寫 API 供其他模塊消費(fèi),重寫類 getter 緩存化讀取。

          • 環(huán)境判斷與隔離,確保根據(jù) CLI 模塊傳達(dá)的命令行參數(shù)可以返回正確的配置項(xiàng)。

          const imflowConfig = await Config.build(cookedOptions);
          // 如果有配置異常在IMFLOW-CORE實(shí)例化之前就會(huì)拋出錯(cuò)誤并結(jié)束進(jìn)程
          const app = new IMFlowX(cookedOptions, imflowConfig, program);

          全局配置文件示例如下:

          {
          "buildKits": [ // 數(shù)組,每個(gè)元素是一個(gè)buildkit對(duì)象
          {
          "name": "@tencent/imflow-buildkit-web", // buildkit名稱
          "installed": true, // 是否已經(jīng)安裝
          "plugins": [ // 該buildkit安裝的業(yè)務(wù)綁定插件
          "@tencent/imflow-plugin-react-template"
          ]
          },
          {
          "name": "@tencent/imflow-buildkit-scf",
          "plugins": [],
          "installed": false
          }
          ],
          "lastUpdateDate": 1632455485712, // 上次更新時(shí)間
          "plugins": [ // 通用插件列表
          "@tencent/feflow-plugin-codecc"
          ],
          "activeBuildKit": "@tencent/imflow-buildkit-web"// 當(dāng)前激活的buildkit
          }

          工程內(nèi)配置文件示例如下:

          {
          plugins:['@tencent/imflow-plugin-postcss', '@tencent/feflow-plugin-codecc'],
          web:{ // 這里key的名字和buildkit的后綴名保持一致,在啟動(dòng)該buildkit時(shí)會(huì)對(duì)應(yīng)加載配置
          ...
          }
          }

          配置模塊會(huì)將上面的兩份配置文件和對(duì)應(yīng)環(huán)境的 packag.json 按如下方式在 Config 的構(gòu)建器中讀取,并通過各種 API 暴露讀寫能力:

          class Config {
          ...
          constructor(options: IMFlowXOptions) {
          // 入?yún)⑼競?/span>
          this.options = options;
          // 初始化相關(guān)路徑
          this.localConfigPath = path.resolve(options.baseDir, "imflow.config.js");
          this.localPkgPath = path.resolve(options.baseDir, "package.json");
          this.globalPkgPath = path.resolve(IMFLOW_CONFIG_ROOT_PATH, "package.json");
          this.globalConfigPath = configPath;
          // 讀取配置
          const { resolveConfig } = Config;
          this.localConfig = resolveConfig(this.localConfigPath);
          this.localPkg = resolveConfig(this.localPkgPath);
          this.globalPkg = resolveConfig(this.globalPkgPath);
          this.globalConfig = resolveConfig(this.globalConfigPath);
          // 初始化相關(guān)數(shù)據(jù)
          this.getterCache = newMap(); // getter的緩存數(shù)據(jù)結(jié)構(gòu)
          this.hasInstalledGlobalBuildKit = this.hasBuildKits(); // 判斷全局是否安裝了任何一個(gè)buildkit
          this.buildKit = this.initBuildKit(options); // 初始化buildkit
          }
          ...
          }

          配置模塊對(duì)于一些用戶或其他模塊可能訪問的計(jì)算屬性實(shí)現(xiàn)了類中 getter 的計(jì)算緩存策略,這樣設(shè)計(jì)的目的是為了壓縮類的體積,減少重復(fù)計(jì)算:

          privategetFromCache(key: string, init: Function, listener?: Array<any>) {
          // TODO listener實(shí)現(xiàn)變量監(jiān)聽
          if (!this.getterCache.has(key)) {
          const value = init();
          this.getterCache.set(key, value);
          }
          returnthis.getterCache.get(key);
          }

          如在插件加載模塊需要獲取到當(dāng)前有哪些插件是需要加載的,則可以通過?context.config.globalPlugins(假設(shè)全局)獲取:

          getglobalPlugins() {
          returnthis.getFromCache("globalPlugins", () => {
          const { activeBuildKit } = this.globalConfig;
          let plugins: Array<string> = [];
          if (this.hasInstalledGlobalBuildKit) {
          this.globalConfig.buildKits.forEach(item => {
          if (item.name === activeBuildKit) plugins.push(...item.plugins);
          });
          }
          plugins.push(...(this.globalConfig.plugins || []));
          return plugins;
          });
          }

          考慮到當(dāng)前的各種使用場景下,不存在寫入配之后再讀取的情況(一般比如安裝插件,寫入配置文件之后進(jìn)程也就結(jié)束了),還沒有實(shí)現(xiàn)監(jiān)聽變量變更的功能,后續(xù)如果出現(xiàn)相關(guān)需要可以補(bǔ)全這部分功能,參照 VUE 中的計(jì)算屬性。具體的配置模塊初始化和錯(cuò)誤攔截機(jī)制詳見第 8 節(jié)。

          7.4. Commander 指令模塊

          包含了全部的 CORE 內(nèi)嵌指令?plugin?和?buildkit,實(shí)現(xiàn)了相關(guān)內(nèi)嵌指令的注冊(cè)和監(jiān)控。

          以下兩個(gè)指令均可以加入?-g?選項(xiàng)強(qiáng)制在全局環(huán)境執(zhí)行

          7.3.1. plugin 指令

          提供了?list?、add?和?delete?三個(gè)子命令,分別用于羅列當(dāng)前安裝了的插件、增加插件和刪除插件。其中?add?指令要求用戶必須鍵入安裝插件的包名,而?delete?指令不強(qiáng)制,如果沒有輸入會(huì)進(jìn)入命令行界面讓用戶選擇要?jiǎng)h除的插件。關(guān)于插件的加載機(jī)制和管理邏輯詳見第 8 節(jié)。

          7.3.2. buildkit 指令

          提供了?list?、add?、delete?和?switch?四個(gè)子命令,分別用于羅列當(dāng)前安裝了的構(gòu)建套件、安裝套件、刪除套件和切換當(dāng)前激活的全局套件。其中?list?支持加入?-a?選項(xiàng)同時(shí)輸出安裝在各 buildKit 下的插件。switch?支持在全局切換激活的 buildKit。delete?和?add?的使用體驗(yàn)和?plugin?的子命令一致。關(guān)于構(gòu)建套件的加載機(jī)制和管理邏輯詳見第 8 節(jié)。

          7.5. PluginSystem 插件模塊

          插件模塊是鏈接 CORE 和插件 / 套件生態(tài)的關(guān)鍵鉸鏈,它提供了以下幾個(gè)關(guān)鍵功能:

          7.5.1. 上下文

          為各類插件 / 套件封裝了加載時(shí)上下文,提供插件 / 套件開發(fā)規(guī)范。

          7.5.2. 加載器

          插件 / 套件的加載器,個(gè)性化加載構(gòu)建套件、通用 IMFLOW 插件、通用 FEFLOW 插件和業(yè)務(wù)綁定插件,供 CORE 的上下文消費(fèi)。插件模塊會(huì)依次加載當(dāng)前激活的 BuildKit 以及對(duì)應(yīng)的插件和全局通用插件。

          如果在全局,則加載的是激活的 BuildKit + 該 BuildKit 在全局安裝的插件 + 全局通用插件

          如果在工程內(nèi),則加載的是工程內(nèi)安裝的 BuildKit(一般唯一)+ 工程內(nèi)配置文件聲明了的插件 + 全局通用插件

          以上的邏輯都都是封裝到加載器內(nèi)部的,對(duì)于使用者而言,閉眼睛加載就完事了。

          // imflow/index.ts
          ...
          privateloadAll() {
          this.loadBuildInCommands();
          if (!this.config.buildKit) return;
          this.loadBuildKit();
          this.loadPlugins();
          }
          ...

          7.5.3. 指令注冊(cè)封裝

          封裝指令注冊(cè)方法,包括重復(fù)指令管理,指令注冊(cè)監(jiān)控上報(bào)等功能。

          // 封裝指令注冊(cè)
          public registerCommand(command: string): Commander {
          // command是一個(gè)如 create
          [type] 的字符串,從中獲取到命令 create 賦值給subCommand
          const subCommand = command.split(" ")[0];
          this.cli.on(`command:${subCommand}`, msg => {
          logger.debug(`執(zhí)行指令${subCommand}`, msg);
          // TODO 上報(bào)
          });
          logger.debug(`正在注冊(cè)指令${command}`);
          if (this.commandMap.has(subCommand)) {
          // 重復(fù)指令因?yàn)椴寮械?commander 鏈?zhǔn)秸{(diào)用,直接返回其它類型會(huì)報(bào)錯(cuò),所以再實(shí)例化一個(gè) commander 作為子程序分離出去
          logger.warn(
          `${
          this.activePlugin
          }
          正在注冊(cè)的指令 ${subCommand} 已經(jīng)被 ${this.commandMap.get(
          subCommand
          )}
          注冊(cè)過,跳過`

          );
          const tempProgram = new Commander();
          return tempProgram.command(command);
          }
          this.commandMap.set(subCommand, this.activePlugin);
          returnthis.cli.command(command); // 通過 Commander.js 注冊(cè)指令
          }

          7.6. Market 插件 / 套件生態(tài)

          包含業(yè)務(wù)綁定插件(下圖左上角兩個(gè) plugins)、通用插件(下圖右側(cè)的 General Plugins)和構(gòu)建套件(下圖下方的 BuildKits)三種類型,三者均按照 IMFLOW 插件開發(fā)規(guī)范編寫,由 PluginSys 統(tǒng)一加載和管理。

          他們的邏輯關(guān)系和配置關(guān)系是完全對(duì)應(yīng)的,以上圖為例,@tencent/imflow-buildkit-web?包含了四個(gè)子插件?react-templatehulk-templatepostcss?和?oci-generator,而 BuildKit?@tencent/imflow-buildkit-scf?則只包含了兩個(gè),全局通用插件則安裝了兩個(gè)?@tencent/imflow-plugin-cache-cors?和?@tencent/feflow-plugin-codecc。加載時(shí)以激活的是?@tencent/imflow-buildkit-web?為例,則加載的是這個(gè) BuildKit + 4 個(gè)子插件 + 2 個(gè)全局通用插件。這些套件和插件的邏輯是完全可以從配置文件上找到蹤跡的,如下是以上邏輯關(guān)系在配置中的體現(xiàn)。

          {
          "buildKits": [ // 數(shù)組,每個(gè)元素是一個(gè)buildkit對(duì)象
          {
          "name": "@tencent/imflow-buildkit-web",
          "installed": true,
          "plugins": [ // 四個(gè)子插件
          "@tencent/imflow-plugin-react-template",
          "@tencent/imflow-plugin-hulk-template",
          "@tencent/imflow-plugin-postcss",
          "@tencent/imflow-plugin-oci-generator"
          ]
          },
          {
          "name": "@tencent/imflow-buildkit-scf",
          "plugins": [ // 兩個(gè)子插件
          "@tencent/imflow-plugin-oci-generator",
          "@tencent/imflow-plugin-standard-template"
          ],
          "installed": true
          }
          ],
          "lastUpdateDate": 1632455485712, // 上次更新時(shí)間
          "plugins": [ // 兩個(gè)通用插件
          "@tencent/feflow-plugin-codecc",
          "@tencent/imflow-plugin-cache-cors"
          ],
          "activeBuildKit": "@tencent/imflow-buildkit-web"// 當(dāng)前激活的buildkit
          }

          7.7. IMFLOW-CORE 上下文

          有了以上幾個(gè)子模塊的支持,IMFLOW-CORE 的上下文現(xiàn)在非常輕量:

          class IMFlowX {
          public logger: any = logger;
          public options: IMFlowXOptions;
          public configSys: ConfigSys;
          public pluginSys: PluginSys;
          public pkgManager: PackageManager;
          ...
          }

          8. 關(guān)鍵機(jī)制 & 邏輯

          8.1. 配置模塊初始化機(jī)制

          配置模塊是整個(gè)流程中最優(yōu)先初始化的模塊,也是 IMFLOW-CORE 實(shí)例化的入?yún)⒅唬谶@一模塊我設(shè)計(jì)了高優(yōu)先級(jí)的異常攔截,來確保 IMFLOW-CORE 實(shí)例是配置上絕對(duì)可執(zhí)行的。

          配置上絕對(duì)可運(yùn)行是指工程 / 全局的配置符合新構(gòu)架的協(xié)議規(guī)范。從代碼上來講,即加載器模塊的出參作為后續(xù)模塊的入?yún)⑹浅浞挚稍L問的,不會(huì)存在意外的 undefined 或者數(shù)據(jù)類型不對(duì)齊的情況。比如構(gòu)架升級(jí)之后,工程內(nèi)有配置文件?imflow.config.js,但是其中沒有?buildKit?字段聲明要啟動(dòng)的構(gòu)建套件,且在?package.json?中也沒有聲明任何一個(gè)構(gòu)建套件依賴,這種情況從配置上來講項(xiàng)目不可能正常啟動(dòng),因?yàn)榘芾砟K的入?yún)?huì)收到一個(gè)指向不明的構(gòu)建套件名,不可能正常加載。

          這一段邏輯可能相對(duì)晦澀,但從結(jié)果上講,經(jīng)過這一套流程后,所有配置上的邊緣邏輯已被報(bào)錯(cuò)攔截,邏輯流程將返回一個(gè)正常可加載的 BuildKit。這里還要特殊的考慮一下?create?指令,create?指令并不是 IMFLOW-CORE 的內(nèi)嵌指令,而是各個(gè) BuildKit 都會(huì)注冊(cè)的一條初始化模版指令,用于創(chuàng)建一個(gè)新項(xiàng)目。從用戶體驗(yàn)上講,正常來說是先有 BuildKit 再在該 BuildKit 環(huán)境下執(zhí)行相應(yīng)指令,而?create?指令不同,用戶在創(chuàng)建時(shí)往往不會(huì)固定一個(gè)業(yè)務(wù)類型,他可能創(chuàng)建一個(gè) Web 項(xiàng)目,也可能創(chuàng)建一個(gè) SCF 項(xiàng)目,我們希望給用戶邏輯和使用對(duì)應(yīng)的體驗(yàn),因此需要用戶先選擇 BuildKit 再初始化配置系統(tǒng)。可正如上文所述,在配置系統(tǒng)實(shí)例化之前,我們是完全無法訪問到 IMFLOW 配置的,我們希望將這個(gè)異步的選擇流程加入到配置模塊初始化的方法中,因?yàn)轭惖某跏蓟遣豢梢杂挟惒椒椒ǖ模@里我使用了靜態(tài)方法用函數(shù)式的方法異步創(chuàng)建一個(gè)配置模塊示例:

          // 通過靜態(tài)方法實(shí)例化
          staticasyncbuild(options: IMFlowXOptions) {
          const cookedOptions = Object.assign({}, options);
          if (isCreating(cookedOptions.cliArgs)) {
          const inquirer = require("inquirer");
          const buildKitsPrompt = {
          type: "list",
          name: "activeBuildKit",
          message: "選擇要?jiǎng)?chuàng)建的項(xiàng)目類型",
          choices: this.staticGetBuildKits()
          };
          const res = await inquirer.prompt(buildKitsPrompt);
          // 選擇全局buildKit并按照全局執(zhí)行
          cookedOptions.buildKit = res.activeBuildKit;
          cookedOptions.global = true;
          }
          returnnew Config(cookedOptions);
          }

          雖然有點(diǎn)復(fù)雜,但實(shí)現(xiàn)了外部使用的統(tǒng)一,符合我們解耦和封裝的目標(biāo):

          const imflowConfig = await Config.build(cookedOptions);

          這樣我們就實(shí)現(xiàn)了當(dāng)用戶執(zhí)行?create?指令時(shí),先選擇 BuildKit,再實(shí)例化配置模塊。

          8.2. 插件加載機(jī)制

          上文已經(jīng)說過我們有兩種插件類型:通用插件和業(yè)務(wù)綁定插件。他們有一個(gè)更細(xì)化的劃分如下:

          • 業(yè)務(wù)綁定的插件

          • 通用插件

            • IMFLOW 生態(tài)插件

            • FEFLOW 生態(tài)插件

          在全局啟動(dòng) IMFLOW-CORE 的時(shí)候,我們會(huì)先加載當(dāng)前全局激活的 BuildKit,再加載該 BuildKit 下安裝的插件,即上文的業(yè)務(wù)綁定插件,最后加載通用插件。

          在工程內(nèi)啟動(dòng) IMFLOW-CORE 的時(shí)候,我們會(huì)先加載工程內(nèi)聲明的 BuildKit,再加載工程配置文件聲明的插件,最后加載全局通用插件。

          可以看出,全局插件是在任何一種環(huán)境(全局 / 工程內(nèi))下都會(huì)加載的插件,這里對(duì)于 IMFLOW 和 FEFLOW 的通用插件還有不同的插件加載方法,因?yàn)?IMFLOW 生態(tài)的插件規(guī)范是基于 Commander.js 實(shí)現(xiàn)的,使用方法是鏈?zhǔn)秸{(diào)用,而 FEFLOW 生態(tài)的插件使用的是聲明式調(diào)用,IMFLOW-CORE 提供了供 FEFLOW 插件消費(fèi)的上下文和指令注冊(cè)的轉(zhuǎn)換封裝:

          privateapplyFEFlowPlugin(feflowPluginConstructor: any) {
          const feflowInterface = new FEFlowInterface(this.context.config); // 實(shí)例化feflowInterface
          // 執(zhí)行插件導(dǎo)出的函數(shù),入?yún)閒eflow實(shí)例
          const commandLineUsage = require("command-line-usage");
          feflowPluginConstructor(feflowInterface);
          // 獲取 feflow 插件的注冊(cè)信息
          const commands = feflowInterface.commander.list();
          commands.map(command => {
          const usage = commandLineUsage(command.options); // 自定義 help 輸出
          // 將對(duì)象屬性嵌入 commander 鏈?zhǔn)秸{(diào)用
          this.registerCommand(command.name)
          .description(command.desc asstring)
          .allowUnknownOption()
          .addHelpText("before", usage)
          .action(<any>command.runFn);
          });
          }

          8.3. 插件安裝 / 卸載邏輯

          上文我們已經(jīng)知道了插件的類型,想必你會(huì)出現(xiàn)一個(gè)疑問:我們是先加載構(gòu)建套件再去獲取他需要加載的插件的,而業(yè)務(wù)綁定的類型又是可以支持多個(gè)構(gòu)建套件的,那么如果插件 plugin-A 支持 buildkit-A 和 buildkit-B,而用戶的環(huán)境中只安裝了 buildkit-A,在安裝 plugin-A 的時(shí)候或許不會(huì)出現(xiàn)什么錯(cuò)誤,可是當(dāng)用戶后面再安裝 buildkit-B 的時(shí)候,plugin-A 是怎么給 buildkit-B 提供支持的呢?

          這里我們靈活使用了配置文件,在安裝插件的時(shí)候,會(huì)去掃描插件上的一個(gè)靜態(tài)屬性(想想為什么用靜態(tài)屬性?)buildKitWhiteList,這是一個(gè)字符串?dāng)?shù)組,每個(gè)元素表示該插件支持的構(gòu)建套件,我會(huì)構(gòu)建這個(gè)白名單和配置文件中已經(jīng)聲明的構(gòu)建套件之間的映射關(guān)系,將插件支持卻沒有在配置文件中寫入的構(gòu)建套件寫入配置文件中:

          if (buildKitWhiteList) {
          try {
          buildKitWhiteList.map((whiteBuildKit: string) => {
          let declared = false;
          buildKits.map(kit => {
          if (kit.name === whiteBuildKit) {
          // 待安裝組件聲明的支持的構(gòu)建套件已在全局配置中聲明
          declared = true;
          kit.plugins.push(plugin);
          }
          });
          if (!declared) {
          // 待安裝組件聲明的支持的構(gòu)建套件沒有在全局配置中聲明過,防止未來安裝了該套件不支持組建,安裝前預(yù)聲明
          const buildKit2declare: BuildKit = {
          name: whiteBuildKit,
          plugins: [plugin],
          installed: false
          };
          buildKits.push(buildKit2declare);
          }
          });
          config.setGlobalConfig("buildKits", buildKits);
          }

          這也是為什么我們的全局配置文件?~/.imflow/.imflow.config.json?中會(huì)出現(xiàn)如下的配置:

          {
          "buildKits": [
          {
          "name": "@tencent/imflow-buildkit-web",
          "installed": true,
          "plugins": [
          "@tencent/imflow-plugin-react-template"
          ]
          },
          {
          "name": "@tencent/imflow-buildkit-scf",
          "plugins": [],
          "installed": false// 看這里看這里看這里看這里看這里
          }
          ],
          "lastUpdateDate": 1632455485712,
          "plugins": [
          "@tencent/feflow-plugin-codecc"
          ],
          "activeBuildKit": "@tencent/imflow-buildkit-web"
          }

          對(duì)于卸載我們目前的處理方法是刪除所有構(gòu)建套件中對(duì)于該插件的聲明,后續(xù)會(huì)通過上報(bào)和主動(dòng)溝通持續(xù)收集用戶的使用體驗(yàn)看是否需要單獨(dú)卸載。

          8.4. 構(gòu)建套件安裝 / 卸載邏輯

          那么有了上文插件的安裝 / 卸載邏輯之后,構(gòu)建套件這里就相對(duì)樸素了,在安裝的時(shí)候會(huì)去判斷配置文件中是否有聲明,有則把 installed 字段置為 true,否則則寫入一個(gè)新對(duì)象。

          9. 成果驗(yàn)收

          9.1. 啟動(dòng)時(shí)間

          優(yōu)化啟動(dòng)時(shí)間不是構(gòu)架升級(jí) 1 期的核心目標(biāo),但是僅僅優(yōu)化架構(gòu)后啟動(dòng)時(shí)間就已經(jīng)得到了質(zhì)的飛躍,2 期中將針對(duì)啟動(dòng)時(shí)間做專項(xiàng)優(yōu)化:

          構(gòu)建工具之前(十次均值)升級(jí)后(十次均值)
          IMF4.9s0.8s
          IMFS6s3.2s

          9.2. 目標(biāo)完成度

          • 解耦解耦再解耦,將流程代碼(上圖的 CORE 部分)和業(yè)務(wù)代碼分離,IMFLOW 重構(gòu)為 IMFLOW-CORE。

            ?,流程代碼和業(yè)務(wù)代碼已經(jīng)完全分離,用當(dāng)前的 CORE 去接 PYTHON 項(xiàng)目也可以。

          • 極致插件化,各構(gòu)建工具重構(gòu)為插件式的構(gòu)建套件,通過 CORE 安裝和加載,同時(shí)支持其他插件生態(tài)。

            ?,實(shí)現(xiàn)了多套件,多種多類型插件的插拔結(jié)構(gòu)。

          • 輕量化,壓縮各構(gòu)建套件的體積,提升啟動(dòng)時(shí)間到 1s 以內(nèi)。

            ??,SCF 構(gòu)建套件的配置收攏和依賴加載相對(duì)繁雜,2 期中將專項(xiàng)優(yōu)化。

          • 收歸配置與依賴,各構(gòu)建套件使用統(tǒng)一的配置規(guī)范、配置文件、依賴管理和環(huán)境判斷邏輯。

            ?,配置和依賴管理模塊對(duì)各套件、插件完成收攏。

          • 極致封裝,規(guī)范化暴露給插件(構(gòu)建套件)的能力,所有可用 API 統(tǒng)一由 CORE 管理,封裝數(shù)據(jù)上報(bào)、配置訪問和指令注冊(cè)等流程。

            ?,所有模塊都盡可能時(shí)間最高程度的封裝,每個(gè)模塊專注于自己的工作,暴露易用 API 供其他模塊消費(fèi)。

          • 擁抱社區(qū),兼容 FEFlow 生態(tài)中的插件,通過 IMFLOW 加載和管理,擁有相同的使用體驗(yàn)。

            ?,IMFLOW-CORE 結(jié)合自己的架構(gòu)模擬出 FEFLOW 的上下文,完全兼容 FEFLOW 插件生態(tài)。

          • 向下兼容,通過盡可能小的配置改動(dòng)即可完成版本更新,降低用戶的更新成本;盡可能小的修改既有插件,完成對(duì)新架構(gòu)的支持。

            ?,PC 項(xiàng)目已無痛接入并通過驗(yàn)證。

          9.3. BETA 版本效果

          其他 IMFLOW 生態(tài)的構(gòu)建套件和插件正在逐步遷移。

          9.3.1. 幫助信息

          下圖可見 IMFLOW 對(duì)指令和選項(xiàng)都按照首字母順序排序,且對(duì)于所有指令都標(biāo)注了注冊(cè)該指令的來源(套件 / 插件)

          9.3.2. buildkit 指令

          • list?與?add

          • delete

            在沒有指定要?jiǎng)h除的 buildkit 時(shí)會(huì)給出選擇

          • switch

          9.3.3. plugin 指令

          • list

            • 全局

            • 工程內(nèi)


          • delete

          9.3.4. create 指令

          在執(zhí)行?create?指令的時(shí)候會(huì)攔截 ConfigSys 的初始化,優(yōu)先讓用戶選擇啟用的 BuildKit 以確保加載到用戶想要?jiǎng)?chuàng)建的業(yè)務(wù)類型,如下圖我們可以選擇創(chuàng)建 Web 項(xiàng)目或者 SCF 項(xiàng)目。

          9.3.5. 插件兼容

          下圖可見在安裝了?@tencent/feflow-plugin-codecc?之后,IMFLOW 可以正常加載并使用該插件:


          往期推薦


          2021 TWeb 騰訊前端技術(shù)大會(huì)精彩回顧(附PPT)
          面試題:說說事件循環(huán)機(jī)制(滿分答案來了)
          專心工作只想搞錢的前端女程序員的2020



          最后


          • 歡迎加我微信,拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...

          • 歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...

          點(diǎn)個(gè)在看支持我吧
          瀏覽 85
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码在线免费视频 | 亚洲黄色视频网站在线播放 | 免费一级a| 久久久久久久免费视频 | 中文字幕在线观看免费视频 |