feat(node): support nodejs frameworks (#14199)

This commit is contained in:
Nicholas Cunningham 2023-01-06 15:04:11 -07:00 committed by GitHub
parent 3db0bf8a18
commit a631af7b62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 213 additions and 43 deletions

View File

@ -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": [],

View File

@ -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": {

View File

@ -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',
},
],
},
},
},
@ -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 () => {

View File

@ -1,4 +1,5 @@
import {
addDependenciesToPackageJson,
addProjectConfiguration,
convertNxGenerator,
extractLayoutDirectory,
@ -15,6 +16,7 @@ import {
TargetConfiguration,
toJS,
Tree,
updateJson,
updateProjectConfiguration,
updateTsConfigsToJs,
} from '@nrwl/devkit';
@ -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',
@ -57,19 +71,27 @@ 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')],
},
};
}
@ -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(
@ -108,7 +133,12 @@ function addProject(tree: Tree, options: NormalizedSchema) {
}
function addAppFiles(tree: Tree, options: NormalizedSchema) {
generateFiles(tree, join(__dirname, './files/app'), options.appProjectRoot, {
generateFiles(
tree,
join(__dirname, './files/common'),
options.appProjectRoot,
{
...options,
tmpl: '',
name: options.name,
root: options.appProjectRoot,
@ -117,7 +147,27 @@ function addAppFiles(tree: Tree, options: NormalizedSchema) {
tree,
options.appProjectRoot
),
});
}
);
if (options.framework) {
generateFiles(
tree,
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);
}
@ -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[] = [];
@ -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, {
@ -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())
@ -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,
};
}

View File

@ -1,3 +0,0 @@
export const environment = {
production: true
};

View File

@ -1,3 +0,0 @@
export const environment = {
production: false
};

View File

@ -1 +0,0 @@
console.log('Hello World!');

View File

@ -0,0 +1 @@
console.log('Hello World');

View File

@ -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
});

View File

@ -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';

View File

@ -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": []

View File

@ -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';