Browse Source

Refactor change-theme schematic

pull/17675/head
Masum ULU 3 years ago
parent
commit
3c232259d8
  1. 2
      npm/ng-packs/package.json
  2. 222
      npm/ng-packs/packages/schematics/src/commands/change-theme/index.ts
  3. 5
      npm/ng-packs/packages/schematics/src/utils/angular/ast-utils.ts

2
npm/ng-packs/package.json

@ -34,7 +34,7 @@
"build:schematics": "cd scripts && yarn && yarn build:schematics && cd ..", "build:schematics": "cd scripts && yarn && yarn build:schematics && cd ..",
"dev:schematics": "tsc -p packages/schematics/tsconfig.json -w", "dev:schematics": "tsc -p packages/schematics/tsconfig.json -w",
"mock:schematics": "cd scripts/mock-schematic && yarn && yarn start", "mock:schematics": "cd scripts/mock-schematic && yarn && yarn start",
"debug:schematics": "./node_modules/.bin/ng g ./packages/schematics/src/collection.json:proxy-add --module __default --apiName __default --source __default --target __default --url https://localhost:44305 --serviceType application --entryPoint __default ", "debug:schematics": "./node_modules/.bin/nx g ./packages/schematics/src/collection.json:proxy-add --module identity --apiName __default --source __default --target __default --url https://localhost:44305 --serviceType application --entryPoint __default ",
"debug:schematics-dist": "./node_modules/.bin/ng g ./dist/packages/schematics/collection.json:proxy-add --module __default --apiName __default --source __default --target __default --url http://localhost:4300 --service-type application --entryPoint __default", "debug:schematics-dist": "./node_modules/.bin/ng g ./dist/packages/schematics/collection.json:proxy-add --module __default --apiName __default --source __default --target __default --url http://localhost:4300 --service-type application --entryPoint __default",
"ci": "yarn affected:lint && yarn affected:build && yarn affected:test", "ci": "yarn affected:lint && yarn affected:build && yarn affected:test",
"lerna": "lerna", "lerna": "lerna",

222
npm/ng-packs/packages/schematics/src/commands/change-theme/index.ts

