Skip to content

Commit

Permalink
feat(nextjs): add fileReplacements to the builder config
Browse files Browse the repository at this point in the history
  • Loading branch information
jdpearce committed Jan 31, 2020
1 parent 3cf5b99 commit 2f72133
Show file tree
Hide file tree
Showing 17 changed files with 373 additions and 90 deletions.
57 changes: 33 additions & 24 deletions e2e/next.test.ts
@@ -1,4 +1,5 @@
import { capitalize } from '@nrwl/workspace/src/utils/strings';
import { fork } from 'child_process';
import * as http from 'http';
import {
checkFilesExist,
Expand All @@ -7,6 +8,8 @@ import {
readFile,
runCLI,
runCLIAsync,
supportUi,
tmpProjPath,
uniq,
updateFile
} from './utils';
Expand Down Expand Up @@ -140,39 +143,45 @@ async function checkApp(appName: string, opts: { checkLint: boolean }) {
const testResults = await runCLIAsync(`test ${appName}`);
expect(testResults.stderr).toContain('Test Suites: 1 passed, 1 total');

// const server = fork(
// `./node_modules/@nrwl/cli/bin/nx.js`,
// [`serve`, appName],
// {
// cwd: tmpProjPath(),
// silent: true
// }
// );
// expect(server).toBeTruthy();
// await new Promise(resolve => {
// setTimeout(() => {
// getPage().then(page => {
// expect(page).toContain(`Here are some things you can do with Nx`);
// treeKill(server.pid, 'SIGTERM', err => {
// expect(err).toBeFalsy();
// resolve();
// });
// });
// }, 5000);
// });
// if (supportUi()) {
// const e2eResults = runCLI(`e2e ${appName}-e2e`);
// expect(e2eResults).toContain('All specs passed!');
// }
// This adds about 80s to the e2e run
// expectAppToRun(appName);

const buildResult = runCLI(`build ${appName}`);
expect(buildResult).toContain(`Compiled successfully`);
checkFilesExist(`dist/apps/${appName}/build-manifest.json`);

const exportResult = runCLI(`export ${appName}`);
expect(exportResult).toContain('Exporting (3/3)');
checkFilesExist(`dist/apps/${appName}/exported/index.html`);
}

async function expectAppToRun(appName: string) {
const server = fork(
`./node_modules/@nrwl/cli/bin/nx.js`,
[`serve`, appName],
{
cwd: tmpProjPath(),
silent: true
}
);
expect(server).toBeTruthy();
await new Promise(resolve => {
setTimeout(() => {
getPage().then(page => {
expect(page).toContain(`Here are some things you can do with Nx`);
treeKill(server.pid, 'SIGTERM', err => {
expect(err).toBeFalsy();
resolve();
});
});
}, 5000);
});
if (supportUi()) {
const e2eResults = runCLI(`e2e ${appName}-e2e`);
expect(e2eResults).toContain('All specs passed!');
}
}

function getPage(): Promise<string> {
return new Promise(resolve => {
http.get('http://localhost:4200/', res => {
Expand Down
41 changes: 41 additions & 0 deletions packages/next/src/builders/build/build.impl.spec.ts
@@ -0,0 +1,41 @@
import { MockBuilderContext } from '@nrwl/workspace/testing';
import * as build from 'next/dist/build';
import { getMockContext } from '../../utils/testing';
import { NextBuildBuilderOptions } from '../../utils/types';
import { run } from './build.impl';

jest.mock('next/dist/build');

describe('Next.js Builder', () => {
let context: MockBuilderContext;
let options: NextBuildBuilderOptions;

beforeEach(async () => {
context = await getMockContext();

options = {
root: 'apps/wibble',
outputPath: 'dist/apps/wibble',
fileReplacements: [
{
replace: 'apps/wibble/src/environment.ts',
with: 'apps/wibble/src/environment.prod.ts'
}
]
};

jest.spyOn(build, 'default').mockReturnValue(Promise.resolve());
});

it('should call next build', async () => {
await run(options, context).toPromise();

expect(build.default).toHaveBeenCalledWith(
'/root/apps/wibble',
expect.objectContaining({
distDir: '../../dist/apps/wibble',
outdir: '../../dist/apps/wibble'
})
);
});
});
12 changes: 4 additions & 8 deletions packages/next/src/builders/build/build.impl.ts
Expand Up @@ -3,26 +3,21 @@ import {
BuilderOutput,
createBuilder
} from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import { PHASE_PRODUCTION_BUILD } from 'next/dist/next-server/lib/constants';
import build from 'next/dist/build';
import { PHASE_PRODUCTION_BUILD } from 'next/dist/next-server/lib/constants';
import * as path from 'path';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { prepareConfig } from '../../utils/config';
import { NextBuildBuilderOptions } from '../../utils/types';

try {
require('dotenv').config();
} catch (e) {}

export interface NextBuildBuilderOptions extends JsonObject {
root: string;
outputPath: string;
}

export default createBuilder<NextBuildBuilderOptions>(run);

function run(
export function run(
options: NextBuildBuilderOptions,
context: BuilderContext
): Observable<BuilderOutput> {
Expand All @@ -31,6 +26,7 @@ function run(
context.workspaceRoot,
options.root,
options.outputPath,
options.fileReplacements,
PHASE_PRODUCTION_BUILD
);
return from(build(root, config as any)).pipe(map(() => ({ success: true })));
Expand Down
30 changes: 29 additions & 1 deletion packages/next/src/builders/build/schema.json
@@ -1,7 +1,35 @@
{
"$schema": "http://json-schema.org/schema",
"title": "Next Build",
"description": "Build a Next.js app",
"type": "object",
"properties": {},
"properties": {
"root": {
"description": "The source root",
"type": "string"
},
"outputPath": {
"type": "string",
"description": "The output path of the generated files."
},
"fileReplacements": {
"description": "Replace files with other files in the build.",
"type": "array",
"items": {
"type": "object",
"properties": {
"replace": {
"type": "string"
},
"with": {
"type": "string"
}
},
"additionalProperties": false,
"required": ["replace", "with"]
},
"default": []
}
},
"required": []
}
29 changes: 11 additions & 18 deletions packages/next/src/builders/dev-server/dev-server.impl.ts
Expand Up @@ -2,10 +2,9 @@ import {
BuilderContext,
BuilderOutput,
createBuilder,
targetFromTargetString,
scheduleTargetAndForget
scheduleTargetAndForget,
targetFromTargetString
} from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import {
PHASE_DEVELOPMENT_SERVER,
PHASE_PRODUCTION_SERVER
Expand All @@ -14,24 +13,17 @@ import startServer from 'next/dist/server/lib/start-server';
import NextServer from 'next/dist/server/next-dev-server';
import * as path from 'path';
import { from, Observable, of } from 'rxjs';
import { switchMap, concatMap, tap } from 'rxjs/operators';
import * as url from 'url';
import { concatMap, switchMap, tap } from 'rxjs/operators';
import { prepareConfig } from '../../utils/config';
import {
NextBuildBuilderOptions,
NextServeBuilderOptions
} from '../../utils/types';

try {
require('dotenv').config();
} catch (e) {}

export interface NextBuildBuilderOptions extends JsonObject {
dev: boolean;
port: number;
staticMarkup: boolean;
quiet: boolean;
buildTarget: string;
customServerPath: string;
hostname: string;
}

export interface NextServerOptions {
dev: boolean;
dir: string;
Expand All @@ -43,7 +35,7 @@ export interface NextServerOptions {
hostname: string;
}

export default createBuilder<NextBuildBuilderOptions>(run);
export default createBuilder<NextServeBuilderOptions>(run);

function defaultServer(settings: NextServerOptions) {
return startServer(settings, settings.port, settings.hostname).then(app =>
Expand All @@ -58,7 +50,7 @@ function customServer(settings: NextServerOptions) {
}

function run(
options: NextBuildBuilderOptions,
options: NextServeBuilderOptions,
context: BuilderContext
): Observable<BuilderOutput> {
const buildTarget = targetFromTargetString(options.buildTarget);
Expand All @@ -72,13 +64,14 @@ function run(
concatMap(r => {
if (!r.success) return of(r);
return from(context.getTargetOptions(buildTarget)).pipe(
concatMap((buildOptions: any) => {
concatMap((buildOptions: NextBuildBuilderOptions) => {
const root = path.resolve(context.workspaceRoot, buildOptions.root);

const config = prepareConfig(
context.workspaceRoot,
buildOptions.root,
buildOptions.outputPath,
buildOptions.fileReplacements,
options.dev ? PHASE_DEVELOPMENT_SERVER : PHASE_PRODUCTION_SERVER
);

Expand Down
28 changes: 12 additions & 16 deletions packages/next/src/builders/export/export.impl.ts
Expand Up @@ -2,32 +2,28 @@ import {
BuilderContext,
BuilderOutput,
createBuilder,
targetFromTargetString,
scheduleTargetAndForget
scheduleTargetAndForget,
targetFromTargetString
} from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import { PHASE_EXPORT } from 'next/dist/next-server/lib/constants';
import exportApp from 'next/dist/export';
import { PHASE_EXPORT } from 'next/dist/next-server/lib/constants';
import * as path from 'path';
import { from, Observable, of } from 'rxjs';
import { map, concatMap } from 'rxjs/operators';
import { concatMap, map } from 'rxjs/operators';
import { prepareConfig } from '../../utils/config';
import {
NextBuildBuilderOptions,
NextExportBuilderOptions
} from '../../utils/types';

try {
require('dotenv').config();
} catch (e) {}

export interface NextBuildBuilderOptions extends JsonObject {
buildTarget: string;
silent: boolean;
threads: number;
concurrency: number;
}

export default createBuilder<NextBuildBuilderOptions>(run);
export default createBuilder<NextExportBuilderOptions>(run);

function run(
options: NextBuildBuilderOptions,
options: NextExportBuilderOptions,
context: BuilderContext
): Observable<BuilderOutput> {
const buildTarget = targetFromTargetString(options.buildTarget);
Expand All @@ -37,12 +33,13 @@ function run(
concatMap(r => {
if (!r.success) return of(r);
return from(context.getTargetOptions(buildTarget)).pipe(
concatMap((buildOptions: any) => {
concatMap((buildOptions: NextBuildBuilderOptions) => {
const root = path.resolve(context.workspaceRoot, buildOptions.root);
const config = prepareConfig(
context.workspaceRoot,
buildOptions.root,
buildOptions.outputPath,
buildOptions.fileReplacements,
PHASE_EXPORT
);
return from(
Expand All @@ -51,7 +48,6 @@ function run(
{
silent: options.silent,
threads: options.threads,
concurrency: options.concurrency,
outdir: `${buildOptions.outputPath}/exported`
} as any,
config
Expand Down
16 changes: 15 additions & 1 deletion packages/next/src/builders/export/schema.json
Expand Up @@ -2,6 +2,20 @@
"title": "Next Export",
"description": "Export a Next.js app. The exported application is located at dist/$outputPath/exported.",
"type": "object",
"properties": {},
"properties": {
"buildTarget": {
"type": "string",
"description": "Target which builds the application"
},
"silent": {
"type": "boolean",
"description": "Hide progress or not (default is false)",
"default": false
},
"threads": {
"type": "number",
"description": "Number of worker threads to utilize (defaults to the number of CPUs)"
}
},
"required": []
}

2 comments on commit 2f72133

@krizic
Copy link

@krizic krizic commented on 2f72133 Feb 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revisit this PR! The replacement doesn't work since you are providing relative url to nx-project by default in packages/next/src/schematics/application/application.ts, but requiring a relative path to nx-workspace for it to work!

@jdpearce
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a bug in the app schematic. The paths in that file should be relative to the workspace root. I'll create a PR to fix it.

Please sign in to comment.