Skip to content

Commit

Permalink
feat(react): add routing support to react app, lib, and component sch…
Browse files Browse the repository at this point in the history
…ematics

* `--routing` for app, lib, component generates component with default
  `<Route>` and `<Link>`

* `--parentRoute` for feature lib adds its routes to the specified
  component (by path)
  • Loading branch information
jaysoo authored and vsavkin committed May 31, 2019
1 parent b5d487a commit 57fe219
Show file tree
Hide file tree
Showing 20 changed files with 542 additions and 46 deletions.
6 changes: 6 additions & 0 deletions docs/api-react/schematics/application.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ Type: `boolean`

Use pascal case component file name (e.g. App.tsx)®

### routing

Type: `boolean`

Generate application with routes

### skipFormat

Default: `false`
Expand Down
6 changes: 6 additions & 0 deletions docs/api-react/schematics/component.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ Type: `string`

The name of the project (as specified in angular.json).

### routing

Type: `boolean`

Generate library with routes

### skipTests

Default: `false`
Expand Down
12 changes: 12 additions & 0 deletions docs/api-react/schematics/library.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ Type: `string`

Library name

### parentRoute

Type: `string`

Add new route to the parent component as specified by this path

### pascalCaseFiles

Default: `false`
Expand All @@ -31,6 +37,12 @@ Type: `boolean`

Use pascal case component file name (e.g. App.tsx)®

### routing

Type: `boolean`

Generate library with routes

### skipFormat

Default: `false`
Expand Down
20 changes: 20 additions & 0 deletions packages/react/src/schematics/application/application.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,4 +443,24 @@ describe('app', () => {
expect(packageJSON.dependencies['@emotion/styled']).toBeDefined();
});
});

describe('--routing', () => {
it('should add routes to the App component', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', routing: true },
appTree
);

const componentSource = tree
.read('apps/my-app/src/app/app.tsx')
.toString();

expect(componentSource).toContain('react-router-dom');
expect(componentSource).toContain('<Router>');
expect(componentSource).toContain('</Router>');
expect(componentSource).toMatch(/<Route\s*path="\/"/);
expect(componentSource).toMatch(/<Link\s*to="\/"/);
});
});
});
72 changes: 47 additions & 25 deletions packages/react/src/schematics/application/application.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { join, normalize } from '@angular-devkit/core';
import { join, normalize, Path } from '@angular-devkit/core';
import {
apply,
chain,
Expand All @@ -12,24 +12,29 @@ import {
Tree,
url
} from '@angular-devkit/schematics';
import { Schema } from './schema';
import {
formatFiles,
insert,
names,
NxJson,
offsetFromRoot,
toFileName,
updateJsonInTree
} from '@nrwl/workspace';
import { addDepsToPackageJson } from '@nrwl/workspace/src/utils/ast-utils';
import ngAdd from '../ng-add/ng-add';
import * as ts from 'typescript';

import { Schema } from './schema';
import { CSS_IN_JS_DEPENDENCIES } from '../../utils/styled';
import { addDepsToPackageJson } from '@nrwl/workspace/src/utils/ast-utils';
import { addRouter } from '../../utils/ast-utils';
import { reactRouterVersion } from '../../utils/versions';

interface NormalizedSchema extends Schema {
projectName: string;
appProjectRoot: string;
appProjectRoot: Path;
e2eProjectName: string;
e2eProjectRoot: string;
e2eProjectRoot: Path;
parsedTags: string[];
fileName: string;
styledModule: null | string;
Expand Down Expand Up @@ -63,6 +68,7 @@ export default function(schema: Schema): Rule {
})
: noop(),
addStyledModuleDependencies(options),
addRouting(options),
formatFiles(options)
]);
};
Expand Down Expand Up @@ -103,34 +109,29 @@ function addProject(options: NormalizedSchema): Rule {
builder: '@nrwl/web:build',
options: {
outputPath: join(normalize('dist'), options.appProjectRoot),
index: join(normalize(options.appProjectRoot), 'src/index.html'),
main: join(normalize(options.appProjectRoot), `src/main.tsx`),
polyfills: join(normalize(options.appProjectRoot), 'src/polyfills.ts'),
tsConfig: join(normalize(options.appProjectRoot), 'tsconfig.app.json'),
index: join(options.appProjectRoot, 'src/index.html'),
main: join(options.appProjectRoot, `src/main.tsx`),
polyfills: join(options.appProjectRoot, 'src/polyfills.ts'),
tsConfig: join(options.appProjectRoot, 'tsconfig.app.json'),
assets: [
join(normalize(options.appProjectRoot), 'src/favicon.ico'),
join(normalize(options.appProjectRoot), 'src/assets')
join(options.appProjectRoot, 'src/favicon.ico'),
join(options.appProjectRoot, 'src/assets')
],
styles: options.styledModule
? []
: [
join(
normalize(options.appProjectRoot),
`src/styles.${options.style}`
)
],
: [join(options.appProjectRoot, `src/styles.${options.style}`)],
scripts: []
},
configurations: {
production: {
fileReplacements: [
{
replace: join(
normalize(options.appProjectRoot),
options.appProjectRoot,
`src/environments/environment.ts`
),
with: join(
normalize(options.appProjectRoot),
options.appProjectRoot,
`src/environments/environment.prod.ts`
)
}
Expand Down Expand Up @@ -168,16 +169,14 @@ function addProject(options: NormalizedSchema): Rule {
architect.lint = {
builder: '@angular-devkit/build-angular:tslint',
options: {
tsConfig: [
join(normalize(options.appProjectRoot), 'tsconfig.app.json')
],
tsConfig: [join(options.appProjectRoot, 'tsconfig.app.json')],
exclude: ['**/node_modules/**']
}
};

json.projects[options.projectName] = {
root: options.appProjectRoot,
sourceRoot: join(normalize(options.appProjectRoot), 'src'),
sourceRoot: join(options.appProjectRoot, 'src'),
projectType: 'application',
schematics: {},
architect
Expand All @@ -200,6 +199,29 @@ function addStyledModuleDependencies(options: NormalizedSchema): Rule {
: noop();
}

function addRouting(options: NormalizedSchema): Rule {
return options.routing
? chain([
function addRouterToComponent(host: Tree) {
const appPath = join(
options.appProjectRoot,
`src/app/${options.fileName}.tsx`
);
const appFileContent = host.read(appPath).toString('utf-8');
const appSource = ts.createSourceFile(
appPath,
appFileContent,
ts.ScriptTarget.Latest,
true
);

insert(host, appPath, addRouter(appPath, appSource));
},
addDepsToPackageJson({ 'react-router-dom': reactRouterVersion }, {})
])
: noop();
}

function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
const appDirectory = options.directory
? `${toFileName(options.directory)}/${toFileName(options.name)}`
Expand All @@ -208,8 +230,8 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-');
const e2eProjectName = `${appProjectName}-e2e`;

const appProjectRoot = `apps/${appDirectory}`;
const e2eProjectRoot = `apps/${appDirectory}-e2e`;
const appProjectRoot = normalize(`apps/${appDirectory}`);
const e2eProjectRoot = normalize(`apps/${appDirectory}-e2e`);

const parsedTags = options.tags
? options.tags.split(',').map(s => s.trim())
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/schematics/application/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export interface Schema {
e2eTestRunner: E2eTestRunner;
pascalCaseFiles?: boolean;
classComponent?: boolean;
routing?: boolean;
}
4 changes: 4 additions & 0 deletions packages/react/src/schematics/application/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@
"type": "boolean",
"description": "Use class components instead of functional component",
"default": false
},
"routing": {
"type": "boolean",
"description": "Generate application with routes"
}
},
"required": []
Expand Down
20 changes: 20 additions & 0 deletions packages/react/src/schematics/component/component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,24 @@ describe('component', () => {
expect(packageJSON.dependencies['@emotion/core']).toBeDefined();
});
});

describe('--routing', () => {
it('should add routes to the component', async () => {
const tree = await runSchematic(
'component',
{ name: 'hello', project: projectName, routing: true },
appTree
);

const content = tree
.read('libs/my-lib/src/lib/hello/hello.tsx')
.toString();
expect(content).toContain('react-router-dom');
expect(content).toMatch(/<Route\s*path="\/"/);
expect(content).toMatch(/<Link\s*to="\/"/);

const packageJSON = readJsonInTree(tree, 'package.json');
expect(packageJSON.dependencies['react-router-dom']).toBeDefined();
});
});
});
8 changes: 5 additions & 3 deletions packages/react/src/schematics/component/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
insert
} from '@nrwl/workspace/src/utils/ast-utils';
import { CSS_IN_JS_DEPENDENCIES } from '../../utils/styled';
import { reactRouterVersion } from '../../utils/versions';

