Skip to content

Commit d87b858

Browse files
clydindgp1130
authored andcommitted
refactor(@angular/cli): add infrastructure support for schematics built-in modules
Infrastructure has been added to the schematics runtime within the `@angular/cli` package to allow schematics executed via the Angular CLI to have access upcoming built-in modules. These built-in modules will be imported/required using the `schematics:` scheme similar to the Node.js `node:` scheme available for Node.js built-in modules. No built-in modules exist yet but will be added in the future. Schematics must be executed via the Angular CLI Schematics runtime's custom VM context to use the upcoming built-in modules. All first-party Angular schematics have been executed in this manner for several major versions. Third-party schematics can now opt-in to the behavior by enabling the `encapsulation` option within a schematic collection JSON file.
1 parent 9a5251c commit d87b858

File tree

1 file changed

+39
-18
lines changed

1 file changed

+39
-18
lines changed

packages/angular/cli/src/command-builder/utilities/schematic-engine-host.ts

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import { RuleFactory, SchematicsException, Tree } from '@angular-devkit/schematics';
10-
import { NodeModulesEngineHost } from '@angular-devkit/schematics/tools';
10+
import { FileSystemCollectionDesc, NodeModulesEngineHost } from '@angular-devkit/schematics/tools';
1111
import { readFileSync } from 'fs';
1212
import { parse as parseJson } from 'jsonc-parser';
1313
import nodeModule from 'module';
@@ -16,22 +16,19 @@ import { Script } from 'vm';
1616

1717
/**
1818
* Environment variable to control schematic package redirection
19-
* Default: Angular schematics only
2019
*/
2120
const schematicRedirectVariable = process.env['NG_SCHEMATIC_REDIRECT']?.toLowerCase();
2221

23-
function shouldWrapSchematic(schematicFile: string): boolean {
22+
function shouldWrapSchematic(schematicFile: string, schematicEncapsulation: boolean): boolean {
2423
// Check environment variable if present
25-
if (schematicRedirectVariable !== undefined) {
26-
switch (schematicRedirectVariable) {
27-
case '0':
28-
case 'false':
29-
case 'off':
30-
case 'none':
31-
return false;
32-
case 'all':
33-
return true;
34-
}
24+
switch (schematicRedirectVariable) {
25+
case '0':
26+
case 'false':
27+
case 'off':
28+
case 'none':
29+
return false;
30+
case 'all':
31+
return true;
3532
}
3633

3734
const normalizedSchematicFile = schematicFile.replace(/\\/g, '/');
@@ -45,20 +42,29 @@ function shouldWrapSchematic(schematicFile: string): boolean {
4542
return false;
4643
}
4744

48-
// Default is only first-party Angular schematic packages
45+
// Check for first-party Angular schematic packages
4946
// Angular schematics are safe to use in the wrapped VM context
50-
return /\/node_modules\/@(?:angular|schematics|nguniversal)\//.test(normalizedSchematicFile);
47+
if (/\/node_modules\/@(?:angular|schematics|nguniversal)\//.test(normalizedSchematicFile)) {
48+
return true;
49+
}
50+
51+
// Otherwise use the value of the schematic collection's encapsulation option (current default of false)
52+
return schematicEncapsulation;
5153
}
5254

5355
export class SchematicEngineHost extends NodeModulesEngineHost {
54-
protected override _resolveReferenceString(refString: string, parentPath: string) {
56+
protected override _resolveReferenceString(
57+
refString: string,
58+
parentPath: string,
59+
collectionDescription?: FileSystemCollectionDesc,
60+
) {
5561
const [path, name] = refString.split('#', 2);
5662
// Mimic behavior of ExportStringRef class used in default behavior
5763
const fullPath = path[0] === '.' ? resolve(parentPath ?? process.cwd(), path) : path;
5864

5965
const schematicFile = require.resolve(fullPath, { paths: [parentPath] });
6066

61-
if (shouldWrapSchematic(schematicFile)) {
67+
if (shouldWrapSchematic(schematicFile, !!collectionDescription?.encapsulation)) {
6268
const schematicPath = dirname(schematicFile);
6369

6470
const moduleCache = new Map<string, unknown>();
@@ -78,7 +84,7 @@ export class SchematicEngineHost extends NodeModulesEngineHost {
7884
}
7985

8086
// All other schematics use default behavior
81-
return super._resolveReferenceString(refString, parentPath);
87+
return super._resolveReferenceString(refString, parentPath, collectionDescription);
8288
}
8389
}
8490

@@ -128,6 +134,17 @@ function wrap(
128134
if (legacyModules[id]) {
129135
// Provide compatibility modules for older versions of @angular/cdk
130136
return legacyModules[id];
137+
} else if (id.startsWith('schematics:')) {
138+
// Schematics built-in modules use the `schematics` scheme (similar to the Node.js `node` scheme)
139+
const builtinId = id.slice(11);
140+
const builtinModule = loadBuiltinModule(builtinId);
141+
if (!builtinModule) {
142+
throw new Error(
143+
`Unknown schematics built-in module '${id}' requested from schematic '${schematicFile}'`,
144+
);
145+
}
146+
147+
return builtinModule;
131148
} else if (id.startsWith('@angular-devkit/') || id.startsWith('@schematics/')) {
132149
// Files should not redirect `@angular/core` and instead use the direct
133150
// dependency if available. This allows old major version migrations to continue to function
@@ -201,3 +218,7 @@ function wrap(
201218

202219
return exportsFactory;
203220
}
221+
222+
function loadBuiltinModule(id: string): unknown {
223+
return undefined;
224+
}

0 commit comments

Comments
 (0)