feat(rspack): move rspack into main nx repo (#27969)
- feat: add rspack plugin (#143) - feat: add rspack plugin (#143) - feat(rspack): update to latest rspack version (#159) - feat(rspack): add missing features (less/sass/stylus, assets, etc.) (#160) - feat(rspack): add missing features (less/sass/stylus, assets, etc.) (#160) - feat(rspack): clean-up project setup (#161) - feat(rspack): clean-up project setup (#161) - fix(rspack): use correct app dir when generating non-root projects (#162) - fix(rspack): use correct app dir when generating non-root projects (#162) - chore(repo): migrate to Nx 15.8.5 (#169) - feat(rspack): update and pin rspack to 0.1.0 (#173) - fix(rspack): fix rspack build - chore(rspack): remove comment (#175) - feat(rspack): set mode in configuration and expose option (#177) - fix(rspack): handle existing stylePreprocessorOptions (#182) - fix(rspack): add dependency to ajv-keywords that match the version used by rspack (#187) - fix(rspack): pass devServer options to devServer (#193) - fix(rspack): set externals for target node (#194) - feat(rspack): install latest patch when configuring (#195) - fix(rspack): add withWeb if web app (#200) - chore(repo): fix release script (#202) - chore(repo): fix release script (#202) - feat(rspack): configuration generator better ux (#201) - feat(rspack): builder returns outfile (#207) - fix(rspack): use ensureTypescript before tsquery (#215) - feat(rspack): simplify app generator (#212) - feat(rspack): simplify app generator (#212) - fix(rspack): implement watch mode (#217) - fix(rspack): do not force cssmodules (#222) - fix(rspack): use builtin minify instead (#172) - fix(rspack): use built-in tsconfig paths support (#227) - fix(rspack): add back `resolve.alias` configuration since `resolve.tsConfigPaths` seem to be incorrect in some scenarios (#229) - feat(misc): update to Nx 16 and rescoped packages (#235) - feat(misc): update to Nx 16 and rescoped packages (#235) - fix(misc): replace missed references to @nrwl scope (#239) - chore(repo): add legacy packages for nx rescope (#238) - chore(repo): add legacy packages for nx rescope (#238) - fix(repo): fix publishing for legacy packages (#240) - fix(repo): fix publishing for legacy packages (#240) - fix(misc): target commonjs for legacy packages (#241) - fix(repo): add json files to assets (#243) - chore(repo): update to 16.0.3 (#244) - chore(repo): update to nx 16.2.1 (#271) - fix(rspack): lock version to 0.1.11 (#279) - fix(rspack): refine output filename patterns (#280) - chore(rspack): update to latest (#278) - feat(rspack): Add extractLicenses option to rspack's project configuration (#230) - feat(rspack): Add extractLicenses option to rspack's project configuration (#230) - fix(rspack): add missing license-webpack-plugin dependency (#301) - chore(repo): upgrade to nx 16.6.0 (#319) - fix(rspack): add fileReplacements support (#231) - chore(reop): update nx to 16.7.1 (#325) - chore(rspack): add jest babel config to e2e (#321) - chore: don't use rspack internal module (#328) - chore(repo): update nx to 16.8.1 (#335) - feat(rspack): add typecheck (#338) - chore(repo): update nx to 17.0.1 (#342) - feat(rspack): add generatePackageJson plugin (#341) - feat(rspack): add generatePackageJson plugin (#341) - feat: upgrade rspack to 0.4.4 (#352) - fix(rspack): Add missing peer dep (#372) - chore(repo): migrate to latest nx (#376) - chore(repo): migrate to latest nx (#376) - feat(rspack): update rspack to install the latest version (#379) - feat(rspack): add option to keep existing versions of packages for init generator (#378) - fix(rspack): do not depend directly on ajv to allow for correct hoisting (#384) - fix(rspack): ensure react-refresh is installed (#385) - fix(rspack): User port should be respected. (#387) - feat(rspack_: update rspack to install latest version (#389) - feat(rspack): support object configs (#402) - feat(rspack): add crystal plugin for inferring projects (#407) - feat(rspack): add crystal plugin for inferring projects (#407) - feat(rspack): bump to latest rspack (#412) - fix(rspack): add postcss-loader for css files (#415) - feat(rspack): add module federation support (#416) - feat(rspack): add module federation support (#416) - fix(rspack): add hook for dev server to log when compilation completed (#417) - feat(rspack): add module-federation-static-server (#418) - fix(rspack): ensure process is default import (#420) - chore(repo): move packages/rspack to packages/rspack to prepare to be imported - chore(repo): move packages-legacy/rspack to packages-legacy/rspack to prepare to be imported - chore(repo): move e2e/rspack-e2e to e2e/rspack to prepare to be imported - chore(repo): fix e2e setup - chore(repo): add rspack commit scope - chore(rspack): configure correctly - chore(rspack): final fixes - docs(rspack): add docs for rspack - chore(rspack): fix rspack e2e - chore(react): add rspack bundler test <!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
commit
e76c7d1428
@ -9875,6 +9875,130 @@
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "rspack",
|
||||
"path": "/nx-api/rspack",
|
||||
"name": "rspack",
|
||||
"children": [
|
||||
{
|
||||
"id": "documents",
|
||||
"path": "/nx-api/rspack/documents",
|
||||
"name": "documents",
|
||||
"children": [
|
||||
{
|
||||
"name": "Overview",
|
||||
"path": "/nx-api/rspack/documents/overview",
|
||||
"id": "overview",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "executors",
|
||||
"path": "/nx-api/rspack/executors",
|
||||
"name": "executors",
|
||||
"children": [
|
||||
{
|
||||
"id": "rspack",
|
||||
"path": "/nx-api/rspack/executors/rspack",
|
||||
"name": "rspack",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "dev-server",
|
||||
"path": "/nx-api/rspack/executors/dev-server",
|
||||
"name": "dev-server",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "ssr-dev-server",
|
||||
"path": "/nx-api/rspack/executors/ssr-dev-server",
|
||||
"name": "ssr-dev-server",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "module-federation-dev-server",
|
||||
"path": "/nx-api/rspack/executors/module-federation-dev-server",
|
||||
"name": "module-federation-dev-server",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "module-federation-ssr-dev-server",
|
||||
"path": "/nx-api/rspack/executors/module-federation-ssr-dev-server",
|
||||
"name": "module-federation-ssr-dev-server",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "module-federation-static-server",
|
||||
"path": "/nx-api/rspack/executors/module-federation-static-server",
|
||||
"name": "module-federation-static-server",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "generators",
|
||||
"path": "/nx-api/rspack/generators",
|
||||
"name": "generators",
|
||||
"children": [
|
||||
{
|
||||
"id": "configuration",
|
||||
"path": "/nx-api/rspack/generators/configuration",
|
||||
"name": "configuration",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "init",
|
||||
"path": "/nx-api/rspack/generators/init",
|
||||
"name": "init",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "preset",
|
||||
"path": "/nx-api/rspack/generators/preset",
|
||||
"name": "preset",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "application",
|
||||
"path": "/nx-api/rspack/generators/application",
|
||||
"name": "application",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "storybook",
|
||||
"path": "/nx-api/storybook",
|
||||
|
||||
@ -2877,6 +2877,122 @@
|
||||
},
|
||||
"path": "/nx-api/rollup"
|
||||
},
|
||||
"rspack": {
|
||||
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||
"name": "rspack",
|
||||
"packageName": "@nx/rspack",
|
||||
"description": "The Nx Plugin for Rspack contains executors and generators that support building applications using Rspack.",
|
||||
"documents": {
|
||||
"/nx-api/rspack/documents/overview": {
|
||||
"id": "overview",
|
||||
"name": "Overview",
|
||||
"description": "The Nx Plugin for Rspack contains executors and generators that support building applications using Rspack.",
|
||||
"file": "generated/packages/rspack/documents/overview",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/nx-api/rspack/documents/overview",
|
||||
"tags": [],
|
||||
"originalFilePath": "shared/packages/rspack/rspack-plugin"
|
||||
}
|
||||
},
|
||||
"root": "/packages/rspack",
|
||||
"source": "/packages/rspack/src",
|
||||
"executors": {
|
||||
"/nx-api/rspack/executors/rspack": {
|
||||
"description": "Run Rspack via an executor for a project.",
|
||||
"file": "generated/packages/rspack/executors/rspack.json",
|
||||
"hidden": false,
|
||||
"name": "rspack",
|
||||
"originalFilePath": "/packages/rspack/src/executors/rspack/schema.json",
|
||||
"path": "/nx-api/rspack/executors/rspack",
|
||||
"type": "executor"
|
||||
},
|
||||
"/nx-api/rspack/executors/dev-server": {
|
||||
"description": "Run @rspack/dev-server to serve a project.",
|
||||
"file": "generated/packages/rspack/executors/dev-server.json",
|
||||
"hidden": false,
|
||||
"name": "dev-server",
|
||||
"originalFilePath": "/packages/rspack/src/executors/dev-server/schema.json",
|
||||
"path": "/nx-api/rspack/executors/dev-server",
|
||||
"type": "executor"
|
||||
},
|
||||
"/nx-api/rspack/executors/ssr-dev-server": {
|
||||
"description": "Serve a SSR application.",
|
||||
"file": "generated/packages/rspack/executors/ssr-dev-server.json",
|
||||
"hidden": false,
|
||||
"name": "ssr-dev-server",
|
||||
"originalFilePath": "/packages/rspack/src/executors/ssr-dev-server/schema.json",
|
||||
"path": "/nx-api/rspack/executors/ssr-dev-server",
|
||||
"type": "executor"
|
||||
},
|
||||
"/nx-api/rspack/executors/module-federation-dev-server": {
|
||||
"description": "Serve a host or remote application.",
|
||||
"file": "generated/packages/rspack/executors/module-federation-dev-server.json",
|
||||
"hidden": false,
|
||||
"name": "module-federation-dev-server",
|
||||
"originalFilePath": "/packages/rspack/src/executors/module-federation-dev-server/schema.json",
|
||||
"path": "/nx-api/rspack/executors/module-federation-dev-server",
|
||||
"type": "executor"
|
||||
},
|
||||
"/nx-api/rspack/executors/module-federation-ssr-dev-server": {
|
||||
"description": "Serve a host application along with it's known remotes.",
|
||||
"file": "generated/packages/rspack/executors/module-federation-ssr-dev-server.json",
|
||||
"hidden": false,
|
||||
"name": "module-federation-ssr-dev-server",
|
||||
"originalFilePath": "/packages/rspack/src/executors/module-federation-ssr-dev-server/schema.json",
|
||||
"path": "/nx-api/rspack/executors/module-federation-ssr-dev-server",
|
||||
"type": "executor"
|
||||
},
|
||||
"/nx-api/rspack/executors/module-federation-static-server": {
|
||||
"description": "Serve a host and its remotes statically.",
|
||||
"file": "generated/packages/rspack/executors/module-federation-static-server.json",
|
||||
"hidden": false,
|
||||
"name": "module-federation-static-server",
|
||||
"originalFilePath": "/packages/rspack/src/executors/module-federation-static-server/schema.json",
|
||||
"path": "/nx-api/rspack/executors/module-federation-static-server",
|
||||
"type": "executor"
|
||||
}
|
||||
},
|
||||
"generators": {
|
||||
"/nx-api/rspack/generators/configuration": {
|
||||
"description": "Rspack configuration generator.",
|
||||
"file": "generated/packages/rspack/generators/configuration.json",
|
||||
"hidden": false,
|
||||
"name": "configuration",
|
||||
"originalFilePath": "/packages/rspack/src/generators/configuration/schema.json",
|
||||
"path": "/nx-api/rspack/generators/configuration",
|
||||
"type": "generator"
|
||||
},
|
||||
"/nx-api/rspack/generators/init": {
|
||||
"description": "Rspack init generator.",
|
||||
"file": "generated/packages/rspack/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
"originalFilePath": "/packages/rspack/src/generators/init/schema.json",
|
||||
"path": "/nx-api/rspack/generators/init",
|
||||
"type": "generator"
|
||||
},
|
||||
"/nx-api/rspack/generators/preset": {
|
||||
"description": "React preset generator.",
|
||||
"file": "generated/packages/rspack/generators/preset.json",
|
||||
"hidden": true,
|
||||
"name": "preset",
|
||||
"originalFilePath": "/packages/rspack/src/generators/preset/schema.json",
|
||||
"path": "/nx-api/rspack/generators/preset",
|
||||
"type": "generator"
|
||||
},
|
||||
"/nx-api/rspack/generators/application": {
|
||||
"description": "React application generator.",
|
||||
"file": "generated/packages/rspack/generators/application.json",
|
||||
"hidden": false,
|
||||
"name": "application",
|
||||
"originalFilePath": "/packages/rspack/src/generators/application/schema.json",
|
||||
"path": "/nx-api/rspack/generators/application",
|
||||
"type": "generator"
|
||||
}
|
||||
},
|
||||
"path": "/nx-api/rspack"
|
||||
},
|
||||
"storybook": {
|
||||
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||
"name": "storybook",
|
||||
|
||||
@ -2852,6 +2852,121 @@
|
||||
"root": "/packages/rollup",
|
||||
"source": "/packages/rollup/src"
|
||||
},
|
||||
{
|
||||
"description": "The Nx Plugin for Rspack contains executors and generators that support building applications using Rspack.",
|
||||
"documents": [
|
||||
{
|
||||
"id": "overview",
|
||||
"name": "Overview",
|
||||
"description": "The Nx Plugin for Rspack contains executors and generators that support building applications using Rspack.",
|
||||
"file": "generated/packages/rspack/documents/overview",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "rspack/documents/overview",
|
||||
"tags": [],
|
||||
"originalFilePath": "shared/packages/rspack/rspack-plugin"
|
||||
}
|
||||
],
|
||||
"executors": [
|
||||
{
|
||||
"description": "Run Rspack via an executor for a project.",
|
||||
"file": "generated/packages/rspack/executors/rspack.json",
|
||||
"hidden": false,
|
||||
"name": "rspack",
|
||||
"originalFilePath": "/packages/rspack/src/executors/rspack/schema.json",
|
||||
"path": "rspack/executors/rspack",
|
||||
"type": "executor"
|
||||
},
|
||||
{
|
||||
"description": "Run @rspack/dev-server to serve a project.",
|
||||
"file": "generated/packages/rspack/executors/dev-server.json",
|
||||
"hidden": false,
|
||||
"name": "dev-server",
|
||||
"originalFilePath": "/packages/rspack/src/executors/dev-server/schema.json",
|
||||
"path": "rspack/executors/dev-server",
|
||||
"type": "executor"
|
||||
},
|
||||
{
|
||||
"description": "Serve a SSR application.",
|
||||
"file": "generated/packages/rspack/executors/ssr-dev-server.json",
|
||||
"hidden": false,
|
||||
"name": "ssr-dev-server",
|
||||
"originalFilePath": "/packages/rspack/src/executors/ssr-dev-server/schema.json",
|
||||
"path": "rspack/executors/ssr-dev-server",
|
||||
"type": "executor"
|
||||
},
|
||||
{
|
||||
"description": "Serve a host or remote application.",
|
||||
"file": "generated/packages/rspack/executors/module-federation-dev-server.json",
|
||||
"hidden": false,
|
||||
"name": "module-federation-dev-server",
|
||||
"originalFilePath": "/packages/rspack/src/executors/module-federation-dev-server/schema.json",
|
||||
"path": "rspack/executors/module-federation-dev-server",
|
||||
"type": "executor"
|
||||
},
|
||||
{
|
||||
"description": "Serve a host application along with it's known remotes.",
|
||||
"file": "generated/packages/rspack/executors/module-federation-ssr-dev-server.json",
|
||||
"hidden": false,
|
||||
"name": "module-federation-ssr-dev-server",
|
||||
"originalFilePath": "/packages/rspack/src/executors/module-federation-ssr-dev-server/schema.json",
|
||||
"path": "rspack/executors/module-federation-ssr-dev-server",
|
||||
"type": "executor"
|
||||
},
|
||||
{
|
||||
"description": "Serve a host and its remotes statically.",
|
||||
"file": "generated/packages/rspack/executors/module-federation-static-server.json",
|
||||
"hidden": false,
|
||||
"name": "module-federation-static-server",
|
||||
"originalFilePath": "/packages/rspack/src/executors/module-federation-static-server/schema.json",
|
||||
"path": "rspack/executors/module-federation-static-server",
|
||||
"type": "executor"
|
||||
}
|
||||
],
|
||||
"generators": [
|
||||
{
|
||||
"description": "Rspack configuration generator.",
|
||||
"file": "generated/packages/rspack/generators/configuration.json",
|
||||
"hidden": false,
|
||||
"name": "configuration",
|
||||
"originalFilePath": "/packages/rspack/src/generators/configuration/schema.json",
|
||||
"path": "rspack/generators/configuration",
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "Rspack init generator.",
|
||||
"file": "generated/packages/rspack/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
"originalFilePath": "/packages/rspack/src/generators/init/schema.json",
|
||||
"path": "rspack/generators/init",
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "React preset generator.",
|
||||
"file": "generated/packages/rspack/generators/preset.json",
|
||||
"hidden": true,
|
||||
"name": "preset",
|
||||
"originalFilePath": "/packages/rspack/src/generators/preset/schema.json",
|
||||
"path": "rspack/generators/preset",
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "React application generator.",
|
||||
"file": "generated/packages/rspack/generators/application.json",
|
||||
"hidden": false,
|
||||
"name": "application",
|
||||
"originalFilePath": "/packages/rspack/src/generators/application/schema.json",
|
||||
"path": "rspack/generators/application",
|
||||
"type": "generator"
|
||||
}
|
||||
],
|
||||
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||
"name": "rspack",
|
||||
"packageName": "@nx/rspack",
|
||||
"root": "/packages/rspack",
|
||||
"source": "/packages/rspack/src"
|
||||
},
|
||||
{
|
||||
"description": "The Nx Plugin for Storybook contains executors and generators for allowing your workspace to use the powerful Storybook integration testing & documenting capabilities.",
|
||||
"documents": [
|
||||
|
||||
98
docs/generated/packages/rspack/documents/overview.md
Normal file
98
docs/generated/packages/rspack/documents/overview.md
Normal file
@ -0,0 +1,98 @@
|
||||
---
|
||||
title: Overview of the Nx Rspack Plugin
|
||||
description: The Nx Plugin for Rspack contains executors, generators, and utilities for managing Rspack projects in an Nx Workspace.
|
||||
---
|
||||
|
||||
The Nx Plugin for Rspack contains executors, generators, and utilities for managing Rspack projects in an Nx Workspace.
|
||||
|
||||
## Setting Up @nx/rspack
|
||||
|
||||
### Installation
|
||||
|
||||
{% callout type="note" title="Keep Nx Package Versions In Sync" %}
|
||||
Make sure to install the `@nx/rspack` version that matches the version of `nx` in your repository. If the version numbers get out of sync, you can encounter some difficult to debug errors. You can [fix Nx version mismatches with this recipe](/recipes/tips-n-tricks/keep-nx-versions-in-sync).
|
||||
{% /callout %}
|
||||
|
||||
In any Nx workspace, you can install `@nx/rspack` by running the following command:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Nx 18+" %}
|
||||
|
||||
```shell {% skipRescope=true %}
|
||||
nx add @nx/rspack
|
||||
```
|
||||
|
||||
This will install the correct version of `@nx/rspack`.
|
||||
|
||||
### How @nx/rspack Infers Tasks
|
||||
|
||||
The `@nx/rspack` plugin will create a task for any project that has a Rspack configuration file present. Any of the following files will be recognized as a Rspack configuration file:
|
||||
|
||||
- `rspack.config.js`
|
||||
- `rspack.config.ts`
|
||||
- `rspack.config.mjs`
|
||||
- `rspack.config.mts`
|
||||
- `rspack.config.cjs`
|
||||
- `rspack.config.cts`
|
||||
|
||||
### View Inferred Tasks
|
||||
|
||||
To view inferred tasks for a project, open the [project details view](/concepts/inferred-tasks) in Nx Console or run `nx show project my-project --web` in the command line.
|
||||
|
||||
### @nx/rspack Configuration
|
||||
|
||||
The `@nx/rspack/plugin` is configured in the `plugins` array in `nx.json`.
|
||||
|
||||
```json {% fileName="nx.json" %}
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/rspack/plugin",
|
||||
"options": {
|
||||
"buildTargetName": "build",
|
||||
"previewTargetName": "preview",
|
||||
"serveTargetName": "serve",
|
||||
"serveStaticTargetName": "serve-static"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The `buildTargetName`, `previewTargetName`, `serveTargetName` and `serveStaticTargetName` options control the names of the inferred Rspack tasks. The default names are `build`, `preview`, `serve` and `serve-static`.
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="Nx < 18" %}
|
||||
|
||||
Install the `@nx/rspack` package with your package manager.
|
||||
|
||||
```shell
|
||||
npm add -D @nx/rspack
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## Using @nx/rspack
|
||||
|
||||
### Generate a new project using Rspack
|
||||
|
||||
You can generate a [React](/nx-api/react) application or library that uses Rspack. The [`@nx/react:app`](/nx-api/react/generators/application) and [`@nx/react:lib`](/nx-api/react/generators/library) generators accept the `bundler` option, where you can pass `rspack`. This will generate a new application configured to use Rspack, and it will also install all the necessary dependencies, including the `@nx/rspack` plugin.
|
||||
|
||||
To generate a React application using Rspack, run the following:
|
||||
|
||||
```bash
|
||||
nx g @nx/react:app my-app --bundler=rspack
|
||||
```
|
||||
|
||||
To generate a React library using Rspack, run the following:
|
||||
|
||||
```bash
|
||||
nx g @nx/react:lib my-lib --bundler=rspack
|
||||
```
|
||||
|
||||
### Modify an existing React project to use Rspack
|
||||
|
||||
You can use the `@nx/rspack:configuration` generator to change your React to use Rspack. This generator will modify your project's configuration to use Rspack, and it will also install all the necessary dependencies, including the `@nx/rspack` plugin.
|
||||
|
||||
You can read more about this generator on the [`@nx/rspack:configuration`](/nx-api/rspack/generators/configuration) generator page.
|
||||
55
docs/generated/packages/rspack/executors/dev-server.json
Normal file
55
docs/generated/packages/rspack/executors/dev-server.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "dev-server",
|
||||
"implementation": "/packages/rspack/src/executors/dev-server/dev-server.impl.ts",
|
||||
"schema": {
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"version": 2,
|
||||
"title": "Rspack dev-server executor",
|
||||
"description": "Run @rspack/dev-server to serve a project.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"buildTarget": {
|
||||
"type": "string",
|
||||
"description": "The build target for rspack."
|
||||
},
|
||||
"port": {
|
||||
"type": "number",
|
||||
"description": "The port to for the dev-server to listen on."
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"description": "Mode to run the server in.",
|
||||
"enum": ["development", "production", "none"]
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
"description": "Host to listen on.",
|
||||
"default": "localhost"
|
||||
},
|
||||
"ssl": {
|
||||
"type": "boolean",
|
||||
"description": "Serve using `HTTPS`.",
|
||||
"default": false
|
||||
},
|
||||
"sslKey": {
|
||||
"type": "string",
|
||||
"description": "SSL key to use for serving `HTTPS`."
|
||||
},
|
||||
"sslCert": {
|
||||
"type": "string",
|
||||
"description": "SSL certificate to use for serving `HTTPS`."
|
||||
},
|
||||
"publicHost": {
|
||||
"type": "string",
|
||||
"description": "Public URL where the application will be served."
|
||||
}
|
||||
},
|
||||
"required": ["buildTarget"],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Run @rspack/dev-server to serve a project.",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/rspack/src/executors/dev-server/schema.json",
|
||||
"type": "executor"
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
{
|
||||
"name": "module-federation-dev-server",
|
||||
"implementation": "/packages/rspack/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts",
|
||||
"schema": {
|
||||
"version": 2,
|
||||
"outputCapture": "direct-nodejs",
|
||||
"title": "Rspack Module Federation Dev Server",
|
||||
"description": "Serve a module federation application.",
|
||||
"cli": "nx",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"devRemotes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{ "type": "string" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"remoteName": { "type": "string" },
|
||||
"configuration": { "type": "string" }
|
||||
},
|
||||
"required": ["remoteName"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "List of remote applications to run in development mode (i.e. using serve target).",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipRemotes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode. This will not remove the remotes from the `module-federation.config` file, and therefore the application may still try to fetch these remotes.\nThis option is useful if you have other means for serving the `remote` application(s).\n**NOTE:** Remotes that are not in the workspace will be skipped automatically.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"buildTarget": {
|
||||
"type": "string",
|
||||
"description": "Target which builds the application.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"port": {
|
||||
"type": "number",
|
||||
"description": "Port to listen on.",
|
||||
"default": 4200,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
"description": "Host to listen on.",
|
||||
"default": "localhost"
|
||||
},
|
||||
"ssl": {
|
||||
"type": "boolean",
|
||||
"description": "Serve using `HTTPS`.",
|
||||
"default": false
|
||||
},
|
||||
"sslKey": {
|
||||
"type": "string",
|
||||
"description": "SSL key to use for serving `HTTPS`."
|
||||
},
|
||||
"sslCert": {
|
||||
"type": "string",
|
||||
"description": "SSL certificate to use for serving `HTTPS`."
|
||||
},
|
||||
"publicHost": {
|
||||
"type": "string",
|
||||
"description": "Public URL where the application will be served."
|
||||
},
|
||||
"static": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to use a static file server instead of the rspack-dev-server. This should be used for remote applications that are also host applications."
|
||||
},
|
||||
"isInitialHost": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the host that is running this executor is the first in the project tree to do so.",
|
||||
"default": true,
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"parallel": {
|
||||
"type": "number",
|
||||
"description": "Max number of parallel processes for building static remotes"
|
||||
},
|
||||
"staticRemotesPort": {
|
||||
"type": "number",
|
||||
"description": "The port at which to serve the file-server for the static remotes."
|
||||
},
|
||||
"pathToManifestFile": {
|
||||
"type": "string",
|
||||
"description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root."
|
||||
}
|
||||
},
|
||||
"presets": []
|
||||
},
|
||||
"description": "Serve a host or remote application.",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/rspack/src/executors/module-federation-dev-server/schema.json",
|
||||
"type": "executor"
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
{
|
||||
"name": "module-federation-ssr-dev-server",
|
||||
"implementation": "/packages/rspack/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts",
|
||||
"schema": {
|
||||
"version": 2,
|
||||
"outputCapture": "direct-nodejs",
|
||||
"title": "Module Federation SSR Dev Server",
|
||||
"description": "Serve a SSR host application along with its known remotes.",
|
||||
"cli": "nx",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"browserTarget": {
|
||||
"type": "string",
|
||||
"description": "Target which builds the browser application.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"serverTarget": {
|
||||
"type": "string",
|
||||
"description": "Target which builds the server application.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"port": {
|
||||
"type": "number",
|
||||
"description": "The port to be set on `process.env.PORT` for use in the server.",
|
||||
"default": 4200,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"devRemotes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "List of remote applications to run in development mode (i.e. using serve target).",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipRemotes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
"description": "Host to listen on.",
|
||||
"default": "localhost"
|
||||
},
|
||||
"staticRemotesPort": {
|
||||
"type": "number",
|
||||
"description": "The port at which to serve the file-server for the static remotes."
|
||||
},
|
||||
"pathToManifestFile": {
|
||||
"type": "string",
|
||||
"description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root."
|
||||
},
|
||||
"ssl": {
|
||||
"type": "boolean",
|
||||
"description": "Serve using HTTPS.",
|
||||
"default": false
|
||||
},
|
||||
"sslKey": {
|
||||
"type": "string",
|
||||
"description": "SSL key to use for serving HTTPS."
|
||||
},
|
||||
"sslCert": {
|
||||
"type": "string",
|
||||
"description": "SSL certificate to use for serving HTTPS."
|
||||
},
|
||||
"publicHost": {
|
||||
"type": "string",
|
||||
"description": "Public URL where the application will be served."
|
||||
},
|
||||
"isInitialHost": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the host that is running this executor is the first in the project tree to do so.",
|
||||
"default": true,
|
||||
"x-priority": "internal"
|
||||
}
|
||||
},
|
||||
"required": ["browserTarget", "serverTarget"],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Serve a host application along with it's known remotes.",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/rspack/src/executors/module-federation-ssr-dev-server/schema.json",
|
||||
"type": "executor"
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "module-federation-static-server",
|
||||
"implementation": "/packages/rspack/src/executors/module-federation-static-server/module-federation-static-server.impl.ts",
|
||||
"schema": {
|
||||
"version": 2,
|
||||
"outputCapture": "direct-nodejs",
|
||||
"title": "Module Federation Static Dev Server",
|
||||
"description": "Serve a host application statically along with it's remotes.",
|
||||
"cli": "nx",
|
||||
"type": "object",
|
||||
"properties": { "serveTarget": { "type": "string" } },
|
||||
"required": ["serveTarget"],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Serve a host and its remotes statically.",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/rspack/src/executors/module-federation-static-server/schema.json",
|
||||
"type": "executor"
|
||||
}
|
||||
202
docs/generated/packages/rspack/executors/rspack.json
Normal file
202
docs/generated/packages/rspack/executors/rspack.json
Normal file
@ -0,0 +1,202 @@
|
||||
{
|
||||
"name": "rspack",
|
||||
"implementation": "/packages/rspack/src/executors/rspack/rspack.impl.ts",
|
||||
"schema": {
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"version": 2,
|
||||
"title": "Rspack build executor",
|
||||
"description": "Run Rspack via an executor for a project.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "The platform to target (e.g. web, node).",
|
||||
"enum": ["web", "node"]
|
||||
},
|
||||
"main": { "type": "string", "description": "The main entry file." },
|
||||
"outputPath": {
|
||||
"type": "string",
|
||||
"description": "The output path for the bundle."
|
||||
},
|
||||
"outputFileName": {
|
||||
"type": "string",
|
||||
"description": "The main output entry file"
|
||||
},
|
||||
"tsConfig": {
|
||||
"type": "string",
|
||||
"description": "The tsconfig file to build the project."
|
||||
},
|
||||
"typeCheck": {
|
||||
"type": "boolean",
|
||||
"description": "Skip the type checking."
|
||||
},
|
||||
"indexHtml": {
|
||||
"type": "string",
|
||||
"description": "The path to the index.html file."
|
||||
},
|
||||
"index": {
|
||||
"type": "string",
|
||||
"description": "HTML File which will be contain the application.",
|
||||
"x-completion-type": "file",
|
||||
"x-completion-glob": "**/*@(.html|.htm)"
|
||||
},
|
||||
"baseHref": {
|
||||
"type": "string",
|
||||
"description": "Base url for the application being built."
|
||||
},
|
||||
"deployUrl": {
|
||||
"type": "string",
|
||||
"description": "URL where the application will be deployed."
|
||||
},
|
||||
"rspackConfig": {
|
||||
"type": "string",
|
||||
"description": "The path to the rspack config file."
|
||||
},
|
||||
"optimization": {
|
||||
"description": "Enables optimization of the build output.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"scripts": {
|
||||
"type": "boolean",
|
||||
"description": "Enables optimization of the scripts output.",
|
||||
"default": true
|
||||
},
|
||||
"styles": {
|
||||
"type": "boolean",
|
||||
"description": "Enables optimization of the styles output.",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{ "type": "boolean" }
|
||||
]
|
||||
},
|
||||
"sourceMap": {
|
||||
"description": "Output sourcemaps. Use 'hidden' for use with error reporting tools without generating sourcemap comment.",
|
||||
"default": true,
|
||||
"oneOf": [{ "type": "boolean" }, { "type": "string" }]
|
||||
},
|
||||
"assets": {
|
||||
"type": "array",
|
||||
"description": "List of static application assets.",
|
||||
"default": [],
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"glob": {
|
||||
"type": "string",
|
||||
"description": "The pattern to match."
|
||||
},
|
||||
"input": {
|
||||
"type": "string",
|
||||
"description": "The input directory path in which to apply 'glob'. Defaults to the project root."
|
||||
},
|
||||
"ignore": {
|
||||
"description": "An array of globs to ignore.",
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"output": {
|
||||
"type": "string",
|
||||
"description": "Absolute path within the output."
|
||||
},
|
||||
"watch": {
|
||||
"type": "boolean",
|
||||
"description": "Enable re-building when files change.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["glob", "input", "output"]
|
||||
},
|
||||
{ "type": "string" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"extractLicenses": {
|
||||
"type": "boolean",
|
||||
"description": "Extract all licenses in a separate file.",
|
||||
"default": true
|
||||
},
|
||||
"fileReplacements": {
|
||||
"description": "Replace files with other files in the build.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"replace": {
|
||||
"type": "string",
|
||||
"description": "The file to be replaced.",
|
||||
"x-completion-type": "file"
|
||||
},
|
||||
"with": {
|
||||
"type": "string",
|
||||
"description": "The file to replace with.",
|
||||
"x-completion-type": "file"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["replace", "with"]
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"description": "Mode to run the build in.",
|
||||
"enum": ["development", "production", "none"]
|
||||
},
|
||||
"generatePackageJson": {
|
||||
"type": "boolean",
|
||||
"description": "Generates a `package.json` and pruned lock file with the project's `node_module` dependencies populated for installing in a container. If a `package.json` exists in the project's directory, it will be reused with dependencies populated."
|
||||
}
|
||||
},
|
||||
"required": ["target", "main", "outputPath", "tsConfig", "rspackConfig"],
|
||||
"definitions": {
|
||||
"assetPattern": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"glob": {
|
||||
"type": "string",
|
||||
"description": "The pattern to match."
|
||||
},
|
||||
"input": {
|
||||
"type": "string",
|
||||
"description": "The input directory path in which to apply 'glob'. Defaults to the project root."
|
||||
},
|
||||
"ignore": {
|
||||
"description": "An array of globs to ignore.",
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"output": {
|
||||
"type": "string",
|
||||
"description": "Absolute path within the output."
|
||||
},
|
||||
"watch": {
|
||||
"type": "boolean",
|
||||
"description": "Enable re-building when files change.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["glob", "input", "output"]
|
||||
},
|
||||
{ "type": "string" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"presets": []
|
||||
},
|
||||
"description": "Run Rspack via an executor for a project.",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/rspack/src/executors/rspack/schema.json",
|
||||
"type": "executor"
|
||||
}
|
||||
46
docs/generated/packages/rspack/executors/ssr-dev-server.json
Normal file
46
docs/generated/packages/rspack/executors/ssr-dev-server.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "ssr-dev-server",
|
||||
"implementation": "/packages/rspack/src/executors/ssr-dev-server/ssr-dev-server.impl.ts",
|
||||
"schema": {
|
||||
"outputCapture": "direct-nodejs",
|
||||
"title": "Rspack SSR Dev Server",
|
||||
"description": "Serve a SSR application using rspack.",
|
||||
"cli": "nx",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"browserTarget": {
|
||||
"type": "string",
|
||||
"description": "Target which builds the browser application.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"serverTarget": {
|
||||
"type": "string",
|
||||
"description": "Target which builds the server application.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"port": {
|
||||
"type": "number",
|
||||
"description": "The port to be set on `process.env.PORT` for use in the server.",
|
||||
"default": 4200,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"browserTargetOptions": {
|
||||
"type": "object",
|
||||
"description": "Additional options to pass into the browser build target.",
|
||||
"default": {}
|
||||
},
|
||||
"serverTargetOptions": {
|
||||
"type": "object",
|
||||
"description": "Additional options to pass into the server build target.",
|
||||
"default": {}
|
||||
}
|
||||
},
|
||||
"required": ["browserTarget", "serverTarget"],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Serve a SSR application.",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/rspack/src/executors/ssr-dev-server/schema.json",
|
||||
"type": "executor"
|
||||
}
|
||||
98
docs/generated/packages/rspack/generators/application.json
Normal file
98
docs/generated/packages/rspack/generators/application.json
Normal file
@ -0,0 +1,98 @@
|
||||
{
|
||||
"name": "application",
|
||||
"factory": "./src/generators/application/application",
|
||||
"schema": {
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "Application",
|
||||
"title": "Application generator for React + rspack",
|
||||
"type": "object",
|
||||
"description": "React + Rspack application generator.",
|
||||
"examples": [
|
||||
{
|
||||
"command": "nx g app myapp --directory=myorg",
|
||||
"description": "Generate `apps/myorg/myapp` and `apps/myorg/myapp-e2e`"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "The name of the application.",
|
||||
"type": "string",
|
||||
"$default": { "$source": "argv", "index": 0 },
|
||||
"x-prompt": "What name would you like to use for the application?",
|
||||
"pattern": "^[a-zA-Z].*$",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"framework": {
|
||||
"type": "string",
|
||||
"description": "The framework to use for the application.",
|
||||
"x-prompt": "What framework do you want to use when generating this application?",
|
||||
"enum": ["none", "react", "web", "nest"],
|
||||
"alias": ["uiFramework"],
|
||||
"x-priority": "important",
|
||||
"default": "react"
|
||||
},
|
||||
"style": {
|
||||
"description": "The file extension to be used for style files.",
|
||||
"type": "string",
|
||||
"default": "css",
|
||||
"alias": "s",
|
||||
"x-prompt": {
|
||||
"message": "Which stylesheet format would you like to use?",
|
||||
"type": "list",
|
||||
"items": [
|
||||
{ "value": "css", "label": "CSS" },
|
||||
{
|
||||
"value": "scss",
|
||||
"label": "SASS(.scss) [ http://sass-lang.com ]"
|
||||
},
|
||||
{
|
||||
"value": "styl",
|
||||
"label": "Stylus(.styl) [ http://stylus-lang.com ]"
|
||||
},
|
||||
{
|
||||
"value": "less",
|
||||
"label": "LESS [ http://lesscss.org ]"
|
||||
},
|
||||
{ "value": "none", "label": "None" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"unitTestRunner": {
|
||||
"type": "string",
|
||||
"description": "The unit test runner to use.",
|
||||
"enum": ["none", "jest"],
|
||||
"default": "jest"
|
||||
},
|
||||
"e2eTestRunner": {
|
||||
"type": "string",
|
||||
"description": "The e2e test runner to use.",
|
||||
"enum": ["none", "cypress"],
|
||||
"default": "cypress"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string",
|
||||
"description": "The directory to nest the app under."
|
||||
},
|
||||
"tags": {
|
||||
"type": "string",
|
||||
"description": "Add tags to the application (used for linting).",
|
||||
"alias": "t"
|
||||
},
|
||||
"monorepo": {
|
||||
"type": "boolean",
|
||||
"description": "Creates an integrated monorepo.",
|
||||
"aliases": ["integrated"]
|
||||
},
|
||||
"rootProject": { "type": "boolean", "x-priority": "internal" }
|
||||
},
|
||||
"required": ["name"],
|
||||
"presets": []
|
||||
},
|
||||
"aliases": ["app"],
|
||||
"x-type": "application",
|
||||
"description": "React application generator.",
|
||||
"implementation": "/packages/rspack/src/generators/application/application.ts",
|
||||
"hidden": false,
|
||||
"path": "/packages/rspack/src/generators/application/schema.json",
|
||||
"type": "generator"
|
||||
}
|
||||
78
docs/generated/packages/rspack/generators/configuration.json
Normal file
78
docs/generated/packages/rspack/generators/configuration.json
Normal file
@ -0,0 +1,78 @@
|
||||
{
|
||||
"name": "configuration",
|
||||
"factory": "./src/generators/configuration/configuration",
|
||||
"schema": {
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "Rspack",
|
||||
"title": "Nx Rspack Configuration Generator",
|
||||
"description": "Rspack configuration generator.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the project.",
|
||||
"$default": { "$source": "argv", "index": 0 },
|
||||
"x-dropdown": "project",
|
||||
"x-prompt": "What is the name of the project to set up a rspack for?",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"framework": {
|
||||
"type": "string",
|
||||
"description": "The framework used by the project.",
|
||||
"x-prompt": "What framework is the project you want to convert using?",
|
||||
"enum": ["none", "react", "web", "nest"],
|
||||
"alias": ["uiFramework"],
|
||||
"x-priority": "important"
|
||||
},
|
||||
"main": {
|
||||
"type": "string",
|
||||
"description": "Path relative to the workspace root for the main entry file. Defaults to '<projectRoot>/src/main.ts'.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"tsConfig": {
|
||||
"type": "string",
|
||||
"description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '<projectRoot>/tsconfig.app.json'.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "Target platform for the build, same as the rspack config option.",
|
||||
"enum": ["node", "web"],
|
||||
"default": "web"
|
||||
},
|
||||
"devServer": {
|
||||
"type": "boolean",
|
||||
"description": "Add a serve target to run a local rspack dev-server",
|
||||
"default": false
|
||||
},
|
||||
"style": {
|
||||
"type": "string",
|
||||
"description": "The style solution to use.",
|
||||
"enum": ["none", "css", "scss", "less"]
|
||||
},
|
||||
"newProject": {
|
||||
"type": "boolean",
|
||||
"description": "Is this a new project?",
|
||||
"default": false,
|
||||
"hidden": true
|
||||
},
|
||||
"buildTarget": {
|
||||
"type": "string",
|
||||
"description": "The build target of the project to be transformed to use the @nx/vite:build executor."
|
||||
},
|
||||
"serveTarget": {
|
||||
"type": "string",
|
||||
"description": "The serve target of the project to be transformed to use the @nx/vite:dev-server and @nx/vite:preview-server executors."
|
||||
},
|
||||
"rootProject": { "type": "boolean", "x-priority": "internal" }
|
||||
},
|
||||
"required": ["project"],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Rspack configuration generator.",
|
||||
"implementation": "/packages/rspack/src/generators/configuration/configuration.ts",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/rspack/src/generators/configuration/schema.json",
|
||||
"type": "generator"
|
||||
}
|
||||
39
docs/generated/packages/rspack/generators/init.json
Normal file
39
docs/generated/packages/rspack/generators/init.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "init",
|
||||
"factory": "./src/generators/init/init",
|
||||
"schema": {
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "Init",
|
||||
"title": "Nx Rspack Init Generator",
|
||||
"type": "object",
|
||||
"description": "Rspack init generator.",
|
||||
"properties": {
|
||||
"framework": {
|
||||
"type": "string",
|
||||
"description": "The UI framework used by the project.",
|
||||
"enum": ["none", "react", "web", "nest"],
|
||||
"alias": ["uiFramework"]
|
||||
},
|
||||
"style": {
|
||||
"type": "string",
|
||||
"description": "The style solution to use.",
|
||||
"enum": ["none", "css", "scss", "less", "styl"]
|
||||
},
|
||||
"rootProject": { "type": "boolean", "x-priority": "internal" },
|
||||
"keepExistingVersions": {
|
||||
"type": "boolean",
|
||||
"x-priority": "internal",
|
||||
"description": "Keep existing dependencies versions",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"required": [],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Rspack init generator.",
|
||||
"hidden": true,
|
||||
"implementation": "/packages/rspack/src/generators/init/init.ts",
|
||||
"aliases": [],
|
||||
"path": "/packages/rspack/src/generators/init/schema.json",
|
||||
"type": "generator"
|
||||
}
|
||||
70
docs/generated/packages/rspack/generators/preset.json
Normal file
70
docs/generated/packages/rspack/generators/preset.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "preset",
|
||||
"factory": "./src/generators/preset/preset",
|
||||
"schema": {
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "Preset",
|
||||
"title": "Standalone React and rspack preset",
|
||||
"description": "React + Rspack preset generator.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "",
|
||||
"$default": { "$source": "argv", "index": 0 },
|
||||
"x-priority": "important"
|
||||
},
|
||||
"framework": {
|
||||
"type": "string",
|
||||
"description": "The framework to use for the application.",
|
||||
"enum": ["none", "react", "web", "nest"],
|
||||
"alias": ["uiFramework"],
|
||||
"x-priority": "important",
|
||||
"default": "react"
|
||||
},
|
||||
"less": { "type": "boolean", "description": "Use less for styling." },
|
||||
"sass": { "type": "boolean", "description": "Use sass for styling." },
|
||||
"stylus": { "type": "boolean", "description": "Use stylus for styling." },
|
||||
"unitTestRunner": {
|
||||
"type": "string",
|
||||
"description": "The unit test runner to use.",
|
||||
"enum": ["none", "jest"],
|
||||
"default": "jest"
|
||||
},
|
||||
"e2eTestRunner": {
|
||||
"type": "string",
|
||||
"description": "The e2e test runner to use.",
|
||||
"enum": ["none", "cypress"],
|
||||
"default": "cypress"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string",
|
||||
"description": "The directory to nest the app under."
|
||||
},
|
||||
"tags": {
|
||||
"type": "string",
|
||||
"description": "Add tags to the project (used for linting).",
|
||||
"alias": "t"
|
||||
},
|
||||
"monorepo": {
|
||||
"type": "boolean",
|
||||
"description": "Creates an integrated monorepo.",
|
||||
"default": false,
|
||||
"aliases": ["integrated"]
|
||||
},
|
||||
"rootProject": {
|
||||
"type": "boolean",
|
||||
"x-priority": "internal",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"presets": []
|
||||
},
|
||||
"description": "React preset generator.",
|
||||
"hidden": true,
|
||||
"implementation": "/packages/rspack/src/generators/preset/preset.ts",
|
||||
"aliases": [],
|
||||
"path": "/packages/rspack/src/generators/preset/schema.json",
|
||||
"type": "generator"
|
||||
}
|
||||
@ -2559,6 +2559,19 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "rspack",
|
||||
"id": "rspack",
|
||||
"description": "Rspack package.",
|
||||
"itemList": [
|
||||
{
|
||||
"name": "Overview",
|
||||
"id": "overview",
|
||||
"path": "/nx-api/rspack",
|
||||
"file": "shared/packages/rspack/rspack-plugin"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "detox",
|
||||
"id": "detox",
|
||||
|
||||
98
docs/shared/packages/rspack/rspack-plugin.md
Normal file
98
docs/shared/packages/rspack/rspack-plugin.md
Normal file
@ -0,0 +1,98 @@
|
||||
---
|
||||
title: Overview of the Nx Rspack Plugin
|
||||
description: The Nx Plugin for Rspack contains executors, generators, and utilities for managing Rspack projects in an Nx Workspace.
|
||||
---
|
||||
|
||||
The Nx Plugin for Rspack contains executors, generators, and utilities for managing Rspack projects in an Nx Workspace.
|
||||
|
||||
## Setting Up @nx/rspack
|
||||
|
||||
### Installation
|
||||
|
||||
{% callout type="note" title="Keep Nx Package Versions In Sync" %}
|
||||
Make sure to install the `@nx/rspack` version that matches the version of `nx` in your repository. If the version numbers get out of sync, you can encounter some difficult to debug errors. You can [fix Nx version mismatches with this recipe](/recipes/tips-n-tricks/keep-nx-versions-in-sync).
|
||||
{% /callout %}
|
||||
|
||||
In any Nx workspace, you can install `@nx/rspack` by running the following command:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Nx 18+" %}
|
||||
|
||||
```shell {% skipRescope=true %}
|
||||
nx add @nx/rspack
|
||||
```
|
||||
|
||||
This will install the correct version of `@nx/rspack`.
|
||||
|
||||
### How @nx/rspack Infers Tasks
|
||||
|
||||
The `@nx/rspack` plugin will create a task for any project that has a Rspack configuration file present. Any of the following files will be recognized as a Rspack configuration file:
|
||||
|
||||
- `rspack.config.js`
|
||||
- `rspack.config.ts`
|
||||
- `rspack.config.mjs`
|
||||
- `rspack.config.mts`
|
||||
- `rspack.config.cjs`
|
||||
- `rspack.config.cts`
|
||||
|
||||
### View Inferred Tasks
|
||||
|
||||
To view inferred tasks for a project, open the [project details view](/concepts/inferred-tasks) in Nx Console or run `nx show project my-project --web` in the command line.
|
||||
|
||||
### @nx/rspack Configuration
|
||||
|
||||
The `@nx/rspack/plugin` is configured in the `plugins` array in `nx.json`.
|
||||
|
||||
```json {% fileName="nx.json" %}
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/rspack/plugin",
|
||||
"options": {
|
||||
"buildTargetName": "build",
|
||||
"previewTargetName": "preview",
|
||||
"serveTargetName": "serve",
|
||||
"serveStaticTargetName": "serve-static"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The `buildTargetName`, `previewTargetName`, `serveTargetName` and `serveStaticTargetName` options control the names of the inferred Rspack tasks. The default names are `build`, `preview`, `serve` and `serve-static`.
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="Nx < 18" %}
|
||||
|
||||
Install the `@nx/rspack` package with your package manager.
|
||||
|
||||
```shell
|
||||
npm add -D @nx/rspack
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## Using @nx/rspack
|
||||
|
||||
### Generate a new project using Rspack
|
||||
|
||||
You can generate a [React](/nx-api/react) application or library that uses Rspack. The [`@nx/react:app`](/nx-api/react/generators/application) and [`@nx/react:lib`](/nx-api/react/generators/library) generators accept the `bundler` option, where you can pass `rspack`. This will generate a new application configured to use Rspack, and it will also install all the necessary dependencies, including the `@nx/rspack` plugin.
|
||||
|
||||
To generate a React application using Rspack, run the following:
|
||||
|
||||
```bash
|
||||
nx g @nx/react:app my-app --bundler=rspack
|
||||
```
|
||||
|
||||
To generate a React library using Rspack, run the following:
|
||||
|
||||
```bash
|
||||
nx g @nx/react:lib my-lib --bundler=rspack
|
||||
```
|
||||
|
||||
### Modify an existing React project to use Rspack
|
||||
|
||||
You can use the `@nx/rspack:configuration` generator to change your React to use Rspack. This generator will modify your project's configuration to use Rspack, and it will also install all the necessary dependencies, including the `@nx/rspack` plugin.
|
||||
|
||||
You can read more about this generator on the [`@nx/rspack:configuration`](/nx-api/rspack/generators/configuration) generator page.
|
||||
@ -682,6 +682,21 @@
|
||||
- [init](/nx-api/rollup/generators/init)
|
||||
- [configuration](/nx-api/rollup/generators/configuration)
|
||||
- [convert-to-inferred](/nx-api/rollup/generators/convert-to-inferred)
|
||||
- [rspack](/nx-api/rspack)
|
||||
- [documents](/nx-api/rspack/documents)
|
||||
- [Overview](/nx-api/rspack/documents/overview)
|
||||
- [executors](/nx-api/rspack/executors)
|
||||
- [rspack](/nx-api/rspack/executors/rspack)
|
||||
- [dev-server](/nx-api/rspack/executors/dev-server)
|
||||
- [ssr-dev-server](/nx-api/rspack/executors/ssr-dev-server)
|
||||
- [module-federation-dev-server](/nx-api/rspack/executors/module-federation-dev-server)
|
||||
- [module-federation-ssr-dev-server](/nx-api/rspack/executors/module-federation-ssr-dev-server)
|
||||
- [module-federation-static-server](/nx-api/rspack/executors/module-federation-static-server)
|
||||
- [generators](/nx-api/rspack/generators)
|
||||
- [configuration](/nx-api/rspack/generators/configuration)
|
||||
- [init](/nx-api/rspack/generators/init)
|
||||
- [preset](/nx-api/rspack/generators/preset)
|
||||
- [application](/nx-api/rspack/generators/application)
|
||||
- [storybook](/nx-api/storybook)
|
||||
- [documents](/nx-api/storybook/documents)
|
||||
- [Overview](/nx-api/storybook/documents/overview)
|
||||
|
||||
@ -63,6 +63,41 @@ describe('React Applications', () => {
|
||||
}
|
||||
}, 250_000);
|
||||
|
||||
it('should be able to use Rspack to build and test apps', async () => {
|
||||
const appName = uniq('app');
|
||||
const libName = uniq('lib');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/react:app ${appName} --bundler=rspack --unit-test-runner=vitest --no-interactive --skipFormat`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/react:lib ${libName} --bundler=none --no-interactive --unit-test-runner=vitest --skipFormat`
|
||||
);
|
||||
|
||||
// Library generated with Vite
|
||||
checkFilesExist(`${libName}/vite.config.ts`);
|
||||
|
||||
const mainPath = `${appName}/src/main.tsx`;
|
||||
updateFile(
|
||||
mainPath,
|
||||
`
|
||||
import '@${proj}/${libName}';
|
||||
${readFile(mainPath)}
|
||||
`
|
||||
);
|
||||
|
||||
runCLI(`build ${appName}`);
|
||||
|
||||
checkFilesExist(`dist/${appName}/index.html`);
|
||||
|
||||
if (runE2ETests()) {
|
||||
// TODO(Colum): investigate why webkit is failing
|
||||
const e2eResults = runCLI(`e2e ${appName}-e2e -- --project=chromium`);
|
||||
expect(e2eResults).toContain('Successfully ran target e2e for project');
|
||||
expect(await killPorts()).toBeTruthy();
|
||||
}
|
||||
}, 250_000);
|
||||
|
||||
it('should be able to generate a react app + lib (with CSR and SSR)', async () => {
|
||||
const appName = uniq('app');
|
||||
const libName = uniq('lib');
|
||||
|
||||
19
e2e/rspack/jest.config.ts
Normal file
19
e2e/rspack/jest.config.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
displayName: 'e2e-rspack',
|
||||
preset: '../jest.preset.e2e.js',
|
||||
maxWorkers: 1,
|
||||
globals: {},
|
||||
globalSetup: '../utils/global-setup.ts',
|
||||
globalTeardown: '../utils/global-teardown.ts',
|
||||
transform: {
|
||||
'^.+\\.[tj]s$': [
|
||||
'ts-jest',
|
||||
{
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
},
|
||||
],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'js', 'html'],
|
||||
coverageDirectory: '../../coverage/e2e/e2e-rspack',
|
||||
};
|
||||
10
e2e/rspack/project.json
Normal file
10
e2e/rspack/project.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "e2e-rspack",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"sourceRoot": "e2e/rspack",
|
||||
"// targets": "to see all targets run: nx show project e2e-rspack --web",
|
||||
"targets": {},
|
||||
"tags": [],
|
||||
"implicitDependencies": ["rspack"]
|
||||
}
|
||||
145
e2e/rspack/tests/rspack.spec.ts
Normal file
145
e2e/rspack/tests/rspack.spec.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import { getPackageManagerCommand } from '@nx/devkit';
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
listFiles,
|
||||
newProject,
|
||||
tmpProjPath,
|
||||
uniq,
|
||||
updateFile,
|
||||
runCLI,
|
||||
runCommand,
|
||||
} from '@nx/e2e/utils';
|
||||
import { execSync } from 'child_process';
|
||||
import { writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
describe('rspack e2e', () => {
|
||||
let proj: string;
|
||||
|
||||
// Setting up individual workspaces per
|
||||
// test can cause e2e runs to take a long time.
|
||||
// For this reason, we recommend each suite only
|
||||
// consumes 1 workspace. The tests should each operate
|
||||
// on a unique project in the workspace, such that they
|
||||
// are not dependant on one another.
|
||||
beforeAll(() => {
|
||||
proj = newProject({ packages: ['@nx/rspack'] });
|
||||
});
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
it('should create rspack root project and additional apps', async () => {
|
||||
const project = uniq('myapp');
|
||||
runCLI(
|
||||
`generate @nx/rspack:preset ${project} --framework=react --unitTestRunner=jest --e2eTestRunner=cypress`
|
||||
);
|
||||
|
||||
// Added this so that the nx-ecosystem-ci tests don't throw jest error
|
||||
writeFileSync(
|
||||
join(tmpProjPath(), '.babelrc'),
|
||||
`
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript",
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": ["@babel/plugin-transform-runtime"]
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
const pm = getPackageManagerCommand();
|
||||
runCommand(
|
||||
pm.addDev +
|
||||
' @babel/preset-react @babel/preset-env @babel/preset-typescript'
|
||||
);
|
||||
|
||||
let result = runCLI(`build ${project}`, {
|
||||
env: { NODE_ENV: 'production' },
|
||||
});
|
||||
expect(result).toContain('Successfully ran target build');
|
||||
// Make sure expected files are present.
|
||||
expect(listFiles(`dist/${project}`)).toHaveLength(5);
|
||||
|
||||
result = runCLI(`test ${project}`);
|
||||
expect(result).toContain('Successfully ran target test');
|
||||
|
||||
// TODO(Colum): re-enable when cypress issue is resolved
|
||||
// result = runCLI(`e2e e2e`);
|
||||
// expect(result.stdout).toContain('Successfully ran target e2e');
|
||||
|
||||
// Update app and make sure previous dist files are not present.
|
||||
updateFile(`src/app/app.tsx`, (content) => {
|
||||
return `${content}\nconsole.log('hello');
|
||||
`;
|
||||
});
|
||||
result = runCLI(`build ${project}`, {
|
||||
env: { NODE_ENV: 'production' },
|
||||
});
|
||||
expect(result).toContain('Successfully ran target build');
|
||||
expect(listFiles(`dist/${project}`)).toHaveLength(5); // same length as before
|
||||
|
||||
// Generate a new app and check that the files are correct
|
||||
const app2 = uniq('app2');
|
||||
runCLI(
|
||||
`generate @nx/rspack:app ${app2} --framework=react --unitTestRunner=jest --e2eTestRunner=cypress --style=css`
|
||||
);
|
||||
checkFilesExist(`${app2}/project.json`, `${app2}-e2e/project.json`);
|
||||
|
||||
// Added this so that the nx-ecosystem-ci tests don't throw jest error
|
||||
writeFileSync(
|
||||
join(tmpProjPath(), app2, '.babelrc'),
|
||||
`
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript",
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": ["@babel/plugin-transform-runtime"]
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
result = runCLI(`build ${app2}`, {
|
||||
env: { NODE_ENV: 'production' },
|
||||
});
|
||||
expect(result).toContain('Successfully ran target build');
|
||||
// Make sure expected files are present.
|
||||
expect(listFiles(`dist/${app2}`)).toHaveLength(5);
|
||||
|
||||
result = runCLI(`test ${app2}`);
|
||||
expect(result).toContain('Successfully ran target test');
|
||||
|
||||
// TODO(Colum): re-enable when cypress issue is resolved
|
||||
// result = runCLI(`e2e ${app2}-e2e`);
|
||||
// expect(result.stdout).toContain('Successfully ran target e2e');
|
||||
|
||||
// Generate a Nest app and verify build output
|
||||
const app3 = uniq('app3');
|
||||
runCLI(
|
||||
`generate @nx/rspack:app ${app3} --framework=nest --unitTestRunner=jest --no-interactive`
|
||||
);
|
||||
checkFilesExist(`${app3}/project.json`);
|
||||
|
||||
result = runCLI(`build ${app3}`);
|
||||
expect(result).toContain('Successfully ran target build');
|
||||
// Make sure expected files are present.
|
||||
expect(listFiles(`dist/${app3}`)).toHaveLength(2);
|
||||
|
||||
result = runCLI(`build ${app3} --generatePackageJson=true`);
|
||||
expect(result).toContain('Successfully ran target build');
|
||||
// Make sure expected files are present.
|
||||
expect(listFiles(`dist/${app3}`)).toHaveLength(4);
|
||||
}, 200_000);
|
||||
});
|
||||
13
e2e/rspack/tsconfig.json
Normal file
13
e2e/rspack/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node", "jest"]
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
9
e2e/rspack/tsconfig.spec.json
Normal file
9
e2e/rspack/tsconfig.spec.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"]
|
||||
}
|
||||
@ -56,6 +56,7 @@ const nxPackages = [
|
||||
`@nx/rollup`,
|
||||
`@nx/react`,
|
||||
`@nx/remix`,
|
||||
`@nx/rspack`,
|
||||
`@nx/storybook`,
|
||||
`@nx/vue`,
|
||||
`@nx/vite`,
|
||||
|
||||
@ -1,72 +0,0 @@
|
||||
import { getPackagesSections } from '@nx/nx-dev/data-access-menu';
|
||||
import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { PackageSchemaSubList } from '@nx/nx-dev/feature-package-schema-viewer/src/lib/package-schema-sub-list';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function DocumentsIndex({
|
||||
menu,
|
||||
pkg,
|
||||
}: {
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
}): JSX.Element {
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
|
||||
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
|
||||
menu: {
|
||||
sections: sortCorePackagesFirst<MenuSection>(
|
||||
getPackagesSections(menu),
|
||||
'id'
|
||||
),
|
||||
},
|
||||
package: pkg,
|
||||
};
|
||||
|
||||
/**
|
||||
* Show either the docviewer or the package view depending on:
|
||||
* - docviewer: it is a documentation document
|
||||
* - packageviewer: it is package generated documentation
|
||||
*/
|
||||
|
||||
return (
|
||||
<div id="shell" className="flex h-full flex-col">
|
||||
<div className="w-full flex-shrink-0">
|
||||
<DocumentationHeader isNavOpen={navIsOpen} toggleNav={toggleNav} />
|
||||
</div>
|
||||
<main
|
||||
id="main"
|
||||
role="main"
|
||||
className="flex h-full flex-1 overflow-y-hidden"
|
||||
>
|
||||
<SidebarContainer
|
||||
menu={vm.menu}
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaSubList pkg={vm.package} type={'document'} />
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(): Promise<{
|
||||
props: {
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
};
|
||||
}> {
|
||||
return {
|
||||
props: {
|
||||
menu: menusApi.getMenu('nx-api', 'nx-api'),
|
||||
pkg,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1,93 +0,0 @@
|
||||
import { getPackagesSections } from '@nx/nx-dev/data-access-menu';
|
||||
import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { DocViewer } from '@nx/nx-dev/feature-doc-viewer';
|
||||
import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { content } from '../../../../lib/rspack/content/overview';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { fetchGithubStarCount } from '../../../../lib/githubStars.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function Overview({
|
||||
document,
|
||||
menu,
|
||||
relatedDocuments,
|
||||
widgetData,
|
||||
}: {
|
||||
document: ProcessedDocument;
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
relatedDocuments: RelatedDocument[];
|
||||
widgetData: { githubStarsCount: number };
|
||||
}): JSX.Element {
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
|
||||
const vm: {
|
||||
document: ProcessedDocument;
|
||||
menu: Menu;
|
||||
relatedDocuments: RelatedDocument[];
|
||||
} = {
|
||||
document,
|
||||
menu: {
|
||||
sections: sortCorePackagesFirst<MenuSection>(
|
||||
getPackagesSections(menu),
|
||||
'id'
|
||||
),
|
||||
},
|
||||
relatedDocuments,
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="shell" className="flex h-full flex-col">
|
||||
<div className="w-full flex-shrink-0">
|
||||
<DocumentationHeader isNavOpen={navIsOpen} toggleNav={toggleNav} />
|
||||
</div>
|
||||
<main
|
||||
id="main"
|
||||
role="main"
|
||||
className="flex h-full flex-1 overflow-y-hidden"
|
||||
>
|
||||
<SidebarContainer
|
||||
menu={vm.menu}
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<DocViewer
|
||||
document={vm.document}
|
||||
relatedDocuments={vm.relatedDocuments}
|
||||
widgetData={widgetData}
|
||||
/>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
const document = {
|
||||
content: content,
|
||||
description: '',
|
||||
filePath: '',
|
||||
id: 'overview',
|
||||
name: 'Overview of the Nx Rspack Plugin',
|
||||
relatedDocuments: {},
|
||||
tags: [],
|
||||
};
|
||||
|
||||
return {
|
||||
props: {
|
||||
pkg,
|
||||
document,
|
||||
widgetData: {
|
||||
githubStarsCount: await fetchGithubStarCount(),
|
||||
},
|
||||
relatedDocuments: [],
|
||||
menu: menusApi.getMenu('nx-api', ''),
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1,94 +0,0 @@
|
||||
import { getPackagesSections } from '@nx/nx-dev/data-access-menu';
|
||||
import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { DocViewer } from '@nx/nx-dev/feature-doc-viewer';
|
||||
import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { content } from '../../../../lib/rspack/content/rspack-config-setup';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { fetchGithubStarCount } from '../../../../lib/githubStars.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function RspackConfigSetup({
|
||||
document,
|
||||
menu,
|
||||
relatedDocuments,
|
||||
widgetData,
|
||||
}: {
|
||||
document: ProcessedDocument;
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
relatedDocuments: RelatedDocument[];
|
||||
widgetData: { githubStarsCount: number };
|
||||
}): JSX.Element {
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
|
||||
const vm: {
|
||||
document: ProcessedDocument;
|
||||
menu: Menu;
|
||||
relatedDocuments: RelatedDocument[];
|
||||
} = {
|
||||
document,
|
||||
menu: {
|
||||
sections: sortCorePackagesFirst<MenuSection>(
|
||||
getPackagesSections(menu),
|
||||
'id'
|
||||
),
|
||||
},
|
||||
relatedDocuments,
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="shell" className="flex h-full flex-col">
|
||||
<div className="w-full flex-shrink-0">
|
||||
<DocumentationHeader isNavOpen={navIsOpen} toggleNav={toggleNav} />
|
||||
</div>
|
||||
<main
|
||||
id="main"
|
||||
role="main"
|
||||
className="flex h-full flex-1 overflow-y-hidden"
|
||||
>
|
||||
<SidebarContainer
|
||||
menu={vm.menu}
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<DocViewer
|
||||
document={vm.document}
|
||||
relatedDocuments={vm.relatedDocuments}
|
||||
widgetData={widgetData}
|
||||
/>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
const document = {
|
||||
content: content,
|
||||
description:
|
||||
'A guide on how to configure Rspack on your Nx workspace, and instructions on how to customize your Rspack configuration.',
|
||||
filePath: '',
|
||||
id: 'rspack-plugins',
|
||||
name: ' How to configure Rspack on your Nx workspace',
|
||||
relatedDocuments: {},
|
||||
tags: [],
|
||||
};
|
||||
|
||||
return {
|
||||
props: {
|
||||
pkg,
|
||||
document,
|
||||
widgetData: {
|
||||
githubStarsCount: await fetchGithubStarCount(),
|
||||
},
|
||||
relatedDocuments: [],
|
||||
menu: menusApi.getMenu('nx-api', ''),
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1,93 +0,0 @@
|
||||
import { getPackagesSections } from '@nx/nx-dev/data-access-menu';
|
||||
import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { DocViewer } from '@nx/nx-dev/feature-doc-viewer';
|
||||
import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { content } from '../../../../lib/rspack/content/rspack-plugin';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { fetchGithubStarCount } from '../../../../lib/githubStars.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function RspackPlugins({
|
||||
document,
|
||||
menu,
|
||||
relatedDocuments,
|
||||
widgetData,
|
||||
}: {
|
||||
document: ProcessedDocument;
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
relatedDocuments: RelatedDocument[];
|
||||
widgetData: { githubStarsCount: number };
|
||||
}): JSX.Element {
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
|
||||
const vm: {
|
||||
document: ProcessedDocument;
|
||||
menu: Menu;
|
||||
relatedDocuments: RelatedDocument[];
|
||||
} = {
|
||||
document,
|
||||
menu: {
|
||||
sections: sortCorePackagesFirst<MenuSection>(
|
||||
getPackagesSections(menu),
|
||||
'id'
|
||||
),
|
||||
},
|
||||
relatedDocuments,
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="shell" className="flex h-full flex-col">
|
||||
<div className="w-full flex-shrink-0">
|
||||
<DocumentationHeader isNavOpen={navIsOpen} toggleNav={toggleNav} />
|
||||
</div>
|
||||
<main
|
||||
id="main"
|
||||
role="main"
|
||||
className="flex h-full flex-1 overflow-y-hidden"
|
||||
>
|
||||
<SidebarContainer
|
||||
menu={vm.menu}
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<DocViewer
|
||||
document={vm.document}
|
||||
relatedDocuments={vm.relatedDocuments}
|
||||
widgetData={widgetData}
|
||||
/>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
const document = {
|
||||
content: content,
|
||||
description: 'Rspack plugins',
|
||||
filePath: '',
|
||||
id: 'rspack-plugins',
|
||||
name: 'Rspack plugins',
|
||||
relatedDocuments: {},
|
||||
tags: [],
|
||||
};
|
||||
|
||||
return {
|
||||
props: {
|
||||
pkg,
|
||||
document,
|
||||
widgetData: {
|
||||
githubStarsCount: await fetchGithubStarCount(),
|
||||
},
|
||||
relatedDocuments: [],
|
||||
menu: menusApi.getMenu('nx-api', ''),
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
import { PackageSchemaViewer } from '@nx/nx-dev/feature-package-schema-viewer';
|
||||
import { getPackagesSections } from '@nx/nx-dev/data-access-menu';
|
||||
import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import {
|
||||
ProcessedPackageMetadata,
|
||||
SchemaMetadata,
|
||||
} from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { schema } from '../../../../lib/rspack/schema/executors/dev-server';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function DevServerExecutor({
|
||||
menu,
|
||||
pkg,
|
||||
schema,
|
||||
}: {
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
}): JSX.Element {
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
|
||||
const vm: {
|
||||
menu: Menu;
|
||||
package: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
} = {
|
||||
menu: {
|
||||
sections: sortCorePackagesFirst<MenuSection>(
|
||||
getPackagesSections(menu),
|
||||
'id'
|
||||
),
|
||||
},
|
||||
package: pkg,
|
||||
schema: schema,
|
||||
};
|
||||
|
||||
/**
|
||||
* Show either the docviewer or the package view depending on:
|
||||
* - docviewer: it is a documentation document
|
||||
* - packageviewer: it is package generated documentation
|
||||
*/
|
||||
|
||||
return (
|
||||
<div id="shell" className="flex h-full flex-col">
|
||||
<div className="w-full flex-shrink-0">
|
||||
<DocumentationHeader isNavOpen={navIsOpen} toggleNav={toggleNav} />
|
||||
</div>
|
||||
<main
|
||||
id="main"
|
||||
role="main"
|
||||
className="flex h-full flex-1 overflow-y-hidden"
|
||||
>
|
||||
<SidebarContainer
|
||||
menu={vm.menu}
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return {
|
||||
props: {
|
||||
pkg,
|
||||
schema,
|
||||
menu: menusApi.getMenu('nx-api', 'nx-api'),
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
import { getPackagesSections } from '@nx/nx-dev/data-access-menu';
|
||||
import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { PackageSchemaSubList } from '@nx/nx-dev/feature-package-schema-viewer/src/lib/package-schema-sub-list';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function ExecutorsIndex({
|
||||
menu,
|
||||
pkg,
|
||||
}: {
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
}): JSX.Element {
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
|
||||
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
|
||||
menu: {
|
||||
sections: sortCorePackagesFirst<MenuSection>(
|
||||
getPackagesSections(menu),
|
||||
'id'
|
||||
),
|
||||
},
|
||||
package: pkg,
|
||||
};
|
||||
|
||||
/**
|
||||
* Show either the docviewer or the package view depending on:
|
||||
* - docviewer: it is a documentation document
|
||||
* - packageviewer: it is package generated documentation
|
||||
*/
|
||||
|
||||
return (
|
||||
<div id="shell" className="flex h-full flex-col">
|
||||
<div className="w-full flex-shrink-0">
|
||||
<DocumentationHeader isNavOpen={navIsOpen} toggleNav={toggleNav} />
|
||||
</div>
|
||||
<main
|
||||
id="main"
|
||||
role="main"
|
||||
className="flex h-full flex-1 overflow-y-hidden"
|
||||
>
|
||||
<SidebarContainer
|
||||
menu={vm.menu}
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaSubList pkg={vm.package} type={'executor'} />
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(): Promise<{
|
||||
props: {
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
};
|
||||
}> {
|
||||
return {
|
||||
props: {
|
||||
menu: menusApi.getMenu('nx-api', 'nx-api'),
|
||||
pkg,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
import { PackageSchemaViewer } from '@nx/nx-dev/feature-package-schema-viewer';
|
||||
import { getPackagesSections } from '@nx/nx-dev/data-access-menu';
|
||||
import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import {
|
||||
ProcessedPackageMetadata,
|
||||
SchemaMetadata,
|
||||
} from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { schema } from '../../../../lib/rspack/schema/executors/rspack';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function RspackExecutor({
|
||||
menu,
|
||||
pkg,
|
||||
schema,
|
||||
}: {
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
}): JSX.Element {
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
|
||||
const vm: {
|
||||
menu: Menu;
|
||||
package: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
} = {
|
||||
menu: {
|
||||
sections: sortCorePackagesFirst<MenuSection>(
|
||||
getPackagesSections(menu),
|
||||
'id'
|
||||
),
|
||||
},
|
||||
package: pkg,
|
||||
schema: schema,
|
||||
};
|
||||
|
||||
/**
|
||||
* Show either the docviewer or the package view depending on:
|
||||
* - docviewer: it is a documentation document
|
||||
* - packageviewer: it is package generated documentation
|
||||
*/
|
||||
|
||||
return (
|
||||
<div id="shell" className="flex h-full flex-col">
|
||||
<div className="w-full flex-shrink-0">
|
||||
<DocumentationHeader isNavOpen={navIsOpen} toggleNav={toggleNav} />
|
||||
</div>
|
||||
<main
|
||||
id="main"
|
||||
role="main"
|
||||
className="flex h-full flex-1 overflow-y-hidden"
|
||||
>
|
||||
<SidebarContainer
|
||||
menu={vm.menu}
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return {
|
||||
props: {
|
||||
pkg,
|
||||
schema,
|
||||
menu: menusApi.getMenu('nx-api', 'nx-api'),
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
import { PackageSchemaViewer } from '@nx/nx-dev/feature-package-schema-viewer';
|
||||
import { getPackagesSections } from '@nx/nx-dev/data-access-menu';
|
||||
import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import {
|
||||
ProcessedPackageMetadata,
|
||||
SchemaMetadata,
|
||||
} from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { schema } from '../../../../lib/rspack/schema/generators/application';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function ApplicationGenerator({
|
||||
menu,
|
||||
pkg,
|
||||
schema,
|
||||
}: {
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
}): JSX.Element {
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
|
||||
const vm: {
|
||||
menu: Menu;
|
||||
package: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
} = {
|
||||
menu: {
|
||||
sections: sortCorePackagesFirst<MenuSection>(
|
||||
getPackagesSections(menu),
|
||||
'id'
|
||||
),
|
||||
},
|
||||
package: pkg,
|
||||
schema: schema,
|
||||
};
|
||||
|
||||
/**
|
||||
* Show either the docviewer or the package view depending on:
|
||||
* - docviewer: it is a documentation document
|
||||
* - packageviewer: it is package generated documentation
|
||||
*/
|
||||
|
||||
return (
|
||||
<div id="shell" className="flex h-full flex-col">
|
||||
<div className="w-full flex-shrink-0">
|
||||
<DocumentationHeader isNavOpen={navIsOpen} toggleNav={toggleNav} />
|
||||
</div>
|
||||
<main
|
||||
id="main"
|
||||
role="main"
|
||||
className="flex h-full flex-1 overflow-y-hidden"
|
||||
>
|
||||
<SidebarContainer
|
||||
menu={vm.menu}
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return {
|
||||
props: {
|
||||
pkg,
|
||||
schema,
|
||||
menu: menusApi.getMenu('nx-api', 'nx-api'),
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
import { PackageSchemaViewer } from '@nx/nx-dev/feature-package-schema-viewer';
|
||||
import { getPackagesSections } from '@nx/nx-dev/data-access-menu';
|
||||
import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import {
|
||||
ProcessedPackageMetadata,
|
||||
SchemaMetadata,
|
||||
} from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { schema } from '../../../../lib/rspack/schema/generators/configuration';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function ConfigurationGenerator({
|
||||
menu,
|
||||
pkg,
|
||||
schema,
|
||||
}: {
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
}): JSX.Element {
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
|
||||
const vm: {
|
||||
menu: Menu;
|
||||
package: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
} = {
|
||||
menu: {
|
||||
sections: sortCorePackagesFirst<MenuSection>(
|
||||
getPackagesSections(menu),
|
||||
'id'
|
||||
),
|
||||
},
|
||||
package: pkg,
|
||||
schema: schema,
|
||||
};
|
||||
|
||||
/**
|
||||
* Show either the docviewer or the package view depending on:
|
||||
* - docviewer: it is a documentation document
|
||||
* - packageviewer: it is package generated documentation
|
||||
*/
|
||||
|
||||
return (
|
||||
<div id="shell" className="flex h-full flex-col">
|
||||
<div className="w-full flex-shrink-0">
|
||||
<DocumentationHeader isNavOpen={navIsOpen} toggleNav={toggleNav} />
|
||||
</div>
|
||||
<main
|
||||
id="main"
|
||||
role="main"
|
||||
className="flex h-full flex-1 overflow-y-hidden"
|
||||
>
|
||||
<SidebarContainer
|
||||
menu={vm.menu}
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return {
|
||||
props: {
|
||||
pkg,
|
||||
schema,
|
||||
menu: menusApi.getMenu('nx-api', 'nx-api'),
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
import { getPackagesSections } from '@nx/nx-dev/data-access-menu';
|
||||
import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { PackageSchemaSubList } from '@nx/nx-dev/feature-package-schema-viewer/src/lib/package-schema-sub-list';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function GeneratorsIndex({
|
||||
menu,
|
||||
pkg,
|
||||
}: {
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
}): JSX.Element {
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
|
||||
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
|
||||
menu: {
|
||||
sections: sortCorePackagesFirst<MenuSection>(
|
||||
getPackagesSections(menu),
|
||||
'id'
|
||||
),
|
||||
},
|
||||
package: pkg,
|
||||
};
|
||||
|
||||
/**
|
||||
* Show either the docviewer or the package view depending on:
|
||||
* - docviewer: it is a documentation document
|
||||
* - packageviewer: it is package generated documentation
|
||||
*/
|
||||
|
||||
return (
|
||||
<div id="shell" className="flex h-full flex-col">
|
||||
<div className="w-full flex-shrink-0">
|
||||
<DocumentationHeader isNavOpen={navIsOpen} toggleNav={toggleNav} />
|
||||
</div>
|
||||
<main
|
||||
id="main"
|
||||
role="main"
|
||||
className="flex h-full flex-1 overflow-y-hidden"
|
||||
>
|
||||
<SidebarContainer
|
||||
menu={vm.menu}
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaSubList pkg={vm.package} type={'generator'} />
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(): Promise<{
|
||||
props: {
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
};
|
||||
}> {
|
||||
return {
|
||||
props: {
|
||||
menu: menusApi.getMenu('nx-api', 'nx-api'),
|
||||
pkg,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
import { PackageSchemaViewer } from '@nx/nx-dev/feature-package-schema-viewer';
|
||||
import { getPackagesSections } from '@nx/nx-dev/data-access-menu';
|
||||
import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import {
|
||||
ProcessedPackageMetadata,
|
||||
SchemaMetadata,
|
||||
} from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { schema } from '../../../../lib/rspack/schema/generators/init';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function InitGenerator({
|
||||
menu,
|
||||
pkg,
|
||||
schema,
|
||||
}: {
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
}): JSX.Element {
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
|
||||
const vm: {
|
||||
menu: Menu;
|
||||
package: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
} = {
|
||||
menu: {
|
||||
sections: sortCorePackagesFirst<MenuSection>(
|
||||
getPackagesSections(menu),
|
||||
'id'
|
||||
),
|
||||
},
|
||||
package: pkg,
|
||||
schema: schema,
|
||||
};
|
||||
|
||||
/**
|
||||
* Show either the docviewer or the package view depending on:
|
||||
* - docviewer: it is a documentation document
|
||||
* - packageviewer: it is package generated documentation
|
||||
*/
|
||||
|
||||
return (
|
||||
<div id="shell" className="flex h-full flex-col">
|
||||
<div className="w-full flex-shrink-0">
|
||||
<DocumentationHeader isNavOpen={navIsOpen} toggleNav={toggleNav} />
|
||||
</div>
|
||||
<main
|
||||
id="main"
|
||||
role="main"
|
||||
className="flex h-full flex-1 overflow-y-hidden"
|
||||
>
|
||||
<SidebarContainer
|
||||
menu={vm.menu}
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return {
|
||||
props: {
|
||||
pkg,
|
||||
schema,
|
||||
menu: menusApi.getMenu('nx-api', 'nx-api'),
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
import { PackageSchemaList } from '@nx/nx-dev/feature-package-schema-viewer';
|
||||
import { getPackagesSections } from '@nx/nx-dev/data-access-menu';
|
||||
import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { menusApi } from '../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../lib/navigation-toggle.effect';
|
||||
import { content } from '../../../lib/rspack/content/overview';
|
||||
import { pkg } from '../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function RspackIndex({
|
||||
overview,
|
||||
menu,
|
||||
pkg,
|
||||
}: {
|
||||
menu: MenuItem[];
|
||||
overview: string;
|
||||
pkg: ProcessedPackageMetadata;
|
||||
}): JSX.Element {
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
|
||||
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
|
||||
menu: {
|
||||
sections: sortCorePackagesFirst<MenuSection>(
|
||||
getPackagesSections(menu),
|
||||
'id'
|
||||
),
|
||||
},
|
||||
package: pkg,
|
||||
};
|
||||
|
||||
/**
|
||||
* Show either the docviewer or the package view depending on:
|
||||
* - docviewer: it is a documentation document
|
||||
* - packageviewer: it is package generated documentation
|
||||
*/
|
||||
|
||||
return (
|
||||
<div id="shell" className="flex h-full flex-col">
|
||||
<div className="w-full flex-shrink-0">
|
||||
<DocumentationHeader isNavOpen={navIsOpen} toggleNav={toggleNav} />
|
||||
</div>
|
||||
<main
|
||||
id="main"
|
||||
role="main"
|
||||
className="flex h-full flex-1 overflow-y-hidden"
|
||||
>
|
||||
<SidebarContainer
|
||||
menu={vm.menu}
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaList pkg={vm.package} overview={overview} />
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return {
|
||||
props: {
|
||||
menu: menusApi.getMenu('nx-api', 'nx-api'),
|
||||
overview: content,
|
||||
pkg,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -100,6 +100,10 @@
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-url": "^8.0.2",
|
||||
"@rspack/core": "1.0.5",
|
||||
"@rspack/dev-server": "1.0.5",
|
||||
"@rspack/plugin-minify": "^0.7.5",
|
||||
"@rspack/plugin-react-refresh": "^1.0.0",
|
||||
"@schematics/angular": "~18.2.0",
|
||||
"@storybook/addon-essentials": "^8.2.8",
|
||||
"@storybook/addon-interactions": "^8.2.8",
|
||||
|
||||
11
packages-legacy/rspack/README.md
Normal file
11
packages-legacy/rspack/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
## @nrwl/rspack has been deprecated!
|
||||
|
||||
@nrwl/rspack has been deprecated in favor of [@nx/rspack](https://www.npmjs.com/package/@nx/rspack). Please use that instead.
|
||||
|
||||
@nrwl/rspack will no longer be published in Nx v17.
|
||||
|
||||
<p style="text-align: center;"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx.png" width="600" alt="Nx - Smart, Fast and Extensible Build System"></p>
|
||||
|
||||
# Nx: Smart, Fast and Extensible Build System
|
||||
|
||||
Nx is a next generation build system with first class monorepo support and powerful integrations.
|
||||
15
packages-legacy/rspack/executors.json
Normal file
15
packages-legacy/rspack/executors.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"executors": {
|
||||
"rspack": {
|
||||
"implementation": "@nx/rspack/src/executors/rspack/rspack.impl",
|
||||
"schema": "@nx/rspack/src/executors/rspack/schema.json",
|
||||
"description": "rspack executor"
|
||||
},
|
||||
"dev-server": {
|
||||
"implementation": "@nx/rspack/src/executors/dev-server/dev-server.impl",
|
||||
"schema": "@nx/rspack/src/executors/dev-server/schema.json",
|
||||
"description": "dev-server executor"
|
||||
}
|
||||
}
|
||||
}
|
||||
4
packages-legacy/rspack/generators.json
Normal file
4
packages-legacy/rspack/generators.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": ["@nx/rspack"],
|
||||
"schematics": {}
|
||||
}
|
||||
1
packages-legacy/rspack/index.ts
Normal file
1
packages-legacy/rspack/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from '@nx/rspack';
|
||||
27
packages-legacy/rspack/package.json
Normal file
27
packages-legacy/rspack/package.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "@nrwl/rspack",
|
||||
"version": "0.0.1",
|
||||
"type": "commonjs",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nrwl/nx-labs.git",
|
||||
"directory": "packages-legacy/rspack"
|
||||
},
|
||||
"keywords": [
|
||||
"Monorepo",
|
||||
"Next",
|
||||
"Vercel"
|
||||
],
|
||||
"author": "Jack Hsu",
|
||||
"license": "MIT",
|
||||
"homepage": "https://nx.dev",
|
||||
"main": "src/index.js",
|
||||
"generators": "./generators.json",
|
||||
"executors": "./executors.json",
|
||||
"dependencies": {
|
||||
"@nx/rspack": "file:../../packages/rspack"
|
||||
},
|
||||
"nx-migrations": {
|
||||
"migrations": "@nx/rspack/migrations.json"
|
||||
}
|
||||
}
|
||||
38
packages-legacy/rspack/project.json
Normal file
38
packages-legacy/rspack/project.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "rspack-legacy",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages-legacy/rspack",
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {
|
||||
"outputs": ["{workspaceRoot}/build/packages/{projectName}/README.md"],
|
||||
"command": "node ./scripts/copy-readme.js rspack-legacy"
|
||||
},
|
||||
"build-base": {
|
||||
"executor": "@nrwl/js:tsc",
|
||||
"dependsOn": ["^build"],
|
||||
"options": {
|
||||
"main": "packages-legacy/rspack/index.ts",
|
||||
"tsConfig": "packages-legacy/rspack/tsconfig.json",
|
||||
"outputPath": "build/packages/rspack-legacy",
|
||||
"updateBuildableProjectDepsInPackageJson": false,
|
||||
"assets": [
|
||||
"packages-legacy/rspack/*.md",
|
||||
{
|
||||
"input": "packages-legacy/rspack",
|
||||
"glob": "**/*.json",
|
||||
"ignore": ["**/tsconfig*.json", "project.json"],
|
||||
"output": "/"
|
||||
},
|
||||
{
|
||||
"input": "packages-legacy/rspack",
|
||||
"glob": "**/*.d.ts",
|
||||
"output": "/"
|
||||
},
|
||||
"LICENSE"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
9
packages-legacy/rspack/tsconfig.json
Normal file
9
packages-legacy/rspack/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"files": ["index.ts"]
|
||||
}
|
||||
@ -142,6 +142,8 @@
|
||||
"@nrwl/rollup",
|
||||
"@nx/remix",
|
||||
"@nrwl/remix",
|
||||
"@nx/rspack",
|
||||
"@nrwl/rspack",
|
||||
"@nx/storybook",
|
||||
"@nrwl/storybook",
|
||||
"@nrwl/tao",
|
||||
|
||||
@ -85,6 +85,10 @@ export const CORE_PLUGINS: CorePlugin[] = [
|
||||
name: '@nx/rollup',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
name: '@nx/rspack',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
name: '@nx/storybook',
|
||||
capabilities: 'executors,generators',
|
||||
|
||||
25
packages/rspack/.eslintrc.json
Normal file
25
packages/rspack/.eslintrc.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"extends": ["../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["./package.json", "./generators.json", "./executors.json"],
|
||||
"parser": "jsonc-eslint-parser",
|
||||
"rules": {
|
||||
"@nx/nx-plugin-checks": "error"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
67
packages/rspack/README.md
Normal file
67
packages/rspack/README.md
Normal file
@ -0,0 +1,67 @@
|
||||
<p style="text-align: center;">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-dark.svg">
|
||||
<img alt="Nx - Smart Monorepos · Fast CI" src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-light.svg" width="100%">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
{{links}}
|
||||
|
||||
<hr>
|
||||
|
||||
# Nx: Smart Monorepos · Fast CI
|
||||
|
||||
Nx is a build system, optimized for monorepos, with plugins for popular frameworks and tools and advanced CI capabilities including caching and distribution.
|
||||
|
||||
This package is a [Rspack plugin for Nx](https://nx.dev/nx-api/rspack).
|
||||
|
||||
{{content}}
|
||||
|
||||
<p style="text-align: center;"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx.png" width="600" alt="Nx - Smart, Fast and Extensible Build System"></p>
|
||||
|
||||
<hr>
|
||||
|
||||
# Nx: Smart, Fast and Extensible Build System
|
||||
|
||||
Nx is a next generation build system with first class monorepo support and powerful integrations.
|
||||
|
||||
This package is a Rspack plugin for Nx.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Use `--preset=@nx/rspack` when creating new workspace.
|
||||
|
||||
e.g.
|
||||
|
||||
```bash
|
||||
npx create-nx-workspace@latest rspack-demo --preset=@nx/rspack
|
||||
```
|
||||
|
||||
Now, you can go into the `rspack-demo` folder and start development.
|
||||
|
||||
```bash
|
||||
cd rspack-demo
|
||||
npm start
|
||||
```
|
||||
|
||||
You can also run lint, test, and e2e scripts for the project.
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
npm run test
|
||||
npm run e2e
|
||||
```
|
||||
|
||||
## Existing workspaces
|
||||
|
||||
You can add Rspack to any existing Nx workspace.
|
||||
|
||||
First, install the plugin:
|
||||
|
||||
```bash
|
||||
npm install --save-dev @nx/rspack
|
||||
```
|
||||
|
||||
Then, r
|
||||
|
||||
**Note:** You must restart the server if you make any changes to your library.
|
||||
35
packages/rspack/executors.json
Normal file
35
packages/rspack/executors.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"executors": {
|
||||
"rspack": {
|
||||
"implementation": "./src/executors/rspack/rspack.impl",
|
||||
"schema": "./src/executors/rspack/schema.json",
|
||||
"description": "Run Rspack via an executor for a project."
|
||||
},
|
||||
"dev-server": {
|
||||
"implementation": "./src/executors/dev-server/dev-server.impl",
|
||||
"schema": "./src/executors/dev-server/schema.json",
|
||||
"description": "Run @rspack/dev-server to serve a project."
|
||||
},
|
||||
"ssr-dev-server": {
|
||||
"implementation": "./src/executors/ssr-dev-server/ssr-dev-server.impl",
|
||||
"schema": "./src/executors/ssr-dev-server/schema.json",
|
||||
"description": "Serve a SSR application."
|
||||
},
|
||||
"module-federation-dev-server": {
|
||||
"implementation": "./src/executors/module-federation-dev-server/module-federation-dev-server.impl",
|
||||
"schema": "./src/executors/module-federation-dev-server/schema.json",
|
||||
"description": "Serve a host or remote application."
|
||||
},
|
||||
"module-federation-ssr-dev-server": {
|
||||
"implementation": "./src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl",
|
||||
"schema": "./src/executors/module-federation-ssr-dev-server/schema.json",
|
||||
"description": "Serve a host application along with it's known remotes."
|
||||
},
|
||||
"module-federation-static-server": {
|
||||
"implementation": "./src/executors/module-federation-static-server/module-federation-static-server.impl",
|
||||
"schema": "./src/executors/module-federation-static-server/schema.json",
|
||||
"description": "Serve a host and its remotes statically."
|
||||
}
|
||||
}
|
||||
}
|
||||
31
packages/rspack/generators.json
Normal file
31
packages/rspack/generators.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"name": "rspack",
|
||||
"version": "0.0.1",
|
||||
"generators": {
|
||||
"configuration": {
|
||||
"factory": "./src/generators/configuration/configuration",
|
||||
"schema": "./src/generators/configuration/schema.json",
|
||||
"description": "Rspack configuration generator."
|
||||
},
|
||||
"init": {
|
||||
"factory": "./src/generators/init/init",
|
||||
"schema": "./src/generators/init/schema.json",
|
||||
"description": "Rspack init generator.",
|
||||
"hidden": true
|
||||
},
|
||||
"preset": {
|
||||
"factory": "./src/generators/preset/preset",
|
||||
"schema": "./src/generators/preset/schema.json",
|
||||
"description": "React preset generator.",
|
||||
"hidden": true
|
||||
},
|
||||
"application": {
|
||||
"factory": "./src/generators/application/application",
|
||||
"schema": "./src/generators/application/schema.json",
|
||||
"aliases": ["app"],
|
||||
"x-type": "application",
|
||||
"description": "React application generator."
|
||||
}
|
||||
}
|
||||
}
|
||||
16
packages/rspack/jest.config.ts
Normal file
16
packages/rspack/jest.config.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
displayName: 'rspack',
|
||||
preset: '../../jest.preset.js',
|
||||
globals: {},
|
||||
transform: {
|
||||
'^.+\\.[tj]s$': [
|
||||
'ts-jest',
|
||||
{
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
},
|
||||
],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'js', 'html'],
|
||||
coverageDirectory: '../../coverage/packages/rspack',
|
||||
};
|
||||
98
packages/rspack/migrations.json
Normal file
98
packages/rspack/migrations.json
Normal file
@ -0,0 +1,98 @@
|
||||
{
|
||||
"generators": {
|
||||
"update-16-0-0-add-nx-packages": {
|
||||
"cli": "nx",
|
||||
"version": "16.0.0-beta.1",
|
||||
"description": "Replace @nrwl/rspack with @nx/rspack",
|
||||
"implementation": "./src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages"
|
||||
}
|
||||
},
|
||||
"packageJsonUpdates": {
|
||||
"16.1.3": {
|
||||
"version": "16.1.3-beta.0",
|
||||
"packages": {
|
||||
"@rspack/core": {
|
||||
"version": "~0.1.12",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@rspack/dev-server": {
|
||||
"version": "~0.1.12",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@rspack/plugin-minify": {
|
||||
"version": "~0.1.12",
|
||||
"alwaysAddToPackageJson": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"18.1.0": {
|
||||
"version": "18.1.0-beta.0",
|
||||
"packages": {
|
||||
"@rspack/core": {
|
||||
"version": "~0.5.6",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@rspack/dev-server": {
|
||||
"version": "~0.5.6",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@rspack/plugin-minify": {
|
||||
"version": "~0.5.6",
|
||||
"alwaysAddToPackageJson": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"18.1.3": {
|
||||
"version": "18.1.3",
|
||||
"packages": {
|
||||
"@rspack/core": {
|
||||
"version": "^0.6.1",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@rspack/dev-server": {
|
||||
"version": "^0.6.1",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@rspack/plugin-minify": {
|
||||
"version": "^0.6.1",
|
||||
"alwaysAddToPackageJson": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"19.3.0": {
|
||||
"version": "19.3.0-beta.0",
|
||||
"packages": {
|
||||
"@rspack/core": {
|
||||
"version": "^0.7.5",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@rspack/dev-server": {
|
||||
"version": "^0.7.5",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@rspack/plugin-minify": {
|
||||
"version": "^0.7.5",
|
||||
"alwaysAddToPackageJson": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"19.7.0": {
|
||||
"version": "19.7.0-beta.1",
|
||||
"packages": {
|
||||
"@rspack/core": {
|
||||
"version": "^1.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@rspack/dev-server": {
|
||||
"version": "^1.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@rspack/plugin-react-refresh": {
|
||||
"version": "^1.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": "0.1"
|
||||
}
|
||||
1
packages/rspack/module-federation.ts
Normal file
1
packages/rspack/module-federation.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './src/utils/module-federation/public-api';
|
||||
48
packages/rspack/package.json
Normal file
48
packages/rspack/package.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@nx/rspack",
|
||||
"description": "The Nx Plugin for Rspack contains executors and generators that support building applications using Rspack.",
|
||||
"version": "0.0.1",
|
||||
"type": "commonjs",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nrwl/nx.git",
|
||||
"directory": "packages/rspack"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/nrwl/nx/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"Monorepo",
|
||||
"Rspack",
|
||||
"Bundling",
|
||||
"Module Federation"
|
||||
],
|
||||
"author": "Jack Hsu",
|
||||
"license": "MIT",
|
||||
"homepage": "https://nx.dev",
|
||||
"main": "src/index.js",
|
||||
"generators": "./generators.json",
|
||||
"executors": "./executors.json",
|
||||
"dependencies": {
|
||||
"@nx/js": "file:../js",
|
||||
"@nx/devkit": "file:../devkit",
|
||||
"@nx/eslint": "file:../eslint",
|
||||
"@phenomnomnominal/tsquery": "~5.0.1",
|
||||
"less-loader": "11.1.0",
|
||||
"license-webpack-plugin": "^4.0.2",
|
||||
"sass-loader": "^12.2.0",
|
||||
"stylus-loader": "^7.1.0",
|
||||
"postcss-loader": "^8.1.1",
|
||||
"@rspack/core": "^1.0.4",
|
||||
"@rspack/plugin-react-refresh": "^1.0.0",
|
||||
"@rspack/plugin-minify": "^0.7.5",
|
||||
"chalk": "~4.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@module-federation/enhanced": "~0.6.0",
|
||||
"@module-federation/node": "~2.5.10"
|
||||
},
|
||||
"nx-migrations": {
|
||||
"migrations": "./migrations.json"
|
||||
}
|
||||
}
|
||||
2
packages/rspack/plugin.ts
Normal file
2
packages/rspack/plugin.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { createDependencies, createNodesV2 } from './src/plugins/plugin';
|
||||
export type { RspackPluginOptions } from './src/plugins/plugin';
|
||||
50
packages/rspack/project.json
Normal file
50
packages/rspack/project.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "rspack",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/rspack/src",
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"add-extra-dependencies": {
|
||||
"outputs": ["{workspaceRoot}/build/packages/rspack"],
|
||||
"command": "node ./scripts/add-dependency-to-build.js rspack @nrwl/rspack"
|
||||
},
|
||||
"build": {
|
||||
"executor": "nx:run-commands",
|
||||
"outputs": ["{workspaceRoot}/build/packages/rspack"],
|
||||
"options": {
|
||||
"command": "node ./scripts/copy-readme.js rspack"
|
||||
}
|
||||
},
|
||||
"build-base": {
|
||||
"dependsOn": ["^build-base"],
|
||||
"executor": "@nx/js:tsc",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "build/packages/rspack",
|
||||
"main": "packages/rspack/src/index.ts",
|
||||
"tsConfig": "packages/rspack/tsconfig.lib.json",
|
||||
"assets": [
|
||||
"packages/rspack/*.md",
|
||||
{
|
||||
"input": "./packages/rspack/src",
|
||||
"glob": "**/!(*.ts)",
|
||||
"output": "./src"
|
||||
},
|
||||
{
|
||||
"input": "./packages/rspack/src",
|
||||
"glob": "**/*.d.ts",
|
||||
"output": "./src"
|
||||
},
|
||||
{
|
||||
"input": "./packages/rspack",
|
||||
"glob": "**.json",
|
||||
"output": ".",
|
||||
"ignore": ["**/tsconfig*.json", "project.json", ".eslintrc.json"]
|
||||
},
|
||||
"LICENSE"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
80
packages/rspack/src/executors/dev-server/dev-server.impl.ts
Normal file
80
packages/rspack/src/executors/dev-server/dev-server.impl.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import {
|
||||
ExecutorContext,
|
||||
logger,
|
||||
parseTargetString,
|
||||
readTargetOptions,
|
||||
} from '@nx/devkit';
|
||||
import { createAsyncIterable } from '@nx/devkit/src/utils/async-iterable';
|
||||
import { Configuration } from '@rspack/core';
|
||||
import { RspackDevServer } from '@rspack/dev-server';
|
||||
import { createCompiler, isMultiCompiler } from '../../utils/create-compiler';
|
||||
import { isMode } from '../../utils/mode-utils';
|
||||
import { getDevServerOptions } from './lib/get-dev-server-config';
|
||||
import { DevServerExecutorSchema } from './schema';
|
||||
|
||||
type DevServer = Configuration['devServer'];
|
||||
export default async function* runExecutor(
|
||||
options: DevServerExecutorSchema,
|
||||
context: ExecutorContext
|
||||
): AsyncIterableIterator<{ success: boolean; baseUrl?: string }> {
|
||||
process.env.NODE_ENV ??= options.mode ?? 'development';
|
||||
|
||||
if (isMode(process.env.NODE_ENV)) {
|
||||
options.mode = process.env.NODE_ENV;
|
||||
}
|
||||
|
||||
const buildTarget = parseTargetString(
|
||||
options.buildTarget,
|
||||
context.projectGraph
|
||||
);
|
||||
|
||||
const buildOptions = readTargetOptions(buildTarget, context);
|
||||
|
||||
let devServerConfig: DevServer = getDevServerOptions(
|
||||
context.root,
|
||||
options,
|
||||
buildOptions
|
||||
);
|
||||
|
||||
const compiler = await createCompiler(
|
||||
{ ...buildOptions, devServer: devServerConfig, mode: options.mode },
|
||||
context
|
||||
);
|
||||
|
||||
// Use the first one if it's MultiCompiler
|
||||
// https://webpack.js.org/configuration/dev-server/#root:~:text=Be%20aware%20that%20when%20exporting%20multiple%20configurations%20only%20the%20devServer%20options%20for%20the%20first%20configuration%20will%20be%20taken%20into%20account%20and%20used%20for%20all%20the%20configurations%20in%20the%20array.
|
||||
const firstCompiler = isMultiCompiler(compiler)
|
||||
? compiler.compilers[0]
|
||||
: compiler;
|
||||
devServerConfig = {
|
||||
...devServerConfig,
|
||||
...firstCompiler.options.devServer,
|
||||
port: devServerConfig.port,
|
||||
};
|
||||
|
||||
const baseUrl = `http://localhost:${options.port ?? 4200}`;
|
||||
|
||||
return yield* createAsyncIterable(({ next }) => {
|
||||
const server = new RspackDevServer(
|
||||
{
|
||||
...devServerConfig,
|
||||
onListening: () => {
|
||||
next({
|
||||
success: true,
|
||||
baseUrl,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
compiler
|
||||
);
|
||||
server.compiler.hooks.done.tap('NX Rspack Dev Server', (stats) => {
|
||||
if (stats.hasErrors()) {
|
||||
logger.error(`NX Compilation failed. See above for more details.`);
|
||||
} else {
|
||||
logger.info(`NX Server ready at ${baseUrl}`);
|
||||
}
|
||||
});
|
||||
server.start();
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
import { logger } from '@nx/devkit';
|
||||
import type { Configuration as RspackDevServerConfiguration } from '@rspack/dev-server';
|
||||
import { readFileSync } from 'fs';
|
||||
import * as path from 'path';
|
||||
import { RspackExecutorSchema } from '../../rspack/schema';
|
||||
import { DevServerExecutorSchema } from '../schema';
|
||||
import { buildServePath } from './serve-path';
|
||||
|
||||
export function getDevServerOptions(
|
||||
root: string,
|
||||
serveOptions: DevServerExecutorSchema,
|
||||
buildOptions: RspackExecutorSchema
|
||||
): RspackDevServerConfiguration {
|
||||
const servePath = buildServePath(buildOptions);
|
||||
|
||||
let scriptsOptimization: boolean;
|
||||
let stylesOptimization: boolean;
|
||||
if (typeof buildOptions.optimization === 'boolean') {
|
||||
scriptsOptimization = stylesOptimization = buildOptions.optimization;
|
||||
} else if (buildOptions.optimization) {
|
||||
scriptsOptimization = buildOptions.optimization.scripts;
|
||||
stylesOptimization = buildOptions.optimization.styles;
|
||||
} else {
|
||||
scriptsOptimization = stylesOptimization = false;
|
||||
}
|
||||
|
||||
const config: RspackDevServerConfiguration = {
|
||||
host: serveOptions.host,
|
||||
port: serveOptions.port,
|
||||
headers: { 'Access-Control-Allow-Origin': '*' },
|
||||
historyApiFallback: {
|
||||
index:
|
||||
buildOptions.index &&
|
||||
`${servePath}${path.basename(buildOptions.index)}`,
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
onListening(server) {
|
||||
const isHttps =
|
||||
server.options.https ||
|
||||
(server.options.server as { type: string })?.type === 'https';
|
||||
logger.info(
|
||||
`NX Web Development Server is listening at ${
|
||||
isHttps ? 'https' : 'http'
|
||||
}://${server.options.host}:${server.options.port}${buildServePath(
|
||||
buildOptions
|
||||
)}`
|
||||
);
|
||||
},
|
||||
open: false,
|
||||
static: false,
|
||||
compress: scriptsOptimization || stylesOptimization,
|
||||
devMiddleware: {
|
||||
publicPath: servePath,
|
||||
stats: false,
|
||||
},
|
||||
client: {
|
||||
webSocketURL: serveOptions.publicHost,
|
||||
overlay: {
|
||||
errors: !(scriptsOptimization || stylesOptimization),
|
||||
warnings: false,
|
||||
},
|
||||
},
|
||||
hot: true,
|
||||
};
|
||||
|
||||
if (serveOptions.ssl) {
|
||||
config.server = {
|
||||
type: 'https',
|
||||
};
|
||||
if (serveOptions.sslKey && serveOptions.sslCert) {
|
||||
config.server.options = getSslConfig(root, serveOptions);
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
function getSslConfig(root: string, options: DevServerExecutorSchema) {
|
||||
return {
|
||||
key: readFileSync(path.resolve(root, options.sslKey), 'utf-8'),
|
||||
cert: readFileSync(path.resolve(root, options.sslCert), 'utf-8'),
|
||||
};
|
||||
}
|
||||
56
packages/rspack/src/executors/dev-server/lib/serve-path.ts
Normal file
56
packages/rspack/src/executors/dev-server/lib/serve-path.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import type { RspackExecutorSchema } from '../../rspack/schema';
|
||||
|
||||
export function buildServePath(browserOptions: RspackExecutorSchema) {
|
||||
let servePath =
|
||||
_findDefaultServePath(browserOptions.baseHref, browserOptions.deployUrl) ||
|
||||
'/';
|
||||
if (servePath.endsWith('/')) {
|
||||
servePath = servePath.slice(0, -1);
|
||||
}
|
||||
if (!servePath.startsWith('/')) {
|
||||
servePath = `/${servePath}`;
|
||||
}
|
||||
|
||||
return servePath;
|
||||
}
|
||||
|
||||
export function _findDefaultServePath(
|
||||
baseHref?: string,
|
||||
deployUrl?: string
|
||||
): string | null {
|
||||
if (!baseHref && !deployUrl) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (
|
||||
/^(\w+:)?\/\//.test(baseHref || '') ||
|
||||
/^(\w+:)?\/\//.test(deployUrl || '')
|
||||
) {
|
||||
// If baseHref or deployUrl is absolute, unsupported by nx serve
|
||||
return null;
|
||||
}
|
||||
|
||||
// normalize baseHref
|
||||
// for nx serve the starting base is always `/` so a relative
|
||||
// and root relative value are identical
|
||||
const baseHrefParts = (baseHref || '')
|
||||
.split('/')
|
||||
.filter((part) => part !== '');
|
||||
if (baseHref && !baseHref.endsWith('/')) {
|
||||
baseHrefParts.pop();
|
||||
}
|
||||
const normalizedBaseHref =
|
||||
baseHrefParts.length === 0 ? '/' : `/${baseHrefParts.join('/')}/`;
|
||||
|
||||
if (deployUrl && deployUrl[0] === '/') {
|
||||
if (baseHref && baseHref[0] === '/' && normalizedBaseHref !== deployUrl) {
|
||||
// If baseHref and deployUrl are root relative and not equivalent, unsupported by nx serve
|
||||
return null;
|
||||
}
|
||||
|
||||
return deployUrl;
|
||||
}
|
||||
|
||||
// Join together baseHref and deployUrl
|
||||
return `${normalizedBaseHref}${deployUrl || ''}`;
|
||||
}
|
||||
12
packages/rspack/src/executors/dev-server/schema.d.ts
vendored
Normal file
12
packages/rspack/src/executors/dev-server/schema.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import type { Mode } from '@rspack/core';
|
||||
|
||||
export interface DevServerExecutorSchema {
|
||||
buildTarget: string;
|
||||
mode?: Mode;
|
||||
host?: string;
|
||||
port?: number;
|
||||
ssl?: boolean;
|
||||
sslKey?: string;
|
||||
sslCert?: string;
|
||||
publicHost?: string;
|
||||
}
|
||||
45
packages/rspack/src/executors/dev-server/schema.json
Normal file
45
packages/rspack/src/executors/dev-server/schema.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"version": 2,
|
||||
"title": "Rspack dev-server executor",
|
||||
"description": "Run @rspack/dev-server to serve a project.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"buildTarget": {
|
||||
"type": "string",
|
||||
"description": "The build target for rspack."
|
||||
},
|
||||
"port": {
|
||||
"type": "number",
|
||||
"description": "The port to for the dev-server to listen on."
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"description": "Mode to run the server in.",
|
||||
"enum": ["development", "production", "none"]
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
"description": "Host to listen on.",
|
||||
"default": "localhost"
|
||||
},
|
||||
"ssl": {
|
||||
"type": "boolean",
|
||||
"description": "Serve using `HTTPS`.",
|
||||
"default": false
|
||||
},
|
||||
"sslKey": {
|
||||
"type": "string",
|
||||
"description": "SSL key to use for serving `HTTPS`."
|
||||
},
|
||||
"sslCert": {
|
||||
"type": "string",
|
||||
"description": "SSL certificate to use for serving `HTTPS`."
|
||||
},
|
||||
"publicHost": {
|
||||
"type": "string",
|
||||
"description": "Public URL where the application will be served."
|
||||
}
|
||||
},
|
||||
"required": ["buildTarget"]
|
||||
}
|
||||
@ -0,0 +1,317 @@
|
||||
import {
|
||||
ExecutorContext,
|
||||
logger,
|
||||
parseTargetString,
|
||||
readTargetOptions,
|
||||
runExecutor,
|
||||
workspaceRoot,
|
||||
} from '@nx/devkit';
|
||||
import {
|
||||
combineAsyncIterables,
|
||||
createAsyncIterable,
|
||||
} from '@nx/devkit/src/utils/async-iterable';
|
||||
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
||||
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
||||
import { cpSync, existsSync } from 'fs';
|
||||
import { extname, join } from 'path';
|
||||
import {
|
||||
getModuleFederationConfig,
|
||||
getRemotes,
|
||||
} from '../../utils/module-federation';
|
||||
import { buildStaticRemotes } from '../../utils/module-federation/build-static.remotes';
|
||||
import {
|
||||
parseStaticRemotesConfig,
|
||||
type StaticRemotesConfig,
|
||||
} from '../../utils/module-federation/parse-static-remotes-config';
|
||||
import { startRemoteProxies } from '../../utils/module-federation/start-remote-proxies';
|
||||
import devServerExecutor from '../dev-server/dev-server.impl';
|
||||
import { ModuleFederationDevServerOptions } from './schema';
|
||||
|
||||
function getBuildOptions(buildTarget: string, context: ExecutorContext) {
|
||||
const target = parseTargetString(buildTarget, context);
|
||||
|
||||
const buildOptions = readTargetOptions(target, context);
|
||||
|
||||
return {
|
||||
...buildOptions,
|
||||
};
|
||||
}
|
||||
|
||||
function startStaticRemotesFileServer(
|
||||
staticRemotesConfig: StaticRemotesConfig,
|
||||
context: ExecutorContext,
|
||||
options: ModuleFederationDevServerOptions
|
||||
) {
|
||||
if (
|
||||
!staticRemotesConfig.remotes ||
|
||||
staticRemotesConfig.remotes.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let shouldMoveToCommonLocation = false;
|
||||
let commonOutputDirectory: string;
|
||||
for (const app of staticRemotesConfig.remotes) {
|
||||
const remoteBasePath = staticRemotesConfig.config[app].basePath;
|
||||
if (!commonOutputDirectory) {
|
||||
commonOutputDirectory = remoteBasePath;
|
||||
} else if (commonOutputDirectory !== remoteBasePath) {
|
||||
shouldMoveToCommonLocation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldMoveToCommonLocation) {
|
||||
commonOutputDirectory = join(workspaceRoot, 'tmp/static-remotes');
|
||||
for (const app of staticRemotesConfig.remotes) {
|
||||
const remoteConfig = staticRemotesConfig.config[app];
|
||||
cpSync(
|
||||
remoteConfig.outputPath,
|
||||
join(commonOutputDirectory, remoteConfig.urlSegment),
|
||||
{
|
||||
force: true,
|
||||
recursive: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const staticRemotesIter = fileServerExecutor(
|
||||
{
|
||||
cors: true,
|
||||
watch: false,
|
||||
staticFilePath: commonOutputDirectory,
|
||||
parallel: false,
|
||||
spa: false,
|
||||
withDeps: false,
|
||||
host: options.host,
|
||||
port: options.staticRemotesPort,
|
||||
ssl: options.ssl,
|
||||
sslCert: options.sslCert,
|
||||
sslKey: options.sslKey,
|
||||
cacheSeconds: -1,
|
||||
},
|
||||
context
|
||||
);
|
||||
|
||||
return staticRemotesIter;
|
||||
}
|
||||
|
||||
async function startRemotes(
|
||||
remotes: string[],
|
||||
context: ExecutorContext,
|
||||
options: ModuleFederationDevServerOptions,
|
||||
target: 'serve' | 'serve-static' = 'serve'
|
||||
) {
|
||||
const remoteIters: AsyncIterable<{ success: boolean }>[] = [];
|
||||
|
||||
for (const app of remotes) {
|
||||
const remoteProjectServeTarget =
|
||||
context.projectGraph.nodes[app].data.targets[target];
|
||||
const isUsingModuleFederationDevServerExecutor =
|
||||
remoteProjectServeTarget.executor.includes(
|
||||
'module-federation-dev-server'
|
||||
);
|
||||
|
||||
const configurationOverride = options.devRemotes?.find(
|
||||
(
|
||||
r
|
||||
): r is {
|
||||
remoteName: string;
|
||||
configuration: string;
|
||||
} => typeof r !== 'string' && r.remoteName === app
|
||||
)?.configuration;
|
||||
|
||||
const defaultOverrides = {
|
||||
...(options.host ? { host: options.host } : {}),
|
||||
...(options.ssl ? { ssl: options.ssl } : {}),
|
||||
...(options.sslCert ? { sslCert: options.sslCert } : {}),
|
||||
...(options.sslKey ? { sslKey: options.sslKey } : {}),
|
||||
};
|
||||
const overrides =
|
||||
target === 'serve'
|
||||
? {
|
||||
watch: true,
|
||||
...(isUsingModuleFederationDevServerExecutor
|
||||
? { isInitialHost: false }
|
||||
: {}),
|
||||
...defaultOverrides,
|
||||
}
|
||||
: { ...defaultOverrides };
|
||||
|
||||
remoteIters.push(
|
||||
await runExecutor(
|
||||
{
|
||||
project: app,
|
||||
target,
|
||||
configuration: configurationOverride ?? context.configurationName,
|
||||
},
|
||||
overrides,
|
||||
context
|
||||
)
|
||||
);
|
||||
}
|
||||
return remoteIters;
|
||||
}
|
||||
|
||||
export default async function* moduleFederationDevServer(
|
||||
options: ModuleFederationDevServerOptions,
|
||||
context: ExecutorContext
|
||||
): AsyncIterableIterator<{ success: boolean; baseUrl?: string }> {
|
||||
// Force Node to resolve to look for the nx binary that is inside node_modules
|
||||
const nxBin = require.resolve('nx/bin/nx');
|
||||
const currIter = options.static
|
||||
? fileServerExecutor(
|
||||
{
|
||||
...options,
|
||||
parallel: false,
|
||||
withDeps: false,
|
||||
spa: false,
|
||||
cors: true,
|
||||
cacheSeconds: -1,
|
||||
},
|
||||
context
|
||||
)
|
||||
: devServerExecutor(options, context);
|
||||
|
||||
const p = context.projectsConfigurations.projects[context.projectName];
|
||||
const buildOptions = getBuildOptions(options.buildTarget, context);
|
||||
|
||||
let pathToManifestFile = join(
|
||||
context.root,
|
||||
p.sourceRoot,
|
||||
'assets/module-federation.manifest.json'
|
||||
);
|
||||
if (options.pathToManifestFile) {
|
||||
const userPathToManifestFile = join(
|
||||
context.root,
|
||||
options.pathToManifestFile
|
||||
);
|
||||
if (!existsSync(userPathToManifestFile)) {
|
||||
throw new Error(
|
||||
`The provided Module Federation manifest file path does not exist. Please check the file exists at "${userPathToManifestFile}".`
|
||||
);
|
||||
} else if (extname(options.pathToManifestFile) !== '.json') {
|
||||
throw new Error(
|
||||
`The Module Federation manifest file must be a JSON. Please ensure the file at ${userPathToManifestFile} is a JSON.`
|
||||
);
|
||||
}
|
||||
|
||||
pathToManifestFile = userPathToManifestFile;
|
||||
}
|
||||
|
||||
if (!options.isInitialHost) {
|
||||
return yield* currIter;
|
||||
}
|
||||
|
||||
const moduleFederationConfig = getModuleFederationConfig(
|
||||
buildOptions.tsConfig,
|
||||
context.root,
|
||||
p.root,
|
||||
'react'
|
||||
);
|
||||
|
||||
const remoteNames = options.devRemotes?.map((r) =>
|
||||
typeof r === 'string' ? r : r.remoteName
|
||||
);
|
||||
|
||||
const remotes = getRemotes(
|
||||
remoteNames,
|
||||
options.skipRemotes,
|
||||
moduleFederationConfig,
|
||||
{
|
||||
projectName: context.projectName,
|
||||
projectGraph: context.projectGraph,
|
||||
root: context.root,
|
||||
},
|
||||
pathToManifestFile
|
||||
);
|
||||
options.staticRemotesPort ??= remotes.staticRemotePort;
|
||||
|
||||
// Set NX_MF_DEV_REMOTES for the Nx Runtime Library Control Plugin
|
||||
process.env.NX_MF_DEV_REMOTES = JSON.stringify([
|
||||
...(remotes.devRemotes.map((r) =>
|
||||
typeof r === 'string' ? r : r.remoteName
|
||||
) ?? []),
|
||||
p.name,
|
||||
]);
|
||||
|
||||
const staticRemotesConfig = parseStaticRemotesConfig(
|
||||
[...remotes.staticRemotes, ...remotes.dynamicRemotes],
|
||||
context
|
||||
);
|
||||
const mappedLocationsOfStaticRemotes = await buildStaticRemotes(
|
||||
staticRemotesConfig,
|
||||
nxBin,
|
||||
context,
|
||||
options
|
||||
);
|
||||
|
||||
const devRemoteIters = await startRemotes(
|
||||
remotes.devRemotes,
|
||||
context,
|
||||
options,
|
||||
'serve'
|
||||
);
|
||||
|
||||
const staticRemotesIter = startStaticRemotesFileServer(
|
||||
staticRemotesConfig,
|
||||
context,
|
||||
options
|
||||
);
|
||||
|
||||
startRemoteProxies(
|
||||
staticRemotesConfig,
|
||||
mappedLocationsOfStaticRemotes,
|
||||
options.ssl
|
||||
? {
|
||||
pathToCert: join(workspaceRoot, options.sslCert),
|
||||
pathToKey: join(workspaceRoot, options.sslKey),
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
|
||||
return yield* combineAsyncIterables(
|
||||
currIter,
|
||||
...devRemoteIters,
|
||||
...(staticRemotesIter ? [staticRemotesIter] : []),
|
||||
createAsyncIterable<{ success: true; baseUrl: string }>(
|
||||
async ({ next, done }) => {
|
||||
if (!options.isInitialHost) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
if (remotes.remotePorts.length === 0) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const host = options.host ?? 'localhost';
|
||||
const baseUrl = `http${options.ssl ? 's' : ''}://${host}:${
|
||||
options.port
|
||||
}`;
|
||||
const portsToWaitFor = staticRemotesIter
|
||||
? [options.staticRemotesPort, ...remotes.remotePorts]
|
||||
: [...remotes.remotePorts];
|
||||
await Promise.all(
|
||||
portsToWaitFor.map((port) =>
|
||||
waitForPortOpen(port, {
|
||||
retries: 480,
|
||||
retryDelay: 2500,
|
||||
host: host,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
logger.info(`NX All remotes started, server ready at ${baseUrl}`);
|
||||
next({ success: true, baseUrl: baseUrl });
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Failed to start remotes. Check above for any errors.`
|
||||
);
|
||||
} finally {
|
||||
done();
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
18
packages/rspack/src/executors/module-federation-dev-server/schema.d.ts
vendored
Normal file
18
packages/rspack/src/executors/module-federation-dev-server/schema.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
import { DevServerExecutorSchema } from '../dev-server/schema';
|
||||
|
||||
export type ModuleFederationDevServerOptions = DevServerExecutorSchema & {
|
||||
// Module Federation Specific Options
|
||||
devRemotes?: (
|
||||
| string
|
||||
| {
|
||||
remoteName: string;
|
||||
configuration: string;
|
||||
}
|
||||
)[];
|
||||
skipRemotes?: string[];
|
||||
static?: boolean;
|
||||
isInitialHost?: boolean;
|
||||
parallel?: number;
|
||||
staticRemotesPort?: number;
|
||||
pathToManifestFile?: string;
|
||||
};
|
||||
@ -0,0 +1,98 @@
|
||||
{
|
||||
"version": 2,
|
||||
"outputCapture": "direct-nodejs",
|
||||
"title": "Rspack Module Federation Dev Server",
|
||||
"description": "Serve a module federation application.",
|
||||
"cli": "nx",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"devRemotes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"remoteName": {
|
||||
"type": "string"
|
||||
},
|
||||
"configuration": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["remoteName"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "List of remote applications to run in development mode (i.e. using serve target).",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipRemotes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode. This will not remove the remotes from the `module-federation.config` file, and therefore the application may still try to fetch these remotes.\nThis option is useful if you have other means for serving the `remote` application(s).\n**NOTE:** Remotes that are not in the workspace will be skipped automatically.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"buildTarget": {
|
||||
"type": "string",
|
||||
"description": "Target which builds the application.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"port": {
|
||||
"type": "number",
|
||||
"description": "Port to listen on.",
|
||||
"default": 4200,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
"description": "Host to listen on.",
|
||||
"default": "localhost"
|
||||
},
|
||||
"ssl": {
|
||||
"type": "boolean",
|
||||
"description": "Serve using `HTTPS`.",
|
||||
"default": false
|
||||
},
|
||||
"sslKey": {
|
||||
"type": "string",
|
||||
"description": "SSL key to use for serving `HTTPS`."
|
||||
},
|
||||
"sslCert": {
|
||||
"type": "string",
|
||||
"description": "SSL certificate to use for serving `HTTPS`."
|
||||
},
|
||||
"publicHost": {
|
||||
"type": "string",
|
||||
"description": "Public URL where the application will be served."
|
||||
},
|
||||
"static": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to use a static file server instead of the rspack-dev-server. This should be used for remote applications that are also host applications."
|
||||
},
|
||||
"isInitialHost": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the host that is running this executor is the first in the project tree to do so.",
|
||||
"default": true,
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"parallel": {
|
||||
"type": "number",
|
||||
"description": "Max number of parallel processes for building static remotes"
|
||||
},
|
||||
"staticRemotesPort": {
|
||||
"type": "number",
|
||||
"description": "The port at which to serve the file-server for the static remotes."
|
||||
},
|
||||
"pathToManifestFile": {
|
||||
"type": "string",
|
||||
"description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root."
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,406 @@
|
||||
import {
|
||||
ExecutorContext,
|
||||
logger,
|
||||
parseTargetString,
|
||||
readTargetOptions,
|
||||
runExecutor,
|
||||
workspaceRoot,
|
||||
} from '@nx/devkit';
|
||||
import { extname, join } from 'path';
|
||||
import {
|
||||
getModuleFederationConfig,
|
||||
getRemotes,
|
||||
} from '../../utils/module-federation';
|
||||
import { RspackSsrDevServerOptions } from '../ssr-dev-server/schema';
|
||||
import ssrDevServerExecutor from '../ssr-dev-server/ssr-dev-server.impl';
|
||||
|
||||
import {
|
||||
combineAsyncIterables,
|
||||
createAsyncIterable,
|
||||
} from '@nx/devkit/src/utils/async-iterable';
|
||||
import { fork } from 'child_process';
|
||||
import { cpSync, createWriteStream, existsSync } from 'fs';
|
||||
|
||||
import {
|
||||
parseStaticSsrRemotesConfig,
|
||||
type StaticRemotesConfig,
|
||||
} from '../../utils/module-federation/parse-static-remotes-config';
|
||||
|
||||
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
||||
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
||||
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
|
||||
import { startSsrRemoteProxies } from '../../utils/module-federation/start-ssr-remote-proxies';
|
||||
|
||||
type ModuleFederationSsrDevServerOptions = RspackSsrDevServerOptions & {
|
||||
devRemotes?: (
|
||||
| string
|
||||
| {
|
||||
remoteName: string;
|
||||
configuration: string;
|
||||
}
|
||||
)[];
|
||||
|
||||
skipRemotes?: string[];
|
||||
host: string;
|
||||
pathToManifestFile?: string;
|
||||
staticRemotesPort?: number;
|
||||
parallel?: number;
|
||||
ssl?: boolean;
|
||||
sslKey?: string;
|
||||
sslCert?: string;
|
||||
isInitialHost?: boolean;
|
||||
};
|
||||
|
||||
function normalizeOptions(
|
||||
options: ModuleFederationSsrDevServerOptions
|
||||
): ModuleFederationSsrDevServerOptions {
|
||||
return {
|
||||
...options,
|
||||
ssl: options.ssl ?? false,
|
||||
sslCert: options.sslCert ? join(workspaceRoot, options.sslCert) : undefined,
|
||||
sslKey: options.sslKey ? join(workspaceRoot, options.sslKey) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function getBuildOptions(buildTarget: string, context: ExecutorContext) {
|
||||
const target = parseTargetString(buildTarget, context);
|
||||
|
||||
const buildOptions = readTargetOptions(target, context);
|
||||
|
||||
return {
|
||||
...buildOptions,
|
||||
};
|
||||
}
|
||||
|
||||
function startSsrStaticRemotesFileServer(
|
||||
ssrStaticRemotesConfig: StaticRemotesConfig,
|
||||
context: ExecutorContext,
|
||||
options: ModuleFederationSsrDevServerOptions
|
||||
) {
|
||||
if (ssrStaticRemotesConfig.remotes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The directories are usually generated with /browser and /server suffixes so we need to copy them to a common directory
|
||||
const commonOutputDirectory = join(workspaceRoot, 'tmp/static-remotes');
|
||||
for (const app of ssrStaticRemotesConfig.remotes) {
|
||||
const remoteConfig = ssrStaticRemotesConfig.config[app];
|
||||
|
||||
cpSync(
|
||||
remoteConfig.outputPath,
|
||||
join(commonOutputDirectory, remoteConfig.urlSegment),
|
||||
{
|
||||
force: true,
|
||||
recursive: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const staticRemotesIter = fileServerExecutor(
|
||||
{
|
||||
cors: true,
|
||||
watch: false,
|
||||
staticFilePath: commonOutputDirectory,
|
||||
parallel: false,
|
||||
spa: false,
|
||||
withDeps: false,
|
||||
host: options.host,
|
||||
port: options.staticRemotesPort,
|
||||
ssl: options.ssl,
|
||||
sslCert: options.sslCert,
|
||||
sslKey: options.sslKey,
|
||||
cacheSeconds: -1,
|
||||
},
|
||||
context
|
||||
);
|
||||
|
||||
return staticRemotesIter;
|
||||
}
|
||||
|
||||
async function startRemotes(
|
||||
remotes: string[],
|
||||
context: ExecutorContext,
|
||||
options: ModuleFederationSsrDevServerOptions
|
||||
) {
|
||||
const remoteIters: AsyncIterable<{ success: boolean }>[] = [];
|
||||
const target = 'serve';
|
||||
for (const app of remotes) {
|
||||
const remoteProjectServeTarget =
|
||||
context.projectGraph.nodes[app].data.targets[target];
|
||||
const isUsingModuleFederationSsrDevServerExecutor =
|
||||
remoteProjectServeTarget.executor.includes(
|
||||
'module-federation-ssr-dev-server'
|
||||
);
|
||||
|
||||
const configurationOverride = options.devRemotes?.find(
|
||||
(remote): remote is { remoteName: string; configuration: string } =>
|
||||
typeof remote !== 'string' && remote.remoteName === app
|
||||
)?.configuration;
|
||||
{
|
||||
const defaultOverrides = {
|
||||
...(options.host ? { host: options.host } : {}),
|
||||
...(options.ssl ? { ssl: options.ssl } : {}),
|
||||
...(options.sslCert ? { sslCert: options.sslCert } : {}),
|
||||
...(options.sslKey ? { sslKey: options.sslKey } : {}),
|
||||
};
|
||||
|
||||
const overrides = {
|
||||
watch: true,
|
||||
...defaultOverrides,
|
||||
...(isUsingModuleFederationSsrDevServerExecutor
|
||||
? { isInitialHost: false }
|
||||
: {}),
|
||||
};
|
||||
|
||||
remoteIters.push(
|
||||
await runExecutor(
|
||||
{
|
||||
project: app,
|
||||
target,
|
||||
configuration: configurationOverride ?? context.configurationName,
|
||||
},
|
||||
overrides,
|
||||
context
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return remoteIters;
|
||||
}
|
||||
|
||||
async function buildSsrStaticRemotes(
|
||||
staticRemotesConfig: StaticRemotesConfig,
|
||||
nxBin,
|
||||
context: ExecutorContext,
|
||||
options: ModuleFederationSsrDevServerOptions
|
||||
) {
|
||||
if (!staticRemotesConfig.remotes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Nx is building ${staticRemotesConfig.remotes.length} static remotes...`
|
||||
);
|
||||
const mapLocationOfRemotes: Record<string, string> = {};
|
||||
|
||||
for (const remoteApp of staticRemotesConfig.remotes) {
|
||||
mapLocationOfRemotes[remoteApp] = `http${options.ssl ? 's' : ''}://${
|
||||
options.host
|
||||
}:${options.staticRemotesPort}/${
|
||||
staticRemotesConfig.config[remoteApp].urlSegment
|
||||
}`;
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
const childProcess = fork(
|
||||
nxBin,
|
||||
[
|
||||
'run-many',
|
||||
'--target=server',
|
||||
'--projects',
|
||||
staticRemotesConfig.remotes.join(','),
|
||||
...(context.configurationName
|
||||
? [`--configuration=${context.configurationName}`]
|
||||
: []),
|
||||
...(options.parallel ? [`--parallel=${options.parallel}`] : []),
|
||||
],
|
||||
{
|
||||
cwd: context.root,
|
||||
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
|
||||
}
|
||||
);
|
||||
|
||||
// Add a listener to the child process to capture the build log
|
||||
const remoteBuildLogFile = join(
|
||||
workspaceDataDirectory,
|
||||
// eslint-disable-next-line
|
||||
`${new Date().toISOString().replace(/[:\.]/g, '_')}-build.log`
|
||||
);
|
||||
|
||||
const remoteBuildLogStream = createWriteStream(remoteBuildLogFile);
|
||||
|
||||
childProcess.stdout.on('data', (data) => {
|
||||
const ANSII_CODE_REGEX =
|
||||
// eslint-disable-next-line no-control-regex
|
||||
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
|
||||
const stdoutString = data.toString().replace(ANSII_CODE_REGEX, '');
|
||||
remoteBuildLogStream.write(stdoutString);
|
||||
|
||||
// in addition to writing into the stdout stream, also show error directly in console
|
||||
// so the error is easily discoverable. 'ERROR in' is the key word to search in webpack output.
|
||||
if (stdoutString.includes('ERROR in')) {
|
||||
logger.log(stdoutString);
|
||||
}
|
||||
|
||||
if (stdoutString.includes('Successfully ran target server')) {
|
||||
childProcess.stdout.removeAllListeners('data');
|
||||
logger.info(
|
||||
`Nx Built ${staticRemotesConfig.remotes.length} static remotes.`
|
||||
);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => childProcess.kill('SIGTERM'));
|
||||
process.on('exit', () => childProcess.kill('SIGTERM'));
|
||||
});
|
||||
return mapLocationOfRemotes;
|
||||
}
|
||||
|
||||
export default async function* moduleFederationSsrDevServer(
|
||||
ssrDevServerOptions: ModuleFederationSsrDevServerOptions,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
const options = normalizeOptions(ssrDevServerOptions);
|
||||
// Force Node to resolve to look for the nx binary that is inside node_modules
|
||||
const nxBin = require.resolve('nx/bin/nx');
|
||||
const iter = ssrDevServerExecutor(options, context);
|
||||
const projectConfig =
|
||||
context.projectsConfigurations.projects[context.projectName];
|
||||
const buildOptions = getBuildOptions(options.browserTarget, context);
|
||||
|
||||
let pathToManifestFile = join(
|
||||
context.root,
|
||||
projectConfig.sourceRoot,
|
||||
'assets/module-federation.manifest.json'
|
||||
);
|
||||
|
||||
if (options.pathToManifestFile) {
|
||||
const userPathToManifestFile = join(
|
||||
context.root,
|
||||
options.pathToManifestFile
|
||||
);
|
||||
|
||||
if (!existsSync(userPathToManifestFile)) {
|
||||
throw new Error(
|
||||
`The provided Module Federation manifest file path does not exist. Please check the file exists at "${userPathToManifestFile}".`
|
||||
);
|
||||
} else if (extname(userPathToManifestFile) !== '.json') {
|
||||
throw new Error(
|
||||
`The Module Federation manifest file must be a JSON. Please ensure the file at ${userPathToManifestFile} is a JSON.`
|
||||
);
|
||||
}
|
||||
pathToManifestFile = userPathToManifestFile;
|
||||
}
|
||||
|
||||
if (!options.isInitialHost) {
|
||||
return yield* iter;
|
||||
}
|
||||
|
||||
const moduleFederationConfig = getModuleFederationConfig(
|
||||
buildOptions.tsConfig,
|
||||
context.root,
|
||||
projectConfig.root,
|
||||
'react'
|
||||
);
|
||||
|
||||
const remoteNames = options.devRemotes?.map((remote) =>
|
||||
typeof remote === 'string' ? remote : remote.remoteName
|
||||
);
|
||||
|
||||
const remotes = getRemotes(
|
||||
remoteNames,
|
||||
options.skipRemotes,
|
||||
moduleFederationConfig,
|
||||
{
|
||||
projectName: context.projectName,
|
||||
projectGraph: context.projectGraph,
|
||||
root: context.root,
|
||||
},
|
||||
pathToManifestFile
|
||||
);
|
||||
|
||||
options.staticRemotesPort ??= remotes.staticRemotePort;
|
||||
|
||||
process.env.NX_MF_DEV_REMOTES = JSON.stringify([
|
||||
...(remotes.devRemotes.map((r) =>
|
||||
typeof r === 'string' ? r : r.remoteName
|
||||
) ?? []),
|
||||
projectConfig.name,
|
||||
]);
|
||||
|
||||
const staticRemotesConfig = parseStaticSsrRemotesConfig(
|
||||
[...remotes.staticRemotes, ...remotes.dynamicRemotes],
|
||||
context
|
||||
);
|
||||
|
||||
const mappedLocationsOfStaticRemotes = await buildSsrStaticRemotes(
|
||||
staticRemotesConfig,
|
||||
nxBin,
|
||||
context,
|
||||
options
|
||||
);
|
||||
|
||||
const devRemoteIters = await startRemotes(
|
||||
remotes.devRemotes,
|
||||
context,
|
||||
options
|
||||
);
|
||||
|
||||
const staticRemotesIter = startSsrStaticRemotesFileServer(
|
||||
staticRemotesConfig,
|
||||
context,
|
||||
options
|
||||
);
|
||||
|
||||
startSsrRemoteProxies(
|
||||
staticRemotesConfig,
|
||||
mappedLocationsOfStaticRemotes,
|
||||
options.ssl
|
||||
? {
|
||||
pathToCert: options.sslCert,
|
||||
pathToKey: options.sslKey,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
|
||||
return yield* combineAsyncIterables(
|
||||
iter,
|
||||
...devRemoteIters,
|
||||
...(staticRemotesIter ? [staticRemotesIter] : []),
|
||||
createAsyncIterable<{ success: true; baseUrl: string }>(
|
||||
async ({ next, done }) => {
|
||||
if (!options.isInitialHost) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
if (remotes.remotePorts.length === 0) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const host = options.host ?? 'localhost';
|
||||
const baseUrl = `http${options.ssl ? 's' : ''}://${host}:${
|
||||
options.port
|
||||
}`;
|
||||
const portsToWaitFor = staticRemotesIter
|
||||
? [options.staticRemotesPort, ...remotes.remotePorts]
|
||||
: [...remotes.remotePorts];
|
||||
|
||||
await Promise.all(
|
||||
portsToWaitFor.map((port) =>
|
||||
waitForPortOpen(port, {
|
||||
retries: 480,
|
||||
retryDelay: 2500,
|
||||
host,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
logger.info(
|
||||
`Nx all ssr remotes have started, server ready at ${baseUrl}`
|
||||
);
|
||||
next({ success: true, baseUrl });
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Nx failed to start ssr remotes. Check above for errors.`
|
||||
);
|
||||
} finally {
|
||||
done();
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
{
|
||||
"version": 2,
|
||||
"outputCapture": "direct-nodejs",
|
||||
"title": "Module Federation SSR Dev Server",
|
||||
"description": "Serve a SSR host application along with its known remotes.",
|
||||
"cli": "nx",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"browserTarget": {
|
||||
"type": "string",
|
||||
"description": "Target which builds the browser application.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"serverTarget": {
|
||||
"type": "string",
|
||||
"description": "Target which builds the server application.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"port": {
|
||||
"type": "number",
|
||||
"description": "The port to be set on `process.env.PORT` for use in the server.",
|
||||
"default": 4200,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"devRemotes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "List of remote applications to run in development mode (i.e. using serve target).",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipRemotes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
"description": "Host to listen on.",
|
||||
"default": "localhost"
|
||||
},
|
||||
"staticRemotesPort": {
|
||||
"type": "number",
|
||||
"description": "The port at which to serve the file-server for the static remotes."
|
||||
},
|
||||
"pathToManifestFile": {
|
||||
"type": "string",
|
||||
"description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root."
|
||||
},
|
||||
"ssl": {
|
||||
"type": "boolean",
|
||||
"description": "Serve using HTTPS.",
|
||||
"default": false
|
||||
},
|
||||
"sslKey": {
|
||||
"type": "string",
|
||||
"description": "SSL key to use for serving HTTPS."
|
||||
},
|
||||
"sslCert": {
|
||||
"type": "string",
|
||||
"description": "SSL certificate to use for serving HTTPS."
|
||||
},
|
||||
"publicHost": {
|
||||
"type": "string",
|
||||
"description": "Public URL where the application will be served."
|
||||
},
|
||||
"isInitialHost": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the host that is running this executor is the first in the project tree to do so.",
|
||||
"default": true,
|
||||
"x-priority": "internal"
|
||||
}
|
||||
},
|
||||
"required": ["browserTarget", "serverTarget"]
|
||||
}
|
||||
@ -0,0 +1,394 @@
|
||||
import {
|
||||
logger,
|
||||
parseTargetString,
|
||||
readTargetOptions,
|
||||
Target,
|
||||
workspaceRoot,
|
||||
} from '@nx/devkit';
|
||||
import {
|
||||
combineAsyncIterables,
|
||||
createAsyncIterable,
|
||||
} from '@nx/devkit/src/utils/async-iterable';
|
||||
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
||||
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
||||
import { fork } from 'child_process';
|
||||
import type { Express } from 'express';
|
||||
import { cpSync, existsSync, readFileSync, rmSync } from 'fs';
|
||||
import { ExecutorContext } from 'nx/src/config/misc-interfaces';
|
||||
import { basename, extname, join } from 'path';
|
||||
import {
|
||||
getModuleFederationConfig,
|
||||
getRemotes,
|
||||
} from '../../utils/module-federation';
|
||||
import { buildStaticRemotes } from '../../utils/module-federation/build-static.remotes';
|
||||
import {
|
||||
parseStaticRemotesConfig,
|
||||
StaticRemotesConfig,
|
||||
} from '../../utils/module-federation/parse-static-remotes-config';
|
||||
import { ModuleFederationDevServerOptions } from '../module-federation-dev-server/schema';
|
||||
import type { RspackExecutorSchema } from '../rspack/schema';
|
||||
import { ModuleFederationStaticServerSchema } from './schema';
|
||||
|
||||
function getBuildAndServeOptionsFromServeTarget(
|
||||
serveTarget: string,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
const target = parseTargetString(serveTarget, context);
|
||||
|
||||
const serveOptions: ModuleFederationDevServerOptions = readTargetOptions(
|
||||
target,
|
||||
context
|
||||
);
|
||||
const buildTarget = parseTargetString(serveOptions.buildTarget, context);
|
||||
|
||||
const buildOptions: RspackExecutorSchema = readTargetOptions(
|
||||
buildTarget,
|
||||
context
|
||||
);
|
||||
|
||||
let pathToManifestFile = join(
|
||||
context.root,
|
||||
context.projectGraph.nodes[context.projectName].data.sourceRoot,
|
||||
'assets/module-federation.manifest.json'
|
||||
);
|
||||
if (serveOptions.pathToManifestFile) {
|
||||
const userPathToManifestFile = join(
|
||||
context.root,
|
||||
serveOptions.pathToManifestFile
|
||||
);
|
||||
if (!existsSync(userPathToManifestFile)) {
|
||||
throw new Error(
|
||||
`The provided Module Federation manifest file path does not exist. Please check the file exists at "${userPathToManifestFile}".`
|
||||
);
|
||||
} else if (extname(serveOptions.pathToManifestFile) !== '.json') {
|
||||
throw new Error(
|
||||
`The Module Federation manifest file must be a JSON. Please ensure the file at ${userPathToManifestFile} is a JSON.`
|
||||
);
|
||||
}
|
||||
|
||||
pathToManifestFile = userPathToManifestFile;
|
||||
}
|
||||
|
||||
return {
|
||||
buildTarget,
|
||||
buildOptions,
|
||||
serveOptions,
|
||||
pathToManifestFile,
|
||||
};
|
||||
}
|
||||
|
||||
async function buildHost(
|
||||
nxBin: string,
|
||||
buildTarget: Target,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
await new Promise<void>((res, rej) => {
|
||||
const staticProcess = fork(
|
||||
nxBin,
|
||||
[
|
||||
`run`,
|
||||
`${buildTarget.project}:${buildTarget.target}${
|
||||
buildTarget.configuration
|
||||
? `:${buildTarget.configuration}`
|
||||
: context.configurationName
|
||||
? `:${context.configurationName}`
|
||||
: ''
|
||||
}`,
|
||||
],
|
||||
{
|
||||
cwd: context.root,
|
||||
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
|
||||
}
|
||||
);
|
||||
staticProcess.stdout.on('data', (data) => {
|
||||
const ANSII_CODE_REGEX =
|
||||
// eslint-disable-next-line no-control-regex
|
||||
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
|
||||
const stdoutString = data.toString().replace(ANSII_CODE_REGEX, '');
|
||||
|
||||
// in addition to writing into the stdout stream, also show error directly in console
|
||||
// so the error is easily discoverable. 'ERROR in' is the key word to search in webpack output.
|
||||
if (stdoutString.includes('ERROR in')) {
|
||||
logger.log(stdoutString);
|
||||
}
|
||||
|
||||
if (stdoutString.includes('Successfully ran target build')) {
|
||||
staticProcess.stdout.removeAllListeners('data');
|
||||
logger.info(`NX Built host`);
|
||||
res();
|
||||
}
|
||||
});
|
||||
staticProcess.stderr.on('data', (data) => logger.info(data.toString()));
|
||||
staticProcess.once('exit', (code) => {
|
||||
staticProcess.stdout.removeAllListeners('data');
|
||||
staticProcess.stderr.removeAllListeners('data');
|
||||
if (code !== 0) {
|
||||
rej(`Host failed to build. See above for details.`);
|
||||
} else {
|
||||
res();
|
||||
}
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => staticProcess.kill('SIGTERM'));
|
||||
process.on('exit', () => staticProcess.kill('SIGTERM'));
|
||||
});
|
||||
}
|
||||
|
||||
function moveToTmpDirectory(
|
||||
staticRemotesConfig: StaticRemotesConfig,
|
||||
hostOutputPath: string,
|
||||
hostUrlSegment: string
|
||||
) {
|
||||
const commonOutputDirectory = join(
|
||||
workspaceRoot,
|
||||
'tmp/static-module-federation'
|
||||
);
|
||||
for (const app of staticRemotesConfig.remotes) {
|
||||
const remoteConfig = staticRemotesConfig.config[app];
|
||||
cpSync(
|
||||
remoteConfig.outputPath,
|
||||
join(commonOutputDirectory, remoteConfig.urlSegment),
|
||||
{
|
||||
force: true,
|
||||
recursive: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
cpSync(hostOutputPath, join(commonOutputDirectory, hostUrlSegment), {
|
||||
force: true,
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
const cleanup = () => {
|
||||
rmSync(commonOutputDirectory, { force: true, recursive: true });
|
||||
};
|
||||
process.on('SIGTERM', () => {
|
||||
cleanup();
|
||||
});
|
||||
process.on('exit', () => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
return commonOutputDirectory;
|
||||
}
|
||||
|
||||
export function startProxies(
|
||||
staticRemotesConfig: StaticRemotesConfig,
|
||||
hostServeOptions: ModuleFederationDevServerOptions,
|
||||
mappedLocationOfHost: string,
|
||||
mappedLocationsOfRemotes: Record<string, string>,
|
||||
sslOptions?: { pathToCert: string; pathToKey: string }
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { createProxyMiddleware } = require('http-proxy-middleware');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const express = require('express');
|
||||
let sslCert: Buffer;
|
||||
let sslKey: Buffer;
|
||||
if (sslOptions && sslOptions.pathToCert && sslOptions.pathToKey) {
|
||||
if (existsSync(sslOptions.pathToCert) && existsSync(sslOptions.pathToKey)) {
|
||||
sslCert = readFileSync(sslOptions.pathToCert);
|
||||
sslKey = readFileSync(sslOptions.pathToKey);
|
||||
} else {
|
||||
logger.warn(
|
||||
`Encountered SSL options in project.json, however, the certificate files do not exist in the filesystem. Using http.`
|
||||
);
|
||||
logger.warn(
|
||||
`Attempted to find '${sslOptions.pathToCert}' and '${sslOptions.pathToKey}'.`
|
||||
);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const http = require('http');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const https = require('https');
|
||||
|
||||
logger.info(`NX Starting static remotes proxies...`);
|
||||
for (const app of staticRemotesConfig.remotes) {
|
||||
const expressProxy: Express = express();
|
||||
expressProxy.use(
|
||||
createProxyMiddleware({
|
||||
target: mappedLocationsOfRemotes[app],
|
||||
changeOrigin: true,
|
||||
secure: sslCert ? false : undefined,
|
||||
})
|
||||
);
|
||||
const proxyServer = (sslCert ? https : http)
|
||||
.createServer({ cert: sslCert, key: sslKey }, expressProxy)
|
||||
.listen(staticRemotesConfig.config[app].port);
|
||||
process.on('SIGTERM', () => proxyServer.close());
|
||||
process.on('exit', () => proxyServer.close());
|
||||
}
|
||||
logger.info(`NX Static remotes proxies started successfully`);
|
||||
logger.info(`NX Starting static host proxy...`);
|
||||
const expressProxy: Express = express();
|
||||
expressProxy.use(
|
||||
createProxyMiddleware({
|
||||
target: mappedLocationOfHost,
|
||||
changeOrigin: true,
|
||||
secure: sslCert ? false : undefined,
|
||||
pathRewrite: (path) => {
|
||||
let pathRewrite = path;
|
||||
for (const app of staticRemotesConfig.remotes) {
|
||||
if (path.endsWith(app)) {
|
||||
pathRewrite = '/';
|
||||
break;
|
||||
}
|
||||
}
|
||||
return pathRewrite;
|
||||
},
|
||||
})
|
||||
);
|
||||
const proxyServer = (sslCert ? https : http)
|
||||
.createServer({ cert: sslCert, key: sslKey }, expressProxy)
|
||||
.listen(hostServeOptions.port);
|
||||
process.on('SIGTERM', () => proxyServer.close());
|
||||
process.on('exit', () => proxyServer.close());
|
||||
logger.info('NX Static host proxy started successfully');
|
||||
}
|
||||
|
||||
export default async function* moduleFederationStaticServer(
|
||||
schema: ModuleFederationStaticServerSchema,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
// Force Node to resolve to look for the nx binary that is inside node_modules
|
||||
const nxBin = require.resolve('nx/bin/nx');
|
||||
|
||||
// Get the remotes from the module federation config
|
||||
const p = context.projectsConfigurations.projects[context.projectName];
|
||||
const options = getBuildAndServeOptionsFromServeTarget(
|
||||
schema.serveTarget,
|
||||
context
|
||||
);
|
||||
|
||||
const moduleFederationConfig = getModuleFederationConfig(
|
||||
options.buildOptions.tsConfig,
|
||||
context.root,
|
||||
p.root,
|
||||
'react'
|
||||
);
|
||||
|
||||
const remotes = getRemotes(
|
||||
[],
|
||||
options.serveOptions.skipRemotes,
|
||||
moduleFederationConfig,
|
||||
{
|
||||
projectName: context.projectName,
|
||||
projectGraph: context.projectGraph,
|
||||
root: context.root,
|
||||
},
|
||||
options.pathToManifestFile
|
||||
);
|
||||
|
||||
const staticRemotesConfig = parseStaticRemotesConfig(
|
||||
[...remotes.staticRemotes, ...remotes.dynamicRemotes],
|
||||
context
|
||||
);
|
||||
|
||||
options.serveOptions.staticRemotesPort ??= remotes.staticRemotePort;
|
||||
const mappedLocationsOfStaticRemotes = await buildStaticRemotes(
|
||||
staticRemotesConfig,
|
||||
nxBin,
|
||||
context,
|
||||
options.serveOptions
|
||||
);
|
||||
|
||||
// Build the host
|
||||
const hostUrlSegment = basename(options.buildOptions.outputPath);
|
||||
const mappedLocationOfHost = `http${options.serveOptions.ssl ? 's' : ''}://${
|
||||
options.serveOptions.host
|
||||
}:${options.serveOptions.staticRemotesPort}/${hostUrlSegment}`;
|
||||
await buildHost(nxBin, options.buildTarget, context);
|
||||
|
||||
// Move to a temporary directory
|
||||
const commonOutputDirectory = moveToTmpDirectory(
|
||||
staticRemotesConfig,
|
||||
options.buildOptions.outputPath,
|
||||
hostUrlSegment
|
||||
);
|
||||
|
||||
// File Serve the temporary directory
|
||||
const staticFileServerIter = fileServerExecutor(
|
||||
{
|
||||
cors: true,
|
||||
watch: false,
|
||||
staticFilePath: commonOutputDirectory,
|
||||
parallel: false,
|
||||
spa: false,
|
||||
withDeps: false,
|
||||
host: options.serveOptions.host,
|
||||
port: options.serveOptions.staticRemotesPort,
|
||||
ssl: options.serveOptions.ssl,
|
||||
sslCert: options.serveOptions.sslCert,
|
||||
sslKey: options.serveOptions.sslKey,
|
||||
cacheSeconds: -1,
|
||||
},
|
||||
context
|
||||
);
|
||||
|
||||
// express proxy all of it
|
||||
startProxies(
|
||||
staticRemotesConfig,
|
||||
options.serveOptions,
|
||||
mappedLocationOfHost,
|
||||
mappedLocationsOfStaticRemotes,
|
||||
options.serveOptions.ssl
|
||||
? {
|
||||
pathToCert: join(workspaceRoot, options.serveOptions.sslCert),
|
||||
pathToKey: join(workspaceRoot, options.serveOptions.sslKey),
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
|
||||
return yield* combineAsyncIterables(
|
||||
staticFileServerIter,
|
||||
createAsyncIterable<{ success: true; baseUrl: string }>(
|
||||
async ({ next, done }) => {
|
||||
const host = options.serveOptions.host ?? 'localhost';
|
||||
const baseUrl = `http${options.serveOptions.ssl ? 's' : ''}://${host}:${
|
||||
options.serveOptions.port
|
||||
}`;
|
||||
|
||||
if (remotes.remotePorts.length === 0) {
|
||||
const portsToWaitFor = [options.serveOptions.staticRemotesPort];
|
||||
await Promise.all(
|
||||
portsToWaitFor.map((port) =>
|
||||
waitForPortOpen(port, {
|
||||
retries: 480,
|
||||
retryDelay: 2500,
|
||||
host: host,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
logger.info(`NX Server ready at ${baseUrl}`);
|
||||
next({ success: true, baseUrl: baseUrl });
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const portsToWaitFor = staticFileServerIter
|
||||
? [options.serveOptions.staticRemotesPort, ...remotes.remotePorts]
|
||||
: [...remotes.remotePorts];
|
||||
await Promise.all(
|
||||
portsToWaitFor.map((port) =>
|
||||
waitForPortOpen(port, {
|
||||
retries: 480,
|
||||
retryDelay: 2500,
|
||||
host: host,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
logger.info(`NX Server ready at ${baseUrl}`);
|
||||
next({ success: true, baseUrl: baseUrl });
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to start. Check above for any errors.`);
|
||||
} finally {
|
||||
done();
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
3
packages/rspack/src/executors/module-federation-static-server/schema.d.ts
vendored
Normal file
3
packages/rspack/src/executors/module-federation-static-server/schema.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
export interface ModuleFederationStaticServerSchema {
|
||||
serveTarget: string;
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": 2,
|
||||
"outputCapture": "direct-nodejs",
|
||||
"title": "Module Federation Static Dev Server",
|
||||
"description": "Serve a host application statically along with it's remotes.",
|
||||
"cli": "nx",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"serveTarget": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["serveTarget"]
|
||||
}
|
||||
146
packages/rspack/src/executors/rspack/rspack.impl.ts
Normal file
146
packages/rspack/src/executors/rspack/rspack.impl.ts
Normal file
@ -0,0 +1,146 @@
|
||||
import { ExecutorContext, logger } from '@nx/devkit';
|
||||
import { createAsyncIterable } from '@nx/devkit/src/utils/async-iterable';
|
||||
import { printDiagnostics, runTypeCheck } from '@nx/js';
|
||||
import { Compiler, MultiCompiler, MultiStats, Stats } from '@rspack/core';
|
||||
import { rmSync } from 'fs';
|
||||
import * as path from 'path';
|
||||
import { createCompiler, isMultiCompiler } from '../../utils/create-compiler';
|
||||
import { isMode } from '../../utils/mode-utils';
|
||||
import { RspackExecutorSchema } from './schema';
|
||||
|
||||
export default async function* runExecutor(
|
||||
options: RspackExecutorSchema,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
process.env.NODE_ENV ??= options.mode ?? 'production';
|
||||
|
||||
if (isMode(process.env.NODE_ENV)) {
|
||||
options.mode = process.env.NODE_ENV;
|
||||
}
|
||||
|
||||
if (options.typeCheck) {
|
||||
await executeTypeCheck(options, context);
|
||||
}
|
||||
|
||||
// Mimic --clean from webpack.
|
||||
rmSync(path.join(context.root, options.outputPath), {
|
||||
force: true,
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
const compiler = await createCompiler(options, context);
|
||||
|
||||
const iterable = createAsyncIterable<{
|
||||
success: boolean;
|
||||
outfile?: string;
|
||||
}>(async ({ next, done }) => {
|
||||
if (options.watch) {
|
||||
const watcher = compiler.watch(
|
||||
{},
|
||||
async (err, stats: Stats | MultiStats) => {
|
||||
if (err) {
|
||||
logger.error(err);
|
||||
next({ success: false });
|
||||
return;
|
||||
}
|
||||
if (!compiler || !stats) {
|
||||
logger.error(new Error('Compiler or stats not available'));
|
||||
next({ success: false });
|
||||
return;
|
||||
}
|
||||
|
||||
const statsOptions = getStatsOptions(compiler);
|
||||
const printedStats = stats.toString(statsOptions);
|
||||
// Avoid extra empty line when `stats: 'none'`
|
||||
if (printedStats) {
|
||||
console.error(printedStats);
|
||||
}
|
||||
next({
|
||||
success: !stats.hasErrors(),
|
||||
outfile: path.resolve(context.root, options.outputPath, 'main.js'),
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
registerCleanupCallback(() => {
|
||||
watcher.close(() => {
|
||||
logger.info('Watcher closed');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
compiler.run(async (err, stats: Stats | MultiStats) => {
|
||||
compiler.close(() => {
|
||||
if (err) {
|
||||
logger.error(err);
|
||||
next({ success: false });
|
||||
return;
|
||||
}
|
||||
if (!compiler || !stats) {
|
||||
logger.error(new Error('Compiler or stats not available'));
|
||||
next({ success: false });
|
||||
return;
|
||||
}
|
||||
|
||||
const statsOptions = getStatsOptions(compiler);
|
||||
const printedStats = stats.toString(statsOptions);
|
||||
// Avoid extra empty line when `stats: 'none'`
|
||||
if (printedStats) {
|
||||
console.error(printedStats);
|
||||
}
|
||||
next({
|
||||
success: !stats.hasErrors(),
|
||||
outfile: path.resolve(context.root, options.outputPath, 'main.js'),
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
yield* iterable;
|
||||
}
|
||||
|
||||
// copied from packages/esbuild/src/executors/esbuild/esbuild.impl.ts
|
||||
function registerCleanupCallback(callback: () => void) {
|
||||
const wrapped = () => {
|
||||
callback();
|
||||
process.off('SIGINT', wrapped);
|
||||
process.off('SIGTERM', wrapped);
|
||||
process.off('exit', wrapped);
|
||||
};
|
||||
|
||||
process.on('SIGINT', wrapped);
|
||||
process.on('SIGTERM', wrapped);
|
||||
process.on('exit', wrapped);
|
||||
}
|
||||
|
||||
async function executeTypeCheck(
|
||||
options: RspackExecutorSchema,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
const projectConfiguration =
|
||||
context.projectGraph.nodes[context.projectName].data;
|
||||
const result = await runTypeCheck({
|
||||
workspaceRoot: path.resolve(projectConfiguration.root),
|
||||
tsConfigPath: options.tsConfig,
|
||||
mode: 'noEmit',
|
||||
});
|
||||
|
||||
await printDiagnostics(result.errors, result.warnings);
|
||||
|
||||
if (result.errors.length > 0) {
|
||||
throw new Error('Found type errors. See above.');
|
||||
}
|
||||
}
|
||||
|
||||
function getStatsOptions(compiler: Compiler | MultiCompiler) {
|
||||
return isMultiCompiler(compiler)
|
||||
? {
|
||||
children: compiler.compilers.map((compiler) =>
|
||||
compiler.options ? compiler.options.stats : undefined
|
||||
),
|
||||
}
|
||||
: compiler.options
|
||||
? compiler.options.stats
|
||||
: undefined;
|
||||
}
|
||||
34
packages/rspack/src/executors/rspack/schema.d.ts
vendored
Normal file
34
packages/rspack/src/executors/rspack/schema.d.ts
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
import type { Mode } from '@rspack/core';
|
||||
|
||||
export interface RspackExecutorSchema {
|
||||
target: 'web' | 'node';
|
||||
main: string;
|
||||
index?: string;
|
||||
tsConfig: string;
|
||||
typeCheck?: boolean;
|
||||
outputPath: string;
|
||||
outputFileName?: string;
|
||||
indexHtml?: string;
|
||||
mode?: Mode;
|
||||
watch?: boolean;
|
||||
baseHref?: string;
|
||||
deployUrl?: string;
|
||||
|
||||
rspackConfig: string;
|
||||
optimization?: boolean | OptimizationOptions;
|
||||
sourceMap?: boolean | string;
|
||||
assets?: any[];
|
||||
extractLicenses?: boolean;
|
||||
fileReplacements?: FileReplacement[];
|
||||
generatePackageJson?: boolean;
|
||||
}
|
||||
|
||||
export interface FileReplacement {
|
||||
replace: string;
|
||||
with: string;
|
||||
}
|
||||
|
||||
export interface OptimizationOptions {
|
||||
scripts: boolean;
|
||||
styles: boolean;
|
||||
}
|
||||
177
packages/rspack/src/executors/rspack/schema.json
Normal file
177
packages/rspack/src/executors/rspack/schema.json
Normal file
@ -0,0 +1,177 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"version": 2,
|
||||
"title": "Rspack build executor",
|
||||
"description": "Run Rspack via an executor for a project.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "The platform to target (e.g. web, node).",
|
||||
"enum": ["web", "node"]
|
||||
},
|
||||
"main": {
|
||||
"type": "string",
|
||||
"description": "The main entry file."
|
||||
},
|
||||
"outputPath": {
|
||||
"type": "string",
|
||||
"description": "The output path for the bundle."
|
||||
},
|
||||
"outputFileName": {
|
||||
"type": "string",
|
||||
"description": "The main output entry file"
|
||||
},
|
||||
"tsConfig": {
|
||||
"type": "string",
|
||||
"description": "The tsconfig file to build the project."
|
||||
},
|
||||
"typeCheck": {
|
||||
"type": "boolean",
|
||||
"description": "Skip the type checking."
|
||||
},
|
||||
"indexHtml": {
|
||||
"type": "string",
|
||||
"description": "The path to the index.html file."
|
||||
},
|
||||
"index": {
|
||||
"type": "string",
|
||||
"description": "HTML File which will be contain the application.",
|
||||
"x-completion-type": "file",
|
||||
"x-completion-glob": "**/*@(.html|.htm)"
|
||||
},
|
||||
"baseHref": {
|
||||
"type": "string",
|
||||
"description": "Base url for the application being built."
|
||||
},
|
||||
"deployUrl": {
|
||||
"type": "string",
|
||||
"description": "URL where the application will be deployed."
|
||||
},
|
||||
"rspackConfig": {
|
||||
"type": "string",
|
||||
"description": "The path to the rspack config file."
|
||||
},
|
||||
"optimization": {
|
||||
"description": "Enables optimization of the build output.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"scripts": {
|
||||
"type": "boolean",
|
||||
"description": "Enables optimization of the scripts output.",
|
||||
"default": true
|
||||
},
|
||||
"styles": {
|
||||
"type": "boolean",
|
||||
"description": "Enables optimization of the styles output.",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sourceMap": {
|
||||
"description": "Output sourcemaps. Use 'hidden' for use with error reporting tools without generating sourcemap comment.",
|
||||
"default": true,
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"assets": {
|
||||
"type": "array",
|
||||
"description": "List of static application assets.",
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/assetPattern"
|
||||
}
|
||||
},
|
||||
"extractLicenses": {
|
||||
"type": "boolean",
|
||||
"description": "Extract all licenses in a separate file.",
|
||||
"default": true
|
||||
},
|
||||
"fileReplacements": {
|
||||
"description": "Replace files with other files in the build.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"replace": {
|
||||
"type": "string",
|
||||
"description": "The file to be replaced.",
|
||||
"x-completion-type": "file"
|
||||
},
|
||||
"with": {
|
||||
"type": "string",
|
||||
"description": "The file to replace with.",
|
||||
"x-completion-type": "file"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["replace", "with"]
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"description": "Mode to run the build in.",
|
||||
"enum": ["development", "production", "none"]
|
||||
},
|
||||
"generatePackageJson": {
|
||||
"type": "boolean",
|
||||
"description": "Generates a `package.json` and pruned lock file with the project's `node_module` dependencies populated for installing in a container. If a `package.json` exists in the project's directory, it will be reused with dependencies populated."
|
||||
}
|
||||
},
|
||||
"required": ["target", "main", "outputPath", "tsConfig", "rspackConfig"],
|
||||
"definitions": {
|
||||
"assetPattern": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"glob": {
|
||||
"type": "string",
|
||||
"description": "The pattern to match."
|
||||
},
|
||||
"input": {
|
||||
"type": "string",
|
||||
"description": "The input directory path in which to apply 'glob'. Defaults to the project root."
|
||||
},
|
||||
"ignore": {
|
||||
"description": "An array of globs to ignore.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"type": "string",
|
||||
"description": "Absolute path within the output."
|
||||
},
|
||||
"watch": {
|
||||
"type": "boolean",
|
||||
"description": "Enable re-building when files change.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["glob", "input", "output"]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
import * as net from 'net';
|
||||
|
||||
export function waitUntilServerIsListening(port: number): Promise<void> {
|
||||
const allowedErrorCodes = ['ECONNREFUSED', 'ECONNRESET'];
|
||||
const maxAttempts = 25;
|
||||
let attempts = 0;
|
||||
const client = new net.Socket();
|
||||
const cleanup = () => {
|
||||
client.removeAllListeners('connect');
|
||||
client.removeAllListeners('error');
|
||||
client.end();
|
||||
client.destroy();
|
||||
client.unref();
|
||||
};
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const listen = () => {
|
||||
client.once('connect', () => {
|
||||
cleanup();
|
||||
resolve();
|
||||
});
|
||||
client.on('error', (err) => {
|
||||
if (
|
||||
attempts > maxAttempts ||
|
||||
!allowedErrorCodes.includes(err['code'])
|
||||
) {
|
||||
cleanup();
|
||||
reject(err);
|
||||
} else {
|
||||
attempts++;
|
||||
setTimeout(listen, 100 * attempts);
|
||||
}
|
||||
});
|
||||
client.connect({ port, host: 'localhost' });
|
||||
};
|
||||
listen();
|
||||
});
|
||||
}
|
||||
11
packages/rspack/src/executors/ssr-dev-server/schema.d.ts
vendored
Normal file
11
packages/rspack/src/executors/ssr-dev-server/schema.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
interface TargetOptions {
|
||||
[key: string]: string | boolean | number | TargetOptions;
|
||||
}
|
||||
|
||||
export interface RspackSsrDevServerOptions {
|
||||
browserTarget: string;
|
||||
serverTarget: string;
|
||||
port: number;
|
||||
browserTargetOptions: TargetOptions;
|
||||
serverTargetOptions: TargetOptions;
|
||||
}
|
||||
36
packages/rspack/src/executors/ssr-dev-server/schema.json
Normal file
36
packages/rspack/src/executors/ssr-dev-server/schema.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"outputCapture": "direct-nodejs",
|
||||
"title": "Rspack SSR Dev Server",
|
||||
"description": "Serve a SSR application using rspack.",
|
||||
"cli": "nx",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"browserTarget": {
|
||||
"type": "string",
|
||||
"description": "Target which builds the browser application.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"serverTarget": {
|
||||
"type": "string",
|
||||
"description": "Target which builds the server application.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"port": {
|
||||
"type": "number",
|
||||
"description": "The port to be set on `process.env.PORT` for use in the server.",
|
||||
"default": 4200,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"browserTargetOptions": {
|
||||
"type": "object",
|
||||
"description": "Additional options to pass into the browser build target.",
|
||||
"default": {}
|
||||
},
|
||||
"serverTargetOptions": {
|
||||
"type": "object",
|
||||
"description": "Additional options to pass into the server build target.",
|
||||
"default": {}
|
||||
}
|
||||
},
|
||||
"required": ["browserTarget", "serverTarget"]
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
import {
|
||||
ExecutorContext,
|
||||
parseTargetString,
|
||||
readTargetOptions,
|
||||
runExecutor,
|
||||
} from '@nx/devkit';
|
||||
import { combineAsyncIterables } from '@nx/devkit/src/utils/async-iterable';
|
||||
import * as chalk from 'chalk';
|
||||
|
||||
import { RspackExecutorSchema } from '../rspack/schema';
|
||||
import { waitUntilServerIsListening } from './lib/wait-until-server-is-listening';
|
||||
import { RspackSsrDevServerOptions, TargetOptions } from './schema';
|
||||
|
||||
export async function* ssrDevServerExecutor(
|
||||
options: RspackSsrDevServerOptions,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
const browserTarget = parseTargetString(
|
||||
options.browserTarget,
|
||||
context.projectGraph
|
||||
);
|
||||
const serverTarget = parseTargetString(options.serverTarget, context);
|
||||
const browserOptions = readTargetOptions<RspackExecutorSchema>(
|
||||
browserTarget,
|
||||
context
|
||||
);
|
||||
const serverOptions = readTargetOptions<RspackExecutorSchema>(
|
||||
serverTarget,
|
||||
context
|
||||
);
|
||||
|
||||
const runBrowser = await runExecutor<{
|
||||
success: boolean;
|
||||
baseUrl?: string;
|
||||
options: TargetOptions;
|
||||
}>(
|
||||
browserTarget,
|
||||
{ ...browserOptions, ...options.browserTargetOptions },
|
||||
context
|
||||
);
|
||||
const runServer = await runExecutor<{
|
||||
success: boolean;
|
||||
baseUrl?: string;
|
||||
options: TargetOptions;
|
||||
}>(
|
||||
serverTarget,
|
||||
{ ...serverOptions, ...options.serverTargetOptions },
|
||||
context
|
||||
);
|
||||
let browserBuilt = false;
|
||||
let nodeStarted = false;
|
||||
const combined = combineAsyncIterables(runBrowser, runServer);
|
||||
|
||||
for await (const output of combined) {
|
||||
if (!output.success) throw new Error('Could not build application');
|
||||
if (output.options?.target === 'node') {
|
||||
nodeStarted = true;
|
||||
} else if (output.options?.target === 'web') {
|
||||
browserBuilt = true;
|
||||
}
|
||||
|
||||
if (nodeStarted && browserBuilt) {
|
||||
await waitUntilServerIsListening(options.port);
|
||||
console.log(
|
||||
`[ ${chalk.green('ready')} ] on http://localhost:${options.port}`
|
||||
);
|
||||
yield {
|
||||
...output,
|
||||
baseUrl: `http://localhost:${options.port}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ssrDevServerExecutor;
|
||||
105
packages/rspack/src/generators/application/application.ts
Normal file
105
packages/rspack/src/generators/application/application.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { ensurePackage, formatFiles, runTasksInSerial, Tree } from '@nx/devkit';
|
||||
import { version as nxVersion } from 'nx/package.json';
|
||||
import configurationGenerator from '../configuration/configuration';
|
||||
import rspackInitGenerator from '../init/init';
|
||||
import { normalizeOptions } from './lib/normalize-options';
|
||||
import { ApplicationGeneratorSchema } from './schema';
|
||||
|
||||
export default async function (
|
||||
tree: Tree,
|
||||
_options: ApplicationGeneratorSchema
|
||||
) {
|
||||
const tasks = [];
|
||||
const initTask = await rspackInitGenerator(tree, {
|
||||
..._options,
|
||||
// TODO: Crystalize the default rspack.config.js file.
|
||||
// The default setup isn't crystalized so don't add plugin.
|
||||
addPlugin: false,
|
||||
});
|
||||
tasks.push(initTask);
|
||||
|
||||
const options = normalizeOptions(tree, _options);
|
||||
|
||||
options.style ??= 'css';
|
||||
|
||||
if (options.framework === 'nest') {
|
||||
const { applicationGenerator: nestAppGenerator } = ensurePackage(
|
||||
'@nx/nest',
|
||||
nxVersion
|
||||
);
|
||||
const createAppTask = await nestAppGenerator(tree, {
|
||||
...options,
|
||||
skipFormat: true,
|
||||
tags: options.tags ?? '',
|
||||
addPlugin: false,
|
||||
});
|
||||
|
||||
const convertAppTask = await configurationGenerator(tree, {
|
||||
project: options.name,
|
||||
target: 'node',
|
||||
newProject: false,
|
||||
buildTarget: 'build',
|
||||
framework: 'nest',
|
||||
});
|
||||
|
||||
tasks.push(createAppTask, convertAppTask);
|
||||
} else if (options.framework === 'web') {
|
||||
const { applicationGenerator: webAppGenerator } = ensurePackage(
|
||||
'@nx/web',
|
||||
nxVersion
|
||||
);
|
||||
const createAppTask = await webAppGenerator(tree, {
|
||||
bundler: 'webpack',
|
||||
name: options.name,
|
||||
style: options.style,
|
||||
directory: options.directory,
|
||||
tags: options.tags ?? '',
|
||||
unitTestRunner: options.unitTestRunner,
|
||||
e2eTestRunner: options.e2eTestRunner,
|
||||
rootProject: options.rootProject,
|
||||
skipFormat: true,
|
||||
addPlugin: false,
|
||||
});
|
||||
const convertAppTask = await configurationGenerator(tree, {
|
||||
project: options.name,
|
||||
target: 'web',
|
||||
newProject: false,
|
||||
buildTarget: 'build',
|
||||
serveTarget: 'serve',
|
||||
framework: 'web',
|
||||
addPlugin: false,
|
||||
});
|
||||
tasks.push(createAppTask, convertAppTask);
|
||||
} else {
|
||||
// default to react
|
||||
const { applicationGenerator: reactAppGenerator } = ensurePackage(
|
||||
'@nx/react',
|
||||
nxVersion
|
||||
);
|
||||
const createAppTask = await reactAppGenerator(tree, {
|
||||
bundler: 'webpack',
|
||||
name: options.name,
|
||||
style: options.style,
|
||||
directory: options.directory,
|
||||
tags: options.tags ?? '',
|
||||
unitTestRunner: options.unitTestRunner,
|
||||
e2eTestRunner: options.e2eTestRunner,
|
||||
rootProject: options.rootProject,
|
||||
skipFormat: true,
|
||||
addPlugin: false,
|
||||
});
|
||||
const convertAppTask = await configurationGenerator(tree, {
|
||||
project: options.name,
|
||||
target: 'web',
|
||||
newProject: false,
|
||||
buildTarget: 'build',
|
||||
serveTarget: 'serve',
|
||||
framework: 'react',
|
||||
});
|
||||
tasks.push(createAppTask, convertAppTask);
|
||||
}
|
||||
|
||||
await formatFiles(tree);
|
||||
|
||||
return runTasksInSerial(...tasks);
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
import { Tree, workspaceRoot, writeJson } from '@nx/devkit';
|
||||
import { relative } from 'path';
|
||||
import { Framework } from '../../init/schema';
|
||||
|
||||
export function editTsConfig(
|
||||
tree: Tree,
|
||||
projectRoot: string,
|
||||
framework: Framework,
|
||||
relativePathToRootTsConfig: string
|
||||
) {
|
||||
// Nx 15.8 moved util to @nx/js, but it is in @nx/workspace in 15.7
|
||||
let shared: any;
|
||||
try {
|
||||
shared = require('@nx/js/src/utils/typescript/create-ts-config');
|
||||
} catch {
|
||||
shared = require('@nx/workspace/src/utils/create-ts-config');
|
||||
}
|
||||
|
||||
if (framework === 'react') {
|
||||
const json = {
|
||||
compilerOptions: {
|
||||
jsx: 'react-jsx',
|
||||
allowJs: false,
|
||||
esModuleInterop: false,
|
||||
allowSyntheticDefaultImports: true,
|
||||
strict: true,
|
||||
},
|
||||
files: [],
|
||||
include: [],
|
||||
references: [
|
||||
{
|
||||
path: './tsconfig.app.json',
|
||||
},
|
||||
],
|
||||
} as any;
|
||||
|
||||
// inline tsconfig.base.json into the project
|
||||
if (projectIsRootProjectInStandaloneWorkspace(projectRoot)) {
|
||||
json.compileOnSave = false;
|
||||
json.compilerOptions = {
|
||||
...shared.tsConfigBaseOptions,
|
||||
...json.compilerOptions,
|
||||
};
|
||||
json.exclude = ['node_modules', 'tmp'];
|
||||
} else {
|
||||
json.extends = relativePathToRootTsConfig;
|
||||
}
|
||||
|
||||
writeJson(tree, `${projectRoot}/tsconfig.json`, json);
|
||||
}
|
||||
}
|
||||
|
||||
function projectIsRootProjectInStandaloneWorkspace(projectRoot: string) {
|
||||
return relative(workspaceRoot, projectRoot).length === 0;
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
import { Tree } from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { normalizeOptions } from './normalize-options';
|
||||
|
||||
describe('normalizeOptions', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
});
|
||||
|
||||
it('should set { rootProject: true } when --rootProject=true is passed', () => {
|
||||
expect(
|
||||
normalizeOptions(tree, {
|
||||
name: 'demo',
|
||||
style: 'css',
|
||||
rootProject: true,
|
||||
}).rootProject
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should set { rootProject: false } when --rootProject=undefined is passed', () => {
|
||||
expect(
|
||||
normalizeOptions(tree, {
|
||||
name: 'demo',
|
||||
style: 'css',
|
||||
}).rootProject
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should set { rootProject: false } when --rootProject=false is passed', () => {
|
||||
expect(
|
||||
normalizeOptions(tree, {
|
||||
name: 'demo',
|
||||
style: 'css',
|
||||
rootProject: false,
|
||||
}).rootProject
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should set { rootProject: false } when --monorepo=true and --rootProject=true is passed', () => {
|
||||
expect(
|
||||
normalizeOptions(tree, {
|
||||
name: 'demo',
|
||||
style: 'css',
|
||||
monorepo: true,
|
||||
rootProject: true,
|
||||
}).rootProject
|
||||
).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,54 @@
|
||||
import {
|
||||
extractLayoutDirectory,
|
||||
getWorkspaceLayout,
|
||||
names,
|
||||
normalizePath,
|
||||
Tree,
|
||||
} from '@nx/devkit';
|
||||
import { ApplicationGeneratorSchema, NormalizedSchema } from '../schema';
|
||||
|
||||
export function normalizeDirectory(options: ApplicationGeneratorSchema) {
|
||||
const { projectDirectory } = extractLayoutDirectory(options.directory);
|
||||
return projectDirectory
|
||||
? `${names(projectDirectory).fileName}/${names(options.name).fileName}`
|
||||
: names(options.name).fileName;
|
||||
}
|
||||
|
||||
export function normalizeProjectName(options: ApplicationGeneratorSchema) {
|
||||
return normalizeDirectory(options).replace(new RegExp('/', 'g'), '-');
|
||||
}
|
||||
|
||||
export function normalizeOptions(
|
||||
host: Tree,
|
||||
options: ApplicationGeneratorSchema
|
||||
): NormalizedSchema {
|
||||
// --monorepo takes precedence over --rootProject
|
||||
// This won't be needed once we add --bundler=rspack to the @nx/react:app preset
|
||||
const rootProject = !options.monorepo && options.rootProject;
|
||||
const appDirectory = normalizeDirectory(options);
|
||||
const appProjectName = normalizeProjectName(options);
|
||||
const e2eProjectName = options.rootProject
|
||||
? 'e2e'
|
||||
: `${names(options.name).fileName}-e2e`;
|
||||
|
||||
const { layoutDirectory } = extractLayoutDirectory(options.directory);
|
||||
const appsDir = layoutDirectory ?? getWorkspaceLayout(host).appsDir;
|
||||
const appProjectRoot = rootProject
|
||||
? '.'
|
||||
: normalizePath(`${appsDir}/${appDirectory}`);
|
||||
|
||||
const normalized = {
|
||||
...options,
|
||||
rootProject,
|
||||
name: names(options.name).fileName,
|
||||
projectName: appProjectName,
|
||||
appProjectRoot,
|
||||
e2eProjectName,
|
||||
fileName: 'app',
|
||||
} as NormalizedSchema;
|
||||
|
||||
normalized.unitTestRunner ??= 'jest';
|
||||
normalized.e2eTestRunner ??= 'cypress';
|
||||
|
||||
return normalized;
|
||||
}
|
||||
16
packages/rspack/src/generators/application/schema.d.ts
vendored
Normal file
16
packages/rspack/src/generators/application/schema.d.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
export interface ApplicationGeneratorSchema {
|
||||
name: string;
|
||||
framework?: Framework;
|
||||
style: 'css' | 'scss' | 'less' | 'styl';
|
||||
unitTestRunner?: 'none' | 'jest';
|
||||
e2eTestRunner?: 'none' | 'cypress';
|
||||
directory?: string;
|
||||
tags?: string;
|
||||
rootProject?: boolean;
|
||||
monorepo?: boolean;
|
||||
}
|
||||
|
||||
export interface NormalizedSchema extends ApplicationGeneratorSchema {
|
||||
appProjectRoot: string;
|
||||
e2eProjectName: string;
|
||||
}
|
||||
98
packages/rspack/src/generators/application/schema.json
Normal file
98
packages/rspack/src/generators/application/schema.json
Normal file
@ -0,0 +1,98 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "Application",
|
||||
"title": "Application generator for React + rspack",
|
||||
"type": "object",
|
||||
"description": "React + Rspack application generator.",
|
||||
"examples": [
|
||||
{
|
||||
"command": "nx g app myapp --directory=myorg",
|
||||
"description": "Generate `apps/myorg/myapp` and `apps/myorg/myapp-e2e`"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "The name of the application.",
|
||||
"type": "string",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
},
|
||||
"x-prompt": "What name would you like to use for the application?",
|
||||
"pattern": "^[a-zA-Z].*$",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"framework": {
|
||||
"type": "string",
|
||||
"description": "The framework to use for the application.",
|
||||
"x-prompt": "What framework do you want to use when generating this application?",
|
||||
"enum": ["none", "react", "web", "nest"],
|
||||
"alias": ["uiFramework"],
|
||||
"x-priority": "important",
|
||||
"default": "react"
|
||||
},
|
||||
"style": {
|
||||
"description": "The file extension to be used for style files.",
|
||||
"type": "string",
|
||||
"default": "css",
|
||||
"alias": "s",
|
||||
"x-prompt": {
|
||||
"message": "Which stylesheet format would you like to use?",
|
||||
"type": "list",
|
||||
"items": [
|
||||
{
|
||||
"value": "css",
|
||||
"label": "CSS"
|
||||
},
|
||||
{
|
||||
"value": "scss",
|
||||
"label": "SASS(.scss) [ http://sass-lang.com ]"
|
||||
},
|
||||
{
|
||||
"value": "styl",
|
||||
"label": "Stylus(.styl) [ http://stylus-lang.com ]"
|
||||
},
|
||||
{
|
||||
"value": "less",
|
||||
"label": "LESS [ http://lesscss.org ]"
|
||||
},
|
||||
{
|
||||
"value": "none",
|
||||
"label": "None"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"unitTestRunner": {
|
||||
"type": "string",
|
||||
"description": "The unit test runner to use.",
|
||||
"enum": ["none", "jest"],
|
||||
"default": "jest"
|
||||
},
|
||||
"e2eTestRunner": {
|
||||
"type": "string",
|
||||
"description": "The e2e test runner to use.",
|
||||
"enum": ["none", "cypress"],
|
||||
"default": "cypress"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string",
|
||||
"description": "The directory to nest the app under."
|
||||
},
|
||||
"tags": {
|
||||
"type": "string",
|
||||
"description": "Add tags to the application (used for linting).",
|
||||
"alias": "t"
|
||||
},
|
||||
"monorepo": {
|
||||
"type": "boolean",
|
||||
"description": "Creates an integrated monorepo.",
|
||||
"aliases": ["integrated"]
|
||||
},
|
||||
"rootProject": {
|
||||
"type": "boolean",
|
||||
"x-priority": "internal"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
153
packages/rspack/src/generators/configuration/configuration.ts
Normal file
153
packages/rspack/src/generators/configuration/configuration.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import {
|
||||
formatFiles,
|
||||
joinPathFragments,
|
||||
offsetFromRoot,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
} from '@nx/devkit';
|
||||
import {
|
||||
addOrChangeBuildTarget,
|
||||
addOrChangeServeTarget,
|
||||
deleteWebpackConfig,
|
||||
determineFrameworkAndTarget,
|
||||
findExistingTargetsInProject,
|
||||
handleUnknownExecutors,
|
||||
handleUnsupportedUserProvidedTargets,
|
||||
TargetFlags,
|
||||
UserProvidedTargetName,
|
||||
writeRspackConfigFile,
|
||||
} from '../../utils/generator-utils';
|
||||
import { editTsConfig } from '../application/lib/create-ts-config';
|
||||
import rspackInitGenerator from '../init/init';
|
||||
import { ConfigurationSchema } from './schema';
|
||||
|
||||
export async function configurationGenerator(
|
||||
tree: Tree,
|
||||
options: ConfigurationSchema
|
||||
) {
|
||||
const task = await rspackInitGenerator(tree, {
|
||||
...options,
|
||||
// TODO: Crystalize the default rspack.config.js file.
|
||||
// The default setup isn't crystalized so don't add plugin.
|
||||
addPlugin: false,
|
||||
});
|
||||
const { targets, root, projectType } = readProjectConfiguration(
|
||||
tree,
|
||||
options.project
|
||||
);
|
||||
|
||||
const { target, framework } = determineFrameworkAndTarget(
|
||||
tree,
|
||||
options,
|
||||
root,
|
||||
targets
|
||||
);
|
||||
options.framework = framework;
|
||||
options.target = target;
|
||||
|
||||
let foundStylePreprocessorOptions: { includePaths?: string[] } | undefined;
|
||||
|
||||
let buildTargetName = 'build';
|
||||
let serveTargetName = 'serve';
|
||||
|
||||
/**
|
||||
* This is for when we are converting an existing project
|
||||
* to use the vite executors.
|
||||
*/
|
||||
let projectAlreadyHasRspackTargets: TargetFlags = {};
|
||||
|
||||
if (!options.newProject) {
|
||||
const userProvidedTargetName: UserProvidedTargetName = {
|
||||
build: options.buildTarget,
|
||||
serve: options.serveTarget,
|
||||
};
|
||||
|
||||
const {
|
||||
validFoundTargetName,
|
||||
projectContainsUnsupportedExecutor,
|
||||
userProvidedTargetIsUnsupported,
|
||||
alreadyHasNxRspackTargets,
|
||||
} = findExistingTargetsInProject(targets, userProvidedTargetName);
|
||||
projectAlreadyHasRspackTargets = alreadyHasNxRspackTargets;
|
||||
|
||||
if (
|
||||
alreadyHasNxRspackTargets.build &&
|
||||
(alreadyHasNxRspackTargets.serve ||
|
||||
projectType === 'library' ||
|
||||
options.framework === 'nest')
|
||||
) {
|
||||
throw new Error(
|
||||
`The project ${options.project} is already configured to use the @nx/rspack executors.
|
||||
Please try a different project, or remove the existing targets
|
||||
and re-run this generator to reset the existing Rspack Configuration.
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
if (!validFoundTargetName.build && projectContainsUnsupportedExecutor) {
|
||||
throw new Error(
|
||||
`The project ${options.project} cannot be converted to use the @nx/rspack executors.`
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!projectContainsUnsupportedExecutor &&
|
||||
!validFoundTargetName.build &&
|
||||
!validFoundTargetName.serve
|
||||
) {
|
||||
await handleUnknownExecutors(options.project);
|
||||
}
|
||||
|
||||
await handleUnsupportedUserProvidedTargets(
|
||||
userProvidedTargetIsUnsupported,
|
||||
userProvidedTargetName,
|
||||
validFoundTargetName,
|
||||
options.framework
|
||||
);
|
||||
|
||||
/**
|
||||
* Once the user is at this stage, then they can go ahead and convert.
|
||||
*/
|
||||
|
||||
buildTargetName = validFoundTargetName.build ?? buildTargetName;
|
||||
serveTargetName = validFoundTargetName.serve ?? serveTargetName;
|
||||
|
||||
// Not needed atm
|
||||
// if (projectType === 'application' && options.target !== 'node') {
|
||||
// moveAndEditIndexHtml(tree, options, buildTargetName);
|
||||
// }
|
||||
|
||||
foundStylePreprocessorOptions =
|
||||
targets?.[buildTargetName]?.options?.stylePreprocessorOptions;
|
||||
|
||||
deleteWebpackConfig(
|
||||
tree,
|
||||
root,
|
||||
targets?.[buildTargetName]?.options?.webpackConfig
|
||||
);
|
||||
|
||||
editTsConfig(
|
||||
tree,
|
||||
root,
|
||||
options.framework,
|
||||
joinPathFragments(offsetFromRoot(root), 'tsconfig.base.json')
|
||||
);
|
||||
}
|
||||
|
||||
if (!projectAlreadyHasRspackTargets.build) {
|
||||
addOrChangeBuildTarget(tree, options, buildTargetName);
|
||||
}
|
||||
|
||||
if (
|
||||
(options.framework !== 'none' || options.devServer) &&
|
||||
options.framework !== 'nest' &&
|
||||
!projectAlreadyHasRspackTargets.serve
|
||||
) {
|
||||
addOrChangeServeTarget(tree, options, serveTargetName);
|
||||
}
|
||||
writeRspackConfigFile(tree, options, foundStylePreprocessorOptions);
|
||||
await formatFiles(tree);
|
||||
return task;
|
||||
}
|
||||
|
||||
export default configurationGenerator;
|
||||
12
packages/rspack/src/generators/configuration/schema.d.ts
vendored
Normal file
12
packages/rspack/src/generators/configuration/schema.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import { InitGeneratorSchema } from '../init/schema';
|
||||
|
||||
export interface ConfigurationSchema extends InitGeneratorSchema {
|
||||
project: string;
|
||||
main?: string;
|
||||
tsConfig?: string;
|
||||
target?: 'node' | 'web';
|
||||
skipValidation?: boolean;
|
||||
newProject?: boolean;
|
||||
buildTarget?: string;
|
||||
serveTarget?: string;
|
||||
}
|
||||
73
packages/rspack/src/generators/configuration/schema.json
Normal file
73
packages/rspack/src/generators/configuration/schema.json
Normal file
@ -0,0 +1,73 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "Rspack",
|
||||
"title": "Nx Rspack Configuration Generator",
|
||||
"description": "Rspack configuration generator.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the project.",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
},
|
||||
"x-dropdown": "project",
|
||||
"x-prompt": "What is the name of the project to set up a rspack for?",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"framework": {
|
||||
"type": "string",
|
||||
"description": "The framework used by the project.",
|
||||
"x-prompt": "What framework is the project you want to convert using?",
|
||||
"enum": ["none", "react", "web", "nest"],
|
||||
"alias": ["uiFramework"],
|
||||
"x-priority": "important"
|
||||
},
|
||||
"main": {
|
||||
"type": "string",
|
||||
"description": "Path relative to the workspace root for the main entry file. Defaults to '<projectRoot>/src/main.ts'.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"tsConfig": {
|
||||
"type": "string",
|
||||
"description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '<projectRoot>/tsconfig.app.json'.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "Target platform for the build, same as the rspack config option.",
|
||||
"enum": ["node", "web"],
|
||||
"default": "web"
|
||||
},
|
||||
"devServer": {
|
||||
"type": "boolean",
|
||||
"description": "Add a serve target to run a local rspack dev-server",
|
||||
"default": false
|
||||
},
|
||||
"style": {
|
||||
"type": "string",
|
||||
"description": "The style solution to use.",
|
||||
"enum": ["none", "css", "scss", "less"]
|
||||
},
|
||||
"newProject": {
|
||||
"type": "boolean",
|
||||
"description": "Is this a new project?",
|
||||
"default": false,
|
||||
"hidden": true
|
||||
},
|
||||
"buildTarget": {
|
||||
"type": "string",
|
||||
"description": "The build target of the project to be transformed to use the @nx/vite:build executor."
|
||||
},
|
||||
"serveTarget": {
|
||||
"type": "string",
|
||||
"description": "The serve target of the project to be transformed to use the @nx/vite:dev-server and @nx/vite:preview-server executors."
|
||||
},
|
||||
"rootProject": {
|
||||
"type": "boolean",
|
||||
"x-priority": "internal"
|
||||
}
|
||||
},
|
||||
"required": ["project"]
|
||||
}
|
||||
113
packages/rspack/src/generators/init/init.ts
Normal file
113
packages/rspack/src/generators/init/init.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import {
|
||||
addDependenciesToPackageJson,
|
||||
convertNxGenerator,
|
||||
createProjectGraphAsync,
|
||||
GeneratorCallback,
|
||||
readNxJson,
|
||||
runTasksInSerial,
|
||||
Tree,
|
||||
} from '@nx/devkit';
|
||||
import { addPlugin } from '@nx/devkit/src/utils/add-plugin';
|
||||
import { initGenerator } from '@nx/js';
|
||||
import { createNodesV2 } from '../../../plugin';
|
||||
import {
|
||||
lessLoaderVersion,
|
||||
reactRefreshVersion,
|
||||
rspackCoreVersion,
|
||||
rspackDevServerVersion,
|
||||
rspackPluginMinifyVersion,
|
||||
rspackPluginReactRefreshVersion,
|
||||
} from '../../utils/versions';
|
||||
import { InitGeneratorSchema } from './schema';
|
||||
|
||||
export async function rspackInitGenerator(
|
||||
tree: Tree,
|
||||
schema: InitGeneratorSchema
|
||||
) {
|
||||
const tasks: GeneratorCallback[] = [];
|
||||
|
||||
const nxJson = readNxJson(tree);
|
||||
const addPluginDefault =
|
||||
process.env.NX_ADD_PLUGINS !== 'false' &&
|
||||
nxJson.useInferencePlugins !== false;
|
||||
schema.addPlugin ??= addPluginDefault;
|
||||
|
||||
if (schema.addPlugin) {
|
||||
await addPlugin(
|
||||
tree,
|
||||
await createProjectGraphAsync(),
|
||||
'@nx/rspack/plugin',
|
||||
createNodesV2,
|
||||
{
|
||||
buildTargetName: [
|
||||
'build',
|
||||
'rspack:build',
|
||||
'build:rspack',
|
||||
'rspack-build',
|
||||
'build-rspack',
|
||||
],
|
||||
serveTargetName: [
|
||||
'serve',
|
||||
'rspack:serve',
|
||||
'serve:rspack',
|
||||
'rspack-serve',
|
||||
'serve-rspack',
|
||||
],
|
||||
previewTargetName: [
|
||||
'preview',
|
||||
'rspack:preview',
|
||||
'preview:rspack',
|
||||
'rspack-preview',
|
||||
'preview-rspack',
|
||||
],
|
||||
},
|
||||
schema.updatePackageScripts
|
||||
);
|
||||
}
|
||||
|
||||
const jsInitTask = await initGenerator(tree, {
|
||||
...schema,
|
||||
tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json',
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
tasks.push(jsInitTask);
|
||||
|
||||
const devDependencies = {
|
||||
'@rspack/core': rspackCoreVersion,
|
||||
'@rspack/cli': rspackCoreVersion,
|
||||
'@rspack/plugin-minify': rspackPluginMinifyVersion,
|
||||
'@rspack/plugin-react-refresh': rspackPluginReactRefreshVersion,
|
||||
'react-refresh': reactRefreshVersion,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const version = require('../../../package.json').version;
|
||||
if (version !== '0.0.1') {
|
||||
// Ignored for local dev / e2e tests.
|
||||
devDependencies['@nx/rspack'] = version;
|
||||
}
|
||||
|
||||
if (schema.style === 'less') {
|
||||
devDependencies['less-loader'] = lessLoaderVersion;
|
||||
}
|
||||
|
||||
if (schema.framework !== 'none' || schema.devServer) {
|
||||
devDependencies['@rspack/dev-server'] = rspackDevServerVersion;
|
||||
}
|
||||
|
||||
const installTask = addDependenciesToPackageJson(
|
||||
tree,
|
||||
{},
|
||||
devDependencies,
|
||||
undefined,
|
||||
schema.keepExistingVersions
|
||||
);
|
||||
tasks.push(installTask);
|
||||
|
||||
return runTasksInSerial(...tasks);
|
||||
}
|
||||
|
||||
export default rspackInitGenerator;
|
||||
|
||||
export const rspackInitSchematic = convertNxGenerator(rspackInitGenerator);
|
||||
11
packages/rspack/src/generators/init/schema.d.ts
vendored
Normal file
11
packages/rspack/src/generators/init/schema.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
export type Framework = 'none' | 'react' | 'web' | 'nest';
|
||||
|
||||
export interface InitGeneratorSchema {
|
||||
addPlugin?: boolean;
|
||||
devServer?: boolean;
|
||||
framework?: Framework;
|
||||
keepExistingVersions?: boolean;
|
||||
rootProject?: boolean;
|
||||
style?: 'none' | 'css' | 'scss' | 'less' | 'styl';
|
||||
updatePackageScripts?: boolean;
|
||||
}
|
||||
31
packages/rspack/src/generators/init/schema.json
Normal file
31
packages/rspack/src/generators/init/schema.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "Init",
|
||||
"title": "Nx Rspack Init Generator",
|
||||
"type": "object",
|
||||
"description": "Rspack init generator.",
|
||||
"properties": {
|
||||
"framework": {
|
||||
"type": "string",
|
||||
"description": "The UI framework used by the project.",
|
||||
"enum": ["none", "react", "web", "nest"],
|
||||
"alias": ["uiFramework"]
|
||||
},
|
||||
"style": {
|
||||
"type": "string",
|
||||
"description": "The style solution to use.",
|
||||
"enum": ["none", "css", "scss", "less", "styl"]
|
||||
},
|
||||
"rootProject": {
|
||||
"type": "boolean",
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"keepExistingVersions": {
|
||||
"type": "boolean",
|
||||
"x-priority": "internal",
|
||||
"description": "Keep existing dependencies versions",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
36
packages/rspack/src/generators/preset/preset.ts
Normal file
36
packages/rspack/src/generators/preset/preset.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Tree, updateJson } from '@nx/devkit';
|
||||
|
||||
import applicationGenerator from '../application/application';
|
||||
import { PresetGeneratorSchema } from './schema';
|
||||
|
||||
export default async function (tree: Tree, options: PresetGeneratorSchema) {
|
||||
const appTask = applicationGenerator(tree, {
|
||||
...options,
|
||||
// Since `--style` is not passed down to custom preset, we're using individual flags for now.
|
||||
style: options.sass
|
||||
? 'scss'
|
||||
: options.less
|
||||
? 'less'
|
||||
: options.stylus
|
||||
? 'styl'
|
||||
: 'css',
|
||||
});
|
||||
|
||||
updateJson(tree, 'package.json', (json) => {
|
||||
json.scripts ??= {};
|
||||
json.scripts.build ??= 'npx nx build';
|
||||
json.scripts.start ??= 'npx nx serve';
|
||||
json.scripts.lint ??= 'npx nx lint';
|
||||
json.scripts.test ??= 'npx nx test';
|
||||
json.scripts.e2e ??= 'npx nx e2e e2e';
|
||||
return json;
|
||||
});
|
||||
|
||||
if (options.rootProject) {
|
||||
// Remove these folders so projects will be generated at the root.
|
||||
tree.delete('apps');
|
||||
tree.delete('libs');
|
||||
}
|
||||
|
||||
return appTask;
|
||||
}
|
||||
18
packages/rspack/src/generators/preset/schema.d.ts
vendored
Normal file
18
packages/rspack/src/generators/preset/schema.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
export interface PresetGeneratorSchema {
|
||||
name: string;
|
||||
framework?: Framework;
|
||||
less?: boolean;
|
||||
sass?: boolean;
|
||||
stylus?: boolean;
|
||||
unitTestRunner?: 'none' | 'jest';
|
||||
e2eTestRunner?: 'none' | 'cypress';
|
||||
directory?: string;
|
||||
tags?: string;
|
||||
rootProject?: boolean;
|
||||
monorepo?: boolean;
|
||||
}
|
||||
|
||||
export interface NormalizedSchema extends PresetGeneratorSchema {
|
||||
appProjectRoot: string;
|
||||
e2eProjectName: string;
|
||||
}
|
||||
71
packages/rspack/src/generators/preset/schema.json
Normal file
71
packages/rspack/src/generators/preset/schema.json
Normal file
@ -0,0 +1,71 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "Preset",
|
||||
"title": "Standalone React and rspack preset",
|
||||
"description": "React + Rspack preset generator.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
},
|
||||
"x-priority": "important"
|
||||
},
|
||||
"framework": {
|
||||
"type": "string",
|
||||
"description": "The framework to use for the application.",
|
||||
"enum": ["none", "react", "web", "nest"],
|
||||
"alias": ["uiFramework"],
|
||||
"x-priority": "important",
|
||||
"default": "react"
|
||||
},
|
||||
"less": {
|
||||
"type": "boolean",
|
||||
"description": "Use less for styling."
|
||||
},
|
||||
"sass": {
|
||||
"type": "boolean",
|
||||
"description": "Use sass for styling."
|
||||
},
|
||||
"stylus": {
|
||||
"type": "boolean",
|
||||
"description": "Use stylus for styling."
|
||||
},
|
||||
"unitTestRunner": {
|
||||
"type": "string",
|
||||
"description": "The unit test runner to use.",
|
||||
"enum": ["none", "jest"],
|
||||
"default": "jest"
|
||||
},
|
||||
"e2eTestRunner": {
|
||||
"type": "string",
|
||||
"description": "The e2e test runner to use.",
|
||||
"enum": ["none", "cypress"],
|
||||
"default": "cypress"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string",
|
||||
"description": "The directory to nest the app under."
|
||||
},
|
||||
"tags": {
|
||||
"type": "string",
|
||||
"description": "Add tags to the project (used for linting).",
|
||||
"alias": "t"
|
||||
},
|
||||
"monorepo": {
|
||||
"type": "boolean",
|
||||
"description": "Creates an integrated monorepo.",
|
||||
"default": false,
|
||||
"aliases": ["integrated"]
|
||||
},
|
||||
"rootProject": {
|
||||
"type": "boolean",
|
||||
"x-priority": "internal",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
6
packages/rspack/src/index.ts
Normal file
6
packages/rspack/src/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export * from './generators/configuration/configuration';
|
||||
export * from './generators/init/init';
|
||||
export * from './utils/config';
|
||||
export * from './utils/with-nx';
|
||||
export * from './utils/with-react';
|
||||
export * from './utils/with-web';
|
||||
@ -0,0 +1,37 @@
|
||||
import { readJson, Tree, updateJson } from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import replacePackage from './update-16-0-0-add-nx-packages';
|
||||
|
||||
describe('update-16-0-0-add-nx-packages', () => {
|
||||
let tree: Tree;
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
|
||||
updateJson(tree, 'package.json', (json) => {
|
||||
json.devDependencies['@nrwl/rspack'] = '16.0.0';
|
||||
return json;
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove the dependency on @nrwl/rspack', async () => {
|
||||
await replacePackage(tree);
|
||||
|
||||
expect(
|
||||
readJson(tree, 'package.json').dependencies['@nrwl/rspack']
|
||||
).not.toBeDefined();
|
||||
expect(
|
||||
readJson(tree, 'package.json').devDependencies['@nrwl/rspack']
|
||||
).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should add a dependency on @nx/rspack', async () => {
|
||||
await replacePackage(tree);
|
||||
|
||||
const packageJson = readJson(tree, 'package.json');
|
||||
const newDependencyVersion =
|
||||
packageJson.devDependencies['@nx/rspack'] ??
|
||||
packageJson.dependencies['@nx/rspack'];
|
||||
|
||||
expect(newDependencyVersion).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,8 @@
|
||||
import { formatFiles, Tree } from '@nx/devkit';
|
||||
import { replaceNrwlPackageWithNxPackage } from '@nx/devkit/src/utils/replace-package';
|
||||
|
||||
export default async function replacePackage(tree: Tree): Promise<void> {
|
||||
await replaceNrwlPackageWithNxPackage(tree, '@nrwl/rspack', '@nx/rspack');
|
||||
|
||||
await formatFiles(tree);
|
||||
}
|
||||
87
packages/rspack/src/plugins/generate-package-json-plugin.ts
Normal file
87
packages/rspack/src/plugins/generate-package-json-plugin.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import {
|
||||
ExecutorContext,
|
||||
detectPackageManager,
|
||||
serializeJson,
|
||||
type ProjectGraph,
|
||||
} from '@nx/devkit';
|
||||
import {
|
||||
HelperDependency,
|
||||
createLockFile,
|
||||
createPackageJson,
|
||||
getHelperDependenciesFromProjectGraph,
|
||||
getLockFileName,
|
||||
readTsConfig,
|
||||
} from '@nx/js';
|
||||
import { type Compiler, type RspackPluginInstance } from '@rspack/core';
|
||||
import { RawSource } from 'webpack-sources';
|
||||
|
||||
const pluginName = 'GeneratePackageJsonPlugin';
|
||||
|
||||
export class GeneratePackageJsonPlugin implements RspackPluginInstance {
|
||||
private readonly projectGraph: ProjectGraph;
|
||||
|
||||
constructor(
|
||||
private readonly options: { tsConfig: string; outputFileName: string },
|
||||
private readonly context: ExecutorContext
|
||||
) {
|
||||
this.projectGraph = context.projectGraph;
|
||||
}
|
||||
|
||||
apply(compiler: Compiler): void {
|
||||
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
|
||||
compilation.hooks.processAssets.tap(
|
||||
{
|
||||
name: pluginName,
|
||||
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
|
||||
},
|
||||
() => {
|
||||
const helperDependencies = getHelperDependenciesFromProjectGraph(
|
||||
this.context.root,
|
||||
this.context.projectName,
|
||||
this.projectGraph
|
||||
);
|
||||
|
||||
const importHelpers = !!readTsConfig(this.options.tsConfig).options
|
||||
.importHelpers;
|
||||
const shouldAddHelperDependency =
|
||||
importHelpers &&
|
||||
helperDependencies.every(
|
||||
(dep) => dep.target !== HelperDependency.tsc
|
||||
);
|
||||
|
||||
if (shouldAddHelperDependency) {
|
||||
helperDependencies.push({
|
||||
type: 'static',
|
||||
source: this.context.projectName,
|
||||
target: HelperDependency.tsc,
|
||||
});
|
||||
}
|
||||
|
||||
const packageJson = createPackageJson(
|
||||
this.context.projectName,
|
||||
this.projectGraph,
|
||||
{
|
||||
target: this.context.targetName,
|
||||
root: this.context.root,
|
||||
isProduction: true,
|
||||
helperDependencies: helperDependencies.map((dep) => dep.target),
|
||||
}
|
||||
);
|
||||
packageJson.main = packageJson.main ?? this.options.outputFileName;
|
||||
|
||||
compilation.emitAsset(
|
||||
'package.json',
|
||||
new RawSource(serializeJson(packageJson))
|
||||
);
|
||||
const packageManager = detectPackageManager(this.context.root);
|
||||
compilation.emitAsset(
|
||||
getLockFileName(packageManager),
|
||||
new RawSource(
|
||||
createLockFile(packageJson, this.projectGraph, packageManager)
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
231
packages/rspack/src/plugins/plugin.ts
Normal file
231
packages/rspack/src/plugins/plugin.ts
Normal file
@ -0,0 +1,231 @@
|
||||
import {
|
||||
CreateDependencies,
|
||||
CreateNodesContext,
|
||||
createNodesFromFiles,
|
||||
CreateNodesV2,
|
||||
detectPackageManager,
|
||||
ProjectConfiguration,
|
||||
readJsonFile,
|
||||
workspaceRoot,
|
||||
writeJsonFile,
|
||||
} from '@nx/devkit';
|
||||
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
|
||||
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
|
||||
import { getLockFileName, getRootTsConfigPath } from '@nx/js';
|
||||
import { existsSync, readdirSync } from 'fs';
|
||||
import { hashObject } from 'nx/src/hasher/file-hasher';
|
||||
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
|
||||
import { dirname, isAbsolute, join, relative, resolve } from 'path';
|
||||
import { readRspackOptions } from '../utils/read-rspack-options';
|
||||
import { resolveUserDefinedRspackConfig } from '../utils/resolve-user-defined-rspack-config';
|
||||
|
||||
export interface RspackPluginOptions {
|
||||
buildTargetName?: string;
|
||||
serveTargetName?: string;
|
||||
serveStaticTargetName?: string;
|
||||
previewTargetName?: string;
|
||||
}
|
||||
|
||||
type RspackTargets = Pick<ProjectConfiguration, 'targets' | 'metadata'>;
|
||||
|
||||
function readTargetsCache(cachePath: string): Record<string, RspackTargets> {
|
||||
return existsSync(cachePath) ? readJsonFile(cachePath) : {};
|
||||
}
|
||||
|
||||
function writeTargetsToCache(
|
||||
cachePath,
|
||||
results?: Record<string, RspackTargets>
|
||||
) {
|
||||
writeJsonFile(cachePath, results);
|
||||
}
|
||||
|
||||
export const createDependencies: CreateDependencies = () => {
|
||||
return [];
|
||||
};
|
||||
|
||||
const rspackConfigGlob = '**/rspack.config.{js,ts,mjs,mts,cjs,cts}';
|
||||
|
||||
export const createNodesV2: CreateNodesV2<RspackPluginOptions> = [
|
||||
rspackConfigGlob,
|
||||
async (configFilePaths, options, context) => {
|
||||
const optionsHash = hashObject(options);
|
||||
const cachePath = join(
|
||||
workspaceDataDirectory,
|
||||
`rspack-${optionsHash}.hash`
|
||||
);
|
||||
const targetsCache = readTargetsCache(cachePath);
|
||||
try {
|
||||
return await createNodesFromFiles(
|
||||
(configFile, options, context) =>
|
||||
createNodesInternal(configFile, options, context, targetsCache),
|
||||
configFilePaths,
|
||||
options,
|
||||
context
|
||||
);
|
||||
} finally {
|
||||
writeTargetsToCache(cachePath, targetsCache);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
async function createNodesInternal(
|
||||
configFilePath: string,
|
||||
options: RspackPluginOptions,
|
||||
context: CreateNodesContext,
|
||||
targetsCache: Record<string, RspackTargets>
|
||||
) {
|
||||
const projectRoot = dirname(configFilePath);
|
||||
// Do not create a project if package.json and project.json isn't there.
|
||||
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
|
||||
if (
|
||||
!siblingFiles.includes('package.json') &&
|
||||
!siblingFiles.includes('project.json')
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const normalizedOptions = normalizeOptions(options);
|
||||
|
||||
// We do not want to alter how the hash is calculated, so appending the config file path to the hash
|
||||
// to prevent vite/vitest files overwriting the target cache created by the other
|
||||
const hash =
|
||||
(await calculateHashForCreateNodes(
|
||||
projectRoot,
|
||||
normalizedOptions,
|
||||
context,
|
||||
[getLockFileName(detectPackageManager(context.workspaceRoot))]
|
||||
)) + configFilePath;
|
||||
|
||||
targetsCache[hash] ??= await createRspackTargets(
|
||||
configFilePath,
|
||||
projectRoot,
|
||||
normalizedOptions,
|
||||
context
|
||||
);
|
||||
|
||||
const { targets, metadata } = targetsCache[hash];
|
||||
|
||||
return {
|
||||
projects: {
|
||||
[projectRoot]: {
|
||||
root: projectRoot,
|
||||
targets,
|
||||
metadata,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function createRspackTargets(
|
||||
configFilePath: string,
|
||||
projectRoot: string,
|
||||
options: RspackPluginOptions,
|
||||
context: CreateNodesContext
|
||||
): Promise<RspackTargets> {
|
||||
const namedInputs = getNamedInputs(projectRoot, context);
|
||||
|
||||
const rspackConfig = resolveUserDefinedRspackConfig(
|
||||
join(context.workspaceRoot, configFilePath),
|
||||
getRootTsConfigPath(),
|
||||
true
|
||||
);
|
||||
|
||||
const rspackOptions = await readRspackOptions(rspackConfig);
|
||||
|
||||
const outputPath = normalizeOutputPath(
|
||||
rspackOptions.output?.path,
|
||||
projectRoot
|
||||
);
|
||||
|
||||
const targets = {};
|
||||
|
||||
targets[options.buildTargetName] = {
|
||||
command: `rspack build`,
|
||||
options: { cwd: projectRoot, args: ['--node-env=production'] },
|
||||
cache: true,
|
||||
dependsOn: [`^${options.buildTargetName}`],
|
||||
inputs:
|
||||
'production' in namedInputs
|
||||
? [
|
||||
'production',
|
||||
'^production',
|
||||
{
|
||||
externalDependencies: ['@rspack/cli'],
|
||||
},
|
||||
]
|
||||
: [
|
||||
'default',
|
||||
'^default',
|
||||
{
|
||||
externalDependencies: ['@rspack/cli'],
|
||||
},
|
||||
],
|
||||
outputs: [outputPath],
|
||||
};
|
||||
|
||||
targets[options.serveTargetName] = {
|
||||
command: `rspack serve`,
|
||||
options: {
|
||||
cwd: projectRoot,
|
||||
args: ['--node-env=development'],
|
||||
},
|
||||
};
|
||||
|
||||
targets[options.previewTargetName] = {
|
||||
command: `rspack serve`,
|
||||
options: {
|
||||
cwd: projectRoot,
|
||||
args: ['--node-env=production'],
|
||||
},
|
||||
};
|
||||
|
||||
targets[options.serveStaticTargetName] = {
|
||||
executor: '@nx/web:file-server',
|
||||
options: {
|
||||
buildTarget: options.buildTargetName,
|
||||
spa: true,
|
||||
},
|
||||
};
|
||||
|
||||
return { targets, metadata: {} };
|
||||
}
|
||||
|
||||
function normalizeOptions(options: RspackPluginOptions): RspackPluginOptions {
|
||||
options ??= {};
|
||||
options.buildTargetName ??= 'build';
|
||||
options.serveTargetName ??= 'serve';
|
||||
options.previewTargetName ??= 'preview';
|
||||
options.serveStaticTargetName ??= 'serve-static';
|
||||
return options;
|
||||
}
|
||||
|
||||
function normalizeOutputPath(
|
||||
outputPath: string | undefined,
|
||||
projectRoot: string
|
||||
): string | undefined {
|
||||
if (!outputPath) {
|
||||
// If outputPath is undefined, use rspack's default `dist` directory.
|
||||
if (projectRoot === '.') {
|
||||
return `{projectRoot}/dist`;
|
||||
} else {
|
||||
return `{workspaceRoot}/dist/{projectRoot}`;
|
||||
}
|
||||
} else {
|
||||
if (isAbsolute(outputPath)) {
|
||||
/**
|
||||
* If outputPath is absolute, we need to resolve it relative to the workspaceRoot first.
|
||||
* After that, we can use the relative path to the workspaceRoot token {workspaceRoot} to generate the output path.
|
||||
*/
|
||||
return `{workspaceRoot}/${relative(
|
||||
workspaceRoot,
|
||||
resolve(workspaceRoot, outputPath)
|
||||
)}`;
|
||||
} else {
|
||||
if (outputPath.startsWith('..')) {
|
||||
return join('{workspaceRoot}', join(projectRoot, outputPath));
|
||||
} else {
|
||||
return join('{projectRoot}', outputPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
packages/rspack/src/utils/config.ts
Normal file
49
packages/rspack/src/utils/config.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import type { ExecutorContext } from '@nx/devkit';
|
||||
import type { Configuration } from '@rspack/core';
|
||||
|
||||
import { SharedConfigContext } from './model';
|
||||
|
||||
export const nxRspackComposablePlugin = 'nxRspackComposablePlugin';
|
||||
|
||||
export function isNxRspackComposablePlugin(
|
||||
a: unknown
|
||||
): a is AsyncNxComposableRspackPlugin {
|
||||
return a?.[nxRspackComposablePlugin] === true;
|
||||
}
|
||||
|
||||
export interface NxRspackExecutionContext {
|
||||
options: unknown;
|
||||
context: ExecutorContext;
|
||||
configuration?: string;
|
||||
}
|
||||
|
||||
export interface NxComposableRspackPlugin {
|
||||
(config: Configuration, ctx: NxRspackExecutionContext): Configuration;
|
||||
}
|
||||
|
||||
export interface AsyncNxComposableRspackPlugin {
|
||||
(config: Configuration, ctx: NxRspackExecutionContext):
|
||||
| Configuration
|
||||
| Promise<Configuration>;
|
||||
}
|
||||
|
||||
export function composePlugins(...plugins: any[]) {
|
||||
return Object.defineProperty(
|
||||
async function combined(
|
||||
config: Configuration,
|
||||
ctx: SharedConfigContext
|
||||
): Promise<Configuration> {
|
||||
for (const plugin of plugins) {
|
||||
const fn = await plugin;
|
||||
config = await fn(config, ctx);
|
||||
}
|
||||
return config;
|
||||
},
|
||||
nxRspackComposablePlugin,
|
||||
{
|
||||
value: true,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
51
packages/rspack/src/utils/create-compiler.ts
Normal file
51
packages/rspack/src/utils/create-compiler.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { ExecutorContext } from '@nx/devkit';
|
||||
import {
|
||||
Compiler,
|
||||
type Configuration,
|
||||
MultiCompiler,
|
||||
rspack,
|
||||
} from '@rspack/core';
|
||||
import * as path from 'path';
|
||||
import { RspackExecutorSchema } from '../executors/rspack/schema';
|
||||
import { resolveUserDefinedRspackConfig } from './resolve-user-defined-rspack-config';
|
||||
|
||||
export async function createCompiler(
|
||||
options: RspackExecutorSchema & {
|
||||
devServer?: any;
|
||||
},
|
||||
context: ExecutorContext
|
||||
): Promise<Compiler | MultiCompiler> {
|
||||
const pathToConfig = path.join(context.root, options.rspackConfig);
|
||||
let userDefinedConfig: any = {};
|
||||
if (options.tsConfig) {
|
||||
userDefinedConfig = resolveUserDefinedRspackConfig(
|
||||
pathToConfig,
|
||||
options.tsConfig
|
||||
);
|
||||
} else {
|
||||
userDefinedConfig = await import(pathToConfig).then((x) => x.default || x);
|
||||
}
|
||||
|
||||
if (typeof userDefinedConfig.then === 'function') {
|
||||
userDefinedConfig = await userDefinedConfig;
|
||||
}
|
||||
|
||||
let config: Configuration = {};
|
||||
if (typeof userDefinedConfig === 'function') {
|
||||
config = await userDefinedConfig(
|
||||
{ devServer: options.devServer },
|
||||
{ options, context }
|
||||
);
|
||||
} else {
|
||||
config = userDefinedConfig;
|
||||
config.devServer ??= options.devServer;
|
||||
}
|
||||
|
||||
return rspack(config);
|
||||
}
|
||||
|
||||
export function isMultiCompiler(
|
||||
compiler: Compiler | MultiCompiler
|
||||
): compiler is MultiCompiler {
|
||||
return 'compilers' in compiler;
|
||||
}
|
||||
619
packages/rspack/src/utils/generator-utils.ts
Normal file
619
packages/rspack/src/utils/generator-utils.ts
Normal file
@ -0,0 +1,619 @@
|
||||
import {
|
||||
joinPathFragments,
|
||||
logger,
|
||||
readProjectConfiguration,
|
||||
TargetConfiguration,
|
||||
Tree,
|
||||
updateProjectConfiguration,
|
||||
} from '@nx/devkit';
|
||||
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
|
||||
import { RspackExecutorSchema } from '../executors/rspack/schema';
|
||||
import { ConfigurationSchema } from '../generators/configuration/schema';
|
||||
import { Framework } from '../generators/init/schema';
|
||||
|
||||
export type Target = 'build' | 'serve';
|
||||
export type TargetFlags = Partial<Record<Target, boolean>>;
|
||||
export type UserProvidedTargetName = Partial<Record<Target, string>>;
|
||||
export type ValidFoundTargetName = Partial<Record<Target, string>>;
|
||||
|
||||
export function findExistingTargetsInProject(
|
||||
targets: {
|
||||
[targetName: string]: TargetConfiguration;
|
||||
},
|
||||
userProvidedTargets?: UserProvidedTargetName
|
||||
): {
|
||||
validFoundTargetName: ValidFoundTargetName;
|
||||
projectContainsUnsupportedExecutor: boolean;
|
||||
userProvidedTargetIsUnsupported: TargetFlags;
|
||||
alreadyHasNxRspackTargets: TargetFlags;
|
||||
} {
|
||||
const output: ReturnType<typeof findExistingTargetsInProject> = {
|
||||
validFoundTargetName: {},
|
||||
projectContainsUnsupportedExecutor: false,
|
||||
userProvidedTargetIsUnsupported: {},
|
||||
alreadyHasNxRspackTargets: {},
|
||||
};
|
||||
|
||||
const supportedExecutors = {
|
||||
build: [
|
||||
'@nxext/vite:build',
|
||||
'@nrwl/webpack:webpack',
|
||||
'@nrwl/rollup:rollup',
|
||||
'@nrwl/web:rollup',
|
||||
'@nrwl/vite:build',
|
||||
'@nx/webpack:webpack',
|
||||
'@nx/rollup:rollup',
|
||||
'@nx/web:rollup',
|
||||
'@nx/vite:build',
|
||||
],
|
||||
serve: [
|
||||
'@nxext/vite:dev',
|
||||
'@nrwl/webpack:dev-server',
|
||||
'@nrwl/vite:dev-server',
|
||||
'@nx/webpack:dev-server',
|
||||
'@nx/vite:dev-server',
|
||||
],
|
||||
};
|
||||
|
||||
const unsupportedExecutors = [
|
||||
'@nx/js:babel',
|
||||
'@nx/js:node',
|
||||
'@nx/js:swc',
|
||||
'@nx/react-native:run-ios',
|
||||
'@nx/react-native:start',
|
||||
'@nx/react-native:run-android',
|
||||
'@nx/react-native:bundle',
|
||||
'@nx/react-native:build-android',
|
||||
'@nx/react-native:bundle',
|
||||
'@nx/next:build',
|
||||
'@nx/next:server',
|
||||
'@nx/js:tsc',
|
||||
'@nx/angular:ng-packagr-lite',
|
||||
'@nx/angular:package',
|
||||
'@nx/angular:webpack-browser',
|
||||
'@nx/esbuild:esbuild',
|
||||
'@nrwl/js:babel',
|
||||
'@nrwl/js:node',
|
||||
'@nrwl/js:swc',
|
||||
'@nrwl/react-native:run-ios',
|
||||
'@nrwl/react-native:start',
|
||||
'@nrwl/react-native:run-android',
|
||||
'@nrwl/react-native:bundle',
|
||||
'@nrwl/react-native:build-android',
|
||||
'@nrwl/react-native:bundle',
|
||||
'@nrwl/next:build',
|
||||
'@nrwl/next:server',
|
||||
'@nrwl/js:tsc',
|
||||
'@nrwl/angular:ng-packagr-lite',
|
||||
'@nrwl/angular:package',
|
||||
'@nrwl/angular:webpack-browser',
|
||||
'@nrwl/esbuild:esbuild',
|
||||
'@angular-devkit/build-angular:browser',
|
||||
'@angular-devkit/build-angular:dev-server',
|
||||
];
|
||||
|
||||
// First, we check if the user has provided a target
|
||||
// If they have, we check if the executor the target is using is supported
|
||||
// If it's not supported, then we set the unsupported flag to true for that target
|
||||
|
||||
function checkUserProvidedTarget(target: Target) {
|
||||
if (userProvidedTargets?.[target]) {
|
||||
if (
|
||||
supportedExecutors[target].includes(
|
||||
targets[userProvidedTargets[target]]?.executor
|
||||
)
|
||||
) {
|
||||
output.validFoundTargetName[target] = userProvidedTargets[target];
|
||||
} else {
|
||||
output.userProvidedTargetIsUnsupported[target] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkUserProvidedTarget('build');
|
||||
checkUserProvidedTarget('serve');
|
||||
|
||||
// Returns early when we have a build, serve, and test targets.
|
||||
if (output.validFoundTargetName.build && output.validFoundTargetName.serve) {
|
||||
return output;
|
||||
}
|
||||
|
||||
// We try to find the targets that are using the supported executors
|
||||
// for build, serve and test, since these are the ones we will be converting
|
||||
for (const target in targets) {
|
||||
const executorName = targets[target].executor;
|
||||
|
||||
const hasRspackTargets = output.alreadyHasNxRspackTargets;
|
||||
hasRspackTargets.build ||= executorName === '@nx/rspack:rspack';
|
||||
hasRspackTargets.serve ||= executorName === '@nx/rspack:dev-server';
|
||||
|
||||
const foundTargets = output.validFoundTargetName;
|
||||
if (
|
||||
!foundTargets.build &&
|
||||
supportedExecutors.build.includes(executorName)
|
||||
) {
|
||||
foundTargets.build = target;
|
||||
}
|
||||
if (
|
||||
!foundTargets.serve &&
|
||||
supportedExecutors.serve.includes(executorName)
|
||||
) {
|
||||
foundTargets.serve = target;
|
||||
}
|
||||
|
||||
output.projectContainsUnsupportedExecutor ||=
|
||||
unsupportedExecutors.includes(executorName);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
export function addOrChangeBuildTarget(
|
||||
tree: Tree,
|
||||
options: ConfigurationSchema,
|
||||
target: string
|
||||
) {
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
const assets = [];
|
||||
if (
|
||||
options.target === 'web' &&
|
||||
tree.exists(joinPathFragments(project.root, 'src/favicon.ico'))
|
||||
) {
|
||||
assets.push(joinPathFragments(project.root, 'src/favicon.ico'));
|
||||
}
|
||||
if (tree.exists(joinPathFragments(project.root, 'src/assets'))) {
|
||||
assets.push(joinPathFragments(project.root, 'src/assets'));
|
||||
}
|
||||
|
||||
const buildOptions: RspackExecutorSchema = {
|
||||
target: options.target ?? 'web',
|
||||
outputPath: joinPathFragments(
|
||||
'dist',
|
||||
// If standalone project then use the project's name in dist.
|
||||
project.root === '.' ? project.name : project.root
|
||||
),
|
||||
main: determineMain(tree, options),
|
||||
tsConfig: determineTsConfig(tree, options),
|
||||
rspackConfig: joinPathFragments(project.root, 'rspack.config.js'),
|
||||
assets,
|
||||
};
|
||||
|
||||
project.targets ??= {};
|
||||
|
||||
project.targets[target] = {
|
||||
executor: '@nx/rspack:rspack',
|
||||
outputs: ['{options.outputPath}'],
|
||||
defaultConfiguration: 'production',
|
||||
options: buildOptions,
|
||||
configurations: {
|
||||
development: {
|
||||
mode: 'development',
|
||||
},
|
||||
production: {
|
||||
mode: 'production',
|
||||
optimization: options.target === 'web' ? true : undefined,
|
||||
sourceMap: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
updateProjectConfiguration(tree, options.project, project);
|
||||
}
|
||||
|
||||
export function addOrChangeServeTarget(
|
||||
tree: Tree,
|
||||
options: ConfigurationSchema,
|
||||
target: string
|
||||
) {
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
|
||||
project.targets ??= {};
|
||||
|
||||
project.targets[target] = {
|
||||
executor: '@nx/rspack:dev-server',
|
||||
options: {
|
||||
buildTarget: `${options.project}:build:development`,
|
||||
},
|
||||
configurations: {
|
||||
development: {},
|
||||
production: {
|
||||
buildTarget: `${options.project}:build:production`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
updateProjectConfiguration(tree, options.project, project);
|
||||
}
|
||||
|
||||
export function writeRspackConfigFile(
|
||||
tree: Tree,
|
||||
options: ConfigurationSchema,
|
||||
stylePreprocessorOptions?: { includePaths?: string[] }
|
||||
) {
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
|
||||
tree.write(
|
||||
joinPathFragments(project.root, 'rspack.config.js'),
|
||||
createConfig(options, stylePreprocessorOptions)
|
||||
);
|
||||
}
|
||||
|
||||
function createConfig(
|
||||
options: ConfigurationSchema,
|
||||
stylePreprocessorOptions?: { includePaths?: string[] }
|
||||
) {
|
||||
if (options.framework === 'react') {
|
||||
return `
|
||||
const { composePlugins, withNx, withReact } = require('@nx/rspack');
|
||||
|
||||
module.exports = composePlugins(withNx(), withReact(${
|
||||
stylePreprocessorOptions
|
||||
? `
|
||||
{
|
||||
stylePreprocessorOptions: ${JSON.stringify(stylePreprocessorOptions)},
|
||||
}
|
||||
`
|
||||
: ''
|
||||
}), (config) => {
|
||||
return config;
|
||||
});
|
||||
`;
|
||||
} else if (options.framework === 'web' || options.target === 'web') {
|
||||
return `
|
||||
const { composePlugins, withNx, withWeb } = require('@nx/rspack');
|
||||
|
||||
module.exports = composePlugins(withNx(), withWeb(${
|
||||
stylePreprocessorOptions
|
||||
? `
|
||||
{
|
||||
stylePreprocessorOptions: ${JSON.stringify(stylePreprocessorOptions)},
|
||||
}
|
||||
`
|
||||
: ''
|
||||
}), (config) => {
|
||||
return config;
|
||||
});
|
||||
`;
|
||||
} else if (options.framework === 'nest') {
|
||||
return `
|
||||
const { composePlugins, withNx } = require('@nx/rspack');
|
||||
|
||||
module.exports = composePlugins(withNx(), (config) => {
|
||||
return config;
|
||||
});
|
||||
`;
|
||||
} else {
|
||||
return `
|
||||
const { composePlugins, withNx${
|
||||
stylePreprocessorOptions ? ', withWeb' : ''
|
||||
} } = require('@nx/rspack');
|
||||
|
||||
module.exports = composePlugins(withNx()${
|
||||
stylePreprocessorOptions
|
||||
? `,
|
||||
withWeb({
|
||||
stylePreprocessorOptions: ${JSON.stringify(stylePreprocessorOptions)},
|
||||
})`
|
||||
: ''
|
||||
}, (config) => {
|
||||
return config;
|
||||
});
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteWebpackConfig(
|
||||
tree: Tree,
|
||||
projectRoot: string,
|
||||
webpackConfigFilePath?: string
|
||||
) {
|
||||
const webpackConfigPath =
|
||||
webpackConfigFilePath && tree.exists(webpackConfigFilePath)
|
||||
? webpackConfigFilePath
|
||||
: tree.exists(`${projectRoot}/webpack.config.js`)
|
||||
? `${projectRoot}/webpack.config.js`
|
||||
: tree.exists(`${projectRoot}/webpack.config.ts`)
|
||||
? `${projectRoot}/webpack.config.ts`
|
||||
: null;
|
||||
if (webpackConfigPath) {
|
||||
tree.delete(webpackConfigPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe add delete vite config?
|
||||
|
||||
export function moveAndEditIndexHtml(
|
||||
tree: Tree,
|
||||
options: ConfigurationSchema,
|
||||
buildTarget: string
|
||||
) {
|
||||
const projectConfig = readProjectConfiguration(tree, options.project);
|
||||
|
||||
let indexHtmlPath =
|
||||
projectConfig.targets?.[buildTarget]?.options?.index ??
|
||||
`${projectConfig.root}/src/index.html`;
|
||||
let mainPath =
|
||||
projectConfig.targets?.[buildTarget]?.options?.main ??
|
||||
`${projectConfig.root}/src/main.ts${
|
||||
options.framework === 'react' ? 'x' : ''
|
||||
}`;
|
||||
|
||||
if (projectConfig.root !== '.') {
|
||||
mainPath = mainPath.replace(projectConfig.root, '');
|
||||
}
|
||||
|
||||
if (
|
||||
!tree.exists(indexHtmlPath) &&
|
||||
tree.exists(`${projectConfig.root}/index.html`)
|
||||
) {
|
||||
indexHtmlPath = `${projectConfig.root}/index.html`;
|
||||
}
|
||||
|
||||
if (tree.exists(indexHtmlPath)) {
|
||||
const indexHtmlContent = tree.read(indexHtmlPath, 'utf8');
|
||||
if (
|
||||
!indexHtmlContent.includes(
|
||||
`<script type="module" src="${mainPath}"></script>`
|
||||
)
|
||||
) {
|
||||
tree.write(
|
||||
`${projectConfig.root}/index.html`,
|
||||
indexHtmlContent.replace(
|
||||
'</body>',
|
||||
`<script type="module" src="${mainPath}"></script>
|
||||
</body>`
|
||||
)
|
||||
);
|
||||
|
||||
if (tree.exists(`${projectConfig.root}/src/index.html`)) {
|
||||
tree.delete(`${projectConfig.root}/src/index.html`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tree.write(
|
||||
`${projectConfig.root}/index.html`,
|
||||
`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="${mainPath}"></script>
|
||||
</body>
|
||||
</html>`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeViteConfigFilePathWithTree(
|
||||
tree: Tree,
|
||||
projectRoot: string,
|
||||
configFile?: string
|
||||
): string {
|
||||
return configFile && tree.exists(configFile)
|
||||
? configFile
|
||||
: tree.exists(joinPathFragments(`${projectRoot}/rspack.config.ts`))
|
||||
? joinPathFragments(`${projectRoot}/rspack.config.ts`)
|
||||
: tree.exists(joinPathFragments(`${projectRoot}/rspack.config.js`))
|
||||
? joinPathFragments(`${projectRoot}/rspack.config.js`)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function getViteConfigPathForProject(
|
||||
tree: Tree,
|
||||
projectName: string,
|
||||
target?: string
|
||||
) {
|
||||
let viteConfigPath: string | undefined;
|
||||
const { targets, root } = readProjectConfiguration(tree, projectName);
|
||||
if (target) {
|
||||
viteConfigPath = targets?.[target]?.options?.configFile;
|
||||
} else {
|
||||
const config = Object.values(targets).find(
|
||||
(config) => config.executor === '@nx/rspack:build'
|
||||
);
|
||||
viteConfigPath = config?.options?.configFile;
|
||||
}
|
||||
|
||||
return normalizeViteConfigFilePathWithTree(tree, root, viteConfigPath);
|
||||
}
|
||||
|
||||
export async function handleUnsupportedUserProvidedTargets(
|
||||
userProvidedTargetIsUnsupported: TargetFlags,
|
||||
userProvidedTargetName: UserProvidedTargetName,
|
||||
validFoundTargetName: ValidFoundTargetName,
|
||||
framework: Framework
|
||||
) {
|
||||
if (userProvidedTargetIsUnsupported.build && validFoundTargetName.build) {
|
||||
await handleUnsupportedUserProvidedTargetsErrors(
|
||||
userProvidedTargetName.build,
|
||||
validFoundTargetName.build,
|
||||
'build',
|
||||
'rspack'
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
framework !== 'nest' &&
|
||||
userProvidedTargetIsUnsupported.serve &&
|
||||
validFoundTargetName.serve
|
||||
) {
|
||||
await handleUnsupportedUserProvidedTargetsErrors(
|
||||
userProvidedTargetName.serve,
|
||||
validFoundTargetName.serve,
|
||||
'serve',
|
||||
'dev-server'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUnsupportedUserProvidedTargetsErrors(
|
||||
userProvidedTargetName: string,
|
||||
validFoundTargetName: string,
|
||||
target: Target,
|
||||
executor: 'rspack' | 'dev-server'
|
||||
) {
|
||||
logger.warn(
|
||||
`The custom ${target} target you provided (${userProvidedTargetName}) cannot be converted to use the @nx/rspack:${executor} executor.
|
||||
However, we found the following ${target} target in your project that can be converted: ${validFoundTargetName}
|
||||
|
||||
Please note that converting a potentially non-compatible project to use Vite.js may result in unexpected behavior. Always commit
|
||||
your changes before converting a project to use Vite.js, and test the converted project thoroughly before deploying it.
|
||||
`
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { Confirm } = require('enquirer');
|
||||
const prompt = new Confirm({
|
||||
name: 'question',
|
||||
message: `Should we convert the ${validFoundTargetName} target to use the @nx/rspack:${executor} executor?`,
|
||||
initial: true,
|
||||
});
|
||||
const shouldConvert = await prompt.run();
|
||||
if (!shouldConvert) {
|
||||
throw new Error(
|
||||
`The ${target} target ${userProvidedTargetName} cannot be converted to use the @nx/rspack:${executor} executor.
|
||||
Please try again, either by providing a different ${target} target or by not providing a target at all (Nx will
|
||||
convert the first one it finds, most probably this one: ${validFoundTargetName})
|
||||
|
||||
Please note that converting a potentially non-compatible project to use Vite.js may result in unexpected behavior. Always commit
|
||||
your changes before converting a project to use Vite.js, and test the converted project thoroughly before deploying it.
|
||||
`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleUnknownExecutors(projectName: string) {
|
||||
logger.warn(
|
||||
`
|
||||
We could not find any targets in project ${projectName} that use executors which
|
||||
can be converted to the @nx/rspack executors.
|
||||
|
||||
This either means that your project may not have a target
|
||||
for building, serving, or testing at all, or that your targets are
|
||||
using executors that are not known to Nx.
|
||||
|
||||
If you still want to convert your project to use the @nx/rspack executors,
|
||||
please make sure to commit your changes before running this generator.
|
||||
`
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { Confirm } = require('enquirer');
|
||||
const prompt = new Confirm({
|
||||
name: 'question',
|
||||
message: `Should Nx convert your project to use the @nx/rspack executors?`,
|
||||
initial: true,
|
||||
});
|
||||
const shouldConvert = await prompt.run();
|
||||
if (!shouldConvert) {
|
||||
throw new Error(`
|
||||
Nx could not verify that the executors you are using can be converted to the @nx/rspack executors.
|
||||
Please try again with a different project.
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
export function determineFrameworkAndTarget(
|
||||
tree: Tree,
|
||||
options: ConfigurationSchema,
|
||||
projectRoot: string,
|
||||
targets: {
|
||||
[targetName: string]: TargetConfiguration<any>;
|
||||
}
|
||||
): { target: 'node' | 'web'; framework?: Framework } {
|
||||
ensureTypescript();
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { tsquery } = require('@phenomnomnominal/tsquery');
|
||||
|
||||
// First try to infer if the target is node
|
||||
if (options.target !== 'node') {
|
||||
// Try to infer from jest config if the env is node
|
||||
let jestConfigPath: string;
|
||||
if (
|
||||
targets?.test?.executor !== '@nx/jest:jest' &&
|
||||
targets?.test?.options?.jestConfig
|
||||
) {
|
||||
jestConfigPath = targets?.test?.options?.jestConfig;
|
||||
} else {
|
||||
jestConfigPath = joinPathFragments(projectRoot, 'jest.config.ts');
|
||||
}
|
||||
|
||||
if (!tree.exists(jestConfigPath)) {
|
||||
return { target: options.target, framework: options.framework };
|
||||
}
|
||||
const appFileContent = tree.read(jestConfigPath, 'utf-8');
|
||||
const file = tsquery.ast(appFileContent);
|
||||
// find testEnvironment: 'node' in jest config
|
||||
const testEnvironment = tsquery(
|
||||
file,
|
||||
`PropertyAssignment:has(Identifier[name="testEnvironment"]) > StringLiteral[value="node"]`
|
||||
);
|
||||
if (testEnvironment.length > 0) {
|
||||
return { target: 'node', framework: options.framework };
|
||||
}
|
||||
|
||||
if (tree.exists(joinPathFragments(projectRoot, 'src/main.ts'))) {
|
||||
const appFileContent = tree.read(
|
||||
joinPathFragments(projectRoot, 'src/main.ts'),
|
||||
'utf-8'
|
||||
);
|
||||
const file = tsquery.ast(appFileContent);
|
||||
const hasNestJsDependency = tsquery(
|
||||
file,
|
||||
`ImportDeclaration:has(StringLiteral[value="@nestjs/common"])`
|
||||
);
|
||||
if (hasNestJsDependency?.length > 0) {
|
||||
return { target: 'node', framework: 'nest' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options.framework === 'nest') {
|
||||
return { target: 'node', framework: 'nest' };
|
||||
}
|
||||
|
||||
if (options.framework !== 'react' && options.target === 'web') {
|
||||
// Look if React is used in the project
|
||||
let tsConfigPath = joinPathFragments(projectRoot, 'tsconfig.json');
|
||||
if (!tree.exists(tsConfigPath)) {
|
||||
tsConfigPath = determineTsConfig(tree, options);
|
||||
}
|
||||
const tsConfig = JSON.parse(tree.read(tsConfigPath).toString());
|
||||
if (tsConfig?.compilerOptions?.jsx?.includes('react')) {
|
||||
return { target: 'web', framework: 'react' };
|
||||
} else {
|
||||
return { target: options.target, framework: options.framework };
|
||||
}
|
||||
}
|
||||
|
||||
return { target: options.target, framework: options.framework };
|
||||
}
|
||||
|
||||
export function determineMain(tree: Tree, options: ConfigurationSchema) {
|
||||
if (options.main) return options.main;
|
||||
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
|
||||
const mainTsx = joinPathFragments(project.root, 'src/main.tsx');
|
||||
if (tree.exists(mainTsx)) return mainTsx;
|
||||
|
||||
return joinPathFragments(project.root, 'src/main.ts');
|
||||
}
|
||||
|
||||
export function determineTsConfig(tree: Tree, options: ConfigurationSchema) {
|
||||
if (options.tsConfig) return options.tsConfig;
|
||||
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
|
||||
const appJson = joinPathFragments(project.root, 'tsconfig.app.json');
|
||||
if (tree.exists(appJson)) return appJson;
|
||||
|
||||
const libJson = joinPathFragments(project.root, 'tsconfig.lib.json');
|
||||
if (tree.exists(libJson)) return libJson;
|
||||
|
||||
return joinPathFragments(project.root, 'tsconfig.json');
|
||||
}
|
||||
19
packages/rspack/src/utils/get-copy-patterns.ts
Normal file
19
packages/rspack/src/utils/get-copy-patterns.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export function getCopyPatterns(assets: any[]) {
|
||||
return assets.map((asset) => {
|
||||
return {
|
||||
context: asset.input,
|
||||
// Now we remove starting slash to make Webpack place it from the output root.
|
||||
to: asset.output,
|
||||
from: asset.glob,
|
||||
globOptions: {
|
||||
ignore: [
|
||||
'.gitkeep',
|
||||
'**/.DS_Store',
|
||||
'**/Thumbs.db',
|
||||
...(asset.ignore ?? []),
|
||||
],
|
||||
dot: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user