详解利用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实现),有了它之后,我们就可以很方便的两边通信了。