详解利用Angular实现多团队模块化SPA开发框架(3)
我们把每个模块,按照 umd 的格式进行打包。然后再需要使用该模块的时候,使用动态构建 script 来运行脚本。
load(moduleName, isDepModule = false): Promise<any> { let module = window['xxx'][moduleName]; if (module) { return Promise.resolve(module); } return new Promise((resolve, reject) => { let path = `${root}${moduleName}/app.js?rnd=${Math.random()}`; this._loadCss(moduleName); this.http.get(path) .toPromise() .then(res => { let code = res.text(); this._DomEval(code); return window['xxx'][moduleName]; }) .then(mod => { window['xxx'][moduleName] = mod; let AppModule = mod.AppModule; // route change will call useModuleStyles function. // this.useModuleStyles(moduleName, isDepModule); resolve(AppModule); }) .catch(err => { console.error('Load module failed: ', err); resolve(EmptyModule); }); }); } // 取自jQuery _DomEval(code, doc?) { doc = doc || document; let script = doc.createElement('script'); script.text = code; doc.head.appendChild(script).parentNode.removeChild(script); }
CSS的动态加载相对比较简单,代码如下:
_loadCss(moduleName: string): void { let cssPath = `${root}${moduleName}/app.css?rnd=${Math.random()}`; let link = document.createElement('link'); link.setAttribute('rel', 'stylesheet'); link.setAttribute('href', cssPath); link.setAttribute('class', `xxx-module-style ${moduleName}`); document.querySelector('head').appendChild(link); }
为了能够在模块切换时卸载,还需要提供一个方法,供路由切换时使用:
useModuleStyles(moduleName: string): void { let xxxModuleStyles = [].slice.apply(document.querySelectorAll('.xxx-module-style')); let moduleDeps = this._getModuleAndDeps(moduleName); moduleDeps.push(moduleName); xxxModuleStyles.forEach(link => { let disabled = true; for (let i = moduleDeps.length - 1; i >= 0; i--) { if (link.className.indexOf(moduleDeps[i]) >= 0) { disabled = false; moduleDeps.splice(i, 1); break; } } link.disabled = disabled; }); }
公共模块依赖
为了处理模块依赖,我们可以借鉴 AMD规范 以及使用 requirejs 作为加载器。当前在我的实现里,是自定义了一套加载器,后期应该会切换到 AMD 规范上去。
如何兼容 AngularJS
模块?
为了兼容 AngularJS 的模块,我们引入了 iframe, iframe会先加载一套曾今的 AngularJS 宿主,然后再这个宿主中,运行 AngularJS 模块。为了实现通信,我们需要两套平台程序中,都引入一个基于 postMessage 实现的跨窗口通信库(因为默认跨域,所以用postMessage实现),有了它之后,我们就可以很方便的两边通信了。