feat(nx): add full stack preset for react/express

This commit is contained in:
Victor Savkin 2019-08-01 12:17:36 -04:00
parent 8c7c0b7721
commit bb858e4db9
6 changed files with 190 additions and 41 deletions

View File

@ -23,20 +23,20 @@ const presetOptions = [
value: 'angular',
name: 'angular [a workspace with a single Angular application]'
},
{
value: 'angular-nest',
name:
'angular-nest [a workspace with a full stack application (Angular + Nest)]'
},
{
value: 'react',
name: 'react [a workspace with a single React application]'
},
{
value: 'nest-angular',
value: 'react-express',
name:
'angular-nest [a workspace with a full stack application (Angular + Nest)]'
'react-express [a workspace with a full stack application (React + Express)]'
}
// {
// value: 'react-express',
// name:
// 'react-express [a workspace with a full stack application (React + Express)]'
// }
];
const tsVersion = 'TYPESCRIPT_VERSION';

View File

@ -5,5 +5,6 @@ module.exports = {
},
resolver: '@nrwl/jest/plugins/resolver',
moduleFileExtensions: ['ts', 'js', 'html'],
coverageReporters: ['html']
coverageReporters: ['html'],
passWithNoTests: true
};

View File

@ -13,6 +13,7 @@ export function getBabelWebpackConfig(config: Configuration) {
{
// Allow importing core-js in entrypoint and use browserlist to select polyfills
useBuiltIns: 'entry',
corejs: 3,
modules: false,
// Exclude transforms that make all code slower
exclude: ['transform-typeof-symbol']

View File

@ -55,9 +55,9 @@ describe('preset', () => {
);
expect(tree.exists('/apps/proj/src/app/app.component.ts')).toBe(true);
expect(tree.exists('/apps/api/src/app/app.controller.ts')).toBe(true);
expect(tree.exists('/libs/api-interface/src/lib/interfaces.ts')).toBe(
true
);
expect(
tree.exists('/libs/api-interfaces/src/lib/api-interfaces.ts')
).toBe(true);
});
it('should work with unnormalized names', async () => {
@ -69,9 +69,36 @@ describe('preset', () => {
expect(tree.exists('/apps/my-proj/src/app/app.component.ts')).toBe(true);
expect(tree.exists('/apps/api/src/app/app.controller.ts')).toBe(true);
expect(tree.exists('/libs/api-interface/src/lib/interfaces.ts')).toBe(
true
expect(
tree.exists('/libs/api-interfaces/src/lib/api-interfaces.ts')
).toBe(true);
});
});
describe('--preset react-express', () => {
it('should create files', async () => {
const tree = await runSchematic(
'preset',
{ name: 'proj', preset: 'react-express' },
projectTree
);
expect(tree.exists('/apps/proj/src/app/app.tsx')).toBe(true);
expect(
tree.exists('/libs/api-interfaces/src/lib/api-interfaces.ts')
).toBe(true);
});
it('should work with unnormalized names', async () => {
const tree = await runSchematic(
'preset',
{ name: 'myProj', preset: 'react-express' },
projectTree
);
expect(tree.exists('/apps/my-proj/src/app/app.tsx')).toBe(true);
expect(
tree.exists('/libs/api-interfaces/src/lib/api-interfaces.ts')
).toBe(true);
});
});
});

View File

