feat(schematics): add --routing to the lib schematic
This commit is contained in:
parent
1cadd19752
commit
409bfff878
@ -43,4 +43,30 @@ describe('Nrwl Workspace', () => {
|
|||||||
},
|
},
|
||||||
100000
|
100000
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
'should support router config generation (lazy)',
|
||||||
|
() => {
|
||||||
|
ngNew('--collection=@nrwl/schematics --npmScope=nrwl');
|
||||||
|
copyMissingPackages();
|
||||||
|
newApp('myapp --routing');
|
||||||
|
newLib('mylib --ngmodule --routing --lazy --parentModule=apps/myapp/src/app/app.module.ts');
|
||||||
|
|
||||||
|
runCLI('build --aot');
|
||||||
|
},
|
||||||
|
100000
|
||||||
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
'should support router config generation (eager)',
|
||||||
|
() => {
|
||||||
|
ngNew('--collection=@nrwl/schematics --npmScope=nrwl');
|
||||||
|
copyMissingPackages();
|
||||||
|
newApp('myapp --routing');
|
||||||
|
newLib('mylib --ngmodule --routing --parentModule=apps/myapp/src/app/app.module.ts');
|
||||||
|
|
||||||
|
runCLI('build --aot');
|
||||||
|
},
|
||||||
|
100000
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -82,6 +82,20 @@ function addAppToAngularCliJson(options: Schema): Rule {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addRouterRootConfiguration(path: string): Rule {
|
||||||
|
return (host: Tree) => {
|
||||||
|
const modulePath = `${path}/app/app.module.ts`;
|
||||||
|
const moduleSource = host.read(modulePath)!.toString('utf-8');
|
||||||
|
const sourceFile = ts.createSourceFile(modulePath, moduleSource, ts.ScriptTarget.Latest, true);
|
||||||
|
insert(host, modulePath, [
|
||||||
|
insertImport(sourceFile, modulePath, 'RouterModule', '@angular/router'),
|
||||||
|
...addImportToModule(sourceFile, modulePath, `RouterModule.forRoot([], {initialNavigation: 'enabled'})`)
|
||||||
|
]);
|
||||||
|
return host;
|
||||||
|
// add onSameUrlNavigation: 'reload'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default function(schema: Schema): Rule {
|
export default function(schema: Schema): Rule {
|
||||||
const options = { ...schema, name: toFileName(schema.name) };
|
const options = { ...schema, name: toFileName(schema.name) };
|
||||||
const templateSource = apply(url('./files'), [
|
const templateSource = apply(url('./files'), [
|
||||||
@ -95,7 +109,7 @@ export default function(schema: Schema): Rule {
|
|||||||
name: 'app',
|
name: 'app',
|
||||||
commonModule: false,
|
commonModule: false,
|
||||||
flat: true,
|
flat: true,
|
||||||
routing: options.routing,
|
routing: false,
|
||||||
sourceDir: fullPath(options),
|
sourceDir: fullPath(options),
|
||||||
spec: false
|
spec: false
|
||||||
}),
|
}),
|
||||||
@ -122,7 +136,8 @@ export default function(schema: Schema): Rule {
|
|||||||
),
|
),
|
||||||
addBootstrap(fullPath(options)),
|
addBootstrap(fullPath(options)),
|
||||||
addNxModule(fullPath(options)),
|
addNxModule(fullPath(options)),
|
||||||
addAppToAngularCliJson(options)
|
addAppToAngularCliJson(options),
|
||||||
|
options.routing ? addRouterRootConfiguration(fullPath(options)) : noop()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -49,6 +49,9 @@ describe('application', () => {
|
|||||||
it('should set right npmScope', () => {
|
it('should set right npmScope', () => {
|
||||||
const tree = schematicRunner.runSchematic('application', { name: 'myApp', directory: 'my-app' }, appTree);
|
const tree = schematicRunner.runSchematic('application', { name: 'myApp', directory: 'my-app' }, appTree);
|
||||||
|
|
||||||
|
const angularCliJson = JSON.parse(getFileContent(tree, '/my-app/.angular-cli.json'));
|
||||||
|
expect(angularCliJson.project.npmScope).toEqual('myApp');
|
||||||
|
|
||||||
const tsconfigJson = JSON.parse(getFileContent(tree, '/my-app/tsconfig.json'));
|
const tsconfigJson = JSON.parse(getFileContent(tree, '/my-app/tsconfig.json'));
|
||||||
expect(tsconfigJson.compilerOptions.paths).toEqual({ '@myApp/*': ['libs/*'] });
|
expect(tsconfigJson.compilerOptions.paths).toEqual({ '@myApp/*': ['libs/*'] });
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
"project": {
|
"project": {
|
||||||
"name": "<%= utils.dasherize(name) %>"
|
"name": "<%= utils.dasherize(name) %>",
|
||||||
|
"npmScope": "<%= npmScope %>"
|
||||||
},
|
},
|
||||||
"apps": [],
|
"apps": [],
|
||||||
"e2e": {
|
"e2e": {
|
||||||
|
|||||||
@ -5,24 +5,23 @@ import {
|
|||||||
externalSchematic,
|
externalSchematic,
|
||||||
mergeWith,
|
mergeWith,
|
||||||
move,
|
move,
|
||||||
|
noop,
|
||||||
Rule,
|
Rule,
|
||||||
template,
|
template,
|
||||||
Tree,
|
Tree,
|
||||||
url
|
url
|
||||||
} from '@angular-devkit/schematics';
|
} from '@angular-devkit/schematics';
|
||||||
import { Schema } from './schema';
|
import { Schema } from './schema';
|
||||||
import { names, toFileName } from '@nrwl/schematics';
|
import { addImportToModule, insert, names, toClassName, toFileName, toPropertyName } from '@nrwl/schematics';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { serializeJson, addApp } from '../utility/fileutils';
|
import { serializeJson, addApp, cliConfig } from '../utility/fileutils';
|
||||||
|
import { insertImport } from '@schematics/angular/utility/route-utils';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
import { addGlobal, addReexport, addRoute } from '../utility/ast-utils';
|
||||||
|
|
||||||
function addLibToAngularCliJson(options: Schema): Rule {
|
function addLibToAngularCliJson(options: Schema): Rule {
|
||||||
return (host: Tree) => {
|
return (host: Tree) => {
|
||||||
if (!host.exists('.angular-cli.json')) {
|
const json = cliConfig(host);
|
||||||
throw new Error('Missing .angular-cli.json');
|
|
||||||
}
|
|
||||||
|
|
||||||
const sourceText = host.read('.angular-cli.json')!.toString('utf-8');
|
|
||||||
const json = JSON.parse(sourceText);
|
|
||||||
json.apps = addApp(json.apps, {
|
json.apps = addApp(json.apps, {
|
||||||
name: options.name,
|
name: options.name,
|
||||||
root: fullPath(options),
|
root: fullPath(options),
|
||||||
@ -35,11 +34,116 @@ function addLibToAngularCliJson(options: Schema): Rule {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addLazyLoadedRouterConfiguration(modulePath: string): Rule {
|
||||||
|
return (host: Tree) => {
|
||||||
|
const moduleSource = host.read(modulePath)!.toString('utf-8');
|
||||||
|
const sourceFile = ts.createSourceFile(modulePath, moduleSource, ts.ScriptTarget.Latest, true);
|
||||||
|
insert(host, modulePath, [
|
||||||
|
insertImport(sourceFile, modulePath, 'RouterModule', '@angular/router'),
|
||||||
|
...addImportToModule(
|
||||||
|
sourceFile,
|
||||||
|
modulePath,
|
||||||
|
`
|
||||||
|
RouterModule.forChild([
|
||||||
|
/* {path: '', pathMatch: 'full', component: InsertYourComponentHere} */
|
||||||
|
]) `
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
return host;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function addRouterConfiguration(
|
||||||
|
schema: Schema,
|
||||||
|
indexFilePath: string,
|
||||||
|
moduleFileName: string,
|
||||||
|
modulePath: string
|
||||||
|
): Rule {
|
||||||
|
return (host: Tree) => {
|
||||||
|
const indexSource = host.read(indexFilePath)!.toString('utf-8');
|
||||||
|
const indexSourceFile = ts.createSourceFile(indexFilePath, indexSource, ts.ScriptTarget.Latest, true);
|
||||||
|
const moduleSource = host.read(modulePath)!.toString('utf-8');
|
||||||
|
const moduleSourceFile = ts.createSourceFile(modulePath, moduleSource, ts.ScriptTarget.Latest, true);
|
||||||
|
const constName = `${toPropertyName(schema.name)}Routes`;
|
||||||
|
|
||||||
|
insert(host, modulePath, [
|
||||||
|
insertImport(moduleSourceFile, modulePath, 'RouterModule, Route', '@angular/router'),
|
||||||
|
...addImportToModule(moduleSourceFile, modulePath, `RouterModule`),
|
||||||
|
...addGlobal(moduleSourceFile, modulePath, `export const ${constName}: Route[] = [];`)
|
||||||
|
]);
|
||||||
|
insert(host, indexFilePath, [...addReexport(indexSourceFile, indexFilePath, moduleFileName, constName)]);
|
||||||
|
return host;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLoadChildren(schema: Schema): Rule {
|
||||||
|
return (host: Tree) => {
|
||||||
|
const json = cliConfig(host);
|
||||||
|
|
||||||
|
const moduleSource = host.read(schema.parentModule)!.toString('utf-8');
|
||||||
|
const sourceFile = ts.createSourceFile(schema.parentModule, moduleSource, ts.ScriptTarget.Latest, true);
|
||||||
|
|
||||||
|
const loadChildren = `@${json.project.npmScope}/${toFileName(schema.name)}#${toClassName(schema.name)}Module`;
|
||||||
|
insert(host, schema.parentModule, [
|
||||||
|
...addRoute(
|
||||||
|
schema.parentModule,
|
||||||
|
sourceFile,
|
||||||
|
`{path: '${toFileName(schema.name)}', loadChildren: '${loadChildren}'}`
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
return host;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function addChildren(schema: Schema): Rule {
|
||||||
|
return (host: Tree) => {
|
||||||
|
const json = cliConfig(host);
|
||||||
|
|
||||||
|
const moduleSource = host.read(schema.parentModule)!.toString('utf-8');
|
||||||
|
const sourceFile = ts.createSourceFile(schema.parentModule, moduleSource, ts.ScriptTarget.Latest, true);
|
||||||
|
const constName = `${toPropertyName(schema.name)}Routes`;
|
||||||
|
const importPath = `@${json.project.npmScope}/${toFileName(schema.name)}`;
|
||||||
|
|
||||||
|
insert(host, schema.parentModule, [
|
||||||
|
insertImport(sourceFile, schema.parentModule, constName, importPath),
|
||||||
|
...addRoute(schema.parentModule, sourceFile, `{path: '${toFileName(schema.name)}', children: ${constName}}`)
|
||||||
|
]);
|
||||||
|
return host;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTsLint(schema: Schema): Rule {
|
||||||
|
return (host: Tree) => {
|
||||||
|
const tsLint = JSON.parse(host.read('tslint.json')!.toString('utf-8'));
|
||||||
|
if (
|
||||||
|
tsLint['rules'] &&
|
||||||
|
tsLint['rules']['nx-enforce-module-boundaries'] &&
|
||||||
|
tsLint['rules']['nx-enforce-module-boundaries'][1] &&
|
||||||
|
tsLint['rules']['nx-enforce-module-boundaries'][1]['lazyLoad']
|
||||||
|
) {
|
||||||
|
tsLint['rules']['nx-enforce-module-boundaries'][1]['lazyLoad'].push(toFileName(schema.name));
|
||||||
|
host.overwrite('tslint.json', serializeJson(tsLint));
|
||||||
|
}
|
||||||
|
return host;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default function(schema: Schema): Rule {
|
export default function(schema: Schema): Rule {
|
||||||
const options = { ...schema, name: toFileName(schema.name) };
|
const options = { ...schema, name: toFileName(schema.name) };
|
||||||
const fullPath = path.join('libs', toFileName(options.name), options.sourceDir);
|
const fullPath = path.join('libs', toFileName(options.name), options.sourceDir);
|
||||||
|
const moduleFileName = `${toFileName(schema.name)}.module`;
|
||||||
|
const modulePath = path.join(fullPath, `${moduleFileName}.ts`);
|
||||||
|
const indexFile = path.join('libs', toFileName(options.name), 'index.ts');
|
||||||
|
|
||||||
const templateSource = apply(url(options.ngmodule ? './ngfiles' : './files'), [
|
if (schema.routing && schema.nomodule) {
|
||||||
|
throw new Error(`nomodule and routing cannot be used together`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!schema.routing && schema.lazy) {
|
||||||
|
throw new Error(`routing must be set`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const templateSource = apply(url(options.nomodule ? './files' : './ngfiles'), [
|
||||||
template({
|
template({
|
||||||
...names(options.name),
|
...names(options.name),
|
||||||
dot: '.',
|
dot: '.',
|
||||||
@ -48,7 +152,16 @@ export default function(schema: Schema): Rule {
|
|||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return chain([branchAndMerge(chain([mergeWith(templateSource)])), addLibToAngularCliJson(options)]);
|
return chain([
|
||||||
|
branchAndMerge(chain([mergeWith(templateSource)])),
|
||||||
|
addLibToAngularCliJson(options),
|
||||||
|
schema.routing && schema.lazy ? addLazyLoadedRouterConfiguration(modulePath) : noop(),
|
||||||
|
schema.routing && schema.lazy ? updateTsLint(schema) : noop(),
|
||||||
|
schema.routing && schema.lazy && schema.parentModule ? addLoadChildren(schema) : noop(),
|
||||||
|
|
||||||
|
schema.routing && !schema.lazy ? addRouterConfiguration(schema, indexFile, moduleFileName, modulePath) : noop(),
|
||||||
|
schema.routing && !schema.lazy && schema.parentModule ? addChildren(schema) : noop()
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fullPath(options: Schema) {
|
function fullPath(options: Schema) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
|
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { Tree, VirtualTree } from '@angular-devkit/schematics';
|
import { Tree, VirtualTree } from '@angular-devkit/schematics';
|
||||||
import { createEmptyWorkspace } from '../testing-utils';
|
import { createApp, createEmptyWorkspace } from '../testing-utils';
|
||||||
import { getFileContent } from '@schematics/angular/utility/test';
|
import { getFileContent } from '@schematics/angular/utility/test';
|
||||||
|
|
||||||
describe('lib', () => {
|
describe('lib', () => {
|
||||||
@ -12,6 +12,8 @@ describe('lib', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
appTree = new VirtualTree();
|
appTree = new VirtualTree();
|
||||||
appTree = createEmptyWorkspace(appTree);
|
appTree = createEmptyWorkspace(appTree);
|
||||||
|
|
||||||
|
schematicRunner.logger.subscribe(s => console.log(s));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update angular-cli.json', () => {
|
it('should update angular-cli.json', () => {
|
||||||
@ -28,18 +30,100 @@ describe('lib', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should generate files', () => {
|
it('should generate files', () => {
|
||||||
const tree = schematicRunner.runSchematic('lib', { name: 'myLib' }, appTree);
|
const tree = schematicRunner.runSchematic('lib', { name: 'myLib', nomodule: true }, appTree);
|
||||||
expect(tree.exists('libs/my-lib/src/my-lib.ts')).toBeTruthy();
|
expect(tree.exists('libs/my-lib/src/my-lib.ts')).toBeTruthy();
|
||||||
expect(tree.exists('libs/my-lib/src/my-lib.spec.ts')).toBeTruthy();
|
expect(tree.exists('libs/my-lib/src/my-lib.spec.ts')).toBeTruthy();
|
||||||
expect(tree.exists('libs/my-lib/index.ts')).toBeTruthy();
|
expect(tree.exists('libs/my-lib/index.ts')).toBeTruthy();
|
||||||
expect(getFileContent(tree, 'libs/my-lib/src/my-lib.ts')).toContain('class MyLib');
|
expect(getFileContent(tree, 'libs/my-lib/src/my-lib.ts')).toContain('class MyLib');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate files (--ngmodule)', () => {
|
it('should generate files', () => {
|
||||||
const tree = schematicRunner.runSchematic('lib', { name: 'myLib', ngmodule: true }, appTree);
|
const tree = schematicRunner.runSchematic('lib', { name: 'myLib' }, appTree);
|
||||||
expect(tree.exists('libs/my-lib/src/my-lib.module.ts')).toBeTruthy();
|
expect(tree.exists('libs/my-lib/src/my-lib.module.ts')).toBeTruthy();
|
||||||
expect(tree.exists('libs/my-lib/src/my-lib.module.spec.ts')).toBeTruthy();
|
expect(tree.exists('libs/my-lib/src/my-lib.module.spec.ts')).toBeTruthy();
|
||||||
expect(tree.exists('libs/my-lib/index.ts')).toBeTruthy();
|
expect(tree.exists('libs/my-lib/index.ts')).toBeTruthy();
|
||||||
expect(getFileContent(tree, 'libs/my-lib/src/my-lib.module.ts')).toContain('class MyLibModule');
|
expect(getFileContent(tree, 'libs/my-lib/src/my-lib.module.ts')).toContain('class MyLibModule');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('router', () => {
|
||||||
|
it('should error when routing is set with nomodule = true', () => {
|
||||||
|
expect(() =>
|
||||||
|
schematicRunner.runSchematic('lib', { name: 'myLib', nomodule: true, routing: true }, appTree)
|
||||||
|
).toThrow('nomodule and routing cannot be used together');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error when lazy is set without routing', () => {
|
||||||
|
expect(() => schematicRunner.runSchematic('lib', { name: 'myLib', lazy: true }, appTree)).toThrow(
|
||||||
|
'routing must be set'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('lazy', () => {
|
||||||
|
it('should add RouterModule.forChild', () => {
|
||||||
|
const tree = schematicRunner.runSchematic('lib', { name: 'myLib', routing: true, lazy: true }, appTree);
|
||||||
|
expect(tree.exists('libs/my-lib/src/my-lib.module.ts')).toBeTruthy();
|
||||||
|
expect(getFileContent(tree, 'libs/my-lib/src/my-lib.module.ts')).toContain('RouterModule.forChild');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the parent module', () => {
|
||||||
|
appTree = createApp(appTree, 'myapp');
|
||||||
|
const tree = schematicRunner.runSchematic(
|
||||||
|
'lib',
|
||||||
|
{ name: 'myLib', routing: true, lazy: true, parentModule: 'apps/myapp/src/app/app.module.ts' },
|
||||||
|
appTree
|
||||||
|
);
|
||||||
|
expect(getFileContent(tree, 'apps/myapp/src/app/app.module.ts')).toContain(
|
||||||
|
`RouterModule.forRoot([{path: 'my-lib', loadChildren: '@proj/my-lib#MyLibModule'}])`
|
||||||
|
);
|
||||||
|
|
||||||
|
const tree2 = schematicRunner.runSchematic(
|
||||||
|
'lib',
|
||||||
|
{ name: 'myLib2', routing: true, lazy: true, parentModule: 'apps/myapp/src/app/app.module.ts' },
|
||||||
|
tree
|
||||||
|
);
|
||||||
|
expect(getFileContent(tree2, 'apps/myapp/src/app/app.module.ts')).toContain(
|
||||||
|
`RouterModule.forRoot([{path: 'my-lib', loadChildren: '@proj/my-lib#MyLibModule'}, {path: 'my-lib2', loadChildren: '@proj/my-lib2#MyLib2Module'}])`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should register the module as lazy loaded in tslint.json', () => {
|
||||||
|
const tree = schematicRunner.runSchematic('lib', { name: 'myLib', routing: true, lazy: true }, appTree);
|
||||||
|
const tslint = JSON.parse(getFileContent(tree, 'tslint.json'));
|
||||||
|
expect(tslint['rules']['nx-enforce-module-boundaries'][1]['lazyLoad']).toEqual(['my-lib']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('eager', () => {
|
||||||
|
it('should add RouterModule and define an array of routes', () => {
|
||||||
|
const tree = schematicRunner.runSchematic('lib', { name: 'myLib', routing: true }, appTree);
|
||||||
|
expect(tree.exists('libs/my-lib/src/my-lib.module.ts')).toBeTruthy();
|
||||||
|
expect(getFileContent(tree, 'libs/my-lib/src/my-lib.module.ts')).toContain('RouterModule]');
|
||||||
|
expect(getFileContent(tree, 'libs/my-lib/src/my-lib.module.ts')).toContain('const myLibRoutes: Route[] = ');
|
||||||
|
expect(getFileContent(tree, 'libs/my-lib/index.ts')).toContain('myLibRoutes');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the parent module', () => {
|
||||||
|
appTree = createApp(appTree, 'myapp');
|
||||||
|
const tree = schematicRunner.runSchematic(
|
||||||
|
'lib',
|
||||||
|
{ name: 'myLib', routing: true, parentModule: 'apps/myapp/src/app/app.module.ts' },
|
||||||
|
appTree
|
||||||
|
);
|
||||||
|
expect(getFileContent(tree, 'apps/myapp/src/app/app.module.ts')).toContain(
|
||||||
|
`RouterModule.forRoot([{path: 'my-lib', children: myLibRoutes}])`
|
||||||
|
);
|
||||||
|
|
||||||
|
const tree2 = schematicRunner.runSchematic(
|
||||||
|
'lib',
|
||||||
|
{ name: 'myLib2', routing: true, parentModule: 'apps/myapp/src/app/app.module.ts' },
|
||||||
|
tree
|
||||||
|
);
|
||||||
|
expect(getFileContent(tree2, 'apps/myapp/src/app/app.module.ts')).toContain(
|
||||||
|
`RouterModule.forRoot([{path: 'my-lib', children: myLibRoutes}, {path: 'my-lib2', children: myLib2Routes}])`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// should throw when no --ngmodule
|
||||||
});
|
});
|
||||||
|
|||||||
7
packages/schematics/src/lib/schema.d.ts
vendored
7
packages/schematics/src/lib/schema.d.ts
vendored
@ -1,10 +1,13 @@
|
|||||||
export interface Schema {
|
export interface Schema {
|
||||||
name: string;
|
name: string;
|
||||||
sourceDir?: string;
|
sourceDir?: string;
|
||||||
ngmodule: boolean;
|
nomodule: boolean;
|
||||||
|
|
||||||
routing?: boolean;
|
|
||||||
spec?: boolean;
|
spec?: boolean;
|
||||||
flat?: boolean;
|
flat?: boolean;
|
||||||
commonModule?: boolean;
|
commonModule?: boolean;
|
||||||
|
|
||||||
|
routing?: boolean;
|
||||||
|
lazy?: boolean;
|
||||||
|
parentModule?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,9 +13,24 @@
|
|||||||
"default": "src",
|
"default": "src",
|
||||||
"alias": "sd"
|
"alias": "sd"
|
||||||
},
|
},
|
||||||
"ngmodule": {
|
"nomodule": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false
|
"default": false,
|
||||||
|
"description": "Generate a simple TS library when set to true."
|
||||||
|
},
|
||||||
|
"routing": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Add router configuration. See lazy for more information."
|
||||||
|
},
|
||||||
|
"lazy": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Add RouterModule.forChild when set to true, and a simple array of routes when set to false."
|
||||||
|
},
|
||||||
|
"parentModule": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Update the router configuration of the parent module using loadChildren or children, depending on what `lazy` is set to."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
@ -3,6 +3,20 @@ import { Tree } from '@angular-devkit/schematics';
|
|||||||
export function createEmptyWorkspace(tree: Tree): Tree {
|
export function createEmptyWorkspace(tree: Tree): Tree {
|
||||||
tree.create('/.angular-cli.json', JSON.stringify({}));
|
tree.create('/.angular-cli.json', JSON.stringify({}));
|
||||||
tree.create('/package.json', JSON.stringify({}));
|
tree.create('/package.json', JSON.stringify({}));
|
||||||
|
tree.create(
|
||||||
|
'/tslint.json',
|
||||||
|
JSON.stringify({
|
||||||
|
rules: {
|
||||||
|
'nx-enforce-module-boundaries': [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
npmScope: '<%= npmScope %>',
|
||||||
|
lazyLoad: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -12,9 +26,10 @@ export function createApp(tree: Tree, appName: string): Tree {
|
|||||||
`
|
`
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [BrowserModule],
|
imports: [BrowserModule, RouterModule.forRoot([])],
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
@ -42,6 +57,10 @@ export function createApp(tree: Tree, appName: string): Tree {
|
|||||||
tree.overwrite(
|
tree.overwrite(
|
||||||
'/.angular-cli.json',
|
'/.angular-cli.json',
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
project: {
|
||||||
|
name: 'proj',
|
||||||
|
npmScope: 'proj'
|
||||||
|
},
|
||||||
apps: [
|
apps: [
|
||||||
{
|
{
|
||||||
name: appName,
|
name: appName,
|
||||||
|
|||||||
@ -234,6 +234,25 @@ export function addImportToModule(source: ts.SourceFile, modulePath: string, sym
|
|||||||
return _addSymbolToNgModuleMetadata(source, modulePath, 'imports', symbolName);
|
return _addSymbolToNgModuleMetadata(source, modulePath, 'imports', symbolName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addReexport(
|
||||||
|
source: ts.SourceFile,
|
||||||
|
modulePath: string,
|
||||||
|
reexportedFileName: string,
|
||||||
|
token: string
|
||||||
|
): Change[] {
|
||||||
|
const allExports = findNodes(source, ts.SyntaxKind.ExportDeclaration);
|
||||||
|
if (allExports.length > 0) {
|
||||||
|
const m = allExports.filter(
|
||||||
|
(e: ts.ExportDeclaration) => e.moduleSpecifier.getText(source).indexOf(reexportedFileName) > -1
|
||||||
|
);
|
||||||
|
if (m.length > 0) {
|
||||||
|
const mm: ts.ExportDeclaration = <any>m[0];
|
||||||
|
return [new InsertChange(modulePath, mm.exportClause.end - 1, `, ${token} `)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
export function getBootstrapComponent(source: ts.SourceFile, moduleClassName: string): string {
|
export function getBootstrapComponent(source: ts.SourceFile, moduleClassName: string): string {
|
||||||
const bootstrap = getMatchingProperty(source, 'bootstrap');
|
const bootstrap = getMatchingProperty(source, 'bootstrap');
|
||||||
if (!bootstrap) {
|
if (!bootstrap) {
|
||||||
@ -275,6 +294,39 @@ function getMatchingProperty(source: ts.SourceFile, property: string): ts.Object
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addRoute(ngModulePath: string, source: ts.SourceFile, route: string): Change[] {
|
||||||
|
const routes = getListOfRoutes(source);
|
||||||
|
if (!routes) return [];
|
||||||
|
|
||||||
|
if (routes.hasTrailingComma || routes.length === 0) {
|
||||||
|
return [new InsertChange(ngModulePath, routes.end, route)];
|
||||||
|
} else {
|
||||||
|
return [new InsertChange(ngModulePath, routes.end, `, ${route}`)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getListOfRoutes(source: ts.SourceFile): ts.NodeArray<ts.Expression> {
|
||||||
|
const imports: any = getMatchingProperty(source, 'imports');
|
||||||
|
|
||||||
|
if (imports.initializer.kind === ts.SyntaxKind.ArrayLiteralExpression) {
|
||||||
|
const a = imports.initializer as ts.ArrayLiteralExpression;
|
||||||
|
|
||||||
|
for (let e of a.elements) {
|
||||||
|
if (e.kind === 181) {
|
||||||
|
const ee = e as ts.CallExpression;
|
||||||
|
const text = ee.expression.getText(source);
|
||||||
|
if ((text === 'RouterModule.forRoot' || text === 'RouterModule.forChild') && ee.arguments.length > 0) {
|
||||||
|
const routes = ee.arguments[0];
|
||||||
|
if (routes.kind === ts.SyntaxKind.ArrayLiteralExpression) {
|
||||||
|
return (routes as ts.ArrayLiteralExpression).elements;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export function getImport(
|
export function getImport(
|
||||||
source: ts.SourceFile,
|
source: ts.SourceFile,
|
||||||
predicate: (a: any) => boolean
|
predicate: (a: any) => boolean
|
||||||
@ -306,6 +358,16 @@ export function addEntryComponents(source: ts.SourceFile, modulePath: string, sy
|
|||||||
return _addSymbolToNgModuleMetadata(source, modulePath, 'entryComponents', symbolName);
|
return _addSymbolToNgModuleMetadata(source, modulePath, 'entryComponents', symbolName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addGlobal(source: ts.SourceFile, modulePath: string, statement: string): Change[] {
|
||||||
|
const allImports = findNodes(source, ts.SyntaxKind.ImportDeclaration);
|
||||||
|
if (allImports.length > 0) {
|
||||||
|
const lastImport = allImports[allImports.length - 1];
|
||||||
|
return [new InsertChange(modulePath, lastImport.end + 1, `\n${statement}\n`)];
|
||||||
|
} else {
|
||||||
|
return [new InsertChange(modulePath, 0, `${statement}\n`)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function insert(host: Tree, modulePath: string, changes: Change[]) {
|
export function insert(host: Tree, modulePath: string, changes: Change[]) {
|
||||||
const recorder = host.beginUpdate(modulePath);
|
const recorder = host.beginUpdate(modulePath);
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
import { Tree } from '@angular-devkit/schematics';
|
||||||
|
|
||||||
export function updateJsonFile(path: string, callback: (a: any) => any) {
|
export function updateJsonFile(path: string, callback: (a: any) => any) {
|
||||||
const json = JSON.parse(fs.readFileSync(path, 'utf-8'));
|
const json = JSON.parse(fs.readFileSync(path, 'utf-8'));
|
||||||
@ -25,3 +26,12 @@ export function addApp(apps: any[] | undefined, newApp: any): any[] {
|
|||||||
export function serializeJson(json: any): string {
|
export function serializeJson(json: any): string {
|
||||||
return `${JSON.stringify(json, null, 2)}\n`;
|
return `${JSON.stringify(json, null, 2)}\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function cliConfig(host: Tree): any {
|
||||||
|
if (!host.exists('.angular-cli.json')) {
|
||||||
|
throw new Error('Missing .angular-cli.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceText = host.read('.angular-cli.json')!.toString('utf-8');
|
||||||
|
return JSON.parse(sourceText);
|
||||||
|
}
|
||||||
|
|||||||
@ -70,6 +70,8 @@ function updateAngularCLIJson(options: Schema) {
|
|||||||
throw new Error('Cannot find .angular-cli.json');
|
throw new Error('Cannot find .angular-cli.json');
|
||||||
}
|
}
|
||||||
const angularCliJson = JSON.parse(host.read('.angular-cli.json')!.toString('utf-8'));
|
const angularCliJson = JSON.parse(host.read('.angular-cli.json')!.toString('utf-8'));
|
||||||
|
angularCliJson.project.npmScope = npmScope(options);
|
||||||
|
|
||||||
if (angularCliJson.apps.length !== 1) {
|
if (angularCliJson.apps.length !== 1) {
|
||||||
throw new Error('Can only convert projects with one app');
|
throw new Error('Can only convert projects with one app');
|
||||||
}
|
}
|
||||||
@ -105,15 +107,13 @@ function updateAngularCLIJson(options: Schema) {
|
|||||||
|
|
||||||
function updateTsConfigsJson(options: Schema) {
|
function updateTsConfigsJson(options: Schema) {
|
||||||
return (host: Tree) => {
|
return (host: Tree) => {
|
||||||
const npmScope = options && options.npmScope ? options.npmScope : options.name;
|
updateJsonFile('tsconfig.json', json => setUpCompilerOptions(json, npmScope(options)));
|
||||||
|
|
||||||
updateJsonFile('tsconfig.json', json => setUpCompilerOptions(json, npmScope));
|
|
||||||
|
|
||||||
updateJsonFile('tsconfig.app.json', json => {
|
updateJsonFile('tsconfig.app.json', json => {
|
||||||
json['extends'] = './tsconfig.json';
|
json['extends'] = './tsconfig.json';
|
||||||
if (!json.exclude) json.exclude = [];
|
if (!json.exclude) json.exclude = [];
|
||||||
json.exclude = dedup(json.exclude.concat(['**/*.spec.ts', '**/*.e2e-spec.ts', 'node_modules', 'tmp']));
|
json.exclude = dedup(json.exclude.concat(['**/*.spec.ts', '**/*.e2e-spec.ts', 'node_modules', 'tmp']));
|
||||||
setUpCompilerOptions(json, npmScope);
|
setUpCompilerOptions(json, npmScope(options));
|
||||||
});
|
});
|
||||||
|
|
||||||
updateJsonFile('tsconfig.spec.json', json => {
|
updateJsonFile('tsconfig.spec.json', json => {
|
||||||
@ -121,14 +121,14 @@ function updateTsConfigsJson(options: Schema) {
|
|||||||
if (!json.exclude) json.exclude = [];
|
if (!json.exclude) json.exclude = [];
|
||||||
json.files = ['test.js'];
|
json.files = ['test.js'];
|
||||||
json.exclude = dedup(json.exclude.concat(['node_modules', 'tmp']));
|
json.exclude = dedup(json.exclude.concat(['node_modules', 'tmp']));
|
||||||
setUpCompilerOptions(json, npmScope);
|
setUpCompilerOptions(json, npmScope(options));
|
||||||
});
|
});
|
||||||
|
|
||||||
updateJsonFile('tsconfig.e2e.json', json => {
|
updateJsonFile('tsconfig.e2e.json', json => {
|
||||||
json['extends'] = './tsconfig.json';
|
json['extends'] = './tsconfig.json';
|
||||||
if (!json.exclude) json.exclude = [];
|
if (!json.exclude) json.exclude = [];
|
||||||
json.exclude = dedup(json.exclude.concat(['**/*.spec.ts', 'node_modules', 'tmp']));
|
json.exclude = dedup(json.exclude.concat(['**/*.spec.ts', 'node_modules', 'tmp']));
|
||||||
setUpCompilerOptions(json, npmScope);
|
setUpCompilerOptions(json, npmScope(options));
|
||||||
});
|
});
|
||||||
|
|
||||||
return host;
|
return host;
|
||||||
@ -137,18 +137,20 @@ function updateTsConfigsJson(options: Schema) {
|
|||||||
|
|
||||||
function updateTsLintJson(options: Schema) {
|
function updateTsLintJson(options: Schema) {
|
||||||
return (host: Tree) => {
|
return (host: Tree) => {
|
||||||
const npmScope = options && options.npmScope ? options.npmScope : options.name;
|
|
||||||
|
|
||||||
updateJsonFile('tslint.json', json => {
|
updateJsonFile('tslint.json', json => {
|
||||||
['no-trailing-whitespace', 'one-line', 'quotemark', 'typedef-whitespace', 'whitespace'].forEach(key => {
|
['no-trailing-whitespace', 'one-line', 'quotemark', 'typedef-whitespace', 'whitespace'].forEach(key => {
|
||||||
json[key] = undefined;
|
json[key] = undefined;
|
||||||
});
|
});
|
||||||
json['nx-enforce-module-boundaries'] = [true, { npmScope: npmScope, lazyLoad: [] }];
|
json['nx-enforce-module-boundaries'] = [true, { npmScope: npmScope(options), lazyLoad: [] }];
|
||||||
});
|
});
|
||||||
return host;
|
return host;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function npmScope(options: Schema): string {
|
||||||
|
return options && options.npmScope ? options.npmScope : options.name;
|
||||||
|
}
|
||||||
|
|
||||||
function updateProtractorConf() {
|
function updateProtractorConf() {
|
||||||
return (host: Tree) => {
|
return (host: Tree) => {
|
||||||
if (!host.exists('protractor.conf.js')) {
|
if (!host.exists('protractor.conf.js')) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user