From c4ea49c3c672056d627743064fb3d02ce4f60bd1 Mon Sep 17 00:00:00 2001 From: Jo Hanna Pearce Date: Fri, 24 Jan 2020 15:00:26 +0000 Subject: [PATCH] fix(nextjs): add SVGR support for jest tests --- docs/angular/api-next/builders/build.md | 32 ++ docs/angular/api-next/builders/export.md | 22 ++ docs/react/api-next/builders/build.md | 32 ++ docs/react/api-next/builders/export.md | 22 ++ docs/web/api-next/builders/build.md | 32 ++ docs/web/api-next/builders/export.md | 22 ++ .../application/application.spec.ts | 14 + .../src/schematics/application/application.ts | 370 +----------------- .../files/assets/nx-logo-white.svg | 17 + .../files/pages/__fileName__.tsx__tmpl__ | 7 +- .../schematics/application/lib/add-cypress.ts | 13 + .../schematics/application/lib/add-jest.ts | 29 ++ .../schematics/application/lib/add-project.ts | 76 ++++ .../lib/add-styled-module-dependencies.ts | 14 + .../lib/create-application-files.ts | 32 ++ .../lib/create-next-server-files.ts | 104 +++++ .../application/lib/normalize-options.ts | 54 +++ .../application/lib/set-defaults.ts | 35 ++ .../application/lib/update-jest-config.ts | 16 + .../application/lib/update-nx-json.ts | 10 + 20 files changed, 593 insertions(+), 360 deletions(-) create mode 100644 packages/next/src/schematics/application/files/assets/nx-logo-white.svg create mode 100644 packages/next/src/schematics/application/lib/add-cypress.ts create mode 100644 packages/next/src/schematics/application/lib/add-jest.ts create mode 100644 packages/next/src/schematics/application/lib/add-project.ts create mode 100644 packages/next/src/schematics/application/lib/add-styled-module-dependencies.ts create mode 100644 packages/next/src/schematics/application/lib/create-application-files.ts create mode 100644 packages/next/src/schematics/application/lib/create-next-server-files.ts create mode 100644 packages/next/src/schematics/application/lib/normalize-options.ts create mode 100644 packages/next/src/schematics/application/lib/set-defaults.ts create mode 100644 packages/next/src/schematics/application/lib/update-jest-config.ts create mode 100644 packages/next/src/schematics/application/lib/update-nx-json.ts diff --git a/docs/angular/api-next/builders/build.md b/docs/angular/api-next/builders/build.md index 1911b473fce61..72abc9bcdc617 100644 --- a/docs/angular/api-next/builders/build.md +++ b/docs/angular/api-next/builders/build.md @@ -3,3 +3,35 @@ Build a Next.js app Builder properties can be configured in angular.json when defining the builder, or when invoking it. + +## Properties + +### fileReplacements + +Type: `array` of `object` + +Replace files with other files in the build. + +#### replace + +Type: `string` + +undefined + +#### with + +Type: `string` + +undefined + +### outputPath + +Type: `string` + +The output path of the generated files. + +### root + +Type: `string` + +The source root diff --git a/docs/angular/api-next/builders/export.md b/docs/angular/api-next/builders/export.md index 73c5a99c95398..830302d8b3e77 100644 --- a/docs/angular/api-next/builders/export.md +++ b/docs/angular/api-next/builders/export.md @@ -3,3 +3,25 @@ Export a Next.js app. The exported application is located at dist/\$outputPath/exported. Builder properties can be configured in angular.json when defining the builder, or when invoking it. + +## Properties + +### buildTarget + +Type: `string` + +Target which builds the application + +### silent + +Default: `false` + +Type: `boolean` + +Hide progress or not (default is false) + +### threads + +Type: `number` + +Number of worker threads to utilize (defaults to the number of CPUs) diff --git a/docs/react/api-next/builders/build.md b/docs/react/api-next/builders/build.md index 4d12372fee582..1cfd9a497e467 100644 --- a/docs/react/api-next/builders/build.md +++ b/docs/react/api-next/builders/build.md @@ -4,3 +4,35 @@ Build a Next.js app Builder properties can be configured in workspace.json when defining the builder, or when invoking it. Read more about how to use builders and the CLI here: https://nx.dev/react/guides/cli. + +## Properties + +### fileReplacements + +Type: `array` of `object` + +Replace files with other files in the build. + +#### replace + +Type: `string` + +undefined + +#### with + +Type: `string` + +undefined + +### outputPath + +Type: `string` + +The output path of the generated files. + +### root + +Type: `string` + +The source root diff --git a/docs/react/api-next/builders/export.md b/docs/react/api-next/builders/export.md index 73ae66286b4e1..e6885d3740c5f 100644 --- a/docs/react/api-next/builders/export.md +++ b/docs/react/api-next/builders/export.md @@ -4,3 +4,25 @@ Export a Next.js app. The exported application is located at dist/\$outputPath/e Builder properties can be configured in workspace.json when defining the builder, or when invoking it. Read more about how to use builders and the CLI here: https://nx.dev/react/guides/cli. + +## Properties + +### buildTarget + +Type: `string` + +Target which builds the application + +### silent + +Default: `false` + +Type: `boolean` + +Hide progress or not (default is false) + +### threads + +Type: `number` + +Number of worker threads to utilize (defaults to the number of CPUs) diff --git a/docs/web/api-next/builders/build.md b/docs/web/api-next/builders/build.md index 9e9dc06641de2..28047b58de830 100644 --- a/docs/web/api-next/builders/build.md +++ b/docs/web/api-next/builders/build.md @@ -4,3 +4,35 @@ Build a Next.js app Builder properties can be configured in workspace.json when defining the builder, or when invoking it. Read more about how to use builders and the CLI here: https://nx.dev/web/guides/cli. + +## Properties + +### fileReplacements + +Type: `array` of `object` + +Replace files with other files in the build. + +#### replace + +Type: `string` + +undefined + +#### with + +Type: `string` + +undefined + +### outputPath + +Type: `string` + +The output path of the generated files. + +### root + +Type: `string` + +The source root diff --git a/docs/web/api-next/builders/export.md b/docs/web/api-next/builders/export.md index 61717b61cbcd1..03e1473fdfda4 100644 --- a/docs/web/api-next/builders/export.md +++ b/docs/web/api-next/builders/export.md @@ -4,3 +4,25 @@ Export a Next.js app. The exported application is located at dist/\$outputPath/e Builder properties can be configured in workspace.json when defining the builder, or when invoking it. Read more about how to use builders and the CLI here: https://nx.dev/web/guides/cli. + +## Properties + +### buildTarget + +Type: `string` + +Target which builds the application + +### silent + +Default: `false` + +Type: `boolean` + +Hide progress or not (default is false) + +### threads + +Type: `number` + +Number of worker threads to utilize (defaults to the number of CPUs) diff --git a/packages/next/src/schematics/application/application.spec.ts b/packages/next/src/schematics/application/application.spec.ts index 2095d5e8b3f20..95db1fb8f8c36 100644 --- a/packages/next/src/schematics/application/application.spec.ts +++ b/packages/next/src/schematics/application/application.spec.ts @@ -77,6 +77,20 @@ describe('app', () => { ); }); + it('should setup jest with SVGR support', async () => { + const tree = await runSchematic( + 'app', + { + name: 'my-app' + }, + appTree + ); + + expect(tree.readContent('apps/my-app/jest.config.js')).toContain( + `'^(?!.*\\\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest'` + ); + }); + it('should set up the nrwl next build builder', async () => { const tree = await runSchematic( 'app', diff --git a/packages/next/src/schematics/application/application.ts b/packages/next/src/schematics/application/application.ts index 2d3a01d0e9973..6714fd1fae9fa 100644 --- a/packages/next/src/schematics/application/application.ts +++ b/packages/next/src/schematics/application/application.ts @@ -1,60 +1,26 @@ import { - dirname, - join, - JsonObject, - normalize, - Path -} from '@angular-devkit/core'; -import { - apply, chain, - externalSchematic, - filter, - mergeWith, - move, - noop, Rule, SchematicContext, - template, - Tree, - url + Tree } from '@angular-devkit/schematics'; -import { - assertValidStyle, - CSS_IN_JS_DEPENDENCIES, - extraEslintDependencies, - reactEslintJson -} from '@nrwl/react'; -import { - addLintFiles, - formatFiles, - generateProjectLint, - names, - NxJson, - offsetFromRoot, - toFileName, - updateJsonInTree, - updateWorkspace -} from '@nrwl/workspace'; -import { - addDepsToPackageJson, - updateWorkspaceInTree -} from '@nrwl/workspace/src/utils/ast-utils'; +import { extraEslintDependencies, reactEslintJson } from '@nrwl/react'; +import { addLintFiles, formatFiles } from '@nrwl/workspace'; import init from '../init/init'; +import { addCypress } from './lib/add-cypress'; +import { addJest } from './lib/add-jest'; +import { addProject } from './lib/add-project'; +import { addStyledModuleDependencies } from './lib/add-styled-module-dependencies'; +import { createApplicationFiles } from './lib/create-application-files'; +import { createNextServerFiles } from './lib/create-next-server-files'; +import { normalizeOptions } from './lib/normalize-options'; +import { setDefaults } from './lib/set-defaults'; +import { updateJestConfig } from './lib/update-jest-config'; +import { updateNxJson } from './lib/update-nx-json'; import { Schema } from './schema'; -interface NormalizedSchema extends Schema { - projectName: string; - appProjectRoot: Path; - e2eProjectName: string; - e2eProjectRoot: Path; - parsedTags: string[]; - fileName: string; - styledModule: null | string; -} - export default function(schema: Schema): Rule { - return (host: Tree, context: SchematicContext) => { + return (host: Tree, _context: SchematicContext) => { const options = normalizeOptions(host, schema); return chain([ @@ -71,316 +37,10 @@ export default function(schema: Schema): Rule { addProject(options), addCypress(options), addJest(options), + updateJestConfig(options), addStyledModuleDependencies(options), setDefaults(options), formatFiles(options) ]); }; } - -function createNextServerFiles(options: NormalizedSchema) { - return (host: Tree) => { - if (options.server) { - const directory = dirname(join(options.appProjectRoot, options.server)); - - host.create( - join(options.appProjectRoot, options.server), - ` - // @ts-check - 'use strict'; - - /** - * @typedef {import('http').Server} Server - * @typedef {import('next/dist/server/next-dev-server').default} DevServer - */ - - const express = require('express'); - - /** - * @param {DevServer} app - * @param {{dev: string; dir: string; staticMarkup: boolean; quiet: boolean; conf: any; port: number;}} options - * @returns {Promise} - */ - module.exports = async function nextServer(app, options) { - const handle = app.getRequestHandler(); - const expressApp = express(); - - await app.prepare(); - - /** - * @returns {Promise} - */ - return new Promise((resolve, reject) => { - - expressApp.all('*', (req, res) => { - return handle(req, res); - }); - - const server = expressApp.listen(options.port, (err) => { - err ? reject(err) : resolve(server); - }); - }); - } - ` - ); - - host.create( - join(directory, 'server.js'), - ` - // @ts-check - 'use strict'; - - /** - * Production Nextjs custom server - * - * Usage: run this script with node - * Adjust dir option for your serve/deploy config - * - * node server.js - */ - - /** - * @typedef {import('next/dist/next-server/server/next-server').default} Server - */ - - const NextServer = require('next/dist/next-server/server/next-server').default; - const express = require('express'); - - const nextApp = new NextServer({ - dir: './dist/apps/<%= name %>', - staticMarkup: false, - quiet: false, - conf: { - distDir: '.' - } - }); - - const serve = async () => { - const handle = nextApp.getRequestHandler(); - const expressApp = express(); - - await nextApp.prepare(); - - return new Promise((resolve, reject) => { - expressApp.all('*', (req, res) => { - return handle(req, res); - }); - - const server = expressApp.listen(4200, err => { - err ? reject(err) : resolve(server); - }); - }); - } - - serve().then(server => console.log('Server is running on port 4200')); - ` - ); - } - }; -} - -function createApplicationFiles(options: NormalizedSchema): Rule { - return mergeWith( - apply(url(`./files`), [ - template({ - ...names(options.name), - ...options, - tmpl: '', - offsetFromRoot: offsetFromRoot(options.appProjectRoot) - }), - options.styledModule - ? filter(file => !file.endsWith(`.${options.style}`)) - : noop(), - options.unitTestRunner === 'none' - ? filter(file => file !== `/specs/index.spec.tsx`) - : noop(), - move(options.appProjectRoot) - ]) - ); -} - -function updateNxJson(options: NormalizedSchema): Rule { - return updateJsonInTree('nx.json', json => { - json.projects[options.projectName] = { tags: options.parsedTags }; - return json; - }); -} - -function addProject(options: NormalizedSchema): Rule { - return updateWorkspaceInTree(json => { - const architect: { [key: string]: any } = {}; - const { server } = options; - - architect.build = { - builder: '@nrwl/next:build', - options: { - root: options.appProjectRoot, - outputPath: join(normalize('dist'), options.appProjectRoot) - }, - configurations: { - production: { - fileReplacements: [ - { - replace: `${options.appProjectRoot}/environments/environment.ts`, - with: `${options.appProjectRoot}/environments/environment.prod.ts` - } - ] - } - } - }; - - architect.serve = { - builder: '@nrwl/next:dev-server', - options: { - buildTarget: `${options.projectName}:build`, - dev: true - }, - configurations: { - production: { - buildTarget: `${options.projectName}:build:production`, - dev: false - } - } - }; - - if (server) { - architect.serve.options = { - ...architect.serve.options, - customServerPath: options.server - }; - } - - architect.export = { - builder: '@nrwl/next:export', - options: { - buildTarget: `${options.projectName}:build:production` - } - }; - - architect.lint = generateProjectLint( - normalize(options.appProjectRoot), - join(normalize(options.appProjectRoot), 'tsconfig.json'), - options.linter - ); - - json.projects[options.projectName] = { - root: options.appProjectRoot, - sourceRoot: options.appProjectRoot, - projectType: 'application', - schematics: {}, - architect - }; - - json.defaultProject = json.defaultProject || options.projectName; - - return json; - }); -} - -function addCypress(options: NormalizedSchema): Rule { - return options.e2eTestRunner === 'cypress' - ? externalSchematic('@nrwl/cypress', 'cypress-project', { - ...options, - name: options.name + '-e2e', - directory: options.directory, - project: options.projectName - }) - : noop(); -} - -function addJest(options: NormalizedSchema): Rule { - return options.unitTestRunner === 'jest' - ? chain([ - externalSchematic('@nrwl/jest', 'jest-project', { - project: options.projectName, - supportTsx: true, - skipSerializers: true, - setupFile: 'none' - }), - updateJsonInTree( - `${options.appProjectRoot}/tsconfig.spec.json`, - json => { - json.compilerOptions.jsx = 'react'; - return json; - } - ) - ]) - : noop(); -} - -function addStyledModuleDependencies(options: NormalizedSchema): Rule { - const extraDependencies = CSS_IN_JS_DEPENDENCIES[options.styledModule]; - return extraDependencies - ? addDepsToPackageJson( - extraDependencies.dependencies, - extraDependencies.devDependencies - ) - : noop(); -} - -function setDefaults(options: NormalizedSchema): Rule { - return options.skipWorkspaceJson - ? noop() - : updateWorkspace(workspace => { - workspace.extensions.schematics = jsonIdentity( - workspace.extensions.schematics || {} - ); - workspace.extensions.schematics['@nrwl/next'] = - workspace.extensions.schematics['@nrwl/next'] || {}; - const prev = jsonIdentity( - workspace.extensions.schematics['@nrwl/next'] - ); - - workspace.extensions.schematics = { - ...workspace.extensions.schematics, - '@nrwl/next': { - ...prev, - application: { - style: options.style, - linter: options.linter, - ...jsonIdentity(prev.application) - } - } - }; - }); -} - -function jsonIdentity(x: any): JsonObject { - return x as JsonObject; -} - -function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { - const appDirectory = options.directory - ? `${toFileName(options.directory)}/${toFileName(options.name)}` - : toFileName(options.name); - - const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-'); - const e2eProjectName = `${appProjectName}-e2e`; - - const appProjectRoot = normalize(`apps/${appDirectory}`); - const e2eProjectRoot = normalize(`apps/${appDirectory}-e2e`); - - const parsedTags = options.tags - ? options.tags.split(',').map(s => s.trim()) - : []; - - const fileName = options.pascalCaseFiles ? 'Index' : 'index'; - - const styledModule = /^(css|scss|less|styl)$/.test(options.style) - ? null - : options.style; - - assertValidStyle(options.style); - - return { - ...options, - name: toFileName(options.name), - projectName: appProjectName, - appProjectRoot, - e2eProjectRoot, - e2eProjectName, - parsedTags, - fileName, - styledModule - }; -} diff --git a/packages/next/src/schematics/application/files/assets/nx-logo-white.svg b/packages/next/src/schematics/application/files/assets/nx-logo-white.svg new file mode 100644 index 0000000000000..577944247dec3 --- /dev/null +++ b/packages/next/src/schematics/application/files/assets/nx-logo-white.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/next/src/schematics/application/files/pages/__fileName__.tsx__tmpl__ b/packages/next/src/schematics/application/files/pages/__fileName__.tsx__tmpl__ index 7572f089d4fd0..e86c87de1cd10 100644 --- a/packages/next/src/schematics/application/files/pages/__fileName__.tsx__tmpl__ +++ b/packages/next/src/schematics/application/files/pages/__fileName__.tsx__tmpl__ @@ -10,6 +10,7 @@ import React from 'react'; %> import './<%= fileName %>.<%= style %>'; +import { ReactComponent as NxLogo } from '../assets/nx-logo-white.svg'; import { environment } from '../environments/environment'; <% } @@ -153,11 +154,7 @@ summary { <% var innerJsx = `
- +

