feat(react): migrate next builders to devkit (#4861)

This commit is contained in:
Jason Jean 2021-02-23 11:39:20 -05:00 committed by GitHub
parent 956dfe6509
commit fd18b5edec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 275 additions and 270 deletions

View File

@ -68,6 +68,8 @@ describe('create-nx-workspace', () => {
style: 'css',
appName,
});
expectNoAngularDevkit();
});
it('should be able to create an web-components workspace', () => {

View File

@ -7,7 +7,7 @@
"excludedFiles": ["./src/migrations/**"],
"rules": {
"no-restricted-imports": [
"warn",
"error",
"@nrwl/workspace",
"@angular-devkit/core",
"@angular-devkit/schematics",

View File

@ -1,19 +1,35 @@
{
"$schema": "@angular-devkit/architect/src/builders-schema.json",
"builders": {
"executors": {
"build": {
"implementation": "./src/builders/build/build.impl",
"schema": "./src/builders/build/schema.json",
"implementation": "./src/executors/build/build.impl",
"schema": "./src/executors/build/schema.json",
"description": "Build a Next.js app"
},
"server": {
"implementation": "./src/builders/server/server.impl",
"schema": "./src/builders/server/schema.json",
"implementation": "./src/executors/server/server.impl",
"schema": "./src/executors/server/schema.json",
"description": "Serve a Next.js app"
},
"export": {
"implementation": "./src/builders/export/export.impl",
"schema": "./src/builders/export/schema.json",
"implementation": "./src/executors/export/export.impl",
"schema": "./src/executors/export/schema.json",
"description": "Export a Next.js app. The exported application is located at dist/$outputPath/exported."
}
},
"builders": {
"build": {
"implementation": "./src/executors/build/compat",
"schema": "./src/executors/build/schema.json",
"description": "Build a Next.js app"
},
"server": {
"implementation": "./src/executors/server/compat",
"schema": "./src/executors/server/schema.json",
"description": "Serve a Next.js app"
},
"export": {
"implementation": "./src/executors/export/compat",
"schema": "./src/executors/export/schema.json",
"description": "Export a Next.js app. The exported application is located at dist/$outputPath/exported."
}
}

View File

@ -37,9 +37,7 @@
"@nrwl/jest": "*",
"@nrwl/linter": "*",
"@nrwl/web": "*",
"@angular-devkit/architect": "~0.1102.0",
"@angular-devkit/schematics": "~11.2.0",
"@angular-devkit/core": "~11.2.0",
"@nrwl/workspace": "*",
"@svgr/webpack": "^5.4.0",
"chalk": "4.1.0",
"copy-webpack-plugin": "6.0.3",

View File

@ -1,38 +0,0 @@
import {
BuilderContext,
BuilderOutput,
createBuilder,
} from '@angular-devkit/architect';
import build from 'next/dist/build';
import { PHASE_PRODUCTION_BUILD } from 'next/dist/next-server/lib/constants';
import * as path from 'path';
import { copySync } from 'fs-extra';
import { from, Observable } from 'rxjs';
import { concatMap, map, tap } from 'rxjs/operators';
import { prepareConfig } from '../../utils/config';
import { NextBuildBuilderOptions } from '../../utils/types';
import { createPackageJson } from './lib/create-package-json';
import { createNextConfigFile } from './lib/create-next-config-file';
import { join } from 'path';
try {
require('dotenv').config();
} catch (e) {}
export default createBuilder<NextBuildBuilderOptions>(run);
export function run(
options: NextBuildBuilderOptions,
context: BuilderContext
): Observable<BuilderOutput> {
const root = path.resolve(context.workspaceRoot, options.root);
const config = prepareConfig(PHASE_PRODUCTION_BUILD, options, context);
return from(build(root, config as any)).pipe(
concatMap(() => from(createPackageJson(options, context))),
concatMap(() => from(createNextConfigFile(options, context))),
tap(() => {
copySync(join(root, 'public'), join(options.outputPath, 'public'));
}),
map(() => ({ success: true }))
);
}

View File

@ -1,55 +0,0 @@
import {
BuilderContext,
BuilderOutput,
createBuilder,
scheduleTargetAndForget,
targetFromTargetString,
} from '@angular-devkit/architect';
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 { concatMap, map } from 'rxjs/operators';
import { prepareConfig } from '../../utils/config';
import {
NextBuildBuilderOptions,
NextExportBuilderOptions,
} from '../../utils/types';
try {
require('dotenv').config();
} catch (e) {}
export default createBuilder<NextExportBuilderOptions>(run);
function run(
options: NextExportBuilderOptions,
context: BuilderContext
): Observable<BuilderOutput> {
const buildTarget = targetFromTargetString(options.buildTarget);
const build$ = scheduleTargetAndForget(context, buildTarget);
return build$.pipe(
concatMap((r) => {
if (!r.success) return of(r);
return from(context.getTargetOptions(buildTarget)).pipe(
concatMap((buildOptions: NextBuildBuilderOptions) => {
const root = path.resolve(context.workspaceRoot, buildOptions.root);
const config = prepareConfig(PHASE_EXPORT, buildOptions, context);
return from(
exportApp(
root,
{
statusMessage: 'Exporting',
silent: options.silent,
threads: options.threads,
outdir: `${buildOptions.outputPath}/exported`,
} as any,
config
)
).pipe(map(() => ({ success: true })));
})
);
})
);
}

View File

@ -1,106 +0,0 @@
import {
BuilderContext,
BuilderOutput,
createBuilder,
scheduleTargetAndForget,
targetFromTargetString,
} from '@angular-devkit/architect';
import * as chalk from 'chalk';
import * as fs from 'fs';
import {
PHASE_DEVELOPMENT_SERVER,
PHASE_PRODUCTION_SERVER,
} from 'next/dist/next-server/lib/constants';
import * as path from 'path';
import { from, Observable, of, throwError } from 'rxjs';
import { catchError, concatMap, switchMap, tap } from 'rxjs/operators';
import { prepareConfig } from '../../utils/config';
import {
NextBuildBuilderOptions,
NextServeBuilderOptions,
NextServer,
NextServerOptions,
ProxyConfig,
} from '../../utils/types';
import { customServer } from './lib/custom-server';
import { defaultServer } from './lib/default-server';
try {
require('dotenv').config();
} catch (e) {}
export default createBuilder<NextServeBuilderOptions>(run);
const infoPrefix = `[ ${chalk.dim(chalk.cyan('info'))} ] `;
const readyPrefix = `[ ${chalk.green('ready')} ]`;
export function run(
options: NextServeBuilderOptions,
context: BuilderContext
): Observable<BuilderOutput> {
const buildTarget = targetFromTargetString(options.buildTarget);
const baseUrl = `http://${options.hostname || 'localhost'}:${options.port}`;
return from(context.getTargetOptions(buildTarget)).pipe(
concatMap((buildOptions: NextBuildBuilderOptions) => {
const root = path.resolve(context.workspaceRoot, buildOptions.root);
const config = prepareConfig(
options.dev ? PHASE_DEVELOPMENT_SERVER : PHASE_PRODUCTION_SERVER,
buildOptions,
context
);
const settings: NextServerOptions = {
dev: options.dev,
dir: root,
staticMarkup: options.staticMarkup,
quiet: options.quiet,
conf: config,
port: options.port,
path: options.customServerPath,
hostname: options.hostname,
};
const server: NextServer = options.customServerPath
? customServer
: defaultServer;
// look for the proxy.conf.json
let proxyConfig: ProxyConfig;
const proxyConfigPath = options.proxyConfig
? path.join(context.workspaceRoot, options.proxyConfig)
: path.join(root, 'proxy.conf.json');
if (fs.existsSync(proxyConfigPath)) {
context.logger.info(
`${infoPrefix} found proxy configuration at ${proxyConfigPath}`
);
proxyConfig = require(proxyConfigPath);
}
return from(server(settings, proxyConfig)).pipe(
catchError((err) => {
if (options.dev) {
throw err;
} else {
throw new Error(
`Could not start production server. Try building your app with \`nx build ${context.target.project}\`.`
);
}
}),
tap(() => {
context.logger.info(`${readyPrefix} on ${baseUrl}`);
}),
switchMap(
(e) =>
new Observable<BuilderOutput>((obs) => {
obs.next({
baseUrl,
success: true,
});
})
)
);
})
);
}

View File

@ -1,8 +1,9 @@
import { MockBuilderContext } from '@nrwl/workspace/testing';
import { ExecutorContext } from '@nrwl/devkit';
import * as build from 'next/dist/build';
import { getMockContext } from '../../utils/testing';
import { NextBuildBuilderOptions } from '../../utils/types';
import { run } from './build.impl';
import buildExecutor from './build.impl';
jest.mock('fs-extra');
jest.mock('next/dist/build');
@ -13,12 +14,21 @@ jest.mock('./lib/create-package-json', () => {
});
describe('Next.js Builder', () => {
let context: MockBuilderContext;
let context: ExecutorContext;
let options: NextBuildBuilderOptions;
beforeEach(async () => {
context = await getMockContext();
context = {
root: '/root',
cwd: '/root',
projectName: 'my-app',
targetName: 'build',
workspace: {
version: 2,
projects: {},
},
isVerbose: false,
};
options = {
root: 'apps/wibble',
outputPath: 'dist/apps/wibble',
@ -34,7 +44,7 @@ describe('Next.js Builder', () => {
});
it('should call next build', async () => {
await run(options, context).toPromise();
await buildExecutor(options, context);
expect(build.default).toHaveBeenCalledWith(
'/root/apps/wibble',

View File

@ -0,0 +1,31 @@
import { ExecutorContext } from '@nrwl/devkit';
import build from 'next/dist/build';
import { PHASE_PRODUCTION_BUILD } from 'next/dist/next-server/lib/constants';
import { join, resolve } from 'path';
import { copySync } from 'fs-extra';
import { prepareConfig } from '../../utils/config';
import { NextBuildBuilderOptions } from '../../utils/types';
import { createPackageJson } from './lib/create-package-json';
import { createNextConfigFile } from './lib/create-next-config-file';
try {
require('dotenv').config();
} catch (e) {}
export default async function buildExecutor(
options: NextBuildBuilderOptions,
context: ExecutorContext
) {
const root = resolve(context.root, options.root);
const config = prepareConfig(PHASE_PRODUCTION_BUILD, options, context);
await build(root, config as any);
createPackageJson(options, context);
createNextConfigFile(options, context);
copySync(join(root, 'public'), join(options.outputPath, 'public'));
return { success: true };
}

View File

@ -0,0 +1,5 @@
import { convertNxExecutor } from '@nrwl/devkit';
import buildExecutor from './build.impl';
export default convertNxExecutor(buildExecutor);

View File

@ -1,15 +1,17 @@
import { ExecutorContext } from '@nrwl/devkit';
import { copyFileSync, existsSync } from 'fs';
import { join } from 'path';
import { BuilderContext } from '@angular-devkit/architect';
import { NextBuildBuilderOptions } from '../../../utils/types';
export async function createNextConfigFile(
export function createNextConfigFile(
options: NextBuildBuilderOptions,
context: BuilderContext
context: ExecutorContext
) {
const nextConfigPath = options.nextConfig
? join(context.workspaceRoot, options.nextConfig)
: join(context.workspaceRoot, options.root, 'next.config.js');
? join(context.root, options.nextConfig)
: join(context.root, options.root, 'next.config.js');
if (existsSync(nextConfigPath)) {
copyFileSync(nextConfigPath, join(options.outputPath, 'next.config.js'));

View File

@ -1,16 +1,22 @@
import { BuilderContext } from '@angular-devkit/architect';
import { ExecutorContext } from '@nrwl/devkit';
import { writeFileSync } from 'fs';
import { join } from 'path';
import { readJsonFile } from '@nrwl/workspace';
import { createProjectGraph } from '@nrwl/workspace/src/core/project-graph';
import { calculateProjectDependencies } from '@nrwl/workspace/src/utils/buildable-libs-utils';
import { readJsonFile } from '@nrwl/workspace/src/utilities/fileutils';
import { calculateProjectDependencies } from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import { NextBuildBuilderOptions } from '../../../utils/types';
function getProjectDeps(context: BuilderContext, rootPackageJson: any) {
function getProjectDeps(context: ExecutorContext, rootPackageJson: any) {
const projGraph = createProjectGraph();
const { dependencies: deps } = calculateProjectDependencies(
projGraph,
context
context.root,
context.projectName,
context.targetName,
context.configurationName
);
const depNames = deps
.map((d) => d.node)
@ -36,20 +42,18 @@ function getProjectDeps(context: BuilderContext, rootPackageJson: any) {
};
}
export async function createPackageJson(
export function createPackageJson(
options: NextBuildBuilderOptions,
context: BuilderContext
context: ExecutorContext
) {
const rootPackageJson = readJsonFile(
join(context.workspaceRoot, 'package.json')
);
const rootPackageJson = readJsonFile(join(context.root, 'package.json'));
const { dependencies, devDependencies } = getProjectDeps(
context,
rootPackageJson
);
const outPackageJson = {
name: context.target.project,
name: context.projectName,
version: '0.0.1',
scripts: {
start: 'next start',

View File

@ -1,5 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
"cli": "nx",
"title": "Next Build",
"description": "Build a Next.js app",
"type": "object",

View File

@ -0,0 +1,5 @@
import { convertNxExecutor } from '@nrwl/devkit';
import exportExecutor from './export.impl';
export default convertNxExecutor(exportExecutor);

View File

@ -0,0 +1,52 @@
import {
ExecutorContext,
parseTargetString,
readTargetOptions,
runExecutor,
} from '@nrwl/devkit';
import exportApp from 'next/dist/export';
import { PHASE_EXPORT } from 'next/dist/next-server/lib/constants';
import { resolve } from 'path';
import { prepareConfig } from '../../utils/config';
import {
NextBuildBuilderOptions,
NextExportBuilderOptions,
} from '../../utils/types';
try {
require('dotenv').config();
} catch (e) {}
export default async function exportExecutor(
options: NextExportBuilderOptions,
context: ExecutorContext
) {
const buildTarget = parseTargetString(options.buildTarget);
const build = await runExecutor(buildTarget, {}, context);
for await (const result of build) {
if (!result.success) {
return result;
}
}
const buildOptions = readTargetOptions<NextBuildBuilderOptions>(
buildTarget,
context
);
const root = resolve(context.root, buildOptions.root);
const config = prepareConfig(PHASE_EXPORT, buildOptions, context);
await exportApp(
root,
{
statusMessage: 'Exporting',
silent: options.silent,
threads: options.threads,
outdir: `${buildOptions.outputPath}/exported`,
} as any,
config
);
return { success: true };
}

View File

@ -1,4 +1,5 @@
{
"cli": "nx",
"title": "Next Export",
"description": "Export a Next.js app. The exported application is located at dist/$outputPath/exported.",
"type": "object",

View File

@ -0,0 +1,5 @@
import { convertNxExecutor } from '@nrwl/devkit';
import serverExecutor from './server.impl';
export default convertNxExecutor(serverExecutor);

View File

@ -1,4 +1,5 @@
{
"cli": "nx",
"title": "Next Serve",
"description": "Serve a Next.js app",
"type": "object",

View File

@ -0,0 +1,98 @@
import {
ExecutorContext,
logger,
parseTargetString,
readTargetOptions,
} from '@nrwl/devkit';
import {
PHASE_DEVELOPMENT_SERVER,
PHASE_PRODUCTION_SERVER,
} from 'next/dist/next-server/lib/constants';
import * as chalk from 'chalk';
import { existsSync } from 'fs';
import { join, resolve } from 'path';
import { prepareConfig } from '../../utils/config';
import {
NextBuildBuilderOptions,
NextServeBuilderOptions,
NextServer,
NextServerOptions,
ProxyConfig,
} from '../../utils/types';
import { customServer } from './lib/custom-server';
import { defaultServer } from './lib/default-server';
try {
require('dotenv').config();
} catch (e) {}
const infoPrefix = `[ ${chalk.dim(chalk.cyan('info'))} ] `;
const readyPrefix = `[ ${chalk.green('ready')} ]`;
export default async function* serveExecutor(
options: NextServeBuilderOptions,
context: ExecutorContext
) {
const buildTarget = parseTargetString(options.buildTarget);
const baseUrl = `http://${options.hostname || 'localhost'}:${options.port}`;
const buildOptions = readTargetOptions<NextBuildBuilderOptions>(
buildTarget,
context
);
const root = resolve(context.root, buildOptions.root);
const config = prepareConfig(
options.dev ? PHASE_DEVELOPMENT_SERVER : PHASE_PRODUCTION_SERVER,
buildOptions,
context
);
const settings: NextServerOptions = {
dev: options.dev,
dir: root,
staticMarkup: options.staticMarkup,
quiet: options.quiet,
conf: config,
port: options.port,
path: options.customServerPath,
hostname: options.hostname,
};
const server: NextServer = options.customServerPath
? customServer
: defaultServer;
// look for the proxy.conf.json
let proxyConfig: ProxyConfig;
const proxyConfigPath = options.proxyConfig
? join(context.root, options.proxyConfig)
: join(root, 'proxy.conf.json');
if (existsSync(proxyConfigPath)) {
logger.info(
`${infoPrefix} found proxy configuration at ${proxyConfigPath}`
);
proxyConfig = require(proxyConfigPath);
}
try {
await server(settings, proxyConfig);
logger.info(`${readyPrefix} on ${baseUrl}`);
yield {
baseUrl,
success: true,
};
// This Promise intentionally never resolves, leaving the process running
await new Promise<{ success: boolean }>(() => {});
} catch (e) {
if (options.dev) {
throw e;
} else {
throw new Error(
`Could not start production server. Try building your app with \`nx build ${context.projectName}\`.`
);
}
}
}

View File

@ -1,6 +1,7 @@
import { PHASE_PRODUCTION_BUILD } from 'next/dist/next-server/lib/constants';
import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';
import { createWebpackConfig, prepareConfig } from './config';
import { NextBuildBuilderOptions } from '@nrwl/next';
jest.mock('tsconfig-paths-webpack-plugin');
jest.mock('next/dist/next-server/server/config', () => ({
@ -93,7 +94,7 @@ describe('Next.js webpack config builder', () => {
fileReplacements: [],
nextConfig: require.resolve('./config.fixture'),
customValue: 'test',
},
} as NextBuildBuilderOptions,
{ workspaceRoot: '/root' } as any
);

View File

@ -1,3 +1,4 @@
import { ExecutorContext, offsetFromRoot } from '@nrwl/devkit';
import {
PHASE_DEVELOPMENT_SERVER,
PHASE_EXPORT,
@ -9,8 +10,6 @@ import { join, resolve } from 'path';
import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';
import { Configuration } from 'webpack';
import { FileReplacement, NextBuildBuilderOptions } from './types';
import { BuilderContext } from '@angular-devkit/architect';
import { offsetFromRoot } from '@nrwl/devkit';
import { normalizeAssets } from '@nrwl/web/src/utils/normalize';
import { createCopyPlugin } from '@nrwl/web/src/utils/config';
@ -122,7 +121,7 @@ export function prepareConfig(
| typeof PHASE_DEVELOPMENT_SERVER
| typeof PHASE_PRODUCTION_SERVER,
options: NextBuildBuilderOptions,
context: BuilderContext
context: ExecutorContext
) {
const config = loadConfig(phase, options.root, null);
const userWebpack = config.webpack;
@ -134,7 +133,7 @@ export function prepareConfig(
config.distDir = join(config.outdir, '.next');
config.webpack = (a, b) =>
createWebpackConfig(
context.workspaceRoot,
context.root,
options.root,
options.fileReplacements,
options.assets

View File

@ -1,25 +0,0 @@
import { Architect } from '@angular-devkit/architect';
import { TestingArchitectHost } from '@angular-devkit/architect/testing';
import { schema } from '@angular-devkit/core';
import { MockBuilderContext } from '@nrwl/workspace/testing';
import { join } from 'path';
export async function getTestArchitect() {
const architectHost = new TestingArchitectHost('/root', '/root');
const registry = new schema.CoreSchemaRegistry();
registry.addPostTransform(schema.transforms.addUndefinedDefaults);
const architect = new Architect(architectHost, registry);
await architectHost.addBuilderFromPackage(join(__dirname, '../..'));
return [architect, architectHost] as [Architect, TestingArchitectHost];
}
export async function getMockContext() {
const [architect, architectHost] = await getTestArchitect();
const context = new MockBuilderContext(architect, architectHost);
await context.addBuilderFromPackage(join(__dirname, '../..'));
return context;
}

View File

@ -1,5 +1,3 @@
import { JsonObject } from '@angular-devkit/core';
export type NextServer = (
options: NextServerOptions,
proxyConfig?: ProxyConfig
@ -25,12 +23,12 @@ export interface NextServerOptions {
hostname: string;
}
export interface FileReplacement extends JsonObject {
export interface FileReplacement {
replace: string;
with: string;
}
export interface NextBuildBuilderOptions extends JsonObject {
export interface NextBuildBuilderOptions {
root: string;
outputPath: string;
fileReplacements: FileReplacement[];
@ -38,7 +36,7 @@ export interface NextBuildBuilderOptions extends JsonObject {
nextConfig?: string;
}
export interface NextServeBuilderOptions extends JsonObject {
export interface NextServeBuilderOptions {
dev: boolean;
port: number;
staticMarkup: boolean;
@ -49,7 +47,7 @@ export interface NextServeBuilderOptions extends JsonObject {
proxyConfig?: string;
}
export interface NextExportBuilderOptions extends JsonObject {
export interface NextExportBuilderOptions {
buildTarget: string;
silent: boolean;
threads: number;