Skip to content

Commit

Permalink
refactor(@angular/cli): add infrastructure support for schematics bui…
Browse files Browse the repository at this point in the history
…lt-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.
  • Loading branch information
clydin authored and dgp1130 committed Apr 11, 2022
1 parent 9a5251c commit d87b858
Showing 1 changed file with 39 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

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

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

function shouldWrapSchematic(schematicFile: string): boolean {
function shouldWrapSchematic(schematicFile: string, schematicEncapsulation: boolean): boolean {
// Check environment variable if present
if (schematicRedirectVariable !== undefined) {
switch (schematicRedirectVariable) {
case '0':
case 'false':
case 'off':
case 'none':
return false;
case 'all':
return true;
}
switch (schematicRedirectVariable) {
case '0':
case 'false':
case 'off':
case 'none':
return false;
case 'all':
return true;
}

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

// Default is only first-party Angular schematic packages
// Check for first-party Angular schematic packages
// Angular schematics are safe to use in the wrapped VM context
return /\/node_modules\/@(?:angular|schematics|nguniversal)\//.test(normalizedSchematicFile);
if (/\/node_modules\/@(?:angular|schematics|nguniversal)\//.test(normalizedSchematicFile)) {
return true;
}

// Otherwise use the value of the schematic collection's encapsulation option (current default of false)
return schematicEncapsulation;
}

export class SchematicEngineHost extends NodeModulesEngineHost {
protected override _resolveReferenceString(refString: string, parentPath: string) {
protected override _resolveReferenceString(
refString: string,
parentPath: string,
collectionDescription?: FileSystemCollectionDesc,
) {
const [path, name] = refString.split('#', 2);
// Mimic behavior of ExportStringRef class used in default behavior
const fullPath = path[0] === '.' ? resolve(parentPath ?? process.cwd(), path) : path;

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

if (shouldWrapSchematic(schematicFile)) {
if (shouldWrapSchematic(schematicFile, !!collectionDescription?.encapsulation)) {
const schematicPath = dirname(schematicFile);

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

// All other schematics use default behavior
return super._resolveReferenceString(refString, parentPath);
return super._resolveReferenceString(refString, parentPath, collectionDescription);
}
}

Expand Down Expand Up @@ -128,6 +134,17 @@ function wrap(
if (legacyModules[id]) {
// Provide compatibility modules for older versions of @angular/cdk
return legacyModules[id];
} else if (id.startsWith('schematics:')) {
// Schematics built-in modules use the `schematics` scheme (similar to the Node.js `node` scheme)
const builtinId = id.slice(11);
const builtinModule = loadBuiltinModule(builtinId);
if (!builtinModule) {
throw new Error(
`Unknown schematics built-in module '${id}' requested from schematic '${schematicFile}'`,
);
}

return builtinModule;
} else if (id.startsWith('@angular-devkit/') || id.startsWith('@schematics/')) {
// Files should not redirect `@angular/core` and instead use the direct
// dependency if available. This allows old major version migrations to continue to function
Expand Down Expand Up @@ -201,3 +218,7 @@ function wrap(

return exportsFactory;
}

function loadBuiltinModule(id: string): unknown {
return undefined;
}

0 comments on commit d87b858

Please sign in to comment.