【实战】动态表单之表单组件的插件式加载方案
前言
组件插件式加载方案的现状
一、Webpack 懒加载
import('./moduleA').then((moduleA) => {
moduleA.add(1,2); // 3
})
import('http://static.cai-inc.com/moduleA.js').then((moduleA) => {
// ERROR,打包过程会出现报错
moduleA.add(1,2);
})
二、现有浏览器支持的 Dynamic Import
import('http://static.cai-inc.com/moduleA.js').then((moduleA) => {
moduleA.add(1,2); // 3
})
三、require.js AMD 规范
// 需要被动态加载的 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); // 使用被动态引入的插件的方法
});
data-main="scripts/main"
作为入口文件。但是我们的 React 项目也有一个入口,这会导致出现两个入口。两者用法并不能很好的并存。需求拆解
一、加载资源
因为插件单独发布之后要放在 CDN 上,所以加载静态资源的方案需要满足没有跨域限制的条件。
二、插件模块打包
插件模块最好能使用现有模块标准例如 CMD、AMD 模块标准,这样我们就可以使用更多的社区开源方案,降低方案的风险性。同时降低团队成员学习使用成本。 插件需要能够被注入依赖,例如项目中已经包含有 Lodash 或者 AntD 组件库的包,这时候插件模块中使用 Lodash 或者 AntD 组件库的话我们当然希望能够直接引用项目中已有的,而不是插件模块中重新引入一个。
需求分析
一、静态资源加载
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); // 节点插入
});
}
二、为加载模块注入依赖
// require.js
const modules = {};
const define = function(moduleName, depends, callback){
modules[moduleName] = { // 将模块存起来,等待后续调用
depends,
callback,
};
}
// moduleA.js
define('moduleA', [], ()=>{
// code
})
备注: 这里是对 AMD 进行了粗略的原理解释,具体实现还有很多细节,想要了解的话,可以在网上找到很多源码解析,这里就不再细讲。 Webpack 打包之后的代码的模块管理方式是 Webpack 自己实现的一套类似 CommonJS 规范的东西。去看看打包生成的代码就可以发现里面都是一些 webpack_modules__,webpack_require,webpack_exports 这样的关键词,和 CommonJS 规范的 modules,require,exports 相对应。
三、模块打包标准
变量:作为一个全局变量,通过 script
标签来访问(libraryTarget:'var'
)。this:通过 this
对象访问(libraryTarget:'this'
)。window:通过 window
对象访问,在浏览器中(libraryTarget:'window'
)。UMD:在 AMD 或 CommonJS 的 require
之后可访问(libraryTarget:'umd'
)。AMD:基于 AMD 规范的打包方式( libraryTarget:'amd'
)。
export default {
test: ()=>{
console.log('测试模块打包!');
}
};
define(["lodash"], (__WEBPACK_EXTERNAL_MODULE__92__) => (() => {
// code ...
// return funciton
})());
(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
});
方案选取
一、加载资源的方案
采用动态插入 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
})
三、与自定义表单结合
总结
欢迎关注「前端杂货铺」,一个有温度且致力于前端分享的杂货铺
关注回复「加群」,可加入杂货铺一起交流学习成长
评论