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

          【實(shí)戰(zhàn)】動(dòng)態(tài)表單之表單組件的插件式加載方案

          共 9462字,需瀏覽 19分鐘

           ·

          2021-03-06 09:25

          前言

          關(guān)于動(dòng)態(tài)化表單方案前面我們已經(jīng)有過(guò)一次分享,沒(méi)看過(guò)的同學(xué)可以看下之前的文章 ZooTeam 拍了拍你,來(lái)看看如何設(shè)計(jì)動(dòng)態(tài)化表單。文章中提到隨著業(yè)務(wù)差異化增多,我們采用了動(dòng)態(tài)表單解決重復(fù)開(kāi)發(fā)及邏輯堆疊的問(wèn)題。隨著動(dòng)態(tài)化表單系統(tǒng)運(yùn)行過(guò)程中業(yè)務(wù)方接入的越來(lái)越多,自定義組件插件式加載的需求開(kāi)始出現(xiàn)并慢慢變得強(qiáng)烈。
          我們希望添加新的自定義組件之后可以不需要重新發(fā)布項(xiàng)目,只需要單獨(dú)發(fā)布自定義組件,然后在系統(tǒng)中注冊(cè)該自定義組件,就能在配置表單頁(yè)面的時(shí)候直接使用了。那么這就引出一個(gè)需求,表單組件的插件式加載并應(yīng)用的能力。

          組件插件式加載方案的現(xiàn)狀

          關(guān)于異步加載,各平臺(tái)上一搜索,大多數(shù)出來(lái)的都是一些 Webpack 代碼分拆相關(guān)的內(nèi)容。而關(guān)于組件插件式加載的內(nèi)容寥寥無(wú)幾。讓我們具體梳理一下。

          一、Webpack 懶加載

          Webpack 懶加載,也就是 Webpack 的拆包按需加載功能,其主要使用 import 方法進(jìn)行靜態(tài)資源的異步加載,具體使用方法為,代碼中采用如下方式引入需要被拆包的文件:
          import('./moduleA').then((moduleA) => {
            moduleA.add(1,2); // 3
          })
          這么 Webpack 在打包時(shí)會(huì)將 moduleA 單獨(dú)拆分出來(lái)作為一個(gè) JS 文件,項(xiàng)目在執(zhí)行到這段代碼的時(shí)候才動(dòng)態(tài)加載這部分 JS 資源。但是如果直接使用 import 方法加載遠(yuǎn)程資源,Webpack 打包過(guò)程會(huì)直接報(bào)錯(cuò)。不滿(mǎn)足需求。
          import('http://static.cai-inc.com/moduleA.js').then((moduleA) => {
             // ERROR,打包過(guò)程會(huì)出現(xiàn)報(bào)錯(cuò)
            moduleA.add(1,2);
          })
          報(bào)錯(cuò)信息:

          二、現(xiàn)有瀏覽器支持的 Dynamic Import

          對(duì)于這種方法,其瀏覽器兼容性問(wèn)題難以滿(mǎn)足要求,IE 瀏覽器完全不支持并且有同域名的限制。使用方法同 Webpack 懶加載一樣:
          import('http://static.cai-inc.com/moduleA.js').then((moduleA) => {
            moduleA.add(1,2); // 3
          })

          三、require.js AMD 規(guī)范

          使用 require.js 去加載一個(gè)符合 AMD 規(guī)范的 JS 文件。具體使用方法如下:
          // 需要被動(dòng)態(tài)加載的 moduleA.js
          define('moduleA', [], function ({
            var add = function (x, y{
              return x + y;
            };
            return {
              add: add
            };
          });
          // 加載和使用
          require.config({
            paths: {
              "moduleA""lib/moduleA"
            }
          });
          require(['moduleA'], function (moduleA){
              // 代碼
              moduleA.add(1,2); // 使用被動(dòng)態(tài)引入的插件的方法
          });
          在這個(gè)方法中,moduleA 是動(dòng)態(tài)插件,要使用動(dòng)態(tài)插件則需要配置好插件的路徑,然后使用 require 進(jìn)行引用。這需要我們引用 require.js 到現(xiàn)有項(xiàng)目中,在項(xiàng)目的 HTML 中定義一個(gè) Script 標(biāo)簽并設(shè)置 data-main="scripts/main" 作為入口文件。但是我們的 React 項(xiàng)目也有一個(gè)入口,這會(huì)導(dǎo)致出現(xiàn)兩個(gè)入口。兩者用法并不能很好的并存。

          需求拆解

          那么現(xiàn)在來(lái)分析一下實(shí)現(xiàn)組件插件式加載的關(guān)鍵問(wèn)題

          一、加載資源

          • 因?yàn)椴寮为?dú)發(fā)布之后要放在 CDN 上,所以加載靜態(tài)資源的方案需要滿(mǎn)足沒(méi)有跨域限制的條件。

          二、插件模塊打包

          • 插件模塊最好能使用現(xiàn)有模塊標(biāo)準(zhǔn)例如 CMD、AMD 模塊標(biāo)準(zhǔn),這樣我們就可以使用更多的社區(qū)開(kāi)源方案,降低方案的風(fēng)險(xiǎn)性。同時(shí)降低團(tuán)隊(duì)成員學(xué)習(xí)使用成本。
          • 插件需要能夠被注入依賴(lài),例如項(xiàng)目中已經(jīng)包含有 Lodash 或者 AntD 組件庫(kù)的包,這時(shí)候插件模塊中使用 Lodash 或者 AntD 組件庫(kù)的話(huà)我們當(dāng)然希望能夠直接引用項(xiàng)目中已有的,而不是插件模塊中重新引入一個(gè)。

          需求分析

          一、靜態(tài)資源加載

          對(duì)于運(yùn)行中加載靜態(tài)資源,現(xiàn)有解決方案中不論是哪一種,都是利用動(dòng)態(tài)插入 Script 或者 Link 標(biāo)簽來(lái)實(shí)現(xiàn)的。而且這種方案不會(huì)有域名限制問(wèn)題。具體實(shí)現(xiàn)大體如下:
          export default function (url{
            return new Promise(function (resolve, reject{
              const el = document.createElement('script'); // 創(chuàng)建 script 元素
              el.src = url; // url 賦值
              el.async = false// 保持時(shí)序
              const loadCallback = function (// 加載成功回調(diào)
                el.removeEventListener('load', loadCallback);
                resolve(result);
              };
              const errorCallback = function (evt// 加載失敗回調(diào)
                el.removeEventListener('error', errorCallback);
                var error = evt.error || new Error("Load javascript failed. src=" + url);
                reject(error);
              };
              el.addEventListener('load', loadCallback);
              el.addEventListener('error', errorCallback);
              document.body.appendChild(el); // 節(jié)點(diǎn)插入
            });
          }

          二、為加載模塊注入依賴(lài)

          關(guān)于這一點(diǎn)我們可以看下遵循 AMD 規(guī)范的 require.js 是怎么做的。代碼:
          // require.js
          const modules = {};
          const define = function(moduleName, depends, callback){
            modules[moduleName] = { // 將模塊存起來(lái),等待后續(xù)調(diào)用
              depends,
              callback,
            };
          }
          // moduleA.js
          define('moduleA', [], ()=>{
            // code
          })
          因?yàn)橥ㄟ^(guò)插入 Script 的方式引入 JS 資源,JS 會(huì)被立刻執(zhí)行,所以在 require.js 中加載進(jìn)來(lái)的 JS 模塊都是被 define 方法包裹著的,真正需要執(zhí)行的代碼是在回調(diào)函數(shù)中等待后續(xù)調(diào)用。當(dāng) moduleA.js 被加載成功之后,立即調(diào)用 define 方法,這里執(zhí)行的內(nèi)容則是把項(xiàng)目的模塊儲(chǔ)存起來(lái)等待調(diào)用。依賴(lài)的注入則是回調(diào)中將依賴(lài)作為參數(shù)注入。其實(shí)不論是基于哪一種規(guī)范,動(dòng)態(tài)加載靜態(tài)資源的策略都大致一樣。模塊中使用一個(gè)函數(shù) A 將目標(biāo)代碼包起來(lái)。將該函數(shù) A 作為一個(gè)函數(shù) D 的參數(shù)。當(dāng)模塊被加載時(shí),瀏覽器中已經(jīng)定義好的 D 函數(shù)中就可以獲取到含有目標(biāo)代碼塊的函數(shù) A 了。接下來(lái)想在哪里調(diào)用就在哪里調(diào)用。想注入什么變量就注入什么變量了。
          • 備注
            • 這里是對(duì) AMD 進(jìn)行了粗略的原理解釋?zhuān)唧w實(shí)現(xiàn)還有很多細(xì)節(jié),想要了解的話(huà),可以在網(wǎng)上找到很多源碼解析,這里就不再細(xì)講。
            • Webpack 打包之后的代碼的模塊管理方式是 Webpack 自己實(shí)現(xiàn)的一套類(lèi)似 CommonJS 規(guī)范的東西。去看看打包生成的代碼就可以發(fā)現(xiàn)里面都是一些 webpack_modules__,webpack_require,webpack_exports 這樣的關(guān)鍵詞,和 CommonJS 規(guī)范的 modules,require,exports 相對(duì)應(yīng)。

          三、模塊打包標(biāo)準(zhǔn)

          由于我們團(tuán)隊(duì)使用的是 Webpack 的打包體系,因此想要保持技術(shù)棧統(tǒng)一,則要先從 Webpack 的打包入手。讓我們將 Webpack 的模塊化打包都試一下看看能得出什么。
          Webpack library 打包方式有 5 種。
          • 變量:作為一個(gè)全局變量,通過(guò) script 標(biāo)簽來(lái)訪(fǎng)問(wèn)(libraryTarget:'var')。
          • this:通過(guò) this 對(duì)象訪(fǎng)問(wèn)(libraryTarget:'this')。
          • window:通過(guò) window 對(duì)象訪(fǎng)問(wèn),在瀏覽器中(libraryTarget:'window')。
          • UMD:在 AMD 或 CommonJS 的 require 之后可訪(fǎng)問(wèn)(libraryTarget:'umd')。
          • AMD:基于 AMD 規(guī)范的打包方式(libraryTarget:'amd')。
          可以排除前三個(gè),我們并不想將模塊掛到 window 或者全局變量下。所以我們需要嘗試的只有后面兩個(gè)。
          需要被打包的代碼塊:
          export default {
            test()=>{
              console.log('測(cè)試模塊打包!');
            }
          };
          AMD 規(guī)范打包后:
          define(["lodash"], (__WEBPACK_EXTERNAL_MODULE__92__) => (() => {
            // code ...
            // return funciton
          })());
          UMD 規(guī)范打包后:
          (function webpackUniversalModuleDefinition(root, factory{
              if(typeof exports === 'object' && typeof module === 'object')
                  module.exports = factory(require("lodash")); // cmd
              else if(typeof define === 'function' && define.amd)
                  define(["lodash"], factory); // amd
              else { // 
                  var a = typeof exports === 'object' ? factory(require("lodash")) : factory(root["_"]);
                  for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
              }
          })(self, function(__WEBPACK_EXTERNAL_MODULE__92__{
              // code
          });
          可以看出來(lái),AMD 規(guī)范打包后,代碼執(zhí)行了一個(gè) define 方法。依賴(lài)注入是通過(guò)回調(diào)方法的參數(shù)進(jìn)行注入的。那么我們是不是可以在加載 JS 文件之前先在 window 下掛一個(gè) define 方法,等文件加載完執(zhí)行 define 方法的時(shí)候,我們就可以在 define 方法中做我們想做的事情了。同理 UMD 打包規(guī)范也可以通過(guò)類(lèi)似的操作達(dá)到我們的目的。所以這兩種方案都可以??紤]到后期動(dòng)態(tài)表單頁(yè)面轉(zhuǎn)本地代碼的需求,希望插件還能被 npm 安裝使用。這里采用了 UMD 規(guī)范。

          方案選取

          一、加載資源的方案

          • 采用動(dòng)態(tài)插入 Script 方式實(shí)現(xiàn) JS 資源加載。

          二、模塊打包方案

          • UMD 規(guī)范的打包方式。
          最終實(shí)現(xiàn)代碼參考:
          // importScript.js
          export default function (url, _{
            const defineTemp = window.define; // 將 window 下的 define 方法暫存起來(lái)。
            let result; // 結(jié)果
            window.define = (depends, func) => { // 自定義 define 方法,
              result = func(_); // 包依賴(lài)注入 
            }
            window.define.amd = true// 偽裝成 amd 的 define。
            return new Promise(function (resolve, reject{
              const el = document.createElement('script'); // 創(chuàng)建 script 元素
              el.src = url;
              el.async = false// 保持時(shí)序
              const loadCallback = function (// 加載完成之后處理
                el.removeEventListener('load', loadCallback);
                window.define = defineTemp;
                resolve(result);
              };
              const errorCallback = function (evt// 加載失敗之后處理
                el.removeEventListener('error', errorCallback);
                window.define = defineTemp;
                var error = evt.error || new Error("Load javascript failed. src=" + url);
                reject(error);
              };
              el.addEventListener('load', loadCallback); // 綁定事件
              el.addEventListener('error', errorCallback); // 綁定事件
              document.body.appendChild(el); // 插入元素
            });
          }
          調(diào)用方式
          import importScript from './importScript.js';
          import _ from 'lodash';
          importScript('http://static.cai-inc.com/app.bundle.js', _).then((mod)=>{
             // code mod.xxx
          })

          三、與自定義表單結(jié)合

          組件插件式引入的方式解決了,但是又引入了一個(gè)新的問(wèn)題,一個(gè)表單頁(yè)面如果有 10 個(gè)自定義組件的話(huà),是不是就得動(dòng)態(tài)加載 10 個(gè)靜態(tài)資源呢,如果每個(gè)組件都有一個(gè) JS,一個(gè) CSS。那就是 20 個(gè)。這是不具備可行性的。
          所以就有了組件合并的需求。
          在配置表單頁(yè)面的時(shí)候當(dāng)用戶(hù)發(fā)布該頁(yè)面的時(shí)候,服務(wù)端建一個(gè)臨時(shí)項(xiàng)目,將該頁(yè)面的所有涉及到的自定義組件安裝到該項(xiàng)目上,并 export 出去。編譯打包,生成符合 UMD 規(guī)范的文件模塊。然后再按照以上方式進(jìn)行引入。這樣就解決了多文件合并的問(wèn)題。

          總結(jié)

          最后方案其實(shí)很簡(jiǎn)單,只是對(duì) UMD 規(guī)范打包的一種靈活應(yīng)用?;?UMD 規(guī)范打包出一個(gè)組件代碼,通過(guò)動(dòng)態(tài)插入 Script 標(biāo)簽的方式引入該組件的 JS 代碼。在引入之前定義一個(gè) window.define 方法。在該組件的 JS 代碼下載成功之后,就會(huì)調(diào)用到我們定義的 window.define 方法。這樣我們就能對(duì)插件模塊進(jìn)行依賴(lài)注入并將它儲(chǔ)存起來(lái)備用了。

          歡迎關(guān)注「前端雜貨鋪」,一個(gè)有溫度且致力于前端分享的雜貨鋪

          關(guān)注回復(fù)「加群」,可加入雜貨鋪一起交流學(xué)習(xí)成長(zhǎng)


          瀏覽 82
          點(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>
                  卡一卡二无码免费在线 | AV中文字幕播放 | 丁香狠狠色婷婷久久 | 7777久久| 强开小嫩苞一区二区三区网站 |