diff --git a/packages/create-nx-workspace/bin/create-nx-workspace.ts b/packages/create-nx-workspace/bin/create-nx-workspace.ts index ee4fa3cac7..8ef78a45fa 100644 --- a/packages/create-nx-workspace/bin/create-nx-workspace.ts +++ b/packages/create-nx-workspace/bin/create-nx-workspace.ts @@ -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'; diff --git a/packages/jest/src/schematics/ng-add/files/jest.config.js b/packages/jest/src/schematics/ng-add/files/jest.config.js index 30b91f3cbe..ffd5ba20bf 100644 --- a/packages/jest/src/schematics/ng-add/files/jest.config.js +++ b/packages/jest/src/schematics/ng-add/files/jest.config.js @@ -5,5 +5,6 @@ module.exports = { }, resolver: '@nrwl/jest/plugins/resolver', moduleFileExtensions: ['ts', 'js', 'html'], - coverageReporters: ['html'] + coverageReporters: ['html'], + passWithNoTests: true }; diff --git a/packages/react/src/plugins/babel.ts b/packages/react/src/plugins/babel.ts index fe6261bd74..952642f047 100644 --- a/packages/react/src/plugins/babel.ts +++ b/packages/react/src/plugins/babel.ts @@ -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'] diff --git a/packages/workspace/src/schematics/preset/preset.spec.ts b/packages/workspace/src/schematics/preset/preset.spec.ts index 5f2affe730..514bc6063f 100644 --- a/packages/workspace/src/schematics/preset/preset.spec.ts +++ b/packages/workspace/src/schematics/preset/preset.spec.ts @@ -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); }); }); }); diff --git a/packages/workspace/src/schematics/preset/preset.ts b/packages/workspace/src/schematics/preset/preset.ts index 0d5e4c9e70..b4204b5a9f 100644 --- a/packages/workspace/src/schematics/preset/preset.ts +++ b/packages/workspace/src/schematics/preset/preset.ts @@ -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: '' }); + + useEffect(() => { + fetch('/api') + .then(r => r.json()) + .then(setMessage); + }, []); + + return ( + <> +
+

Welcome to ${options.name}!

+ +
+
{m.message}
+ + ); +}; + +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(); + 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) { diff --git a/packages/workspace/src/schematics/shared-new/shared-new.ts b/packages/workspace/src/schematics/shared-new/shared-new.ts index f26f464de3..eee8d348e9 100644 --- a/packages/workspace/src/schematics/shared-new/shared-new.ts +++ b/packages/workspace/src/schematics/shared-new/shared-new.ts @@ -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(); } }