interface NormalizedSchema extends Schema {
projectSourceRoot: Path;
Expand All @@ -37,6 +38,9 @@ export default function(schema: Schema): Rule {
createComponentFiles(options),
addStyledModuleDependencies(options),
addExportsToBarrel(options),
options.routing
? addDepsToPackageJson({ 'react-router-dom': reactRouterVersion }, {})
: noop(),
formatFiles({ skipFormat: false })
]);
};
Expand Down Expand Up @@ -103,9 +107,7 @@ function addExportsToBarrel(options: NormalizedSchema): Rule {
addGlobal(
indexSourceFile,
indexFilePath,
`export { default as ${options.className}, ${
options.className
}Props } from './lib/${options.name}/${options.fileName}';`
`export * from './lib/${options.name}/${options.fileName}';`
)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import React, { Component } from 'react';
<% } else { %>
import React from 'react';
<% } %>

<% if (routing) { %>
import { Route, Link } from 'react-router-dom';
<% } %>
<% if (styledModule) {
var wrapper = 'Styled' + className;
%>
Expand All @@ -27,7 +31,13 @@ export class <%= className %> extends Component<<%= className %>Props> {
render() {
return (
<<%= wrapper %>>
Welcome to <%= name %> component!
<h1>Welcome to <%= name %> component!</h1>
<% if (routing) { %>
<ul>
<li><Link to="/"><%= name %> root</Link></li>
</ul>
<Route path="/" render={() => <div>This is the <%= name %> root route.</div>} />
<% } %>
</<%= wrapper %>>
);
}
Expand All @@ -36,7 +46,13 @@ export class <%= className %> extends Component<<%= className %>Props> {
export const <%= className %> = (props: <%= className %>Props) => {
return (
<<%= wrapper %>>
Welcome to <%= name %> component!
<h1>Welcome to <%= name %> component!</h1>
<% if (routing) { %>
<ul>
<li><Link to="/"><%= name %> root</Link></li>
</ul>
<Route path="/" render={() => <div>This is the <%= name %> root route.</div>} />
<% } %>
</<%= wrapper %>>
);
};
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/schematics/component/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export interface Schema {
export?: boolean;
pascalCaseFiles?: boolean;
classComponent?: boolean;
routing?: boolean;
}
4 changes: 4 additions & 0 deletions packages/react/src/schematics/component/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@
"type": "boolean",
"description": "Use class components instead of functional component",
"default": false
},
"routing": {
"type": "boolean",
"description": "Generate library with routes"
}
},
"required": ["name", "project"]
Expand Down
Loading

0 comments on commit 57fe219

Please sign in to comment.