赞
踩
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
假如是新项目,在ng new生成的模板,可以直接通过如下步骤实现,不然请check tsconfig 的module值为esnext
。
- 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();
- }
- }
观察如下动图,在鼠标点击了按钮之后,才回去加载需要的库。
整个使用是非常简单的,import
的结果是个module的promise,用vscode也可以提供舒适的module补齐,需要注意的是,你需要确保在应用的其他地方没有如import {DynamicLib} from './dynamic-lib';
之类的抵消dynamic import作用的import语句,和路由懒加载不能将懒加载模快import进app.module是一个道理。
项目地址:
angular-dynamic-importgithub.com上节的demo看上去非常简单吧,要感谢angular-cli团队,把所有构建bundle的工作都做了。如果在angular-cli通过ng generate library my-lib
生成的库项目中,使用动态import则不能成功,也就是X项目
遇到的情况。参考该ng-packagr issue, 大体原因是
output.dir
而不是ng-packagr
定义的output.name
导致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-editor
。projects
目录下依次是
后两者作为core逻辑的组件封装存在,没有dynamic import的代码。
2. 动态载入的库可以作为ng-packagr的submodule,在具体angular应用中载入,通过共享服务进行通知,比如服务定义一个Subject<DynamicLibrary>
在angular应用中import
之后调用Subject.next
,然后在lib中得到。曲线救国,代码比较冗余。具体的实现参考
LibService
作为中间通信的服务,来通知lib组件动态库加载完毕
总结,根据具体需要对应用进行切割。
对,假如你看到这里我分享的一点浅见,抱歉题目有点忽悠大家进来看,但将我对import()
的应用和限制分享完毕,希望对各位搬砖的有所用处。假如有更好的方法或者错误,可以留言,欢迎交流,扔鸡蛋。
本文着重介绍了angular之于dynamic在library里的应用,其他关于dynamic import的具体背景等,可参看:
https://medium.com/lacolaco-blog/angular-dynamic-importing-large-libraries-8ec079603d0
目前本博主是自由职业,假如有有挑战的项目,欢迎私信,不限技术框架,不限前后端。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。