@ -1,22 +1,20 @@
import { JsonArray, JsonValue } from '@angular-devkit/core'; import { JsonArray, JsonValue } from '@angular-devkit/core';
import { Rule, SchematicsException, Tree, UpdateRecorder, chain } from '@angular-devkit/schematics'; import { Rule, SchematicsException, Tree, chain } from '@angular-devkit/schematics';
import { ProjectDefinition } from '@angular-devkit/core/src/workspace'; import { ProjectDefinition } from '@angular-devkit/core/src/workspace';
import * as ts from 'typescript'; import * as ts from 'typescript';
import { ImportDefinition, allStyles, importMap, styleMap } from './style-map'; import { allStyles, importMap, styleMap } from './style-map';
import { ChangeThemeOptions } from './model'; import { ChangeThemeOptions } from './model';
import { Change, InsertChange, isLibrary, updateWorkspace, WorkspaceDefinition } from '../../utils';
import { ThemeOptionsEnum } from './theme-options.enum';
import { import {
addImportToModule, addImportToModule,
Change, findNodes,
InsertChange, getDecoratorMetadata,
isLibrary, getMetadataField,
updateWorkspace, } from '../../utils/angular/ast-utils';
WorkspaceDefinition,
} from '../../utils';
import { ThemeOptionsEnum } from './theme-options.enum';
import { findNodes, getDecoratorMetadata, getMetadataField } from '../../utils/angular/ast-utils';
export default function (_options: ChangeThemeOptions): Rule { export default function (_options: ChangeThemeOptions): Rule {
return async (host: Tree) => { return async () => {
const targetThemeName = _options.name; const targetThemeName = _options.name;
const selectedProject = _options.targetProject; const selectedProject = _options.targetProject;
if (!targetThemeName) { if (!targetThemeName) {
@ -27,9 +25,7 @@ export default function (_options: ChangeThemeOptions): Rule {
updateWorkspace(storedWorkspace => { updateWorkspace(storedWorkspace => {
updateProjectStyle(selectedProject, storedWorkspace, targetThemeName); updateProjectStyle(selectedProject, storedWorkspace, targetThemeName);
}), }),
updateWorkspace(storedWorkspace => { updateAppModule(selectedProject, targetThemeName),
updateAppModule(host, selectedProject, storedWorkspace, targetThemeName);
}),
]); ]);
}; };
} }
@ -62,114 +58,136 @@ function updateProjectStyle(
targetOption.styles = [...newStyles, ...sanitizedStyles] as JsonArray; targetOption.styles = [...newStyles, ...sanitizedStyles] as JsonArray;
} }
function updateAppModule( function updateAppModule(selectedProject: string, targetThemeName: ThemeOptionsEnum): Rule {
host: Tree, return (host: Tree) => {
projectName: string, const angularJSON = host.read('angular.json');
workspace: WorkspaceDefinition, if (!angularJSON) {
targetThemeName: ThemeOptionsEnum, throw new SchematicsException('The angular.json does not found');
) { }
const selectedTheme = importMap.get(targetThemeName);
if (!selectedTheme) {
throw new SchematicsException('The theme does not found');
}
const project = workspace.projects.get(projectName);
const appModulePath = `${project?.sourceRoot}/app/app.module.ts`;
const text = host.read(appModulePath);
if (!text) {
throw new SchematicsException('The app module does not found');
}
const sourceText = text.toString('utf-8');
const source = ts.createSourceFile(
appModulePath,
sourceText,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TS,
);
const recorder = host.beginUpdate(appModulePath);
const impMap = Array.from(importMap.values()) const workspace = JSON.parse(angularJSON.toString());
.filter(f => f !== importMap.get(targetThemeName)) const project = workspace.projects[selectedProject];
.reduce((acc, val) => [...acc, ...val], []);
removeImportPath(source, recorder, impMap); if (!project || !project.sourceRoot) {
removeImportFromNgModuleMetadata(source, recorder, impMap); throw new SchematicsException('The target project does not found');
}
insertImports(selectedTheme, source, appModulePath, recorder); const appModulePath = project.sourceRoot + '/app/app.module.ts';
host.commitUpdate(recorder); return chain([
return host; removeImportPath(appModulePath, targetThemeName),
removeImportFromNgModuleMetadata(appModulePath, targetThemeName),
insertImports(appModulePath, targetThemeName),
]);
};
} }
function removeImportPath( function removeImportPath(appModulePath: string, selectedTheme: ThemeOptionsEnum): Rule {
source: ts.SourceFile, return (host: Tree) => {
recorder: UpdateRecorder, const recorder = host.beginUpdate(appModulePath);
arr: ImportDefinition[], const sourceText = host.read(appModulePath)?.toString('utf-8');
) { const source = ts.createSourceFile(
const nodes = findNodes(source, ts.isImportDeclaration); appModulePath,
const filteredNodes = nodes.filter(n => arr.some(f => n.getFullText().match(f.path))); sourceText!,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TS,
);
const impMap = Array.from(importMap.values())
.filter(f => f !== importMap.get(selectedTheme))
.reduce((acc, val) => [...acc, ...val], []);
const nodes = findNodes(source, ts.isImportDeclaration);
const filteredNodes = nodes.filter(n => impMap.some(f => n.getFullText().match(f.path)));
if (!filteredNodes || filteredNodes.length < 1) {
return;
}
if (!filteredNodes || filteredNodes.length < 1) { filteredNodes.map(importPath =>
return; recorder.remove(importPath.getStart(), importPath.getWidth() + 1),
} );
filteredNodes.map(importPath => host.commitUpdate(recorder);
recorder.remove(importPath.getStart(), importPath.getWidth() + 1), return host;
); };
} }
function removeImportFromNgModuleMetadata( function removeImportFromNgModuleMetadata(
source: ts.SourceFile, appModulePath: string,
recorder: UpdateRecorder, selectedTheme: ThemeOptionsEnum,
arr: ImportDefinition[], ): Rule {
) { return (host: Tree) => {
const node = getDecoratorMetadata(source, 'NgModule', '@angular/core')[0] || {}; const recorder = host.beginUpdate(appModulePath);
if (!node) { const sourceText = host.read(appModulePath)?.toString('utf-8');
throw new SchematicsException('The app module does not found'); const source = ts.createSourceFile(
} appModulePath,
sourceText!,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TS,
);
const impMap = Array.from(importMap.values())
.filter(f => f !== importMap.get(selectedTheme))
.reduce((acc, val) => [...acc, ...val], []);
const matchingProperties = getMetadataField(node as ts.ObjectLiteralExpression, 'imports'); const node = getDecoratorMetadata(source, 'NgModule', '@angular/core')[0] || {};
if (!node) {
throw new SchematicsException('The app module does not found');
}
const assignment = matchingProperties[0] as ts.PropertyAssignment; const matchingProperties = getMetadataField(node as ts.ObjectLiteralExpression, 'imports');
const assignmentInit = assignment.initializer as ts.ArrayLiteralExpression;
const elements = assignmentInit.elements; const assignment = matchingProperties[0] as ts.PropertyAssignment;
if (!elements || elements.length < 1) { const assignmentInit = assignment.initializer as ts.ArrayLiteralExpression;
return;
}
const filteredElements = elements.filter(f => arr.some(s => f.getText().match(s.importName))); const elements = assignmentInit.elements;
if (!filteredElements || filteredElements.length < 1) { if (!elements || elements.length < 1) {
return; return;
} }
const filteredElements = elements.filter(f =>
impMap.some(s => f.getText().match(s.importName)),
);
if (!filteredElements || filteredElements.length < 1) {
return;
}
filteredElements.map(willRemoveModule => filteredElements.map(willRemoveModule =>
recorder.remove(willRemoveModule.getStart(), willRemoveModule.getWidth() + 1), recorder.remove(willRemoveModule.getStart(), willRemoveModule.getWidth() + 1),
); );
host.commitUpdate(recorder);
return host;
};
} }
function insertImports( function insertImports(appModulePath: string, selectedTheme: ThemeOptionsEnum): Rule {
selectedTheme: ImportDefinition[], return (host: Tree) => {
source: ts.SourceFile, const recorder = host.beginUpdate(appModulePath);
appModulePath: string, const sourceText = host.read(appModulePath)?.toString('utf-8');
recorder: UpdateRecorder, const source = ts.createSourceFile(
) { appModulePath,
const changes: Change[] = []; sourceText!,
selectedTheme.map(({ importName, path }) => ts.ScriptTarget.Latest,
changes.push(...addImportToModule(source, appModulePath, importName, path)), true,
); ts.ScriptKind.TS,
);
if (changes.length > 0) { const selected = importMap.get(selectedTheme);
for (const change of changes) {
if (change instanceof InsertChange) { const changes: Change[] = [];
recorder.insertLeft(change.pos, change.toAdd); selected!.map(({ importName, path }) =>
changes.push(...addImportToModule(source, appModulePath, importName, path)),
);
if (changes.length > 0) {
for (const change of changes) {
if (change instanceof InsertChange) {
recorder.insertLeft(change.pos, change.toAdd);
}
} }
} }
} host.commitUpdate(recorder);
return host;
};
} }
export function getProjectTargetOptions( export function getProjectTargetOptions(

5
npm/ng-packs/packages/schematics/src/utils/angular/ast-utils.ts

@ -441,12 +441,11 @@ export function addSymbolToNgModuleMetadata(
const text = expresssion.getFullText(source); const text = expresssion.getFullText(source);
const matches = text.match(/^(\r?\n)(\s*)/); const matches = text.match(/^(\r?\n)(\s*)/);
if (matches) { if (matches) {
toInsert = `${matches[1]}${tags.indentBy(matches[2].length)`${symbolName},`}`; toInsert = `,${matches[1]}${tags.indentBy(matches[2].length)`${symbolName}`}`;
} else { } else {
toInsert = `${symbolName},`; toInsert = `, ${symbolName}`;
} }
} }
if (importPath !== null) { if (importPath !== null) {
return [ return [
new InsertChange(ngModulePath, position, toInsert), new InsertChange(ngModulePath, position, toInsert),

Loading…
Cancel
Save