@ -93,32 +93,49 @@ function createPreset(options: Schema): Rule {
},
{ interactive: false }
),
schematic(
'library',
{ name: 'api-interface', framework: 'none' },
{ interactive: false }
),
schematic('library', { name: 'api-interfaces' }, { interactive: false }),
setDefaultCollection('@nrwl/angular'),
connectFrontendAndApi(options)
connectAngularAndNest(options)
]);
} else if (options.preset === 'react-express') {
throw new Error(`Not implemented yet`);
return chain([
externalSchematic(
'@nrwl/react',
'application',
{
name: options.name,
style: options.style,
babel: true,
linter
},
{ interactive: false }
),
externalSchematic(
'@nrwl/express',
'application',
{
name: 'api',
frontendProject: options.name
},
{ interactive: false }
),
schematic('library', { name: 'api-interfaces' }, { interactive: false }),
setDefaultCollection('@nrwl/react'),
setDefaultLinter(linter),
connectReactAndExpress(options)
]);
} else {
throw new Error(`Invalid preset ${options.preset}`);
}
}
function connectFrontendAndApi(options: Schema) {
function connectAngularAndNest(options: Schema) {
const addImportToModule = require('@nrwl/angular/src/utils/ast-utils')
.addImportToModule;
return (host: Tree) => {
host.create(
'libs/api-interface/src/lib/interfaces.ts',
`export interface Message { message: string }`
);
host.overwrite(
'libs/api-interface/src/index.ts',
`export * from './lib/interfaces';`
'libs/api-interfaces/src/lib/api-interfaces.ts',
`export interface Message { message: string }`
);
const modulePath = `apps/${options.name}/src/app/app.module.ts`;
@ -148,7 +165,7 @@ function connectFrontendAndApi(options: Schema) {
`apps/${options.name}/src/app/app.component.ts`,
`import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Message } from '@${scope}/api-interface';
import { Message } from '@${scope}/api-interfaces';
@Component({
selector: '${scope}-root',
@ -203,7 +220,7 @@ describe('AppComponent', () => {
`apps/api/src/app/app.controller.ts`,
`import { Controller, Get } from '@nestjs/common';
import { Message } from '@${scope}/api-interface';
import { Message } from '@${scope}/api-interfaces';
import { AppService } from './app.service';
@ -222,7 +239,7 @@ export class AppController {
host.overwrite(
`apps/api/src/app/app.service.ts`,
`import { Injectable } from '@nestjs/common';
import { Message } from '@${scope}/api-interface';
import { Message } from '@${scope}/api-interfaces';
@Injectable()
export class AppService {
@ -235,6 +252,98 @@ export class AppService {
};
}
function connectReactAndExpress(options: Schema) {
return (host: Tree) => {
const scope = options.npmScope;
const style = options.style ? options.style : 'css';
host.overwrite(
'libs/api-interfaces/src/lib/api-interfaces.ts',
`export interface Message { message: string }`
);
host.overwrite(
`apps/${options.name}/src/app/app.tsx`,
`import React, { useEffect, useState } from 'react';
import { Message } from '@${scope}/api-interfaces';
import './app.${style}';
export const App = () => {
const [m, setMessage] = useState<Message>({ message: '' });
useEffect(() => {
fetch('/api')
.then(r => r.json())
.then(setMessage);
}, []);
return (
<>
<div style={{ textAlign: 'center' }}>
<h1>Welcome to ${options.name}!</h1>
<img
width="450"
src="https://raw.githubusercontent.com/nrwl/nx/master/nx-logo.png"
/>
</div>
<div>{m.message}</div>
</>
);
};
export default App;
`
);
host.overwrite(
`apps/${options.name}/src/app/app.spec.tsx`,
`import { cleanup, getByText, render, wait } from '@testing-library/react';
import React from 'react';
import App from './app';
describe('App', () => {
afterEach(() => {
delete global['fetch'];
cleanup();
});
it('should render successfully', async () => {
global['fetch'] = jest.fn().mockResolvedValueOnce({
json: () => ({
message: 'my message'
})
});
const { baseElement } = render(<App />);
await wait(() => getByText(baseElement, 'my message'));
});
});
`
);
host.overwrite(
`apps/api/src/main.ts`,
`import * as express from 'express';
import { Message } from '@${scope}/api-interfaces';
const app = express();
const greeting: Message = { message: 'Welcome to api!' };
app.get('/api', (req, res) => {
res.send(greeting);
});
const port = process.env.port || 3333;
const server = app.listen(port, () => {
console.log('Listening at http://localhost:' + port + '/api');
});
server.on('error', console.error);
`
);
};
}
function setDefaultCollection(defaultCollection: string) {
return updateWorkspaceInTree(json => {
if (!json.cli) {

View File

@ -117,6 +117,14 @@ export function sharedNew(cli: string, options: Schema): Rule {
function addDependencies(options: Schema) {
if (options.preset === 'empty') {
return noop();
} else if (options.preset === 'web-components') {
return addDepsToPackageJson(
{},
{
'@nrwl/web': nxVersion
},
false
);
} else if (options.preset === 'angular') {
return addDepsToPackageJson(
{
@ -125,6 +133,16 @@ function addDependencies(options: Schema) {
{},
false
);
} else if (options.preset === 'angular-nest') {
return addDepsToPackageJson(
{
'@nrwl/angular': nxVersion
},
{
'@nrwl/nest': nxVersion
},
false
);
} else if (options.preset === 'react') {
return addDepsToPackageJson(
{},
@ -133,24 +151,17 @@ function addDependencies(options: Schema) {
},
false
);
} else if (options.preset === 'web-components') {
} else if (options.preset === 'react-express') {
return addDepsToPackageJson(
{},
{
'@nrwl/web': nxVersion
'@nrwl/react': nxVersion,
'@nrwl/express': nxVersion
},
false
);
} else {
return addDepsToPackageJson(
{
'@nrwl/angular': nxVersion
},
{
'@nrwl/nest': nxVersion
},
false
);
return noop();
}
}