nrwl / nx

Smart Monorepos · Fast CI
https://nx.dev
MIT License
23.17k stars 2.31k forks source link

Differences between @nx/node with Express and @nx/express? #21981

Open luchillo17 opened 6 months ago

luchillo17 commented 6 months ago

Documentation issue

Is there a specific documentation page you are reporting?

@nx/express @nx/node generator express framework

Additional context or description

Originally I asked for context in Discord Default express app vs @nx/express generator, why different setup?.

In basic terms the default Express app we get when we create a new monorepo will be created using the @nx/node generator with the framework option set to Express, on the other hand, a plugin explicitly made for Express exists, @nx/express and the application it generates is different than the one @nx/node generates.

In simple terms, one ends up with ESBuild as a bundler, the other with Webpack by default, and as the only option.

A few things should be more explicit in the docs:

moatorres commented 6 months ago

As far as I can tell, @nx/express will:

  1. Automatically add express types

https://github.com/nrwl/nx/blob/26b266faf46b1e72ba79b1bdbed8a66d896fa864/packages/express/src/generators/application/application.ts#L22-L31

  1. Add a boilerplate Express server into main.ts file

https://github.com/nrwl/nx/blob/26b266faf46b1e72ba79b1bdbed8a66d896fa864/packages/express/src/generators/application/application.ts#L33-L39

  1. Set webpack bundler by default

https://github.com/nrwl/nx/blob/26b266faf46b1e72ba79b1bdbed8a66d896fa864/packages/express/src/generators/application/application.ts#L79-L83

Other than that, they're pretty much the same thing. There are no custom executors. Answering your questions:

I personally prefer having a custom workspace plugin that extends @nx/node with a bespoke express boilerplate that includes (opinionated) middleware and configuration presets, similarly to what @nx/express does.

TL;DR @nx/express appeals to the widespread popularity of express and may indirectly attract/address users not familiarised with nx inner workings.

luchillo17 commented 6 months ago

Hi @moatorres thanks for the extensive explanation, let me state a few things about your input:

  1. Both generate tsconfig.json, tsconfig.app.json, and tsconfig.spec.json, and as far as I can tell, are almost identical.
  2. Both versions have a main.ts file, the biggest difference in their template is where the constant for port is, and the @nx/node version has a host constant, while the @nx/express adds assets static serving by default, and I didn't have to tell it to use TS, that is the default for both it seems (1 point for @nx/express for handling static assets from the get-go, @nx/node does have an assets option in the build target, but I don't see it working, my small test asset is nowhere to be seen in the dist folder, it seems usually you do this kind of stuff with esbuild-plugin-copy, not sure if its used by @nx/esbuild under the hood or not...).
  3. The biggest difference to me seems to be the bundler, while one uses Webpack, the other uses ESBuild with the default config for it coming from project.json targets, the @nx/node version has a 10-line build target and a serve target that uses @nx/js:node executor, I would guess that would be equivalent to the @nx/express Webpack config file.

image

I have felt the difference mainly in the bundler, I had tons of issues enabling true ESM for Express when using Webpack (not saying is not possible, just that I couldn't make it work myself in the short time I tried locally), while ESbuild was easier to enable and enforce ESM (why ESM? top-level await and faster compile times, I don't care for ancient browsers, honestly thinking whether I should just ignore @nx/express config and replace it with Vite or something, and there was also 1 library I was using that had an issue and the only version that worked was the ESM one).

moatorres commented 6 months ago

Yup. It can be confusing. I have no idea why Webpack is used on @nx/express's application generator configuration.

What I sometimes end up doing is extending plugins with:

nx g @nx/plugin:plugin my-plugin

Then editing the relevant generator.ts plugin file:

import type { Tree } from '@nx/devkit'
import { formatFiles, updateJson } from '@nx/devkit'
import { applicationGenerator as nodeApplicationGenerator } from '@nx/node'

import type { Schema } from './schema'
import { initGenerator } from '../init/init'

export async function applicationGenerator(tree: Tree, schema: Schema) {
  const options = normalizeOptions(tree, schema)

  const initTask = await initGenerator(tree, {
    ...options,
    skipFormat: true,
  })

  const applicationTask = await nodeApplicationGenerator(tree, {
    ...schema,
    skipFormat: true,
  })

  addTypes(tree, options)

  if (!options.skipFormat) await formatFiles(tree)

  return async () => {
    await initTask()
    await applicationTask()
  }
}

function addTypes(tree: Tree, options: NormalizedSchema) {
  updateJson(tree, joinPathFragments(options.projectRoot, 'tsconfig.app.json'), (json) => {
    json.compilerOptions.types = [...json.compilerOptions.types, 'express']
    return json
  })
}