Welcome to ${name}! [{environment.production ? 'PROD' : 'DEV'}]

diff --git a/packages/next/src/schematics/application/lib/add-cypress.ts b/packages/next/src/schematics/application/lib/add-cypress.ts new file mode 100644 index 0000000000000..6d7ce588048fe --- /dev/null +++ b/packages/next/src/schematics/application/lib/add-cypress.ts @@ -0,0 +1,13 @@ +import { externalSchematic, noop, Rule } from '@angular-devkit/schematics'; +import { NormalizedSchema } from './normalize-options'; + +export function addCypress(options: NormalizedSchema): Rule { + return options.e2eTestRunner === 'cypress' + ? externalSchematic('@nrwl/cypress', 'cypress-project', { + ...options, + name: options.name + '-e2e', + directory: options.directory, + project: options.projectName + }) + : noop(); +} diff --git a/packages/next/src/schematics/application/lib/add-jest.ts b/packages/next/src/schematics/application/lib/add-jest.ts new file mode 100644 index 0000000000000..ee947a451d3de --- /dev/null +++ b/packages/next/src/schematics/application/lib/add-jest.ts @@ -0,0 +1,29 @@ +import { + chain, + externalSchematic, + noop, + Rule +} from '@angular-devkit/schematics'; +import { updateJsonInTree } from '@nrwl/workspace'; +import { NormalizedSchema } from './normalize-options'; + +export function addJest(options: NormalizedSchema): Rule { + return options.unitTestRunner === 'jest' + ? chain([ + externalSchematic('@nrwl/jest', 'jest-project', { + project: options.projectName, + supportTsx: true, + skipSerializers: true, + setupFile: 'none' + }), + + updateJsonInTree( + `${options.appProjectRoot}/tsconfig.spec.json`, + json => { + json.compilerOptions.jsx = 'react'; + return json; + } + ) + ]) + : noop(); +} diff --git a/packages/next/src/schematics/application/lib/add-project.ts b/packages/next/src/schematics/application/lib/add-project.ts new file mode 100644 index 0000000000000..e93ca8f7bc660 --- /dev/null +++ b/packages/next/src/schematics/application/lib/add-project.ts @@ -0,0 +1,76 @@ +import { join, normalize } from '@angular-devkit/core'; +import { Rule } from '@angular-devkit/schematics'; +import { generateProjectLint } from '@nrwl/workspace'; +import { updateWorkspaceInTree } from '@nrwl/workspace/src/utils/ast-utils'; +import { NormalizedSchema } from './normalize-options'; + +export function addProject(options: NormalizedSchema): Rule { + return updateWorkspaceInTree(json => { + const architect: { [key: string]: any } = {}; + const { server } = options; + + architect.build = { + builder: '@nrwl/next:build', + options: { + root: options.appProjectRoot, + outputPath: join(normalize('dist'), options.appProjectRoot) + }, + configurations: { + production: { + fileReplacements: [ + { + replace: `environments/environment.ts`, + with: `environments/environment.prod.ts` + } + ] + } + } + }; + + architect.serve = { + builder: '@nrwl/next:dev-server', + options: { + buildTarget: `${options.projectName}:build`, + dev: true + }, + configurations: { + production: { + buildTarget: `${options.projectName}:build:production`, + dev: false + } + } + }; + + if (server) { + architect.serve.options = { + ...architect.serve.options, + customServerPath: options.server + }; + } + + architect.export = { + builder: '@nrwl/next:export', + options: { + buildTarget: `${options.projectName}:build:production` + } + }; + + architect.lint = generateProjectLint( + normalize(options.appProjectRoot), + join(normalize(options.appProjectRoot), 'tsconfig.json'), + options.linter + ); + + json.projects[options.projectName] = { + root: options.appProjectRoot, + sourceRoot: options.appProjectRoot, + projectType: 'application', + schematics: {}, + architect + }; + + json.defaultProject = json.defaultProject || options.projectName; + + return json; + }); +} diff --git a/packages/next/src/schematics/application/lib/add-styled-module-dependencies.ts b/packages/next/src/schematics/application/lib/add-styled-module-dependencies.ts new file mode 100644 index 0000000000000..5a3937b19cac9 --- /dev/null +++ b/packages/next/src/schematics/application/lib/add-styled-module-dependencies.ts @@ -0,0 +1,14 @@ +import { noop, Rule } from '@angular-devkit/schematics'; +import { CSS_IN_JS_DEPENDENCIES } from '@nrwl/react'; +import { addDepsToPackageJson } from '@nrwl/workspace'; +import { NormalizedSchema } from './normalize-options'; + +export function addStyledModuleDependencies(options: NormalizedSchema): Rule { + const extraDependencies = CSS_IN_JS_DEPENDENCIES[options.styledModule]; + return extraDependencies + ? addDepsToPackageJson( + extraDependencies.dependencies, + extraDependencies.devDependencies + ) + : noop(); +} diff --git a/packages/next/src/schematics/application/lib/create-application-files.ts b/packages/next/src/schematics/application/lib/create-application-files.ts new file mode 100644 index 0000000000000..39f21ab7bff63 --- /dev/null +++ b/packages/next/src/schematics/application/lib/create-application-files.ts @@ -0,0 +1,32 @@ +import { + apply, + filter, + mergeWith, + move, + noop, + Rule, + template, + url +} from '@angular-devkit/schematics'; +import { names, offsetFromRoot } from '@nrwl/workspace'; +import { NormalizedSchema } from './normalize-options'; + +export function createApplicationFiles(options: NormalizedSchema): Rule { + return mergeWith( + apply(url(`./files`), [ + template({ + ...names(options.name), + ...options, + tmpl: '', + offsetFromRoot: offsetFromRoot(options.appProjectRoot) + }), + options.styledModule + ? filter(file => !file.endsWith(`.${options.style}`)) + : noop(), + options.unitTestRunner === 'none' + ? filter(file => file !== `/specs/index.spec.tsx`) + : noop(), + move(options.appProjectRoot) + ]) + ); +} diff --git a/packages/next/src/schematics/application/lib/create-next-server-files.ts b/packages/next/src/schematics/application/lib/create-next-server-files.ts new file mode 100644 index 0000000000000..a763502eb7e46 --- /dev/null +++ b/packages/next/src/schematics/application/lib/create-next-server-files.ts @@ -0,0 +1,104 @@ +import { dirname, join } from '@angular-devkit/core'; +import { Tree } from '@angular-devkit/schematics'; +import { NormalizedSchema } from './normalize-options'; + +export function createNextServerFiles(options: NormalizedSchema) { + return (host: Tree) => { + if (options.server) { + const directory = dirname(join(options.appProjectRoot, options.server)); + + host.create( + join(options.appProjectRoot, options.server), + ` + // @ts-check + 'use strict'; + + /** + * @typedef {import('http').Server} Server + * @typedef {import('next/dist/server/next-dev-server').default} DevServer + */ + + const express = require('express'); + + /** + * @param {DevServer} app + * @param {{dev: string; dir: string; staticMarkup: boolean; quiet: boolean; conf: any; port: number;}} options + * @returns {Promise} + */ + module.exports = async function nextServer(app, options) { + const handle = app.getRequestHandler(); + const expressApp = express(); + + await app.prepare(); + + /** + * @returns {Promise} + */ + return new Promise((resolve, reject) => { + + expressApp.all('*', (req, res) => { + return handle(req, res); + }); + + const server = expressApp.listen(options.port, (err) => { + err ? reject(err) : resolve(server); + }); + }); + } + ` + ); + + host.create( + join(directory, 'server.js'), + ` + // @ts-check + 'use strict'; + + /** + * Production Nextjs custom server + * + * Usage: run this script with node + * Adjust dir option for your serve/deploy config + * + * node server.js + */ + + /** + * @typedef {import('next/dist/next-server/server/next-server').default} Server + */ + + const NextServer = require('next/dist/next-server/server/next-server').default; + const express = require('express'); + + const nextApp = new NextServer({ + dir: './dist/apps/<%= name %>', + staticMarkup: false, + quiet: false, + conf: { + distDir: '.' + } + }); + + const serve = async () => { + const handle = nextApp.getRequestHandler(); + const expressApp = express(); + + await nextApp.prepare(); + + return new Promise((resolve, reject) => { + expressApp.all('*', (req, res) => { + return handle(req, res); + }); + + const server = expressApp.listen(4200, err => { + err ? reject(err) : resolve(server); + }); + }); + } + + serve().then(server => console.log('Server is running on port 4200')); + ` + ); + } + }; +} diff --git a/packages/next/src/schematics/application/lib/normalize-options.ts b/packages/next/src/schematics/application/lib/normalize-options.ts new file mode 100644 index 0000000000000..8cb6b1aae6b3a --- /dev/null +++ b/packages/next/src/schematics/application/lib/normalize-options.ts @@ -0,0 +1,54 @@ +import { normalize, Path } from '@angular-devkit/core'; +import { Tree } from '@angular-devkit/schematics'; +import { assertValidStyle } from '@nrwl/react'; +import { toFileName } from '@nrwl/workspace'; +import { Schema } from '../schema'; + +export interface NormalizedSchema extends Schema { + projectName: string; + appProjectRoot: Path; + e2eProjectName: string; + e2eProjectRoot: Path; + parsedTags: string[]; + fileName: string; + styledModule: null | string; +} + +export function normalizeOptions( + _host: Tree, + options: Schema +): NormalizedSchema { + const appDirectory = options.directory + ? `${toFileName(options.directory)}/${toFileName(options.name)}` + : toFileName(options.name); + + const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-'); + const e2eProjectName = `${appProjectName}-e2e`; + + const appProjectRoot = normalize(`apps/${appDirectory}`); + const e2eProjectRoot = normalize(`apps/${appDirectory}-e2e`); + + const parsedTags = options.tags + ? options.tags.split(',').map(s => s.trim()) + : []; + + const fileName = options.pascalCaseFiles ? 'Index' : 'index'; + + const styledModule = /^(css|scss|less|styl)$/.test(options.style) + ? null + : options.style; + + assertValidStyle(options.style); + + return { + ...options, + name: toFileName(options.name), + projectName: appProjectName, + appProjectRoot, + e2eProjectRoot, + e2eProjectName, + parsedTags, + fileName, + styledModule + }; +} diff --git a/packages/next/src/schematics/application/lib/set-defaults.ts b/packages/next/src/schematics/application/lib/set-defaults.ts new file mode 100644 index 0000000000000..e3a3fdff5fb7c --- /dev/null +++ b/packages/next/src/schematics/application/lib/set-defaults.ts @@ -0,0 +1,35 @@ +import { JsonObject } from '@angular-devkit/core'; +import { noop, Rule } from '@angular-devkit/schematics'; +import { updateWorkspace } from '@nrwl/workspace'; +import { NormalizedSchema } from './normalize-options'; + +export function setDefaults(options: NormalizedSchema): Rule { + return options.skipWorkspaceJson + ? noop() + : updateWorkspace(workspace => { + workspace.extensions.schematics = jsonIdentity( + workspace.extensions.schematics || {} + ); + workspace.extensions.schematics['@nrwl/next'] = + workspace.extensions.schematics['@nrwl/next'] || {}; + const prev = jsonIdentity( + workspace.extensions.schematics['@nrwl/next'] + ); + + workspace.extensions.schematics = { + ...workspace.extensions.schematics, + '@nrwl/next': { + ...prev, + application: { + style: options.style, + linter: options.linter, + ...jsonIdentity(prev.application) + } + } + }; + }); +} + +function jsonIdentity(x: any): JsonObject { + return x as JsonObject; +} diff --git a/packages/next/src/schematics/application/lib/update-jest-config.ts b/packages/next/src/schematics/application/lib/update-jest-config.ts new file mode 100644 index 0000000000000..4c840179dca0d --- /dev/null +++ b/packages/next/src/schematics/application/lib/update-jest-config.ts @@ -0,0 +1,16 @@ +import { noop, Rule } from '@angular-devkit/schematics'; +import { NormalizedSchema } from './normalize-options'; + +export function updateJestConfig(options: NormalizedSchema): Rule { + return options.unitTestRunner === 'none' + ? noop() + : host => { + const configPath = `${options.appProjectRoot}/jest.config.js`; + const originalContent = host.read(configPath).toString(); + const content = originalContent.replace( + 'transform: {', + "transform: {\n '^(?!.*\\\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest'," + ); + host.overwrite(configPath, content); + }; +} diff --git a/packages/next/src/schematics/application/lib/update-nx-json.ts b/packages/next/src/schematics/application/lib/update-nx-json.ts new file mode 100644 index 0000000000000..6c20b17cb9913 --- /dev/null +++ b/packages/next/src/schematics/application/lib/update-nx-json.ts @@ -0,0 +1,10 @@ +import { Rule } from '@angular-devkit/schematics'; +import { NxJson, updateJsonInTree } from '@nrwl/workspace'; +import { NormalizedSchema } from './normalize-options'; + +export function updateNxJson(options: NormalizedSchema): Rule { + return updateJsonInTree('nx.json', json => { + json.projects[options.projectName] = { tags: options.parsedTags }; + return json; + }); +}