关于前端:动态表单之表单组件的插件式加载方案

5次阅读

共计 6261 个字符,预计需要花费 16 分钟才能阅读完成。

这是第 88 篇不掺水的原创,想获取更多原创好文,请搜寻公众号关注咱们吧~ 本文首发于政采云前端博客:动静表单之表单组件的插件式加载计划

前言

对于动态化表单计划后面咱们曾经有过一次分享,没看过的同学能够看下之前的文章 ZooTeam 拍了拍你,来看看如何设计动态化表单。文章中提到随着业务差异化增多,咱们采纳了动静表单解决反复开发及逻辑重叠的问题。随着动态化表单零碎运行过程中业务方接入的越来越多,自定义组件插件式加载的需要开始呈现并缓缓变得强烈。

咱们心愿增加新的自定义组件之后能够不须要从新公布我的项目,只须要独自公布自定义组件,而后在零碎中注册该自定义组件,就能在配置表单页面的时候间接应用了。那么这就引出一个需要,表单组件的插件式加载并利用的能力。

组件插件式加载计划的现状

对于异步加载,各平台上一搜寻,大多数进去的都是一些 Webpack 代码分拆相干的内容。而对于组件插件式加载的内容寥寥无几。让咱们具体梳理一下。

一、Webpack 懒加载

Webpack 懒加载,也就是 Webpack 的拆包按需加载性能,其次要应用 import 办法进行动态资源的异步加载,具体应用办法为,代码中采纳如下形式引入须要被拆包的文件:

import('./moduleA').then((moduleA) => {moduleA.add(1,2); // 3
})

此时 Webpack 在打包时会将 moduleA 独自拆分进去作为一个 JS 文件,我的项目在执行到这段代码的时候才动静加载这部分 JS 资源。然而如果间接应用 import 办法加载近程资源,Webpack 打包过程会间接报错。不满足需要。

import('http://static.cai-inc.com/moduleA.js').then((moduleA) => {
   // ERROR,打包过程会呈现报错
  moduleA.add(1,2);
})

报错信息:

二、现有浏览器反对的 Dynamic Import

对于这种办法,其浏览器兼容性问题难以满足要求,IE 浏览器齐全不反对并且有同域名的限度。应用办法同 Webpack 懒加载一样:

import('http://static.cai-inc.com/moduleA.js').then((moduleA) => {moduleA.add(1,2); // 3
})

三、require.js AMD 标准

应用 require.js 去加载一个合乎 AMD 标准的 JS 文件。具体应用办法如下:

// 须要被动静加载的 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); // 应用被动静引入的插件的办法
});

在这个办法中,moduleA 是动静插件,要应用动静插件则须要配置好插件的门路,而后应用 require 进行援用。这须要咱们援用 require.js 到现有我的项目中,在我的项目的 HTML 中定义一个 Script 标签并设置 data-main="scripts/main" 作为入口文件。然而咱们的 React 我的项目也有一个入口,这会导致呈现两个入口。两者用法并不能很好的并存。

需要拆解

那么当初来剖析一下实现组件插件式加载的 关键问题

一、加载资源

  • 因为插件独自公布之后要放在 CDN 上,所以加载动态资源的计划须要满足没有跨域限度的条件。

二、插件模块打包

  • 插件模块最好能应用现有模块规范例如 CMD、AMD 模块规范,这样咱们就能够应用更多的社区开源计划,升高计划的风险性。同时升高团队成员学习应用老本。
  • 插件须要可能被注入依赖,例如我的项目中曾经蕴含有 Lodash 或者 AntD 组件库的包,这时候插件模块中应用 Lodash 或者 AntD 组件库的话咱们当然心愿可能间接援用我的项目中已有的,而不是插件模块中从新引入一个。

需要剖析

一、动态资源加载

对于运行中加载动态资源,现有解决方案中不论是哪一种,都是利用动静插入 Script 或者 Link 标签来实现的。而且这种计划不会有域名限度问题。具体实现大体如下:

export default function (url) {return new Promise(function (resolve, reject) {const el = document.createElement('script'); // 创立 script 元素
    el.src = url; // url 赋值
    el.async = false; // 放弃时序
    const loadCallback = function () { // 加载胜利回调
      el.removeEventListener('load', loadCallback);
      resolve(result);
    };
    const errorCallback = function (evt) { // 加载失败回调
      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); // 节点插入
  });
}

二、为加载模块注入依赖

对于这一点咱们能够看下遵循 AMD 标准的 require.js 是怎么做的。代码:

