|
| 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). The collection has `"encapsulation": true` so every |
| 8 | +migration is loaded in its own isolated CommonJS context by the Angular |
| 9 | +schematics tooling. |
| 10 | + |
| 11 | +## Dynamic `@angular/compiler` import via `nativeImport` |
| 12 | + |
| 13 | +`@angular/compiler` is an ES module. The migrations themselves are emitted as |
| 14 | +CommonJS, so they cannot statically `import` it; they have to use a runtime |
| 15 | +dynamic `import()`. To avoid TypeScript downleveling that dynamic call into a |
| 16 | +synchronous `require()` (which would crash on an ESM target), the call is |
| 17 | +routed through a tiny `.cjs` helper: |
| 18 | + |
| 19 | +- [`common/import-helper.cts`](./common/import-helper.cts) — authored as |
| 20 | + a TypeScript CJS module. The dynamic `import()` is preserved in the emitted |
| 21 | + [`common/import-helper.cjs`](./common/import-helper.cjs) because the file's |
| 22 | + forced `.cjs` extension prevents the compiler from rewriting it. |
| 23 | +- Migrations call it via: |
| 24 | + ```ts |
| 25 | + import { nativeImport } from 'igniteui-angular/migrations/common/import-helper.cjs'; |
| 26 | + // ... |
| 27 | + const { HtmlParser } = await nativeImport<typeof import('@angular/compiler')>( |
| 28 | + '@angular/compiler' |
| 29 | + ); |
| 30 | + ``` |
| 31 | + |
| 32 | +### Why the bare `igniteui-angular/...` specifier |
| 33 | + |
| 34 | +Schematics encapsulation (`"encapsulation": true` in |
| 35 | +[`migration-collection.json`](./migration-collection.json)) wraps every |
| 36 | +migration in a sandboxed module loader. Relative requires (`../common/...`) |
| 37 | +go through that wrapper and the dynamic `import()` ends up being intercepted |
| 38 | +and converted by the wrapper, defeating the purpose of the helper. A **bare |
| 39 | +package specifier** bypasses the wrapper because the wrapper only encapsulates |
| 40 | +the migration's own files, not requires that resolve to a real package. |
| 41 | + |
| 42 | +This is the trick PR |
| 43 | +[#13712 — fix(migrations,ng-add): turn on encapsulation for devkit/schematics deps](https://github.com/IgniteUI/igniteui-angular/pull/13712) |
| 44 | +introduced when encapsulation was first enabled. Every migration that needs |
| 45 | +`@angular/compiler` follows the same pattern. |
| 46 | + |
| 47 | +To keep TypeScript happy with the bare specifier at compile time, |
| 48 | +[`tsconfig.json`](./tsconfig.json) maps it back to the source: |
| 49 | + |
| 50 | +```jsonc |
| 51 | +"paths": { |
| 52 | + "igniteui-angular/*": ["../*"], |
| 53 | + "@infragistics/igniteui-angular/*": ["../*"] |
| 54 | +} |
| 55 | +``` |
| 56 | + |
| 57 | +At consumer install time the specifier resolves naturally through their |
| 58 | +`node_modules/igniteui-angular/migrations/common/import-helper.cjs`. |
| 59 | + |
| 60 | +## Running migration tests locally |
| 61 | + |
| 62 | +`npm run test:schematics` compiles the migrations with |
| 63 | +[`tsconfig.spec.json`](./tsconfig.spec.json) into `dist/igniteui-angular/...` |
| 64 | +and runs the compiled `*.spec.js` files through Jasmine. In this repo there is |
| 65 | +no `node_modules/igniteui-angular` (the library is the workspace itself), so |
| 66 | +the bare `igniteui-angular/migrations/common/import-helper.cjs` specifier |
| 67 | +would not resolve at runtime. |
| 68 | + |
| 69 | +[`tsconfig-paths-bootstrap.js`](./tsconfig-paths-bootstrap.js) fixes that. It |
| 70 | +is loaded with `node --require` before Jasmine starts and installs a runtime |
| 71 | +path map (via the `tsconfig-paths` package) that points |
| 72 | +`igniteui-angular/*` at `dist/igniteui-angular/*`. The bootstrap deliberately |
| 73 | +does **not** read [`tsconfig.json`](./tsconfig.json) because: |
| 74 | + |
| 75 | +- the compile-time map targets source under `../*` (i.e. `.ts` / `.cts`), |
| 76 | + which Jasmine cannot execute; |
| 77 | +- the runtime map must point at the compiled output in `dist/`. |
| 78 | + |
| 79 | +The wiring in [`package.json`](../../../package.json) looks like: |
| 80 | + |
| 81 | +```text |
| 82 | +node -r ./projects/igniteui-angular/migrations/tsconfig-paths-bootstrap.js \ |
| 83 | + ./node_modules/jasmine/bin/jasmine.js \ |
| 84 | + "dist/igniteui-angular/migrations/**/*.spec.js" \ |
| 85 | + "dist/igniteui-angular/schematics/**/*.spec.js" |
| 86 | +``` |
| 87 | + |
| 88 | +This setup is internal to the repo's test runner; published migrations are |
| 89 | +unaffected and rely on the consumer's regular `node_modules` resolution. |
0 commit comments