NX How to

npx create-nx-workspace
cd monorepo

React lib

npm install --save-dev @nx/react
npx nx g @nx/react:lib packages/{LIB_NAME} --linter eslint --bundler vite --style scss --unitTestRunner jest
npx nx run {LIB_NAME}:build

in vite.config.js, set all dependencies external:

    rollupOptions: {
      external: (id) => !id.startsWith('.') && !id.startsWith('/'),
    },

node lib

npm install -D @nx/node
npx nx g @nx/node:lib packages/{LIB_NAME} --buildable --linter eslint --unitTestRunner jest
npx nx run {LIB_NAME}:build

add compilerOptions in {LIB_NAME}/tsconfig.lib.json

{
  "compilerOptions": {
    "module": "ESNext", // Change from default (CommonJS) to ES module output
    "moduleResolution": "Node" // Use Node-style resolution for ES modules
  }
}

react app

npx nx g @nx/react:app packages/{APP_NAME} --style scss --bundler vite --linter eslint
npx nx run {APP_NAME}:build
npx nx run {APP_NAME}:serve

node app

npx nx g @nx/node:app  packages/{APP_NAME} --linter eslint --e2eTestRunner jest --framework none --unitTestRunner jest

In packages/{APP_NAME}/package.json :

  • (optional) set bundle to true and thirdParty to true
  • set react and react-dom as external to not bundle them when importing a lib that use jsx
  • set runBuildTargetDependencies in serve target to force recompilation on file changes

In packages/{APP_NAME}/tsconfig.app.json :

remove :

{
  "compilerOptions": {
    "module": "nodenext",
    "moduleResolution": "nodenext"
  }
}

if necessary, in package.json

"external": [
    "react",
    "react-dom",
    "*.css",
    "*.svg",
    "*.woff",
    "*.woff2",
    "*.eot",
    "*.ttf",
    "*.otf"
  ],
  ...
  "esbuildOptions": {
    "sourcemap": true,
    "outExtension": {
      ".js": ".js"
    },
    "loader": {
      ".css": "empty",
      ".svg": "empty",
      ".woff": "empty",
      ".woff2": "empty",
      ".eot": "empty",
      ".ttf": "empty",
      ".otf": "empty"
    }
  }

app node ESM

Rename all .ts file to .mts

Use .mjs extension in relative local import

In packages/{APP_NAME}/package.json :

  • add type: module
  • change outExtension from .js to .mjs
  • change main path from .ts to .mts
{
  "type": "module",
  "nx": {
    "targets": {
      "build": {
        "options": {
          "format": ["esm"],
          "bundle": true,
          "thirdParty": true,
          "external": ["react", "react-dom"],
          "main": "packages/app-node-1/src/main.mts",
          "esbuildOptions": {
            "outExtension": {
              ".js": ".mjs"
            }
          }
        },
        "configurations": {
          "production": {
            "esbuildOptions": {
              "outExtension": {
                ".js": ".mjs"
              }
            }
          }
        }
      },
      "serve": {
        "options": {
          "runBuildTargetDependencies": true
        }
      }
    }
  }
}

In packages/{APP_NAME}/tsconfig.app.json : Add .mts to include array

{
  "include": ["src/**/*.ts", "src/**/*.mts"]
}

storybook

npm install --save-dev @nx/storybook
npx nx g @nx/react:storybook-configuration {LIB_NAME}

add nodePolyfills in vite.config.ts

tailwind

npm install -D tailwindcss@3.4.17 autoprefixer postcss

create tailwind.config.js, postcss.config.js

# in monorepo root
npx tailwindcss init -p

edit tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
};

edit postcss.config.js

const { join } = require('path');

module.exports = {
  plugins: {
    tailwindcss: {
      config: join(__dirname, 'tailwind.config.js'),
    },
    autoprefixer: {},
  },
};

Create a scss file that import tailwind styles

packages/{LIB_NAME}/src/lib/index.scss :

@tailwind base;
@tailwind components;
@tailwind utilities;

Import it from index.ts

packages/{LIB_NAME}/src/index.ts :

import './lib/index.scss';

// ...
// export { ... } from ...

In package.json, declare style and export the resulting compiled css file

packages/{LIB_NAME}/package.json :

{
  "style": "./dist/style.css",
  "exports": {
    "./style": "./dist/style.css"
  }
}

In the app using the react lib, import the lib styles

packages/{APP_NAME}/src/main.tsx :

import '@holistix/{LIB_NAME}/style';

setup storybook

If necessary, create a storybook global wrapper for adding contexts and mock

packages/{LIB_NAME}/.storybook/global-wrapper.tsx :

// add common wrapper, mock context etc in this component
export const GlobalWrapper = (Story: any) => <Story />;

Import the index.scss (containing tailwind styles) file from preview.ts

packages/{LIB_NAME}/.storybook/preview.ts :

import type { Preview } from '@storybook/react';
import { GlobalWrapper } from './global-wrapper';

import '../src/lib/index.scss';

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
    backgrounds: {
      default: 'dark',
      values: [
        {
          name: 'dark',
          value: '#222',
        },
      ],
    },
  },
  decorators: [GlobalWrapper],
  tags: [],
};

export default preview;

json

add "resolveJsonModule": true in compilerOptions on tsconfig.app.json or tsconfig.lib.json

{
  "compilerOptions": {
    "resolveJsonModule": true
  },
  "files": [
    "src/app/oas30.json",
    "src/app/exec-pipes.json",
    "src/app/data-connections.json",
    "src/app/sql-api-pg.json"
  ],
  "include": ["src/**/*.ts"]
}

Others Actions

build all

npx nx run-many -t build

Rename lib

nx g @nrwl/workspace:mv --project XXXXXXX --destination YYYYYYY

Delete remove

nx g remove three-flow

Update everything

# upgrade nodejs
# remove package.json: overrides: {}
npm install -g npm@latest
nx migrate latest
nx migrate --run-migrations
npm install --global npm-check-updates@latest
npm-check-updates
npm install xxxxx@X.Y.Z
npm audit
# package.json: overrides: {}

Run NX monorepo jest tests

npx nx run-many --all --target=test --parallel