Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(node): support nodejs frameworks #14199

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/generated/packages/node/generators/application.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,22 @@
"standaloneConfig": {
"description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside `workspace.json`.",
"type": "boolean"
},
"bundler": {
"description": "Bundler which is used to package the application",
"type": "string",
"enum": ["esbuild", "webpack"],
"default": "esbuild"
},
"framework": {
"description": "Generate the node application using a framework",
"type": "string",
"enum": ["express", "koa", "fastify", "connect"]
},
"port": {
"description": "The port which the server will be run on",
"type": "number",
"default": 3000
}
},
"required": [],
Expand Down
1 change: 1 addition & 0 deletions packages/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@nrwl/webpack": "file:../webpack",
"@nrwl/workspace": "file:../workspace",
"chalk": "4.1.0",
"enquirer": "~2.3.6",
"tslib": "^2.3.0"
},
"publishConfig": {
Expand Down
12 changes: 0 additions & 12 deletions packages/node/src/generators/application/application.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,6 @@ describe('app', () => {
optimization: true,
extractLicenses: true,
inspect: false,
fileReplacements: [
{
replace: 'apps/my-node-app/src/environments/environment.ts',
with: 'apps/my-node-app/src/environments/environment.prod.ts',
},
],
},
},
},
Expand Down Expand Up @@ -434,12 +428,6 @@ describe('app', () => {
const buildTarget = project.architect.build;

expect(buildTarget.options.main).toEqual('apps/my-node-app/src/main.js');
expect(buildTarget.configurations.production.fileReplacements).toEqual([
{
replace: 'apps/my-node-app/src/environments/environment.js',
with: 'apps/my-node-app/src/environments/environment.prod.js',
},
]);
});

