angular除了支持路由懒加载之外,在angular 8版本中支持es标准的动态import,可更自由的根据需求进行懒加载。

import('/modules/my-module.js')
.then((module) => {
// Do something with the module.
});


正好在搬一个复杂度比较高项目的砖时,由于砖山砖海(第三方依赖以及生产的代码量非常大,minify之后还有5m,以下体积假如没有特殊说明,都是minify之后的结果)。这里称这个项目为X项目X项目是语言编辑器项目,支持语句的高亮,智能自动补齐等。第三方依赖为monaco-editor, antlr4ts,前者本身的大小在2m左右,可以根据需要的features尽可能减小bundle体积到1.5m左右。antlr4ts提供语法语义支持,生成的parser文件非常庞大,在8w行,2m左右,vscode读取也会卡。

该项目使用ng-packagr进行打包,供下游angular项目使用。打包由于用了dynamic import,遇到了问题,后续篇章会详细介绍。首先介绍一下怎么在angular项目中使用dynamic import

angular 项目使用dynamic import

假如是新项目,在ng new生成的模板,可以直接通过如下步骤实现,不然请check tsconfig 的module值为esnext

  1. 定义你的dynamic lib,比如
export class DynamicLib {
  cheers() {
    console.log('cheers');
    const date = (new Date()).toString();
    return `cheers at ${date}`;
  }
}



2. 在需要动态载入的地方使用

@Component({
  selector: 'app-root',
  template: `
  <p>
    <button (click)="loadLib()">dynamic load lib</button>
    {{libMsg}}
  </p>
  `,
  styleUrls: ['./app.component.sass']
})
export class AppComponent  {
  name = 'Angular';
  libMsg = '';

  async loadLib() {
    const {DynamicLib} = await import('./dynamic-lib');
    const lib = new DynamicLib();
    this.libMsg = lib.cheers();
  }
}


观察如下动图,在鼠标点击了按钮之后,才回去加载需要的库。




angularjs java 上传 angularjs import_vscode angular智能提示


整个使用是非常简单的,import的结果是个module的promise,用vscode也可以提供舒适的module补齐,需要注意的是,你需要确保在应用的其他地方没有如import {DynamicLib} from './dynamic-lib';之类的抵消dynamic import作用的import语句,和路由懒加载不能将懒加载模快import进app.module是一个道理。

项目地址:

angular-dynamic-importgithub.com


library应用

上节的demo看上去非常简单吧,要感谢angular-cli团队,把所有构建bundle的工作都做了。如果在angular-cli通过ng generate library my-lib生成的库项目中,使用动态import则不能成功,也就是X项目遇到的情况。参考该ng-packagr issue, 大体原因是

  1. ng-packagr 使用rollup来构建ts源文件,rollup当检测到代码中有dynamic import时,会自动生成multiple chunk,读取output.dir而不是ng-packagr定义的output.name导致rollup报错
  2. 即使ng-packagr修改rollup配置,rollup也会继续报错Error: UMD and IIFE output formats are not supported for code-splitting builds.,这是rollup本身不支持dynamic import构建umd格式代码,参考rollup维护者的评论:umd格式不支持dynamic import,可以观察如下umd dist的代码,就知道为什么umd支持动态载入会很困难。而ng-packagr遵从Angular Package Format格式,会构建umd格式。
// lib.umd.js. umd格式并不是模块化的js系统,他像是IIFE,稳定并且自包含,可直接在浏览器中执行。
// 而dynamic import天然得构建生成multiple chunk,需要模块支持。观察之前的动图,js是分块按需加载的
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('rxjs'), require('rxjs/operators')) :
    typeof define === 'function' && define.amd ? define('lib', ['exports', '@angular/core', 'rxjs', 'rxjs/operators'], factory) :
    (global = global || self, factory(global.lib = {}, global.ng.core, global.rxjs, global.rxjs.operators));
}(this, function (exports, core, rxjs, operators) { 'use strict';
...
...
}));


其实假如你的动态载入代码块体积不大,并不建议使用dynamic import,多个js文件在页面初次加载时耗时会比单个js文件更慢。分割模块本身需要根据具体情况出发,在我遇到的情况,的确需要优化实现dynamic import。

解决方案

在目前情况下,因为ng-packagr不支持dynamic import,可以选择了其他方案绕过ng-packagr直接构建带有dynamic import的代码。当然你也可以直接不用ng-packagr,使用诸如angular-library-seed等暴露构建配置的library源码进行开发,更改umd dist输出为es模块输出。当然,作为非常厌烦配webpack的我来说,还是有其他的方案:

1. 假如你动态载入的逻辑是纯粹typescript写的,可以从angular中分离出来,可以使用typescript-library-starter在projects目录下平行的再开发一个项目,并修改rollup.config.ts,删掉umd format的output,并且指定dist dir。然后你angular的lib可以peer dependency依赖这个库。虽然也需要配一点构建,但工作量非常小,后续维护成本也很小。angular相关的构建工作还是放在ng-packagr项目里。

X项目就是这么解决的,本身这项目还有跨前端技术方案的要求,同时支持angular和react,所以核心代码直接用typescript和原生dom操作来写(并不多),对你没有看错,其实很多项目也是用原生dom操作的,比如monaco-editorprojects目录下依次是

  • core
  • angular-editor
  • react-editor

后两者作为core逻辑的组件封装存在,没有dynamic import的代码。

2. 动态载入的库可以作为ng-packagr的submodule,在具体angular应用中载入,通过共享服务进行通知,比如服务定义一个Subject<DynamicLibrary>在angular应用中import之后调用Subject.next,然后在lib中得到。曲线救国,代码比较冗余。具体的实现参考


feat: dynamic import with ng-packagr project · kingfolk/angular-dynamic-import@9deb152github.com

angularjs java 上传 angularjs import_angular starter_02


LibService作为中间通信的服务,来通知lib组件动态库加载完毕


angularjs java 上传 angularjs import_angularjs java 上传_03


总结,根据具体需要对应用进行切割。

  • 假如库是组件库,可以定义NgModule进行按需加载;
  • 假如是typescript库,可以使用typescript-library-starter进行开发(方法1)。
  • 假如真的执意需要将动态载入代码放在ng-packagr项目里,可参看方法2,略麻烦。

对,假如你看到这里我分享的一点浅见,抱歉题目有点忽悠大家进来看,但将我对import()的应用和限制分享完毕,希望对各位搬砖的有所用处。假如有更好的方法或者错误,可以留言,欢迎交流,扔鸡蛋。

其他引用补充材料

本文着重介绍了angular之于dynamic在library里的应用,其他关于dynamic import的具体背景等,可参看:

https://medium.com/lacolaco-blog/angular-dynamic-importing-large-libraries-8ec079603d0