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); })报错信息:
import('http://static.cai-inc.com/moduleA.js').then((moduleA) => { moduleA.add(1,2); // 3 })三、require.js AMD 规范
// 堆代码 duidaima.com // 需要被动态加载的 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 项目也有一个入口,这会导致出现两个入口。两者用法并不能很好的并存。
插件模块最好能使用现有模块标准例如 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 })因为通过插入 Script 的方式引入 JS 资源,JS 会被立刻执行,所以在 require.js 中加载进来的 JS 模块都是被 define 方法包裹着的,真正需要执行的代码是在回调函数中等待后续调用。当 moduleA.js 被加载成功之后,立即调用 define 方法,这里执行的内容则是把项目的模块储存起来等待调用。依赖的注入则是回调中将依赖作为参数注入。
Webpack 打包之后的代码的模块管理方式是 Webpack 自己实现的一套类似 CommonJS 规范的东西。去看看打包生成的代码就可以发现里面都是一些 webpack_modules__,webpack_require,webpack_exports 这样的关键词,和 CommonJS 规范的 modules,require,exports 相对应。
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 规范。
// 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 })三、与自定义表单结合