it('should generate js files for nested libs as well', async () => {
Expand Down
174 changes: 150 additions & 24 deletions packages/node/src/generators/application/application.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
addDependenciesToPackageJson,
addProjectConfiguration,
convertNxGenerator,
extractLayoutDirectory,
Expand All @@ -15,6 +16,7 @@ import {
TargetConfiguration,
toJS,
Tree,
updateJson,
updateProjectConfiguration,
updateTsConfigsToJs,
} from '@nrwl/devkit';
Expand All @@ -28,18 +30,30 @@ import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-ser
import { Schema } from './schema';
import { initGenerator } from '../init/init';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript';
import {
connectTypingsVersion,
connectVersion,
esbuildVersion,
expressTypingsVersion,
expressVersion,
fastifyVersion,
koaTypingsVersion,
koaVersion,
nxVersion,
} from '../../utils/versions';
import { prompt } from 'enquirer';

export interface NormalizedSchema extends Schema {
appProjectRoot: string;
parsedTags: string[];
}

function getBuildConfig(
function getWebpackBuildConfig(
project: ProjectConfiguration,
options: NormalizedSchema
): TargetConfiguration {
return {
executor: '@nrwl/webpack:webpack',
executor: `@nrwl/webpack:webpack`,
outputs: ['{options.outputPath}'],
options: {
target: 'node',
Expand All @@ -57,23 +71,31 @@ function getBuildConfig(
optimization: true,
extractLicenses: true,
inspect: false,
fileReplacements: [
{
replace: joinPathFragments(
project.sourceRoot,
'environments/environment' + (options.js ? '.js' : '.ts')
),
with: joinPathFragments(
project.sourceRoot,
'environments/environment.prod' + (options.js ? '.js' : '.ts')
),
},
],
},
},
};
}

function getEsBuildConfig(
project: ProjectConfiguration,
options: NormalizedSchema
): TargetConfiguration {
return {
executor: '@nrwl/esbuild:esbuild',
outputs: ['{options.outputPath}'],
options: {
outputPath: joinPathFragments('dist', options.appProjectRoot),
format: ['cjs'],
main: joinPathFragments(
project.sourceRoot,
'main' + (options.js ? '.js' : '.ts')
),
tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
assets: [joinPathFragments(project.sourceRoot, 'assets')],
},
};
}

function getServeConfig(options: NormalizedSchema): TargetConfiguration {
return {
executor: '@nrwl/js:node',
Expand All @@ -96,7 +118,10 @@ function addProject(tree: Tree, options: NormalizedSchema) {
targets: {},
tags: options.parsedTags,
};
project.targets.build = getBuildConfig(project, options);
project.targets.build =
options.bundler === 'esbuild'
? getEsBuildConfig(project, options)
: getWebpackBuildConfig(project, options);
project.targets.serve = getServeConfig(options);

addProjectConfiguration(
Expand All @@ -108,16 +133,41 @@ function addProject(tree: Tree, options: NormalizedSchema) {
}

function addAppFiles(tree: Tree, options: NormalizedSchema) {
generateFiles(tree, join(__dirname, './files/app'), options.appProjectRoot, {
tmpl: '',
name: options.name,
root: options.appProjectRoot,
offset: offsetFromRoot(options.appProjectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig(
generateFiles(
tree,
join(__dirname, './files/common'),
options.appProjectRoot,
{
...options,
tmpl: '',
name: options.name,
root: options.appProjectRoot,
offset: offsetFromRoot(options.appProjectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig(
tree,
options.appProjectRoot
),
}
);

if (options.framework) {
generateFiles(
tree,
options.appProjectRoot
),
});
join(__dirname, `./files/${options.framework}`),
options.appProjectRoot,
{
...options,
tmpl: '',
name: options.name,
root: options.appProjectRoot,
offset: offsetFromRoot(options.appProjectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig(
tree,
options.appProjectRoot
),
}
);
}
if (options.js) {
toJS(tree);
}
Expand Down Expand Up @@ -189,7 +239,73 @@ export async function addLintingToApplication(
return lintTask;
}

function addProjectDependencies(
tree: Tree,
options: NormalizedSchema
): GeneratorCallback {
const bundlers = {
webpack: {
'@nrwl/webpack': nxVersion,
},
esbuild: {
'@nrwl/esbuild': nxVersion,
esbuild: esbuildVersion,
},
};

const frameworkDependencies = {
express: {
express: expressVersion,
'@types/express': expressTypingsVersion,
},
koa: {
koa: koaVersion,
'@types/koa': koaTypingsVersion,
},
fastify: {
fastify: fastifyVersion,
},
connect: {
connect: connectVersion,
'@types/connect': connectTypingsVersion,
},
};
return addDependenciesToPackageJson(
tree,
{},
{
...frameworkDependencies[options.framework],
...bundlers[options.bundler],
}
);
}

function updateTsConfigOptions(tree: Tree, options: NormalizedSchema) {
// updatae tsconfig.app.json to typecheck default exports https://www.typescriptlang.org/tsconfig#esModuleInterop
updateJson(tree, `${options.appProjectRoot}/tsconfig.app.json`, (json) => ({
...json,
compilerOptions: {
...json.compilerOptions,
esModuleInterop: true,
},
}));
}

export async function applicationGenerator(tree: Tree, schema: Schema) {
// Prompt for bundler webpack / esbuild
if (schema.framework) {
schema.bundler = (
await prompt<{ bundler: 'esbuild' | 'webpack' }>([
{
message: 'What bundler would you like to use?',
type: 'select',
name: 'bundler',
choices: ['esbuild', 'webpack'],
},
])
).bundler;
}

const options = normalizeOptions(tree, schema);

const tasks: GeneratorCallback[] = [];
Expand All @@ -199,8 +315,12 @@ export async function applicationGenerator(tree: Tree, schema: Schema) {
});
tasks.push(initTask);

addProjectDependencies(tree, options);
addAppFiles(tree, options);
addProject(tree, options);
if (options.framework && options?.bundler === 'esbuild') {
updateTsConfigOptions(tree, options);
}

if (options.linter !== Linter.None) {
const lintTask = await addLintingToApplication(tree, {
Expand Down Expand Up @@ -251,6 +371,11 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-');

const appProjectRoot = joinPathFragments(appsDir, appDirectory);
if (options.framework) {
options.bundler = options.bundler ?? 'esbuild';
} else {
options.bundler = 'webpack';
}

const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim())
Expand All @@ -266,6 +391,7 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
parsedTags,
linter: options.linter ?? Linter.EsLint,
unitTestRunner: options.unitTestRunner ?? 'jest',
port: options.port ?? 3000,
};
}

Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('Hello World');
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import express from 'express';
const app = express();


app.get('/', (req, res) => {
res.send('Hello from Nrwl 🐳 API');
});

app.listen(<%= port %>, () => {
// Server is running
});
5 changes: 5 additions & 0 deletions packages/node/src/generators/application/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ export interface Schema {
pascalCaseFiles?: boolean;
setParserOptionsProject?: boolean;
standaloneConfig?: boolean;
bundler?: 'esbuild' | 'webpack';
framework?: NodeJsFrameWorks;
port?: number;
}

export type NodeJsFrameWorks = 'express' | 'koa' | 'fastify' | 'connect';
16 changes: 16 additions & 0 deletions packages/node/src/generators/application/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,22 @@
"standaloneConfig": {
"description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside `workspace.json`.",
"type": "boolean"
},
"bundler": {
"description": "Bundler which is used to package the application",
"type": "string",
"enum": ["esbuild", "webpack"],
"default": "esbuild"
},
"framework": {
"description": "Generate the node application using a framework",
"type": "string",
"enum": ["express", "koa", "fastify", "connect"]
},
"port": {
"description": "The port which the server will be run on",
"type": "number",
"default": 3000
}
},
"required": []
Expand Down
13 changes: 13 additions & 0 deletions packages/node/src/utils/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,16 @@ export const nxVersion = require('../../package.json').version;
export const tslibVersion = '^2.3.0';

export const typesNodeVersion = '18.7.1';

export const esbuildVersion = '^0.15.7';

export const expressVersion = '^4.18.1';
export const expressTypingsVersion = '4.17.13';

export const koaVersion = '2.14.1';
export const koaTypingsVersion = '2.13.5';

export const fastifyVersion = '4.11.0';

export const connectVersion = '3.7.0';
export const connectTypingsVersion = '3.4.35';