Skip to content

Commit c9e94a7

Browse files
authored
Merge pull request #17360 from IgniteUI/dpetev/migrations-dynamic-import-fix-22
fix(migrations): restore dynamic import helper as cjs
2 parents 5b4c989 + 4ce8598 commit c9e94a7

17 files changed

Lines changed: 181 additions & 24 deletions

File tree

package-lock.json

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"test:lib:pgrid": "ng test igniteui-angular --watch=false --no-progress --code-coverage --karma-config=./projects/igniteui-angular/karma.pivot-grid.conf.js",
1919
"test:lib:others": "ng test igniteui-angular --watch=false --no-progress --code-coverage --karma-config=./projects/igniteui-angular/karma.non-grid.conf.js",
2020
"test:lib:watch": "ng test igniteui-angular --karma-config=./projects/igniteui-angular/karma.watch.conf.js",
21-
"test:schematics": "gulp copySchematics && tsc --project ./projects/igniteui-angular/schematics/tsconfig.json && gulp copyMigrations && tsc --project ./projects/igniteui-angular/migrations/tsconfig.spec.json && node ./node_modules/jasmine/bin/jasmine.js \"dist/igniteui-angular/migrations/**/*.spec.js\" \"dist/igniteui-angular/schematics/**/*.spec.js\"",
21+
"test:schematics": "gulp copySchematics && tsc --project ./projects/igniteui-angular/schematics/tsconfig.json && gulp copyMigrations && tsc --project ./projects/igniteui-angular/migrations/tsconfig.spec.json && node -r ./projects/igniteui-angular/migrations/tsconfig-paths-bootstrap.js ./node_modules/jasmine/bin/jasmine.js \"dist/igniteui-angular/migrations/**/*.spec.js\" \"dist/igniteui-angular/schematics/**/*.spec.js\"",
2222
"test:styles": "ts-node --skip-project ./node_modules/jasmine/bin/jasmine.js ./projects/igniteui-angular/core/src/core/styles/spec/tests.mjs",
2323
"test:i18n": "ts-node --skip-project ./projects/igniteui-angular/core/src/core/i18n/tests/tests.mjs",
2424
"test:elements": "ng test igniteui-angular-elements --watch=false --no-progress --code-coverage --source-map=false",
@@ -163,6 +163,7 @@
163163
"stylelint-prettier": "^5.0.2",
164164
"stylelint-scss": "^6.9.0",
165165
"ts-node": "^10.8.1",
166+
"tsconfig-paths": "^4.2.0",
166167
"typedoc": "^0.28.14",
167168
"typedoc-plugin-localization": "^3.1.0",
168169
"typescript": "6.0.3"
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Ignite UI for Angular — `ng update` Migrations
2+
3+
This directory hosts the migration schematics that run when consumers execute
4+
`ng update igniteui-angular`. Each `update-*` folder is a versioned migration
5+
listed in [`migration-collection.json`](./migration-collection.json) and
6+
implemented in `update-*/index.ts`. Shared helpers live in
7+
[`common/`](./common).
8+
9+
See [Wiki pages under Migrations & Schematics](https://github.com/IgniteUI/igniteui-angular/wiki#migrations--schematics)
10+
for authoring and testing guidance.
11+
12+
## Encapsulation and external ESM like `@angular/compiler`
13+
The collection has `"encapsulation": true` so every
14+
migration is loaded in its own isolated VM context by the Angular
15+
schematics tooling. That also restricts some API access/functionality.
16+
17+
See the description of [#13712 — fix(migrations,ng-add): turn on encapsulation for devkit/schematics deps](https://github.com/IgniteUI/igniteui-angular/pull/13712) when encapsulation was first enabled for mechanism details.
18+
19+
### Dynamic `@angular/compiler` import via `nativeImport`
20+
21+
`@angular/compiler` is an ES module. The migrations themselves are emitted and consumed
22+
(`require()`-d) by the Angular schematics as CommonJS, so they cannot statically `import` it;
23+
they have to use a runtime dynamic `import()`.
24+
25+
- [`common/import-helper.cts`](./common/import-helper.cts) — authored as
26+
a TypeScript CJS module. The dynamic `import()` is preserved in the emitted.
27+
- Migrations call it via:
28+
```ts
29+
import { nativeImport } from 'igniteui-angular/migrations/common/import-helper.cjs';
30+
// ...
31+
const { HtmlParser } = await nativeImport('@angular/compiler');
32+
```
33+
34+
### Why the bare `igniteui-angular/...` specifier
35+
36+
Schematics encapsulation being a VM execution is affected by the
37+
[Support of dynamic import() in compilation APIs](https://nodejs.org/api/vm.html#support-of-dynamic-import-in-compilation-apis)
38+
and at this time Angular schematics do not implement [importModuleDynamically](https://github.com/angular/angular-cli/blob/e5eb3e37c9756669b3b91b5b681a73cc9b616cb9/packages/angular/cli/src/command-builder/utilities/schematic-engine-host.ts#L50).
39+
40+
A **bare package specifier** effectively passes through the schematics host encapsulation again and escapes [the relative wrapping ](https://github.com/angular/angular-cli/blob/e5eb3e37c9756669b3b91b5b681a73cc9b616cb9/packages/angular/cli/src/command-builder/utilities/schematic-engine-host.ts#L177-L178) out into [the original schematic require](https://github.com/angular/angular-cli/blob/e5eb3e37c9756669b3b91b5b681a73cc9b616cb9/packages/angular/cli/src/command-builder/utilities/schematic-engine-host.ts#L203-L204).
41+
This means just the `import-helper.cjs` script is ran outside of encapsulation and as such can perform dynamic imports.
42+
43+
Every migration that needs `@angular/compiler` follows the same pattern.
44+
45+
To keep TypeScript happy with the bare specifier at compile time,
46+
[`tsconfig.json`](./tsconfig.json) maps it back to the source:
47+
48+
```jsonc
49+
"paths": {
50+
"igniteui-angular/*": ["../*"],
51+
"@infragistics/igniteui-angular/*": ["../*"]
52+
}
53+
```
54+
55+
At consumer install time the specifier resolves naturally through their
56+
`node_modules/igniteui-angular/migrations/common/import-helper.cjs`.
57+
58+
### Tests setup for the bare specifier
59+
60+
`npm run test:schematics` compiles the migrations with
61+
[`tsconfig.spec.json`](./tsconfig.spec.json) into `dist/igniteui-angular/...`
62+
and runs the compiled `*.spec.js` files through Jasmine. In this repo there is
63+
no `node_modules/igniteui-angular` (the library is the workspace itself), so
64+
the bare `igniteui-angular/migrations/common/import-helper.cjs` specifier
65+
would not resolve at runtime.
66+
67+
[`tsconfig-paths-bootstrap.js`](./tsconfig-paths-bootstrap.js) fixes that. It
68+
is loaded with `node --require` before Jasmine starts and installs a runtime
69+
path map (via the `tsconfig-paths` package) that points
70+
`igniteui-angular/*` at `./dist/igniteui-angular/*`.
71+
72+
The wiring in [`package.json`](../../../package.json) looks like:
73+
74+
```text
75+
node -r ./projects/igniteui-angular/migrations/tsconfig-paths-bootstrap.js \
76+
./node_modules/jasmine/bin/jasmine.js \
77+
"dist/igniteui-angular/migrations/**/*.spec.js" \
78+
"dist/igniteui-angular/schematics/**/*.spec.js"
79+
```
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/** Explicit module map, as generic T can't be resolved to a direct string literal via inference */
2+
type ModuleMap = {
3+
'@angular/compiler': typeof import('@angular/compiler', { with: { "resolution-mode": "import" }});
4+
};
5+
6+
/**
7+
* Native Node dynamic import helper
8+
* @remarks
9+
* NB: Import this via a bare specifier (igniteui-angular/migrations/common/import-helper.cjs)
10+
* to escape the schematics encapsulation for this file and thus the dynamic import inside.
11+
* This allows to dynamically import ESM modules from outside the schematics host context.
12+
*/
13+
export function nativeImport<T extends keyof ModuleMap>(name: T): Promise<ModuleMap[T]> {
14+
return import(name);
15+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Test-time bootstrap. Registered via `node --require` before jasmine runs so
3+
* the `igniteui-angular/...` bare specifier used by migrations (to escape the
4+
* schematic encapsulation for the dynamic import helper) resolves against the
5+
* built migrations in dist instead of `node_modules` (which has no
6+
* igniteui-angular install in this repo).
7+
*
8+
* The compile-time path map in ./tsconfig.json points at the sources under
9+
* ../*; at runtime we point at the compiled output under
10+
* <repo>/dist/igniteui-angular/* so the require resolves to the emitted .cjs.
11+
*/
12+
const path = require("path");
13+
const tsConfigPaths = require("tsconfig-paths");
14+
15+
const distRoot = path.resolve(__dirname, "../../../dist/igniteui-angular");
16+
17+
tsConfigPaths.register({
18+
baseUrl: distRoot,
19+
paths: {
20+
"igniteui-angular/*": ["./*"],
21+
"@infragistics/igniteui-angular/*": ["./*"],
22+
},
23+
});

projects/igniteui-angular/migrations/update-11_0_0/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ import type { Element } from '@angular/compiler' with { "resolution-mode": "impo
22
import type { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
33
import { UpdateChanges } from '../common/UpdateChanges';
44
import { FileChange, findElementNodes, getAttribute, getSourceOffset, hasAttribute, parseFile, serializeNodes } from '../common/util';
5+
// use bare specifier to escape the schematics encapsulation for the dynamic import:
6+
import { nativeImport } from 'igniteui-angular/migrations/common/import-helper.cjs';
57

68
const version = '11.0.0';
79

810
export default (): Rule => async (host: Tree, context: SchematicContext) => {
911
context.logger.info(
1012
`Applying migration for Ignite UI for Angular to version ${version}`
1113
);
12-
// bare specifier escapes schematics encapsulation for the compiler dynamic import:
13-
const { HtmlParser, getHtmlTagDefinition } = await import('@angular/compiler');
14+
const { HtmlParser, getHtmlTagDefinition } = await nativeImport('@angular/compiler');
1415

1516
const update = new UpdateChanges(__dirname, host, context);
1617

projects/igniteui-angular/migrations/update-12_0_0/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type { Element } from '@angular/compiler' with { "resolution-mode": "impo
22
import type { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
33
import { UpdateChanges } from '../common/UpdateChanges';
44
import { FileChange, getAttribute, findElementNodes, getSourceOffset, hasAttribute, parseFile } from '../common/util';
5+
// use bare specifier to escape the schematics encapsulation for the dynamic import:
6+
import { nativeImport } from 'igniteui-angular/migrations/common/import-helper.cjs';
57
import type { Options } from '../../schematics/interfaces/options';
68

79
const version = '12.0.0';
@@ -12,8 +14,7 @@ export default (options: Options): Rule =>
1214
`Applying migration for Ignite UI for Angular to version ${version}`
1315
);
1416

15-
// bare specifier escapes schematics encapsulation for the compiler dynamic import:
16-
const { HtmlParser } = await import('@angular/compiler');
17+
const { HtmlParser } = await nativeImport('@angular/compiler');
1718

1819
// eslint-disable-next-line max-len
1920
const UPDATE_NOTE = `<!--NOTE: This component has been updated by Infragistics migration: v${version}\nPlease check your template whether all bindings/event handlers are correct.-->\n`;

projects/igniteui-angular/migrations/update-12_1_0/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import {
55
FileChange, findElementNodes, getAttribute, getSourceOffset, hasAttribute, parseFile,
66
serializeNodes, makeNgIf, stringifyAttributes
77
} from '../common/util';
8+
// use bare specifier to escape the schematics encapsulation for the dynamic import:
9+
import { nativeImport } from 'igniteui-angular/migrations/common/import-helper.cjs';
810

911
const version = '12.1.0';
1012

1113
export default (): Rule => async (host: Tree, context: SchematicContext) => {
1214
context.logger.info(`Applying migration for Ignite UI for Angular to version ${version}`);
1315

14-
// bare specifier escapes schematics encapsulation for the compiler dynamic import:
15-
const { HtmlParser, getHtmlTagDefinition } = await import('@angular/compiler');
16+
const { HtmlParser, getHtmlTagDefinition } = await nativeImport('@angular/compiler');
1617

1718
const update = new UpdateChanges(__dirname, host, context);
1819
const TAGS = ['igx-grid', 'igx-tree-grid', 'igx-hierarchical-grid'];

projects/igniteui-angular/migrations/update-13_0_0/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import type {
77
import type { Options } from '../../schematics/interfaces/options';
88
import { UpdateChanges } from '../common/UpdateChanges';
99
import { FileChange, findElementNodes, getAttribute, getSourceOffset, hasAttribute, parseFile } from '../common/util';
10+
// use bare specifier to escape the schematics encapsulation for the dynamic import:
11+
import { nativeImport } from 'igniteui-angular/migrations/common/import-helper.cjs';
1012

1113
const version = '13.0.0';
1214

@@ -25,8 +27,7 @@ export default (options: Options): Rule =>
2527
'[exportExcel]', 'exportExcel', '[exportExcelText]', 'exportExcelText',
2628
'[exportCsv]', 'exportCsv', '[exportCsvText]', 'exportCsvText', '[exportText]', 'exportText'];
2729
const actionsLeft = ['igx-grid-toolbar-advanced-filtering'];
28-
// bare specifier escapes schematics encapsulation for the compiler dynamic import:
29-
const { HtmlParser } = await import('@angular/compiler');
30+
const { HtmlParser } = await nativeImport('@angular/compiler');
3031

3132
const moduleTsFiles = tsFiles.filter(x => x.endsWith('.module.ts'));
3233
for (const path of moduleTsFiles) {

projects/igniteui-angular/migrations/update-13_1_0/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@ import type {
44
Tree
55
} from '@angular-devkit/schematics';
66
import type { Element } from '@angular/compiler' with { "resolution-mode": "import" };
7+
// use bare specifier to escape the schematics encapsulation for the dynamic import:
8+
import { nativeImport } from 'igniteui-angular/migrations/common/import-helper.cjs';
79
import { UpdateChanges } from '../common/UpdateChanges';
810
import { FileChange, findElementNodes, getAttribute, getSourceOffset, hasAttribute, parseFile } from '../common/util';
911

1012
const version = '13.1.0';
1113

1214
export default (): Rule => async (host: Tree, context: SchematicContext) => {
1315
context.logger.info(`Applying migration for Ignite UI for Angular to version ${version}`);
14-
// bare specifier escapes schematics encapsulation for the compiler dynamic import:
15-
const { HtmlParser } = await import('@angular/compiler');
16+
const { HtmlParser } = await nativeImport('@angular/compiler');
1617

1718
const update = new UpdateChanges(__dirname, host, context);
1819
const GRID_TAGS = ['igx-grid', 'igx-tree-grid', 'igx-hierarchical-grid', 'igx-row-island'];

0 commit comments

Comments
 (0)