feat(nx): add full stack preset for react/express
This commit is contained in:
parent
8c7c0b7721
commit
bb858e4db9
@ -23,20 +23,20 @@ const presetOptions = [
|
|||||||
value: 'angular',
|
value: 'angular',
|
||||||
name: 'angular [a workspace with a single Angular application]'
|
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',
|
value: 'react',
|
||||||
name: 'react [a workspace with a single React application]'
|
name: 'react [a workspace with a single React application]'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'nest-angular',
|
value: 'react-express',
|
||||||
name:
|
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';
|
const tsVersion = 'TYPESCRIPT_VERSION';
|
||||||
|
|||||||
@ -5,5 +5,6 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
resolver: '@nrwl/jest/plugins/resolver',
|
resolver: '@nrwl/jest/plugins/resolver',
|
||||||
moduleFileExtensions: ['ts', 'js', 'html'],
|
moduleFileExtensions: ['ts', 'js', 'html'],
|
||||||
coverageReporters: ['html']
|
coverageReporters: ['html'],
|
||||||
|
passWithNoTests: true
|
||||||
};
|
};
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export function getBabelWebpackConfig(config: Configuration) {
|
|||||||
{
|
{
|
||||||
// Allow importing core-js in entrypoint and use browserlist to select polyfills
|
// Allow importing core-js in entrypoint and use browserlist to select polyfills
|
||||||
useBuiltIns: 'entry',
|
useBuiltIns: 'entry',
|
||||||
|
corejs: 3,
|
||||||
modules: false,
|
modules: false,
|
||||||
// Exclude transforms that make all code slower
|
// Exclude transforms that make all code slower
|
||||||
exclude: ['transform-typeof-symbol']
|
exclude: ['transform-typeof-symbol']
|
||||||
|
|||||||
@ -55,9 +55,9 @@ describe('preset', () => {
|
|||||||
);
|
);
|
||||||
expect(tree.exists('/apps/proj/src/app/app.component.ts')).toBe(true);
|
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('/apps/api/src/app/app.controller.ts')).toBe(true);
|
||||||
expect(tree.exists('/libs/api-interface/src/lib/interfaces.ts')).toBe(
|
expect(
|
||||||
true
|
tree.exists('/libs/api-interfaces/src/lib/api-interfaces.ts')
|
||||||
);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with unnormalized names', async () => {
|
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/my-proj/src/app/app.component.ts')).toBe(true);
|
||||||
expect(tree.exists('/apps/api/src/app/app.controller.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(
|
expect(
|
||||||
true
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -93,32 +93,49 @@ function createPreset(options: Schema): Rule {
|
|||||||
},
|
},
|
||||||
{ interactive: false }
|
{ interactive: false }
|
||||||
),
|
),
|
||||||
schematic(
|
schematic('library', { name: 'api-interfaces' }, { interactive: false }),
|
||||||
'library',
|
|
||||||
{ name: 'api-interface', framework: 'none' },
|
|
||||||
{ interactive: false }
|
|
||||||
),
|
|
||||||
setDefaultCollection('@nrwl/angular'),
|
setDefaultCollection('@nrwl/angular'),
|
||||||
connectFrontendAndApi(options)
|
connectAngularAndNest(options)
|
||||||
]);
|
]);
|
||||||
} else if (options.preset === 'react-express') {
|
} 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 {
|
} else {
|
||||||
throw new Error(`Invalid preset ${options.preset}`);
|
throw new Error(`Invalid preset ${options.preset}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectFrontendAndApi(options: Schema) {
|
function connectAngularAndNest(options: Schema) {
|
||||||
const addImportToModule = require('@nrwl/angular/src/utils/ast-utils')
|
const addImportToModule = require('@nrwl/angular/src/utils/ast-utils')
|
||||||
.addImportToModule;
|
.addImportToModule;
|
||||||
return (host: Tree) => {
|
return (host: Tree) => {
|
||||||
host.create(
|
|
||||||
'libs/api-interface/src/lib/interfaces.ts',
|
|
||||||
`export interface Message { message: string }`
|
|
||||||
);
|
|
||||||
host.overwrite(
|
host.overwrite(
|
||||||
'libs/api-interface/src/index.ts',
|
'libs/api-interfaces/src/lib/api-interfaces.ts',
|
||||||
`export * from './lib/interfaces';`
|
`export interface Message { message: string }`
|
||||||
);
|
);
|
||||||
|
|
||||||
const modulePath = `apps/${options.name}/src/app/app.module.ts`;
|
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`,
|
`apps/${options.name}/src/app/app.component.ts`,
|
||||||
`import { Component } from '@angular/core';
|
`import { Component } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Message } from '@${scope}/api-interface';
|
import { Message } from '@${scope}/api-interfaces';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: '${scope}-root',
|
selector: '${scope}-root',
|
||||||
@ -203,7 +220,7 @@ describe('AppComponent', () => {
|
|||||||
`apps/api/src/app/app.controller.ts`,
|
`apps/api/src/app/app.controller.ts`,
|
||||||
`import { Controller, Get } from '@nestjs/common';
|
`import { Controller, Get } from '@nestjs/common';
|
||||||
|
|
||||||
import { Message } from '@${scope}/api-interface';
|
import { Message } from '@${scope}/api-interfaces';
|
||||||
|
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
|
|
||||||
@ -222,7 +239,7 @@ export class AppController {
|
|||||||
host.overwrite(
|
host.overwrite(
|
||||||
`apps/api/src/app/app.service.ts`,
|
`apps/api/src/app/app.service.ts`,
|
||||||
`import { Injectable } from '@nestjs/common';
|
`import { Injectable } from '@nestjs/common';
|
||||||
import { Message } from '@${scope}/api-interface';
|
import { Message } from '@${scope}/api-interfaces';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppService {
|
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) {
|
function setDefaultCollection(defaultCollection: string) {
|
||||||
return updateWorkspaceInTree(json => {
|
return updateWorkspaceInTree(json => {
|
||||||
if (!json.cli) {
|
if (!json.cli) {
|
||||||
|
|||||||
@ -117,6 +117,14 @@ export function sharedNew(cli: string, options: Schema): Rule {
|
|||||||
function addDependencies(options: Schema) {
|
function addDependencies(options: Schema) {
|
||||||
if (options.preset === 'empty') {
|
if (options.preset === 'empty') {
|
||||||
return noop();
|
return noop();
|
||||||
|
} else if (options.preset === 'web-components') {
|
||||||
|
return addDepsToPackageJson(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
'@nrwl/web': nxVersion
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
} else if (options.preset === 'angular') {
|
} else if (options.preset === 'angular') {
|
||||||
return addDepsToPackageJson(
|
return addDepsToPackageJson(
|
||||||
{
|
{
|
||||||
@ -125,6 +133,16 @@ function addDependencies(options: Schema) {
|
|||||||
{},
|
{},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
} else if (options.preset === 'angular-nest') {
|
||||||
|
return addDepsToPackageJson(
|
||||||
|
{
|
||||||
|
'@nrwl/angular': nxVersion
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'@nrwl/nest': nxVersion
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
} else if (options.preset === 'react') {
|
} else if (options.preset === 'react') {
|
||||||
return addDepsToPackageJson(
|
return addDepsToPackageJson(
|
||||||
{},
|
{},
|
||||||
@ -133,24 +151,17 @@ function addDependencies(options: Schema) {
|
|||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
} else if (options.preset === 'web-components') {
|
} else if (options.preset === 'react-express') {
|
||||||
return addDepsToPackageJson(
|
return addDepsToPackageJson(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
'@nrwl/web': nxVersion
|
'@nrwl/react': nxVersion,
|
||||||
|
'@nrwl/express': nxVersion
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return addDepsToPackageJson(
|
return noop();
|
||||||
{
|
|
||||||
'@nrwl/angular': nxVersion
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'@nrwl/nest': nxVersion
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user