// require.js
const modules = {};
const define = function(moduleName, depends, callback){modules[moduleName] = { // 将模块存起来,期待后续调用
    depends,
    callback,
  };
}
// moduleA.js
define('moduleA', [], ()=>{// code})

因为通过插入 Script 的形式引入 JS 资源,JS 会被立即执行,所以在 require.js 中加载进来的 JS 模块都是被 define 办法包裹着的,真正须要执行的代码是在回调函数中期待后续调用。当 moduleA.js 被加载胜利之后,立刻调用 define 办法,这里执行的内容则是把我的项目的模块储存起来期待调用。依赖的注入则是回调中将依赖作为参数注入。

其实不论是基于哪一种标准,动静加载动态资源的策略都大抵一样。模块中应用一个函数 A 将指标代码包起来。将该函数 A 作为一个函数 D 的参数。当模块被加载时,浏览器中曾经定义好的 D 函数中就能够获取到含有指标代码块的函数 A 了。接下来想在哪里调用就在哪里调用。想注入什么变量就注入什么变量了。

  • 备注

    • 这里是对 AMD 进行了粗略的原理解释,具体实现还有很多细节,想要理解的话,能够在网上找到很多源码解析,这里就不再细讲
    • Webpack 打包之后的代码的模块治理形式是 Webpack 本人实现的一套相似 CommonJS 标准的货色。去看看打包生成的代码就能够发现外面都是一些 webpack_modules__,webpack_require,webpack_exports 这样的关键词,和 CommonJS 标准的 modules,require,exports 绝对应

三、模块打包规范

因为咱们团队应用的是 Webpack 的打包体系,因而想要放弃技术栈对立,则要先从 Webpack 的打包动手。让咱们将 Webpack 的模块化打包都试一下看看能得出什么。

Webpack library 打包形式有 5 种。

  • 变量:作为一个全局变量,通过 script 标签来拜访(libraryTarget:'var')。
  • this:通过 this 对象拜访(libraryTarget:'this')。
  • window:通过 window 对象拜访,在浏览器中(libraryTarget:'window')。
  • UMD:在 AMD 或 CommonJS 的 require 之后可拜访(libraryTarget:'umd')。
  • AMD:基于 AMD 标准的打包形式(libraryTarget:'amd')。

能够排除前三个,咱们并不想将模块挂到 window 或者全局变量下。所以咱们须要尝试的只有前面两个。

须要被打包的代码块:

export default {test: ()=>{console.log('测试模块打包!');
  }
};

AMD 标准打包后:

define(["lodash"], (__WEBPACK_EXTERNAL_MODULE__92__) => (() => {
  // code ...
  // return funciton
})());

UMD 标准打包后:

(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});

能够看进去,AMD 标准打包后,代码执行了一个 define 办法。依赖注入是通过回调办法的参数进行注入的。那么咱们是不是能够在加载 JS 文件之前先在 window 下挂一个 define 办法,等文件加载完执行 define 办法的时候,咱们就能够在 define 办法中做咱们想做的事件了。同理 UMD 打包标准也能够通过相似的操作达到咱们的目标。所以这两种计划都能够。思考到前期动静表单页面转本地代码的需要,心愿插件还能被 npm 装置应用。这里采纳了 UMD 标准。

计划选取

一、加载资源的计划

  • 采纳动静插入 Script 形式实现 JS 资源加载。

二、模块打包计划

  • UMD 标准的打包形式。

最终实现代码参考:

// importScript.js
export default function (url, _) {
  const defineTemp = window.define; // 将 window 下的 define 办法暂存起来。let result; // 后果
  window.define = (depends, func) => { // 自定义 define 办法,result = func(_); // 包依赖注入 
  }
  window.define.amd = true; // 伪装成 amd 的 define。return new Promise(function (resolve, reject) {const el = document.createElement('script'); // 创立 script 元素
    el.src = url;
    el.async = false; // 放弃时序
    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); // 插入元素
  });
}

调用形式

import importScript from './importScript.js';
import _ from 'lodash';
importScript('http://static.cai-inc.com/app.bundle.js',_).then((mod)=>{// code mod.xxx})

三、与自定义表单联合

组件插件式引入的形式解决了,然而又引入了一个新的问题,一个表单页面如果有 10 个自定义组件的话,是不是就得动静加载 10 个动态资源呢,如果每个组件都有一个 JS,一个 CSS。那就是 20 个。这是不具备可行性的。

所以就有了组件合并的需要。

在配置表单页面的时候当用户公布该页面的时候,服务端建一个长期我的项目,将该页面的所有波及到的自定义组件装置到该我的项目上,并 export 进来。编译打包,生成合乎 UMD 标准的文件模块。而后再依照以上形式进行引入。这样就解决了多文件合并的问题。

总结

最初计划其实很简略,只是对 UMD 标准打包的一种灵便利用。基于 UMD 标准打包出一个组件代码,通过动静插入 Script 标签的形式引入该组件的 JS 代码。在引入之前定义一个 window.define 办法。在该组件的 JS 代码下载胜利之后,就会调用到咱们定义的 window.define 办法。这样咱们就能对插件模块进行依赖注入并将它储存起来备用了。

招贤纳士

政采云前端团队(ZooTeam),一个年老富裕激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员形成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端利用、数据分析及可视化等方向进行技术摸索和实战,推动并落地了一系列的外部技术产品,继续摸索前端技术体系的新边界。

如果你想扭转始终被事折腾,心愿开始能折腾事;如果你想扭转始终被告诫须要多些想法,却无从破局;如果你想扭转你有能力去做成那个后果,却不须要你;如果你想扭转你想做成的事须要一个团队去撑持,但没你带人的地位;如果你想扭转既定的节奏,将会是“5 年工作工夫 3 年工作教训”;如果你想扭转原本悟性不错,但总是有那一层窗户纸的含糊… 如果你置信置信的力量,置信平凡人能成就不凡事,置信能遇到更好的本人。如果你心愿参加到随着业务腾飞的过程,亲手推动一个有着深刻的业务了解、欠缺的技术体系、技术发明价值、影响力外溢的前端团队的成长历程,我感觉咱们该聊聊。任何工夫,等着你写点什么,发给 ZooTeam@cai-inc.com

正文完
 0