diff --git a/docs/generated/manifests/menus.json b/docs/generated/manifests/menus.json index 7616245b7fcc9..c749b699c4f27 100644 --- a/docs/generated/manifests/menus.json +++ b/docs/generated/manifests/menus.json @@ -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", diff --git a/docs/generated/manifests/nx-api.json b/docs/generated/manifests/nx-api.json index 7b62c652587d2..060a871ac0764 100644 --- a/docs/generated/manifests/nx-api.json +++ b/docs/generated/manifests/nx-api.json @@ -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", diff --git a/docs/generated/packages-metadata.json b/docs/generated/packages-metadata.json index dbfe9fb058a61..b1c35172a318b 100644 --- a/docs/generated/packages-metadata.json +++ b/docs/generated/packages-metadata.json @@ -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": [ diff --git a/docs/generated/packages/rspack/documents/overview.md b/docs/generated/packages/rspack/documents/overview.md new file mode 100644 index 0000000000000..95e3375773c14 --- /dev/null +++ b/docs/generated/packages/rspack/documents/overview.md @@ -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. diff --git a/docs/generated/packages/rspack/executors/dev-server.json b/docs/generated/packages/rspack/executors/dev-server.json new file mode 100644 index 0000000000000..fd9399c7db558 --- /dev/null +++ b/docs/generated/packages/rspack/executors/dev-server.json @@ -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" +} diff --git a/docs/generated/packages/rspack/executors/module-federation-dev-server.json b/docs/generated/packages/rspack/executors/module-federation-dev-server.json new file mode 100644 index 0000000000000..571c583e136a2 --- /dev/null +++ b/docs/generated/packages/rspack/executors/module-federation-dev-server.json @@ -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" +} diff --git a/docs/generated/packages/rspack/executors/module-federation-ssr-dev-server.json b/docs/generated/packages/rspack/executors/module-federation-ssr-dev-server.json new file mode 100644 index 0000000000000..f91a75342f7f6 --- /dev/null +++ b/docs/generated/packages/rspack/executors/module-federation-ssr-dev-server.json @@ -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" +} diff --git a/docs/generated/packages/rspack/executors/module-federation-static-server.json b/docs/generated/packages/rspack/executors/module-federation-static-server.json new file mode 100644 index 0000000000000..83d2a7072f677 --- /dev/null +++ b/docs/generated/packages/rspack/executors/module-federation-static-server.json @@ -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" +} diff --git a/docs/generated/packages/rspack/executors/rspack.json b/docs/generated/packages/rspack/executors/rspack.json new file mode 100644 index 0000000000000..78308a42bc2f1 --- /dev/null +++ b/docs/generated/packages/rspack/executors/rspack.json @@ -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" +} diff --git a/docs/generated/packages/rspack/executors/ssr-dev-server.json b/docs/generated/packages/rspack/executors/ssr-dev-server.json new file mode 100644 index 0000000000000..cd0f60eca23f7 --- /dev/null +++ b/docs/generated/packages/rspack/executors/ssr-dev-server.json @@ -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" +} diff --git a/docs/generated/packages/rspack/generators/application.json b/docs/generated/packages/rspack/generators/application.json new file mode 100644 index 0000000000000..5671e424cd53d --- /dev/null +++ b/docs/generated/packages/rspack/generators/application.json @@ -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" +} diff --git a/docs/generated/packages/rspack/generators/configuration.json b/docs/generated/packages/rspack/generators/configuration.json new file mode 100644 index 0000000000000..8b2b66ec0ef6c --- /dev/null +++ b/docs/generated/packages/rspack/generators/configuration.json @@ -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 '/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 '/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" +} diff --git a/docs/generated/packages/rspack/generators/init.json b/docs/generated/packages/rspack/generators/init.json new file mode 100644 index 0000000000000..332b01a133fe8 --- /dev/null +++ b/docs/generated/packages/rspack/generators/init.json @@ -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" +} diff --git a/docs/generated/packages/rspack/generators/preset.json b/docs/generated/packages/rspack/generators/preset.json new file mode 100644 index 0000000000000..33703949ce2a4 --- /dev/null +++ b/docs/generated/packages/rspack/generators/preset.json @@ -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" +} diff --git a/docs/map.json b/docs/map.json index 04c7f6989a8c1..42d0533b08ce6 100644 --- a/docs/map.json +++ b/docs/map.json @@ -2558,6 +2558,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", diff --git a/docs/shared/packages/rspack/rspack-plugin.md b/docs/shared/packages/rspack/rspack-plugin.md new file mode 100644 index 0000000000000..95e3375773c14 --- /dev/null +++ b/docs/shared/packages/rspack/rspack-plugin.md @@ -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. diff --git a/docs/shared/reference/sitemap.md b/docs/shared/reference/sitemap.md index 59241b6e85b5c..bc9c361125e6e 100644 --- a/docs/shared/reference/sitemap.md +++ b/docs/shared/reference/sitemap.md @@ -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) diff --git a/e2e/react/src/react.test.ts b/e2e/react/src/react.test.ts index 72c282fe97024..e627fda2f5eff 100644 --- a/e2e/react/src/react.test.ts +++ b/e2e/react/src/react.test.ts @@ -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'); diff --git a/e2e/rspack/jest.config.ts b/e2e/rspack/jest.config.ts new file mode 100644 index 0000000000000..75b517b2d57f7 --- /dev/null +++ b/e2e/rspack/jest.config.ts @@ -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: '/tsconfig.spec.json', + }, + ], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/e2e/e2e-rspack', +}; diff --git a/e2e/rspack/project.json b/e2e/rspack/project.json new file mode 100644 index 0000000000000..51c879eec91cd --- /dev/null +++ b/e2e/rspack/project.json @@ -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"] +} diff --git a/e2e/rspack/tests/rspack.spec.ts b/e2e/rspack/tests/rspack.spec.ts new file mode 100644 index 0000000000000..abeac82f237a9 --- /dev/null +++ b/e2e/rspack/tests/rspack.spec.ts @@ -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); +}); diff --git a/e2e/rspack/tsconfig.json b/e2e/rspack/tsconfig.json new file mode 100644 index 0000000000000..f6e253991353b --- /dev/null +++ b/e2e/rspack/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["node", "jest"] + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/e2e/rspack/tsconfig.spec.json b/e2e/rspack/tsconfig.spec.json new file mode 100644 index 0000000000000..546f12877f7f0 --- /dev/null +++ b/e2e/rspack/tsconfig.spec.json @@ -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"] +} diff --git a/e2e/utils/create-project-utils.ts b/e2e/utils/create-project-utils.ts index ff89a41e51b19..73c41a95d1a16 100644 --- a/e2e/utils/create-project-utils.ts +++ b/e2e/utils/create-project-utils.ts @@ -56,6 +56,7 @@ const nxPackages = [ `@nx/rollup`, `@nx/react`, `@nx/remix`, + `@nx/rspack`, `@nx/storybook`, `@nx/vue`, `@nx/vite`, diff --git a/nx-dev/nx-dev/pages/nx-api/rspack/documents/index.tsx b/nx-dev/nx-dev/pages/nx-api/rspack/documents/index.tsx deleted file mode 100644 index 3775bf3c1fa3e..0000000000000 --- a/nx-dev/nx-dev/pages/nx-api/rspack/documents/index.tsx +++ /dev/null @@ -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( - 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 ( -
-
- -
-
- - - - -
-
- ); -} - -export async function getStaticProps(): Promise<{ - props: { - menu: MenuItem[]; - pkg: ProcessedPackageMetadata; - }; -}> { - return { - props: { - menu: menusApi.getMenu('nx-api', 'nx-api'), - pkg, - }, - }; -} diff --git a/nx-dev/nx-dev/pages/nx-api/rspack/documents/overview.tsx b/nx-dev/nx-dev/pages/nx-api/rspack/documents/overview.tsx deleted file mode 100644 index 5a9091d157600..0000000000000 --- a/nx-dev/nx-dev/pages/nx-api/rspack/documents/overview.tsx +++ /dev/null @@ -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( - getPackagesSections(menu), - 'id' - ), - }, - relatedDocuments, - }; - - return ( -
-
- -
-
- - - - -
-
- ); -} - -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', ''), - }, - }; -} diff --git a/nx-dev/nx-dev/pages/nx-api/rspack/documents/rspack-config-setup.tsx b/nx-dev/nx-dev/pages/nx-api/rspack/documents/rspack-config-setup.tsx deleted file mode 100644 index 251cbbc8d9861..0000000000000 --- a/nx-dev/nx-dev/pages/nx-api/rspack/documents/rspack-config-setup.tsx +++ /dev/null @@ -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( - getPackagesSections(menu), - 'id' - ), - }, - relatedDocuments, - }; - - return ( -
-
- -
-
- - - - -
-
- ); -} - -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', ''), - }, - }; -} diff --git a/nx-dev/nx-dev/pages/nx-api/rspack/documents/rspack-plugins.tsx b/nx-dev/nx-dev/pages/nx-api/rspack/documents/rspack-plugins.tsx deleted file mode 100644 index dbf1f2e61fd52..0000000000000 --- a/nx-dev/nx-dev/pages/nx-api/rspack/documents/rspack-plugins.tsx +++ /dev/null @@ -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( - getPackagesSections(menu), - 'id' - ), - }, - relatedDocuments, - }; - - return ( -
-
- -
-
- - - - -
-
- ); -} - -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', ''), - }, - }; -} diff --git a/nx-dev/nx-dev/pages/nx-api/rspack/executors/dev-server.tsx b/nx-dev/nx-dev/pages/nx-api/rspack/executors/dev-server.tsx deleted file mode 100644 index fa20030839bc1..0000000000000 --- a/nx-dev/nx-dev/pages/nx-api/rspack/executors/dev-server.tsx +++ /dev/null @@ -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( - 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 ( -
-
- -
-
- - - - -
-
- ); -} - -export async function getStaticProps() { - return { - props: { - pkg, - schema, - menu: menusApi.getMenu('nx-api', 'nx-api'), - }, - }; -} diff --git a/nx-dev/nx-dev/pages/nx-api/rspack/executors/index.tsx b/nx-dev/nx-dev/pages/nx-api/rspack/executors/index.tsx deleted file mode 100644 index 672c136c2270b..0000000000000 --- a/nx-dev/nx-dev/pages/nx-api/rspack/executors/index.tsx +++ /dev/null @@ -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( - 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 ( -
-
- -
-
- - - - -
-
- ); -} - -export async function getStaticProps(): Promise<{ - props: { - menu: MenuItem[]; - pkg: ProcessedPackageMetadata; - }; -}> { - return { - props: { - menu: menusApi.getMenu('nx-api', 'nx-api'), - pkg, - }, - }; -} diff --git a/nx-dev/nx-dev/pages/nx-api/rspack/executors/rspack.tsx b/nx-dev/nx-dev/pages/nx-api/rspack/executors/rspack.tsx deleted file mode 100644 index e5e43ce6a71e2..0000000000000 --- a/nx-dev/nx-dev/pages/nx-api/rspack/executors/rspack.tsx +++ /dev/null @@ -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( - 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 ( -
-
- -
-
- - - - -
-
- ); -} - -export async function getStaticProps() { - return { - props: { - pkg, - schema, - menu: menusApi.getMenu('nx-api', 'nx-api'), - }, - }; -} diff --git a/nx-dev/nx-dev/pages/nx-api/rspack/generators/application.tsx b/nx-dev/nx-dev/pages/nx-api/rspack/generators/application.tsx deleted file mode 100644 index 325c9ea2d3af1..0000000000000 --- a/nx-dev/nx-dev/pages/nx-api/rspack/generators/application.tsx +++ /dev/null @@ -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( - 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 ( -
-
- -
-
- - - - -
-
- ); -} - -export async function getStaticProps() { - return { - props: { - pkg, - schema, - menu: menusApi.getMenu('nx-api', 'nx-api'), - }, - }; -} diff --git a/nx-dev/nx-dev/pages/nx-api/rspack/generators/configuration.tsx b/nx-dev/nx-dev/pages/nx-api/rspack/generators/configuration.tsx deleted file mode 100644 index 6515cd1252854..0000000000000 --- a/nx-dev/nx-dev/pages/nx-api/rspack/generators/configuration.tsx +++ /dev/null @@ -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( - 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 ( -
-
- -
-
- - - - -
-
- ); -} - -export async function getStaticProps() { - return { - props: { - pkg, - schema, - menu: menusApi.getMenu('nx-api', 'nx-api'), - }, - }; -} diff --git a/nx-dev/nx-dev/pages/nx-api/rspack/generators/index.tsx b/nx-dev/nx-dev/pages/nx-api/rspack/generators/index.tsx deleted file mode 100644 index 8c656663f617f..0000000000000 --- a/nx-dev/nx-dev/pages/nx-api/rspack/generators/index.tsx +++ /dev/null @@ -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( - 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 ( -
-
- -
-
- - - - -
-
- ); -} - -export async function getStaticProps(): Promise<{ - props: { - menu: MenuItem[]; - pkg: ProcessedPackageMetadata; - }; -}> { - return { - props: { - menu: menusApi.getMenu('nx-api', 'nx-api'), - pkg, - }, - }; -} diff --git a/nx-dev/nx-dev/pages/nx-api/rspack/generators/init.tsx b/nx-dev/nx-dev/pages/nx-api/rspack/generators/init.tsx deleted file mode 100644 index 3b29972ac104e..0000000000000 --- a/nx-dev/nx-dev/pages/nx-api/rspack/generators/init.tsx +++ /dev/null @@ -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( - 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 ( -
-
- -
-
- - - - -
-
- ); -} - -export async function getStaticProps() { - return { - props: { - pkg, - schema, - menu: menusApi.getMenu('nx-api', 'nx-api'), - }, - }; -} diff --git a/nx-dev/nx-dev/pages/nx-api/rspack/index.tsx b/nx-dev/nx-dev/pages/nx-api/rspack/index.tsx deleted file mode 100644 index 1610634f02ebb..0000000000000 --- a/nx-dev/nx-dev/pages/nx-api/rspack/index.tsx +++ /dev/null @@ -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( - 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 ( -
-
- -
-
- - - - -
-
- ); -} - -export async function getStaticProps() { - return { - props: { - menu: menusApi.getMenu('nx-api', 'nx-api'), - overview: content, - pkg, - }, - }; -} diff --git a/package.json b/package.json index d81c5de7970d2..2ab47bca463fc 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages-legacy/rspack/README.md b/packages-legacy/rspack/README.md new file mode 100644 index 0000000000000..12f09ec1da465 --- /dev/null +++ b/packages-legacy/rspack/README.md @@ -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. + +

Nx - Smart, Fast and Extensible Build System

+ +# Nx: Smart, Fast and Extensible Build System + +Nx is a next generation build system with first class monorepo support and powerful integrations. diff --git a/packages-legacy/rspack/executors.json b/packages-legacy/rspack/executors.json new file mode 100644 index 0000000000000..292b254c513d0 --- /dev/null +++ b/packages-legacy/rspack/executors.json @@ -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" + } + } +} diff --git a/packages-legacy/rspack/generators.json b/packages-legacy/rspack/generators.json new file mode 100644 index 0000000000000..dc70c4a6afd02 --- /dev/null +++ b/packages-legacy/rspack/generators.json @@ -0,0 +1,4 @@ +{ + "extends": ["@nx/rspack"], + "schematics": {} +} diff --git a/packages-legacy/rspack/index.ts b/packages-legacy/rspack/index.ts new file mode 100644 index 0000000000000..64c76247e0e10 --- /dev/null +++ b/packages-legacy/rspack/index.ts @@ -0,0 +1 @@ +export * from '@nx/rspack'; diff --git a/packages-legacy/rspack/package.json b/packages-legacy/rspack/package.json new file mode 100644 index 0000000000000..f5e2066f960db --- /dev/null +++ b/packages-legacy/rspack/package.json @@ -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" + } +} diff --git a/packages-legacy/rspack/project.json b/packages-legacy/rspack/project.json new file mode 100644 index 0000000000000..9c4f8ea14b46f --- /dev/null +++ b/packages-legacy/rspack/project.json @@ -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": [] +} diff --git a/packages-legacy/rspack/tsconfig.json b/packages-legacy/rspack/tsconfig.json new file mode 100644 index 0000000000000..9cf8a29c54160 --- /dev/null +++ b/packages-legacy/rspack/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "declaration": true + }, + "include": ["**/*.ts"], + "files": ["index.ts"] +} diff --git a/packages/nx/package.json b/packages/nx/package.json index 4f532ccfe6685..4b0d2199718d0 100644 --- a/packages/nx/package.json +++ b/packages/nx/package.json @@ -142,6 +142,8 @@ "@nrwl/rollup", "@nx/remix", "@nrwl/remix", + "@nx/rspack", + "@nrwl/rspack", "@nx/storybook", "@nrwl/storybook", "@nrwl/tao", diff --git a/packages/nx/src/utils/plugins/core-plugins.ts b/packages/nx/src/utils/plugins/core-plugins.ts index 64da8777b8626..aaf628a9aec10 100644 --- a/packages/nx/src/utils/plugins/core-plugins.ts +++ b/packages/nx/src/utils/plugins/core-plugins.ts @@ -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', diff --git a/packages/rspack/.eslintrc.json b/packages/rspack/.eslintrc.json new file mode 100644 index 0000000000000..4cf7f347f6a85 --- /dev/null +++ b/packages/rspack/.eslintrc.json @@ -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" + } + } + ] +} diff --git a/packages/rspack/README.md b/packages/rspack/README.md new file mode 100644 index 0000000000000..123c6c283384c --- /dev/null +++ b/packages/rspack/README.md @@ -0,0 +1,67 @@ +

+ + + Nx - Smart Monorepos · Fast CI + +

+ +{{links}} + +
+ +# 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}} + +

Nx - Smart, Fast and Extensible Build System

+ +
+ +# 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. diff --git a/packages/rspack/executors.json b/packages/rspack/executors.json new file mode 100644 index 0000000000000..df7d79d1b8c13 --- /dev/null +++ b/packages/rspack/executors.json @@ -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." + } + } +} diff --git a/packages/rspack/generators.json b/packages/rspack/generators.json new file mode 100644 index 0000000000000..1d2e2968c4a84 --- /dev/null +++ b/packages/rspack/generators.json @@ -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." + } + } +} diff --git a/packages/rspack/jest.config.ts b/packages/rspack/jest.config.ts new file mode 100644 index 0000000000000..1223e1f4addd5 --- /dev/null +++ b/packages/rspack/jest.config.ts @@ -0,0 +1,16 @@ +/* eslint-disable */ +export default { + displayName: 'rspack', + preset: '../../jest.preset.js', + globals: {}, + transform: { + '^.+\\.[tj]s$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.spec.json', + }, + ], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/packages/rspack', +}; diff --git a/packages/rspack/migrations.json b/packages/rspack/migrations.json new file mode 100644 index 0000000000000..88f20a517d0b2 --- /dev/null +++ b/packages/rspack/migrations.json @@ -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" +} diff --git a/packages/rspack/module-federation.ts b/packages/rspack/module-federation.ts new file mode 100644 index 0000000000000..a4cf0dd1a4364 --- /dev/null +++ b/packages/rspack/module-federation.ts @@ -0,0 +1 @@ +export * from './src/utils/module-federation/public-api'; diff --git a/packages/rspack/package.json b/packages/rspack/package.json new file mode 100644 index 0000000000000..48d97a6d6538a --- /dev/null +++ b/packages/rspack/package.json @@ -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" + } +} diff --git a/packages/rspack/plugin.ts b/packages/rspack/plugin.ts new file mode 100644 index 0000000000000..8d3c1a001313d --- /dev/null +++ b/packages/rspack/plugin.ts @@ -0,0 +1,2 @@ +export { createDependencies, createNodesV2 } from './src/plugins/plugin'; +export type { RspackPluginOptions } from './src/plugins/plugin'; diff --git a/packages/rspack/project.json b/packages/rspack/project.json new file mode 100644 index 0000000000000..54e1479666f75 --- /dev/null +++ b/packages/rspack/project.json @@ -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": [] +} diff --git a/packages/rspack/src/executors/dev-server/dev-server.impl.ts b/packages/rspack/src/executors/dev-server/dev-server.impl.ts new file mode 100644 index 0000000000000..2f1f1925b0bca --- /dev/null +++ b/packages/rspack/src/executors/dev-server/dev-server.impl.ts @@ -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(); + }); +} diff --git a/packages/rspack/src/executors/dev-server/lib/get-dev-server-config.ts b/packages/rspack/src/executors/dev-server/lib/get-dev-server-config.ts new file mode 100644 index 0000000000000..c7ff6a0d8f242 --- /dev/null +++ b/packages/rspack/src/executors/dev-server/lib/get-dev-server-config.ts @@ -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'), + }; +} diff --git a/packages/rspack/src/executors/dev-server/lib/serve-path.ts b/packages/rspack/src/executors/dev-server/lib/serve-path.ts new file mode 100644 index 0000000000000..15b1c0ea3f617 --- /dev/null +++ b/packages/rspack/src/executors/dev-server/lib/serve-path.ts @@ -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 || ''}`; +} diff --git a/packages/rspack/src/executors/dev-server/schema.d.ts b/packages/rspack/src/executors/dev-server/schema.d.ts new file mode 100644 index 0000000000000..730b1b66b5aca --- /dev/null +++ b/packages/rspack/src/executors/dev-server/schema.d.ts @@ -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; +} diff --git a/packages/rspack/src/executors/dev-server/schema.json b/packages/rspack/src/executors/dev-server/schema.json new file mode 100644 index 0000000000000..f636780ae0bf4 --- /dev/null +++ b/packages/rspack/src/executors/dev-server/schema.json @@ -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"] +} diff --git a/packages/rspack/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts b/packages/rspack/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts new file mode 100644 index 0000000000000..2c77ff2bb940f --- /dev/null +++ b/packages/rspack/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts @@ -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(); + } + } + ) + ); +} diff --git a/packages/rspack/src/executors/module-federation-dev-server/schema.d.ts b/packages/rspack/src/executors/module-federation-dev-server/schema.d.ts new file mode 100644 index 0000000000000..61c4c0657eb06 --- /dev/null +++ b/packages/rspack/src/executors/module-federation-dev-server/schema.d.ts @@ -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; +}; diff --git a/packages/rspack/src/executors/module-federation-dev-server/schema.json b/packages/rspack/src/executors/module-federation-dev-server/schema.json new file mode 100644 index 0000000000000..75b019a5e6654 --- /dev/null +++ b/packages/rspack/src/executors/module-federation-dev-server/schema.json @@ -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." + } + } +} diff --git a/packages/rspack/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts b/packages/rspack/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts new file mode 100644 index 0000000000000..5301a26a4f8d7 --- /dev/null +++ b/packages/rspack/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts @@ -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 = {}; + + for (const remoteApp of staticRemotesConfig.remotes) { + mapLocationOfRemotes[remoteApp] = `http${options.ssl ? 's' : ''}://${ + options.host + }:${options.staticRemotesPort}/${ + staticRemotesConfig.config[remoteApp].urlSegment + }`; + } + + await new Promise((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(); + } + } + ) + ); +} diff --git a/packages/rspack/src/executors/module-federation-ssr-dev-server/schema.json b/packages/rspack/src/executors/module-federation-ssr-dev-server/schema.json new file mode 100644 index 0000000000000..8f86f09ec874e --- /dev/null +++ b/packages/rspack/src/executors/module-federation-ssr-dev-server/schema.json @@ -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"] +} diff --git a/packages/rspack/src/executors/module-federation-static-server/module-federation-static-server.impl.ts b/packages/rspack/src/executors/module-federation-static-server/module-federation-static-server.impl.ts new file mode 100644 index 0000000000000..41b8c549492c6 --- /dev/null +++ b/packages/rspack/src/executors/module-federation-static-server/module-federation-static-server.impl.ts @@ -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((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, + 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(); + } + } + ) + ); +} diff --git a/packages/rspack/src/executors/module-federation-static-server/schema.d.ts b/packages/rspack/src/executors/module-federation-static-server/schema.d.ts new file mode 100644 index 0000000000000..8386a0f2006ed --- /dev/null +++ b/packages/rspack/src/executors/module-federation-static-server/schema.d.ts @@ -0,0 +1,3 @@ +export interface ModuleFederationStaticServerSchema { + serveTarget: string; +} diff --git a/packages/rspack/src/executors/module-federation-static-server/schema.json b/packages/rspack/src/executors/module-federation-static-server/schema.json new file mode 100644 index 0000000000000..329ac0a6226d5 --- /dev/null +++ b/packages/rspack/src/executors/module-federation-static-server/schema.json @@ -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"] +} diff --git a/packages/rspack/src/executors/rspack/rspack.impl.ts b/packages/rspack/src/executors/rspack/rspack.impl.ts new file mode 100644 index 0000000000000..afcd885ddc7fd --- /dev/null +++ b/packages/rspack/src/executors/rspack/rspack.impl.ts @@ -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; +} diff --git a/packages/rspack/src/executors/rspack/schema.d.ts b/packages/rspack/src/executors/rspack/schema.d.ts new file mode 100644 index 0000000000000..fbc07402b5096 --- /dev/null +++ b/packages/rspack/src/executors/rspack/schema.d.ts @@ -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; +} diff --git a/packages/rspack/src/executors/rspack/schema.json b/packages/rspack/src/executors/rspack/schema.json new file mode 100644 index 0000000000000..2faa51e02df7c --- /dev/null +++ b/packages/rspack/src/executors/rspack/schema.json @@ -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" + } + ] + } + } +} diff --git a/packages/rspack/src/executors/ssr-dev-server/lib/wait-until-server-is-listening.ts b/packages/rspack/src/executors/ssr-dev-server/lib/wait-until-server-is-listening.ts new file mode 100644 index 0000000000000..35de9978cddd0 --- /dev/null +++ b/packages/rspack/src/executors/ssr-dev-server/lib/wait-until-server-is-listening.ts @@ -0,0 +1,38 @@ +import * as net from 'net'; + +export function waitUntilServerIsListening(port: number): Promise { + 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((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(); + }); +} diff --git a/packages/rspack/src/executors/ssr-dev-server/schema.d.ts b/packages/rspack/src/executors/ssr-dev-server/schema.d.ts new file mode 100644 index 0000000000000..ee03668413a03 --- /dev/null +++ b/packages/rspack/src/executors/ssr-dev-server/schema.d.ts @@ -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; +} diff --git a/packages/rspack/src/executors/ssr-dev-server/schema.json b/packages/rspack/src/executors/ssr-dev-server/schema.json new file mode 100644 index 0000000000000..fc22ad14d251a --- /dev/null +++ b/packages/rspack/src/executors/ssr-dev-server/schema.json @@ -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"] +} diff --git a/packages/rspack/src/executors/ssr-dev-server/ssr-dev-server.impl.ts b/packages/rspack/src/executors/ssr-dev-server/ssr-dev-server.impl.ts new file mode 100644 index 0000000000000..92f614abb07a4 --- /dev/null +++ b/packages/rspack/src/executors/ssr-dev-server/ssr-dev-server.impl.ts @@ -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( + browserTarget, + context + ); + const serverOptions = readTargetOptions( + 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; diff --git a/packages/rspack/src/generators/application/application.ts b/packages/rspack/src/generators/application/application.ts new file mode 100644 index 0000000000000..10e65626395a3 --- /dev/null +++ b/packages/rspack/src/generators/application/application.ts @@ -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); +} diff --git a/packages/rspack/src/generators/application/lib/create-ts-config.ts b/packages/rspack/src/generators/application/lib/create-ts-config.ts new file mode 100644 index 0000000000000..4bbfa2b836565 --- /dev/null +++ b/packages/rspack/src/generators/application/lib/create-ts-config.ts @@ -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; +} diff --git a/packages/rspack/src/generators/application/lib/normalize-options.spec.ts b/packages/rspack/src/generators/application/lib/normalize-options.spec.ts new file mode 100644 index 0000000000000..3e560296fc41e --- /dev/null +++ b/packages/rspack/src/generators/application/lib/normalize-options.spec.ts @@ -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(); + }); +}); diff --git a/packages/rspack/src/generators/application/lib/normalize-options.ts b/packages/rspack/src/generators/application/lib/normalize-options.ts new file mode 100644 index 0000000000000..72c03ffec87c2 --- /dev/null +++ b/packages/rspack/src/generators/application/lib/normalize-options.ts @@ -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; +} diff --git a/packages/rspack/src/generators/application/schema.d.ts b/packages/rspack/src/generators/application/schema.d.ts new file mode 100644 index 0000000000000..c14d74bf346f7 --- /dev/null +++ b/packages/rspack/src/generators/application/schema.d.ts @@ -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; +} diff --git a/packages/rspack/src/generators/application/schema.json b/packages/rspack/src/generators/application/schema.json new file mode 100644 index 0000000000000..aac817b81adef --- /dev/null +++ b/packages/rspack/src/generators/application/schema.json @@ -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"] +} diff --git a/packages/rspack/src/generators/configuration/configuration.ts b/packages/rspack/src/generators/configuration/configuration.ts new file mode 100644 index 0000000000000..4a977e450f320 --- /dev/null +++ b/packages/rspack/src/generators/configuration/configuration.ts @@ -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; diff --git a/packages/rspack/src/generators/configuration/schema.d.ts b/packages/rspack/src/generators/configuration/schema.d.ts new file mode 100644 index 0000000000000..0d4ee469bfea9 --- /dev/null +++ b/packages/rspack/src/generators/configuration/schema.d.ts @@ -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; +} diff --git a/packages/rspack/src/generators/configuration/schema.json b/packages/rspack/src/generators/configuration/schema.json new file mode 100644 index 0000000000000..79ec519f70d8d --- /dev/null +++ b/packages/rspack/src/generators/configuration/schema.json @@ -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 '/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 '/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"] +} diff --git a/packages/rspack/src/generators/init/init.ts b/packages/rspack/src/generators/init/init.ts new file mode 100644 index 0000000000000..f32af80d9c696 --- /dev/null +++ b/packages/rspack/src/generators/init/init.ts @@ -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); diff --git a/packages/rspack/src/generators/init/schema.d.ts b/packages/rspack/src/generators/init/schema.d.ts new file mode 100644 index 0000000000000..cd48a7882b497 --- /dev/null +++ b/packages/rspack/src/generators/init/schema.d.ts @@ -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; +} diff --git a/packages/rspack/src/generators/init/schema.json b/packages/rspack/src/generators/init/schema.json new file mode 100644 index 0000000000000..5242ea96dfc67 --- /dev/null +++ b/packages/rspack/src/generators/init/schema.json @@ -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": [] +} diff --git a/packages/rspack/src/generators/preset/preset.ts b/packages/rspack/src/generators/preset/preset.ts new file mode 100644 index 0000000000000..69bec32baa5c0 --- /dev/null +++ b/packages/rspack/src/generators/preset/preset.ts @@ -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; +} diff --git a/packages/rspack/src/generators/preset/schema.d.ts b/packages/rspack/src/generators/preset/schema.d.ts new file mode 100644 index 0000000000000..f06b33bced746 --- /dev/null +++ b/packages/rspack/src/generators/preset/schema.d.ts @@ -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; +} diff --git a/packages/rspack/src/generators/preset/schema.json b/packages/rspack/src/generators/preset/schema.json new file mode 100644 index 0000000000000..f9216a4fccbe7 --- /dev/null +++ b/packages/rspack/src/generators/preset/schema.json @@ -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"] +} diff --git a/packages/rspack/src/index.ts b/packages/rspack/src/index.ts new file mode 100644 index 0000000000000..10a9306018e7d --- /dev/null +++ b/packages/rspack/src/index.ts @@ -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'; diff --git a/packages/rspack/src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages.spec.ts b/packages/rspack/src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages.spec.ts new file mode 100644 index 0000000000000..8fc2324fb4e19 --- /dev/null +++ b/packages/rspack/src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages.spec.ts @@ -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(); + }); +}); diff --git a/packages/rspack/src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages.ts b/packages/rspack/src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages.ts new file mode 100644 index 0000000000000..bf44165b3c4d4 --- /dev/null +++ b/packages/rspack/src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages.ts @@ -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 { + await replaceNrwlPackageWithNxPackage(tree, '@nrwl/rspack', '@nx/rspack'); + + await formatFiles(tree); +} diff --git a/packages/rspack/src/plugins/generate-package-json-plugin.ts b/packages/rspack/src/plugins/generate-package-json-plugin.ts new file mode 100644 index 0000000000000..51454264db292 --- /dev/null +++ b/packages/rspack/src/plugins/generate-package-json-plugin.ts @@ -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) + ) + ); + } + ); + }); + } +} diff --git a/packages/rspack/src/plugins/plugin.ts b/packages/rspack/src/plugins/plugin.ts new file mode 100644 index 0000000000000..1e05fdaafeee2 --- /dev/null +++ b/packages/rspack/src/plugins/plugin.ts @@ -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; + +function readTargetsCache(cachePath: string): Record { + return existsSync(cachePath) ? readJsonFile(cachePath) : {}; +} + +function writeTargetsToCache( + cachePath, + results?: Record +) { + writeJsonFile(cachePath, results); +} + +export const createDependencies: CreateDependencies = () => { + return []; +}; + +const rspackConfigGlob = '**/rspack.config.{js,ts,mjs,mts,cjs,cts}'; + +export const createNodesV2: CreateNodesV2 = [ + 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 +) { + 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 { + 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); + } + } + } +} diff --git a/packages/rspack/src/utils/config.ts b/packages/rspack/src/utils/config.ts new file mode 100644 index 0000000000000..848d2b56a7300 --- /dev/null +++ b/packages/rspack/src/utils/config.ts @@ -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; +} + +export function composePlugins(...plugins: any[]) { + return Object.defineProperty( + async function combined( + config: Configuration, + ctx: SharedConfigContext + ): Promise { + for (const plugin of plugins) { + const fn = await plugin; + config = await fn(config, ctx); + } + return config; + }, + nxRspackComposablePlugin, + { + value: true, + enumerable: false, + writable: false, + } + ); +} diff --git a/packages/rspack/src/utils/create-compiler.ts b/packages/rspack/src/utils/create-compiler.ts new file mode 100644 index 0000000000000..cbe05992d0315 --- /dev/null +++ b/packages/rspack/src/utils/create-compiler.ts @@ -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 { + 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; +} diff --git a/packages/rspack/src/utils/generator-utils.ts b/packages/rspack/src/utils/generator-utils.ts new file mode 100644 index 0000000000000..a89a4d5e9cfd8 --- /dev/null +++ b/packages/rspack/src/utils/generator-utils.ts @@ -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>; +export type UserProvidedTargetName = Partial>; +export type ValidFoundTargetName = Partial>; + +export function findExistingTargetsInProject( + targets: { + [targetName: string]: TargetConfiguration; + }, + userProvidedTargets?: UserProvidedTargetName +): { + validFoundTargetName: ValidFoundTargetName; + projectContainsUnsupportedExecutor: boolean; + userProvidedTargetIsUnsupported: TargetFlags; + alreadyHasNxRspackTargets: TargetFlags; +} { + const output: ReturnType = { + 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( + `` + ) + ) { + tree.write( + `${projectConfig.root}/index.html`, + indexHtmlContent.replace( + '', + ` + ` + ) + ); + + if (tree.exists(`${projectConfig.root}/src/index.html`)) { + tree.delete(`${projectConfig.root}/src/index.html`); + } + } + } else { + tree.write( + `${projectConfig.root}/index.html`, + ` + + + + + + Vite + + +
+ + + ` + ); + } +} + +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; + } +): { 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'); +} diff --git a/packages/rspack/src/utils/get-copy-patterns.ts b/packages/rspack/src/utils/get-copy-patterns.ts new file mode 100644 index 0000000000000..6b6134df99632 --- /dev/null +++ b/packages/rspack/src/utils/get-copy-patterns.ts @@ -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, + }, + }; + }); +} diff --git a/packages/rspack/src/utils/jest-utils.ts b/packages/rspack/src/utils/jest-utils.ts new file mode 100644 index 0000000000000..2000cede4692b --- /dev/null +++ b/packages/rspack/src/utils/jest-utils.ts @@ -0,0 +1,11 @@ +export function updateJestConfigContent(content: string) { + return content + .replace( + 'transform: {', + "transform: {\n '^(?!.*\\\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest'," + ) + .replace( + `'babel-jest'`, + `['babel-jest', { presets: ['@nx/react/babel'] }]` + ); +} diff --git a/packages/rspack/src/utils/mode-utils.ts b/packages/rspack/src/utils/mode-utils.ts new file mode 100644 index 0000000000000..4ec3d704b21b2 --- /dev/null +++ b/packages/rspack/src/utils/mode-utils.ts @@ -0,0 +1,5 @@ +import { Mode } from '@rspack/core'; + +export function isMode(mode: string): mode is Mode { + return mode === 'development' || mode === 'production' || mode === 'none'; +} diff --git a/packages/rspack/src/utils/model.ts b/packages/rspack/src/utils/model.ts new file mode 100644 index 0000000000000..3ca89adaaadfd --- /dev/null +++ b/packages/rspack/src/utils/model.ts @@ -0,0 +1,7 @@ +import { ExecutorContext } from '@nx/devkit'; +import { RspackExecutorSchema } from '../executors/rspack/schema'; + +export interface SharedConfigContext { + options: RspackExecutorSchema; + context: ExecutorContext; +} diff --git a/packages/rspack/src/utils/module-federation/build-static.remotes.ts b/packages/rspack/src/utils/module-federation/build-static.remotes.ts new file mode 100644 index 0000000000000..e4d1aa96e239d --- /dev/null +++ b/packages/rspack/src/utils/module-federation/build-static.remotes.ts @@ -0,0 +1,97 @@ +import { ExecutorContext } from '@nx/devkit'; +import { createWriteStream } from 'fs'; +import { fork } from 'node:child_process'; +import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; +import { logger } from 'nx/src/utils/logger'; +import { join } from 'path'; +import { ModuleFederationDevServerOptions } from '../../executors/module-federation-dev-server/schema'; +import type { StaticRemotesConfig } from './parse-static-remotes-config'; + +export async function buildStaticRemotes( + staticRemotesConfig: StaticRemotesConfig, + nxBin, + context: ExecutorContext, + options: ModuleFederationDevServerOptions +) { + if (!staticRemotesConfig.remotes.length) { + return; + } + logger.info( + `NX Building ${staticRemotesConfig.remotes.length} static remotes...` + ); + const mappedLocationOfRemotes: Record = {}; + + for (const app of staticRemotesConfig.remotes) { + mappedLocationOfRemotes[app] = `http${options.ssl ? 's' : ''}://${ + options.host + }:${options.staticRemotesPort}/${ + staticRemotesConfig.config[app].urlSegment + }`; + } + + await new Promise((res, rej) => { + const staticProcess = fork( + nxBin, + [ + 'run-many', + `--target=build`, + `--projects=${staticRemotesConfig.remotes.join(',')}`, + ...(context.configurationName + ? [`--configuration=${context.configurationName}`] + : []), + ...(options.parallel ? [`--parallel=${options.parallel}`] : []), + ], + { + cwd: context.root, + stdio: ['ignore', 'pipe', 'pipe', 'ipc'], + } + ); + + // File to debug build failures e.g. 2024-01-01T00_00_0_0Z-build.log' + const remoteBuildLogFile = join( + workspaceDataDirectory, + // eslint-disable-next-line + `${new Date().toISOString().replace(/[:\.]/g, '_')}-build.log` + ); + const stdoutStream = createWriteStream(remoteBuildLogFile); + + 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, ''); + stdoutStream.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 build')) { + staticProcess.stdout.removeAllListeners('data'); + logger.info( + `NX Built ${staticRemotesConfig.remotes.length} static remotes` + ); + res(); + } + }); + staticProcess.stderr.on('data', (data) => logger.info(data.toString())); + staticProcess.once('exit', (code) => { + stdoutStream.end(); + staticProcess.stdout.removeAllListeners('data'); + staticProcess.stderr.removeAllListeners('data'); + if (code !== 0) { + rej( + `Remote failed to start. A complete log can be found in: ${remoteBuildLogFile}` + ); + } else { + res(); + } + }); + process.on('SIGTERM', () => staticProcess.kill('SIGTERM')); + process.on('exit', () => staticProcess.kill('SIGTERM')); + }); + + return mappedLocationOfRemotes; +} diff --git a/packages/rspack/src/utils/module-federation/dependencies.spec.ts b/packages/rspack/src/utils/module-federation/dependencies.spec.ts new file mode 100644 index 0000000000000..3a120b7c6dfb6 --- /dev/null +++ b/packages/rspack/src/utils/module-federation/dependencies.spec.ts @@ -0,0 +1,158 @@ +import * as tsUtils from './typescript'; +import { getDependentPackagesForProject } from './dependencies'; + +describe('getDependentPackagesForProject', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should collect npm packages and workspaces libraries without duplicates', () => { + jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({ + '@myorg/lib1': ['libs/lib1/src/index.ts'], + '@myorg/lib2': ['libs/lib2/src/index.ts'], + }); + + const dependencies = getDependentPackagesForProject( + { + dependencies: { + shell: [ + { source: 'shell', target: 'lib1', type: 'static' }, + { source: 'shell', target: 'lib2', type: 'static' }, + { source: 'shell', target: 'npm:lodash', type: 'static' }, + ], + lib1: [{ source: 'lib1', target: 'lib2', type: 'static' }], + lib2: [{ source: 'lib2', target: 'npm:lodash', type: 'static' }], + }, + nodes: { + shell: { + name: 'shell', + data: { root: 'apps/shell', sourceRoot: 'apps/shell/src' }, + type: 'app', + }, + lib1: { + name: 'lib1', + data: { root: 'libs/lib1', sourceRoot: 'libs/lib1/src' }, + type: 'lib', + }, + lib2: { + name: 'lib2', + data: { root: 'libs/lib2', sourceRoot: 'libs/lib2/src' }, + type: 'lib', + }, + } as any, + }, + 'shell' + ); + + expect(dependencies).toEqual({ + workspaceLibraries: [ + { name: 'lib1', root: 'libs/lib1', importKey: '@myorg/lib1' }, + { name: 'lib2', root: 'libs/lib2', importKey: '@myorg/lib2' }, + ], + npmPackages: ['lodash'], + }); + }); + + it('should collect workspaces libraries recursively', () => { + jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({ + '@myorg/lib1': ['libs/lib1/src/index.ts'], + '@myorg/lib2': ['libs/lib2/src/index.ts'], + '@myorg/lib3': ['libs/lib3/src/index.ts'], + }); + + const dependencies = getDependentPackagesForProject( + { + dependencies: { + shell: [{ source: 'shell', target: 'lib1', type: 'static' }], + lib1: [{ source: 'lib1', target: 'lib2', type: 'static' }], + lib2: [{ source: 'lib2', target: 'lib3', type: 'static' }], + }, + nodes: { + shell: { + name: 'shell', + data: { root: 'apps/shell', sourceRoot: 'apps/shell/src' }, + type: 'app', + }, + lib1: { + name: 'lib1', + data: { root: 'libs/lib1', sourceRoot: 'libs/lib1/src' }, + type: 'lib', + }, + lib2: { + name: 'lib2', + data: { root: 'libs/lib2', sourceRoot: 'libs/lib2/src' }, + type: 'lib', + }, + lib3: { + name: 'lib3', + data: { root: 'libs/lib3', sourceRoot: 'libs/lib3/src' }, + type: 'lib', + }, + } as any, + }, + 'shell' + ); + + expect(dependencies).toEqual({ + workspaceLibraries: [ + { name: 'lib1', root: 'libs/lib1', importKey: '@myorg/lib1' }, + { name: 'lib2', root: 'libs/lib2', importKey: '@myorg/lib2' }, + { name: 'lib3', root: 'libs/lib3', importKey: '@myorg/lib3' }, + ], + npmPackages: [], + }); + }); + + it('should ignore TS path mappings with wildcards', () => { + jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({ + '@myorg/lib1': ['libs/lib1/src/index.ts'], + '@myorg/lib1/*': ['libs/lib1/src/lib/*'], + '@myorg/lib2': ['libs/lib2/src/index.ts'], + '@myorg/lib2/*': ['libs/lib2/src/lib/*'], + '@myorg/lib3': ['libs/lib3/src/index.ts'], + '@myorg/lib3/*': ['libs/lib3/src/lib/*'], + }); + + const dependencies = getDependentPackagesForProject( + { + dependencies: { + shell: [{ source: 'shell', target: 'lib1', type: 'static' }], + lib1: [{ source: 'lib1', target: 'lib2', type: 'static' }], + lib2: [{ source: 'lib2', target: 'lib3', type: 'static' }], + }, + nodes: { + shell: { + name: 'shell', + data: { root: 'apps/shell', sourceRoot: 'apps/shell/src' }, + type: 'app', + }, + lib1: { + name: 'lib1', + data: { root: 'libs/lib1', sourceRoot: 'libs/lib1/src' }, + type: 'lib', + }, + lib2: { + name: 'lib2', + data: { root: 'libs/lib2', sourceRoot: 'libs/lib2/src' }, + type: 'lib', + }, + lib3: { + name: 'lib3', + data: { root: 'libs/lib3', sourceRoot: 'libs/lib3/src' }, + type: 'lib', + }, + } as any, + }, + 'shell' + ); + + expect(dependencies).toEqual({ + workspaceLibraries: [ + { name: 'lib1', root: 'libs/lib1', importKey: '@myorg/lib1' }, + { name: 'lib2', root: 'libs/lib2', importKey: '@myorg/lib2' }, + { name: 'lib3', root: 'libs/lib3', importKey: '@myorg/lib3' }, + ], + npmPackages: [], + }); + }); +}); diff --git a/packages/rspack/src/utils/module-federation/dependencies.ts b/packages/rspack/src/utils/module-federation/dependencies.ts new file mode 100644 index 0000000000000..18263ecf0f29a --- /dev/null +++ b/packages/rspack/src/utils/module-federation/dependencies.ts @@ -0,0 +1,94 @@ +import type { ProjectGraph } from '@nx/devkit'; +import type { WorkspaceLibrary } from './models'; +import { readTsPathMappings } from './typescript'; +import { + getOutputsForTargetAndConfiguration, + parseTargetString, +} from '@nx/devkit'; + +export function getDependentPackagesForProject( + projectGraph: ProjectGraph, + name: string +): { + workspaceLibraries: WorkspaceLibrary[]; + npmPackages: string[]; +} { + const { npmPackages, workspaceLibraries } = collectDependencies( + projectGraph, + name + ); + + return { + workspaceLibraries: [...workspaceLibraries.values()], + npmPackages: [...npmPackages], + }; +} + +function collectDependencies( + projectGraph: ProjectGraph, + name: string, + dependencies = { + workspaceLibraries: new Map(), + npmPackages: new Set(), + }, + seen: Set = new Set() +): { + workspaceLibraries: Map; + npmPackages: Set; +} { + if (seen.has(name)) { + return dependencies; + } + seen.add(name); + + (projectGraph.dependencies[name] ?? []).forEach((dependency) => { + if (dependency.target.startsWith('npm:')) { + dependencies.npmPackages.add(dependency.target.replace('npm:', '')); + } else { + dependencies.workspaceLibraries.set(dependency.target, { + name: dependency.target, + root: projectGraph.nodes[dependency.target].data.root, + importKey: getLibraryImportPath(dependency.target, projectGraph), + }); + collectDependencies(projectGraph, dependency.target, dependencies, seen); + } + }); + + return dependencies; +} + +function getLibraryImportPath( + library: string, + projectGraph: ProjectGraph +): string | undefined { + let buildLibsFromSource = true; + if (process.env.NX_BUILD_LIBS_FROM_SOURCE) { + buildLibsFromSource = process.env.NX_BUILD_LIBS_FROM_SOURCE === 'true'; + } + const libraryNode = projectGraph.nodes[library]; + let sourceRoots = [libraryNode.data.sourceRoot]; + + if (!buildLibsFromSource && process.env.NX_BUILD_TARGET) { + const buildTarget = parseTargetString( + process.env.NX_BUILD_TARGET, + projectGraph + ); + sourceRoots = getOutputsForTargetAndConfiguration( + buildTarget, + {}, + libraryNode + ); + } + + const tsConfigPathMappings = readTsPathMappings(); + + for (const [key, value] of Object.entries(tsConfigPathMappings)) { + for (const src of sourceRoots) { + if (value.find((path) => path.startsWith(src))) { + return key; + } + } + } + + return undefined; +} diff --git a/packages/rspack/src/utils/module-federation/get-remotes-for-host.ts b/packages/rspack/src/utils/module-federation/get-remotes-for-host.ts new file mode 100644 index 0000000000000..a1f3abf617741 --- /dev/null +++ b/packages/rspack/src/utils/module-federation/get-remotes-for-host.ts @@ -0,0 +1,195 @@ +import { logger, type ProjectGraph } from '@nx/devkit'; +import { registerTsProject } from '@nx/js/src/internal'; +import chalk from 'chalk'; +import { existsSync, readFileSync } from 'fs'; +import { findMatchingProjects } from 'nx/src/utils/find-matching-projects'; +import { join } from 'path'; +import { ModuleFederationConfig } from './models'; + +interface ModuleFederationExecutorContext { + projectName: string; + projectGraph: ProjectGraph; + root: string; +} + +function extractRemoteProjectsFromConfig( + config: ModuleFederationConfig, + pathToManifestFile?: string +) { + const remotes = []; + const dynamicRemotes = []; + if (pathToManifestFile && existsSync(pathToManifestFile)) { + const moduleFederationManifestJson = readFileSync( + pathToManifestFile, + 'utf-8' + ); + + if (moduleFederationManifestJson) { + // This should have shape of + // { + // "remoteName": "remoteLocation", + // } + const parsedManifest = JSON.parse(moduleFederationManifestJson); + if ( + Object.keys(parsedManifest).every( + (key) => + typeof key === 'string' && typeof parsedManifest[key] === 'string' + ) + ) { + dynamicRemotes.push(...Object.keys(parsedManifest)); + } + } + } + const staticRemotes = + config.remotes?.map((r) => (Array.isArray(r) ? r[0] : r)) ?? []; + remotes.push(...staticRemotes); + return { remotes, dynamicRemotes }; +} + +function collectRemoteProjects( + remote: string, + collected: Set, + context: ModuleFederationExecutorContext +) { + const remoteProject = context.projectGraph.nodes[remote]?.data; + if (!context.projectGraph.nodes[remote] || collected.has(remote)) { + return; + } + + collected.add(remote); + + const remoteProjectRoot = remoteProject.root; + const remoteProjectTsConfig = remoteProject.targets['build'].options.tsConfig; + const remoteProjectConfig = getModuleFederationConfig( + remoteProjectTsConfig, + context.root, + remoteProjectRoot + ); + const { remotes: remoteProjectRemotes } = + extractRemoteProjectsFromConfig(remoteProjectConfig); + + remoteProjectRemotes.forEach((r) => + collectRemoteProjects(r, collected, context) + ); +} + +export function getRemotes( + devRemotes: string[], + skipRemotes: string[], + config: ModuleFederationConfig, + context: ModuleFederationExecutorContext, + pathToManifestFile?: string +) { + const collectedRemotes = new Set(); + const { remotes, dynamicRemotes } = extractRemoteProjectsFromConfig( + config, + pathToManifestFile + ); + remotes.forEach((r) => collectRemoteProjects(r, collectedRemotes, context)); + const remotesToSkip = new Set( + findMatchingProjects(skipRemotes, context.projectGraph.nodes) ?? [] + ); + + if (remotesToSkip.size > 0) { + logger.info( + `Remotes not served automatically: ${[...remotesToSkip.values()].join( + ', ' + )}` + ); + } + + const knownRemotes = Array.from(collectedRemotes).filter( + (r) => !remotesToSkip.has(r) + ); + + const knownDynamicRemotes = dynamicRemotes.filter( + (r) => !remotesToSkip.has(r) && context.projectGraph.nodes[r] + ); + + logger.info( + `NX Starting module federation dev-server for ${chalk.bold( + context.projectName + )} with ${[...knownRemotes, ...knownDynamicRemotes].length} remotes` + ); + + const devServeApps = new Set( + !devRemotes + ? [] + : Array.isArray(devRemotes) + ? findMatchingProjects(devRemotes, context.projectGraph.nodes) + : findMatchingProjects([devRemotes], context.projectGraph.nodes) + ); + + const staticRemotes = knownRemotes.filter((r) => !devServeApps.has(r)); + const devServeRemotes = [...knownRemotes, ...knownDynamicRemotes].filter( + (r) => devServeApps.has(r) + ); + const staticDynamicRemotes = knownDynamicRemotes.filter( + (r) => !devServeApps.has(r) + ); + const remotePorts = [...devServeRemotes, ...staticDynamicRemotes].map( + (r) => context.projectGraph.nodes[r].data.targets['serve'].options.port + ); + const staticRemotePort = + Math.max( + ...([ + ...remotePorts, + ...staticRemotes.map( + (r) => + context.projectGraph.nodes[r].data.targets['serve'].options.port + ), + ] as number[]) + ) + + (remotesToSkip.size + 1); + + return { + staticRemotes, + devRemotes: devServeRemotes, + dynamicRemotes: staticDynamicRemotes, + remotePorts, + staticRemotePort, + }; +} + +export function getModuleFederationConfig( + tsconfigPath: string, + workspaceRoot: string, + projectRoot: string, + pluginName: 'react' | 'angular' = 'react' +) { + const moduleFederationConfigPathJS = join( + workspaceRoot, + projectRoot, + 'module-federation.config.js' + ); + + const moduleFederationConfigPathTS = join( + workspaceRoot, + projectRoot, + 'module-federation.config.ts' + ); + + let moduleFederationConfigPath = moduleFederationConfigPathJS; + + // create a no-op so this can be called with issue + const fullTSconfigPath = tsconfigPath.startsWith(workspaceRoot) + ? tsconfigPath + : join(workspaceRoot, tsconfigPath); + let cleanupTranspiler = () => undefined; + if (existsSync(moduleFederationConfigPathTS)) { + cleanupTranspiler = registerTsProject(fullTSconfigPath); + moduleFederationConfigPath = moduleFederationConfigPathTS; + } + + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const config = require(moduleFederationConfigPath); + cleanupTranspiler(); + + return config.default || config; + } catch { + throw new Error( + `Could not load ${moduleFederationConfigPath}. Was this project generated with "@nx/${pluginName}:host"?\nSee: https://nx.dev/concepts/more-concepts/faster-builds-with-module-federation` + ); + } +} diff --git a/packages/rspack/src/utils/module-federation/index.ts b/packages/rspack/src/utils/module-federation/index.ts new file mode 100644 index 0000000000000..d8ccb8fd03d76 --- /dev/null +++ b/packages/rspack/src/utils/module-federation/index.ts @@ -0,0 +1,6 @@ +export * from './share'; +export * from './dependencies'; +export * from './package-json'; +export * from './remotes'; +export * from './models'; +export * from './get-remotes-for-host'; diff --git a/packages/rspack/src/utils/module-federation/models/index.ts b/packages/rspack/src/utils/module-federation/models/index.ts new file mode 100644 index 0000000000000..4061ed811fb86 --- /dev/null +++ b/packages/rspack/src/utils/module-federation/models/index.ts @@ -0,0 +1,70 @@ +import type { moduleFederationPlugin } from '@module-federation/sdk'; +import type { NormalModuleReplacementPlugin } from '@rspack/core'; + +export type ModuleFederationLibrary = { type: string; name: string }; + +export type WorkspaceLibrary = { + name: string; + root: string; + importKey: string | undefined; +}; + +export type SharedWorkspaceLibraryConfig = { + getAliases: () => Record; + getLibraries: ( + projectRoot: string, + eager?: boolean + ) => Record; + getReplacementPlugin: () => NormalModuleReplacementPlugin; +}; + +export type Remotes = Array; + +export interface SharedLibraryConfig { + singleton?: boolean; + strictVersion?: boolean; + requiredVersion?: false | string; + eager?: boolean; +} + +export type SharedFunction = ( + libraryName: string, + sharedConfig: SharedLibraryConfig +) => undefined | false | SharedLibraryConfig; + +export type AdditionalSharedConfig = Array< + | string + | [libraryName: string, sharedConfig: SharedLibraryConfig] + | { libraryName: string; sharedConfig: SharedLibraryConfig } +>; + +export interface ModuleFederationConfig { + name: string; + remotes?: Remotes; + library?: ModuleFederationLibrary; + exposes?: Record; + shared?: SharedFunction; + additionalShared?: AdditionalSharedConfig; + /** + * `nxRuntimeLibraryControlPlugin` is a runtime module federation plugin to ensure + * that shared libraries are resolved from a remote with live reload capabilities. + * If you run into any issues with loading shared libraries, try disabling this option. + */ + disableNxRuntimeLibraryControlPlugin?: boolean; +} + +export type NxModuleFederationConfigOverride = Omit< + moduleFederationPlugin.ModuleFederationPluginOptions, + | 'exposes' + | 'remotes' + | 'name' + | 'library' + | 'shared' + | 'filename' + | 'remoteType' +>; + +export type WorkspaceLibrarySecondaryEntryPoint = { + name: string; + path: string; +}; diff --git a/packages/rspack/src/utils/module-federation/package-json.ts b/packages/rspack/src/utils/module-federation/package-json.ts new file mode 100644 index 0000000000000..52e64b7fb983c --- /dev/null +++ b/packages/rspack/src/utils/module-federation/package-json.ts @@ -0,0 +1,16 @@ +import { existsSync } from 'fs'; +import { workspaceRoot, readJsonFile, joinPathFragments } from '@nx/devkit'; + +export function readRootPackageJson(): { + dependencies?: { [key: string]: string }; + devDependencies?: { [key: string]: string }; +} { + const pkgJsonPath = joinPathFragments(workspaceRoot, 'package.json'); + if (!existsSync(pkgJsonPath)) { + throw new Error( + 'NX MF: Could not find root package.json to determine dependency versions.' + ); + } + + return readJsonFile(pkgJsonPath); +} diff --git a/packages/rspack/src/utils/module-federation/parse-static-remotes-config.ts b/packages/rspack/src/utils/module-federation/parse-static-remotes-config.ts new file mode 100644 index 0000000000000..ef918839d18e0 --- /dev/null +++ b/packages/rspack/src/utils/module-federation/parse-static-remotes-config.ts @@ -0,0 +1,57 @@ +import type { ExecutorContext } from '@nx/devkit'; +import { basename, dirname } from 'path'; + +export type StaticRemoteConfig = { + basePath: string; + outputPath: string; + urlSegment: string; + port: number; +}; +export type StaticRemotesConfig = { + remotes: string[]; + config: Record | undefined; +}; + +export function parseStaticRemotesConfig( + staticRemotes: string[] | undefined, + context: ExecutorContext +): StaticRemotesConfig { + if (!staticRemotes?.length) { + return { remotes: [], config: undefined }; + } + + const config: Record = {}; + for (const app of staticRemotes) { + const outputPath = + context.projectGraph.nodes[app].data.targets['build'].options.outputPath; + const basePath = dirname(outputPath); + const urlSegment = basename(outputPath); + const port = + context.projectGraph.nodes[app].data.targets['serve'].options.port; + config[app] = { basePath, outputPath, urlSegment, port }; + } + + return { remotes: staticRemotes, config }; +} + +export function parseStaticSsrRemotesConfig( + staticRemotes: string[] | undefined, + context: ExecutorContext +): StaticRemotesConfig { + if (!staticRemotes?.length) { + return { remotes: [], config: undefined }; + } + const config: Record = {}; + for (const app of staticRemotes) { + const outputPath = dirname( + context.projectGraph.nodes[app].data.targets['build'].options.outputPath // dist/checkout/browser -> checkout + ) as string; + const basePath = dirname(outputPath); // dist/checkout -> dist + const urlSegment = basename(outputPath); // dist/checkout -> checkout + const port = + context.projectGraph.nodes[app].data.targets['serve'].options.port; + config[app] = { basePath, outputPath, urlSegment, port }; + } + + return { remotes: staticRemotes, config }; +} diff --git a/packages/rspack/src/utils/module-federation/plugins/runtime-library-control.plugin.ts b/packages/rspack/src/utils/module-federation/plugins/runtime-library-control.plugin.ts new file mode 100644 index 0000000000000..4f5b57d2d0c5a --- /dev/null +++ b/packages/rspack/src/utils/module-federation/plugins/runtime-library-control.plugin.ts @@ -0,0 +1,71 @@ +import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; + +const runtimeStore: { + name?: string; + devRemotes?: string[]; + sharedPackagesFromDev: Record; +} = { + sharedPackagesFromDev: {}, +}; + +if (process.env.NX_MF_DEV_REMOTES) { + // process.env.NX_MF_DEV_REMOTES is replaced by an array value via DefinePlugin, even though the original value is a stringified array. + runtimeStore.devRemotes = process.env + .NX_MF_DEV_REMOTES as unknown as string[]; +} + +const nxRuntimeLibraryControlPlugin: () => FederationRuntimePlugin = + function () { + return { + name: 'nx-runtime-library-control-plugin', + beforeInit(args) { + runtimeStore.name = args.options.name; + return args; + }, + resolveShare: (args) => { + const { shareScopeMap, scope, pkgName, version, GlobalFederation } = + args; + + const originalResolver = args.resolver; + args.resolver = function () { + if (!runtimeStore.sharedPackagesFromDev[pkgName]) { + if (!GlobalFederation.__INSTANCES__) { + return originalResolver(); + } else if (!runtimeStore.devRemotes) { + return originalResolver(); + } + const devRemoteInstanceToUse = GlobalFederation.__INSTANCES__.find( + (instance) => + instance.options.shared[pkgName] && + runtimeStore.devRemotes.find((dr) => instance.name === dr) + ); + if (!devRemoteInstanceToUse) { + return originalResolver(); + } + runtimeStore.sharedPackagesFromDev[pkgName] = + devRemoteInstanceToUse.name; + } + + const remoteInstanceName = + runtimeStore.sharedPackagesFromDev[pkgName]; + const remoteInstance = GlobalFederation.__INSTANCES__.find( + (instance) => instance.name === remoteInstanceName + ); + try { + const remotePkgInfo = remoteInstance.options.shared[pkgName].find( + (shared) => shared.from === remoteInstanceName + ); + remotePkgInfo.useIn.push(runtimeStore.name); + remotePkgInfo.useIn = Array.from(new Set(remotePkgInfo.useIn)); + shareScopeMap[scope][pkgName][version] = remotePkgInfo; + return remotePkgInfo; + } catch { + return originalResolver(); + } + }; + return args; + }, + }; + }; + +export default nxRuntimeLibraryControlPlugin; diff --git a/packages/rspack/src/utils/module-federation/public-api.ts b/packages/rspack/src/utils/module-federation/public-api.ts new file mode 100644 index 0000000000000..996daa5c8600c --- /dev/null +++ b/packages/rspack/src/utils/module-federation/public-api.ts @@ -0,0 +1,51 @@ +import { + AdditionalSharedConfig, + ModuleFederationConfig, + ModuleFederationLibrary, + Remotes, + SharedFunction, + SharedLibraryConfig, + SharedWorkspaceLibraryConfig, + WorkspaceLibrary, + WorkspaceLibrarySecondaryEntryPoint, +} from './models'; + +import { + applyAdditionalShared, + applySharedFunction, + getNpmPackageSharedConfig, + sharePackages, + shareWorkspaceLibraries, +} from './share'; + +import { mapRemotes, mapRemotesForSSR } from './remotes'; + +import { getDependentPackagesForProject } from './dependencies'; + +import { readRootPackageJson } from './package-json'; + +import { withModuleFederation } from './with-module-federation/with-module-federation'; +import { withModuleFederationForSSR } from './with-module-federation/with-module-federation-ssr'; + +export { + AdditionalSharedConfig, + applyAdditionalShared, + applySharedFunction, + getDependentPackagesForProject, + getNpmPackageSharedConfig, + mapRemotes, + mapRemotesForSSR, + ModuleFederationConfig, + ModuleFederationLibrary, + readRootPackageJson, + Remotes, + SharedFunction, + SharedLibraryConfig, + SharedWorkspaceLibraryConfig, + sharePackages, + shareWorkspaceLibraries, + withModuleFederation, + withModuleFederationForSSR, + WorkspaceLibrary, + WorkspaceLibrarySecondaryEntryPoint, +}; diff --git a/packages/rspack/src/utils/module-federation/remotes.ts b/packages/rspack/src/utils/module-federation/remotes.ts new file mode 100644 index 0000000000000..de98778fb9754 --- /dev/null +++ b/packages/rspack/src/utils/module-federation/remotes.ts @@ -0,0 +1,102 @@ +import { extname } from 'path'; +import { Remotes } from './models'; + +/** + * Map remote names to a format that can be understood and used by Module + * Federation. + * + * @param remotes - The remotes to map + * @param remoteEntryExt - The file extension of the remoteEntry file + * @param determineRemoteUrl - The function used to lookup the URL of the served remote + */ +export function mapRemotes( + remotes: Remotes, + remoteEntryExt: 'js' | 'mjs', + determineRemoteUrl: (remote: string) => string +): Record { + const mappedRemotes = {}; + + for (const remote of remotes) { + if (Array.isArray(remote)) { + mappedRemotes[remote[0]] = handleArrayRemote(remote, remoteEntryExt); + } else if (typeof remote === 'string') { + mappedRemotes[remote] = handleStringRemote(remote, determineRemoteUrl); + } + } + + return mappedRemotes; +} + +// Helper function to deal with remotes that are arrays +function handleArrayRemote( + remote: [string, string], + remoteEntryExt: 'js' | 'mjs' +): string { + const [remoteName, remoteLocation] = remote; + const remoteLocationExt = extname(remoteLocation); + + // If remote location already has .js or .mjs extension + if (['.js', '.mjs'].includes(remoteLocationExt)) { + return remoteLocation; + } + + const baseRemote = remoteLocation.endsWith('/') + ? remoteLocation.slice(0, -1) + : remoteLocation; + + const globalPrefix = `${remoteName.replace(/-/g, '_')}@`; + + // if the remote is defined with anything other than http then we assume it's a promise based remote + // In that case we should use what the user provides as the remote location + if (!remoteLocation.startsWith('promise new Promise')) { + return `${globalPrefix}${baseRemote}/remoteEntry.${remoteEntryExt}`; + } else { + return remoteLocation; + } +} + +// Helper function to deal with remotes that are strings +function handleStringRemote( + remote: string, + determineRemoteUrl: (remote: string) => string +): string { + const globalPrefix = `${remote.replace(/-/g, '_')}@`; + + return `${globalPrefix}${determineRemoteUrl(remote)}`; +} + +/** + * Map remote names to a format that can be understood and used by Module + * Federation. + * + * @param remotes - The remotes to map + * @param remoteEntryExt - The file extension of the remoteEntry file + * @param determineRemoteUrl - The function used to lookup the URL of the served remote + */ +export function mapRemotesForSSR( + remotes: Remotes, + remoteEntryExt: 'js' | 'mjs', + determineRemoteUrl: (remote: string) => string +): Record { + const mappedRemotes = {}; + + for (const remote of remotes) { + if (Array.isArray(remote)) { + const [remoteName, remoteLocation] = remote; + const remoteLocationExt = extname(remoteLocation); + mappedRemotes[remoteName] = `${remoteName}@${ + ['.js', '.mjs'].includes(remoteLocationExt) + ? remoteLocation + : `${ + remoteLocation.endsWith('/') + ? remoteLocation.slice(0, -1) + : remoteLocation + }/remoteEntry.${remoteEntryExt}` + }`; + } else if (typeof remote === 'string') { + mappedRemotes[remote] = `${remote}@${determineRemoteUrl(remote)}`; + } + } + + return mappedRemotes; +} diff --git a/packages/rspack/src/utils/module-federation/secondary-entry-points.ts b/packages/rspack/src/utils/module-federation/secondary-entry-points.ts new file mode 100644 index 0000000000000..06637211fd0e4 --- /dev/null +++ b/packages/rspack/src/utils/module-federation/secondary-entry-points.ts @@ -0,0 +1,144 @@ +import { joinPathFragments, readJsonFile, workspaceRoot } from '@nx/devkit'; +import { existsSync, lstatSync, readdirSync } from 'fs'; +import { PackageJson, readModulePackageJson } from 'nx/src/utils/package-json'; +import { dirname, join, relative } from 'path'; +import type { WorkspaceLibrary } from './models'; +import { WorkspaceLibrarySecondaryEntryPoint } from './models'; + +export function collectWorkspaceLibrarySecondaryEntryPoints( + library: WorkspaceLibrary, + tsconfigPathAliases: Record +): WorkspaceLibrarySecondaryEntryPoint[] { + const libraryRoot = join(workspaceRoot, library.root); + const needsSecondaryEntryPointsCollected = existsSync( + join(libraryRoot, 'ng-package.json') + ); + + const secondaryEntryPoints: WorkspaceLibrarySecondaryEntryPoint[] = []; + if (needsSecondaryEntryPointsCollected) { + const tsConfigAliasesForLibWithSecondaryEntryPoints = Object.entries( + tsconfigPathAliases + ).reduce((acc, [tsKey, tsPaths]) => { + if (!tsKey.startsWith(library.importKey)) { + return { ...acc }; + } + + if (tsPaths.some((path) => path.startsWith(`${library.root}/`))) { + acc = { ...acc, [tsKey]: tsPaths }; + } + + return acc; + }, {}); + + for (const [alias] of Object.entries( + tsConfigAliasesForLibWithSecondaryEntryPoints + )) { + const pathToLib = dirname( + join(workspaceRoot, tsconfigPathAliases[alias][0]) + ); + let searchDir = pathToLib; + while (searchDir !== libraryRoot) { + if (existsSync(join(searchDir, 'ng-package.json'))) { + secondaryEntryPoints.push({ name: alias, path: pathToLib }); + break; + } + searchDir = dirname(searchDir); + } + } + } + + return secondaryEntryPoints; +} + +export function getNonNodeModulesSubDirs(directory: string): string[] { + return readdirSync(directory) + .filter((file) => file !== 'node_modules') + .map((file) => join(directory, file)) + .filter((file) => lstatSync(file).isDirectory()); +} + +export function recursivelyCollectSecondaryEntryPointsFromDirectory( + pkgName: string, + pkgVersion: string, + pkgRoot: string, + mainEntryPointExports: any | undefined, + directories: string[], + collectedPackages: { name: string; version: string }[] +): void { + for (const directory of directories) { + const packageJsonPath = join(directory, 'package.json'); + const relativeEntryPointPath = relative(pkgRoot, directory); + const entryPointName = joinPathFragments(pkgName, relativeEntryPointPath); + if (existsSync(packageJsonPath)) { + try { + // require the secondary entry point to try to rule out sample code + require.resolve(entryPointName, { paths: [workspaceRoot] }); + const { name } = readJsonFile(packageJsonPath); + // further check to make sure what we were able to require is the + // same as the package name + if (name === entryPointName) { + collectedPackages.push({ name, version: pkgVersion }); + } + } catch { + // do nothing + } + } else if (mainEntryPointExports) { + // if the package.json doesn't exist, check if the directory is + // exported by the main entry point + const entryPointExportKey = `./${relativeEntryPointPath}`; + const entryPointInfo = mainEntryPointExports[entryPointExportKey]; + if (entryPointInfo) { + collectedPackages.push({ + name: entryPointName, + version: pkgVersion, + }); + } + } + + const subDirs = getNonNodeModulesSubDirs(directory); + recursivelyCollectSecondaryEntryPointsFromDirectory( + pkgName, + pkgVersion, + pkgRoot, + mainEntryPointExports, + subDirs, + collectedPackages + ); + } +} + +export function collectPackageSecondaryEntryPoints( + pkgName: string, + pkgVersion: string, + collectedPackages: { name: string; version: string }[] +): void { + let pathToPackage: string; + let packageJsonPath: string; + let packageJson: PackageJson; + try { + ({ path: packageJsonPath, packageJson } = readModulePackageJson(pkgName)); + pathToPackage = dirname(packageJsonPath); + } catch { + // the package.json might not resolve if the package has the "exports" + // entry and is not exporting the package.json file, fall back to trying + // to find it from the top-level node_modules + pathToPackage = join(workspaceRoot, 'node_modules', pkgName); + packageJsonPath = join(pathToPackage, 'package.json'); + if (!existsSync(packageJsonPath)) { + // might not exist if it's nested in another package, just return here + return; + } + packageJson = readJsonFile(packageJsonPath); + } + + const { exports } = packageJson; + const subDirs = getNonNodeModulesSubDirs(pathToPackage); + recursivelyCollectSecondaryEntryPointsFromDirectory( + pkgName, + pkgVersion, + pathToPackage, + exports, + subDirs, + collectedPackages + ); +} diff --git a/packages/rspack/src/utils/module-federation/share.spec.ts b/packages/rspack/src/utils/module-federation/share.spec.ts new file mode 100644 index 0000000000000..a87299dede0cc --- /dev/null +++ b/packages/rspack/src/utils/module-federation/share.spec.ts @@ -0,0 +1,381 @@ +import * as fs from 'fs'; +import * as nxFileutils from 'nx/src/devkit-exports'; +import { sharePackages, shareWorkspaceLibraries } from './share'; +import * as tsUtils from './typescript'; + +jest.mock('nx/src/devkit-exports', () => { + return { + ...jest.requireActual('nx/src/devkit-exports'), + readJsonFile: jest.fn(), + }; +}); + +describe('MF Share Utils', () => { + afterEach(() => jest.clearAllMocks()); + + describe('ShareWorkspaceLibraries', () => { + it('should error when the tsconfig file does not exist', () => { + // ARRANGE + jest + .spyOn(fs, 'existsSync') + .mockImplementation((p: string) => p?.endsWith('.node')); + + // ACT + try { + shareWorkspaceLibraries([ + { name: 'shared', root: 'libs/shared', importKey: '@myorg/shared' }, + ]); + } catch (error) { + // ASSERT + expect(error.message).toContain( + 'NX MF: TsConfig Path for workspace libraries does not exist!' + ); + } + }); + + it('should create an object with correct setup', () => { + // ARRANGE + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({ + '@myorg/shared': ['/libs/shared/src/index.ts'], + }); + + // ACT + const sharedLibraries = shareWorkspaceLibraries([ + { name: 'shared', root: 'libs/shared', importKey: '@myorg/shared' }, + ]); + + // ASSERT + expect(sharedLibraries.getAliases()).toHaveProperty('@myorg/shared'); + expect(sharedLibraries.getAliases()['@myorg/shared']).toContain( + 'libs/shared/src/index.ts' + ); + expect(sharedLibraries.getLibraries('libs/shared')).toEqual({ + '@myorg/shared': { + eager: undefined, + requiredVersion: false, + }, + }); + }); + + it('should order nested projects first', () => { + // ARRANGE + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({ + '@myorg/shared': ['/libs/shared/src/index.ts'], + '@myorg/shared/components': ['/libs/shared/components/src/index.ts'], + }); + + // ACT + const sharedLibraries = shareWorkspaceLibraries([ + { name: 'shared', root: 'libs/shared', importKey: '@myorg/shared' }, + { + name: 'shared-components', + root: 'libs/shared/components', + importKey: '@myorg/shared/components', + }, + ]); + + // ASSERT + expect(Object.keys(sharedLibraries.getAliases())[0]).toEqual( + '@myorg/shared/components' + ); + }); + + it('should handle path mappings with wildcards correctly in non-buildable libraries', () => { + // ARRANGE + jest.spyOn(fs, 'existsSync').mockImplementation(() => true); + jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({ + '@myorg/shared': ['/libs/shared/src/index.ts'], + '@myorg/shared/*': ['/libs/shared/src/lib/*'], + }); + + // ACT + const sharedLibraries = shareWorkspaceLibraries([ + { name: 'shared', root: 'libs/shared', importKey: '@myorg/shared' }, + ]); + + // ASSERT + expect(sharedLibraries.getAliases()).toHaveProperty('@myorg/shared'); + expect(sharedLibraries.getAliases()['@myorg/shared']).toContain( + 'libs/shared/src/index.ts' + ); + expect(sharedLibraries.getLibraries('libs/shared')).toEqual({ + '@myorg/shared': { + eager: undefined, + requiredVersion: false, + }, + }); + }); + + it('should create an object with empty setup when tsconfig does not contain the shared lib', () => { + // ARRANGE + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({}); + + // ACT + const sharedLibraries = shareWorkspaceLibraries([ + { name: 'shared', root: 'libs/shared', importKey: '@myorg/shared' }, + ]); + + // ASSERT + expect(sharedLibraries.getAliases()).toEqual({}); + expect(sharedLibraries.getLibraries('libs/shared')).toEqual({}); + }); + }); + + describe('SharePackages', () => { + it('should throw when it cannot find root package.json', () => { + // ARRANGE + jest + .spyOn(fs, 'existsSync') + .mockImplementation((p: string) => p.endsWith('.node')); + + // ACT + try { + sharePackages(['@angular/core']); + } catch (error) { + // ASSERT + expect(error.message).toEqual( + 'NX MF: Could not find root package.json to determine dependency versions.' + ); + } + }); + + it('should correctly map the shared packages to objects', () => { + // ARRANGE + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + jest.spyOn(nxFileutils, 'readJsonFile').mockImplementation((file) => ({ + name: file.replace(/\\/g, '/').replace(/^.*node_modules[/]/, ''), + dependencies: { + '@angular/core': '~13.2.0', + '@angular/common': '~13.2.0', + rxjs: '~7.4.0', + }, + })); + jest.spyOn(fs, 'readdirSync').mockReturnValue([]); + + // ACT + const packages = sharePackages([ + '@angular/core', + '@angular/common', + 'rxjs', + ]); + // ASSERT + expect(packages).toEqual({ + '@angular/core': { + singleton: true, + strictVersion: true, + requiredVersion: '~13.2.0', + }, + '@angular/common': { + singleton: true, + strictVersion: true, + requiredVersion: '~13.2.0', + }, + rxjs: { + singleton: true, + strictVersion: true, + requiredVersion: '~7.4.0', + }, + }); + }); + + // TODO: Get with colum and figure out why this stopped working + xit('should correctly map the shared packages to objects even with nested entry points', () => { + // ARRANGE + + /** + * This creates a bunch of mocks that aims to test that + * the sharePackages function can handle nested + * entrypoints in the package that is being shared. + * + * This will set up a directory structure that matches + * the following: + * + * - @angular/core/ + * - package.json + * - @angular/common/ + * - http/ + * - testing/ + * - package.json + * - package.json + * - rxjs + * - package.json + * + * The result is that there would be 4 packages that + * need to be shared, as determined by the folders + * containing the package.json files + */ + createMockedFSForNestedEntryPoints(); + + // ACT + const packages = sharePackages([ + '@angular/core', + '@angular/common', + 'rxjs', + ]); + // ASSERT + expect(packages).toEqual({ + '@angular/core': { + singleton: true, + strictVersion: true, + requiredVersion: '~13.2.0', + }, + '@angular/common': { + singleton: true, + strictVersion: true, + requiredVersion: '~13.2.0', + }, + '@angular/common/http/testing': { + singleton: true, + strictVersion: true, + requiredVersion: '~13.2.0', + }, + rxjs: { + singleton: true, + strictVersion: true, + requiredVersion: '~7.4.0', + }, + }); + }); + + it('should not throw when the main entry point package.json cannot be required', () => { + // ARRANGE + jest + .spyOn(fs, 'existsSync') + .mockImplementation( + (file: string) => + !file.endsWith('non-existent-top-level-package/package.json') + ); + jest.spyOn(nxFileutils, 'readJsonFile').mockImplementation((file) => { + return { + name: file + .replace(/\\/g, '/') + .replace(/^.*node_modules[/]/, '') + .replace('/package.json', ''), + dependencies: { '@angular/core': '~13.2.0' }, + }; + }); + + // ACT & ASSERT + expect(() => + sharePackages(['non-existent-top-level-package']) + ).not.toThrow(); + }); + }); + + it('should using shared library version from root package.json if available', () => { + // ARRANGE + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + jest + .spyOn(nxFileutils, 'readJsonFile') + .mockImplementation((file: string) => { + if (file.endsWith('package.json')) { + return { + dependencies: { + '@myorg/shared': '1.0.0', + }, + }; + } + }); + + jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({ + '@myorg/shared': ['/libs/shared/src/index.ts'], + '@myorg/shared/*': ['/libs/shared/src/lib/*'], + }); + + // ACT + const sharedLibraries = shareWorkspaceLibraries( + [{ name: 'shared', root: 'libs/shared', importKey: '@myorg/shared' }], + '/' + ); + + // ASSERT + expect(sharedLibraries.getLibraries('libs/shared')).toEqual({ + '@myorg/shared': { + eager: undefined, + requiredVersion: '1.0.0', + singleton: true, + }, + }); + }); + + it('should use shared library version from library package.json if project package.json does not have it', () => { + // ARRANGE + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + jest + .spyOn(nxFileutils, 'readJsonFile') + .mockImplementation((file: string) => { + if (file.endsWith('libs/shared/package.json')) { + return { + version: '1.0.0', + }; + } else { + return {}; + } + }); + + jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({ + '@myorg/shared': ['/libs/shared/src/index.ts'], + '@myorg/shared/*': ['/libs/shared/src/lib/*'], + }); + + // ACT + const sharedLibraries = shareWorkspaceLibraries( + [{ name: 'shared', root: 'libs/shared', importKey: '@myorg/shared' }], + null + ); + + // ASSERT + expect(sharedLibraries.getLibraries('libs/shared')).toEqual({ + '@myorg/shared': { + eager: undefined, + requiredVersion: '1.0.0', + singleton: true, + }, + }); + }); +}); + +function createMockedFSForNestedEntryPoints() { + jest.spyOn(fs, 'existsSync').mockImplementation((file: string) => { + if (file.endsWith('http/package.json')) { + return false; + } else { + return true; + } + }); + + jest.spyOn(nxFileutils, 'readJsonFile').mockImplementation((file) => ({ + name: file + .replace(/\\/g, '/') + .replace(/^.*node_modules[/]/, '') + .replace('/package.json', ''), + dependencies: { + '@angular/core': '~13.2.0', + '@angular/common': '~13.2.0', + rxjs: '~7.4.0', + }, + })); + + jest.spyOn(fs, 'readdirSync').mockImplementation((directoryPath: string) => { + const PACKAGE_SETUP = { + '@angular/core': [], + '@angular/common': ['http'], + http: ['testing'], + testing: [], + }; + + for (const key of Object.keys(PACKAGE_SETUP)) { + if (directoryPath.endsWith(key)) { + return PACKAGE_SETUP[key]; + } + } + return []; + }); + + jest + .spyOn(fs, 'lstatSync') + .mockReturnValue({ isDirectory: () => true } as any); +} diff --git a/packages/rspack/src/utils/module-federation/share.ts b/packages/rspack/src/utils/module-federation/share.ts new file mode 100644 index 0000000000000..c92c378490de9 --- /dev/null +++ b/packages/rspack/src/utils/module-federation/share.ts @@ -0,0 +1,322 @@ +import { + joinPathFragments, + logger, + type ProjectGraph, + readJsonFile, + workspaceRoot, +} from '@nx/devkit'; +import { NormalModuleReplacementPlugin } from '@rspack/core'; +import { existsSync } from 'fs'; +import type { PackageJson } from 'nx/src/utils/package-json'; +import { dirname, join, normalize } from 'path'; +import type { + SharedLibraryConfig, + SharedWorkspaceLibraryConfig, + WorkspaceLibrary, +} from './models'; +import { AdditionalSharedConfig, SharedFunction } from './models'; +import { readRootPackageJson } from './package-json'; +import { + collectPackageSecondaryEntryPoints, + collectWorkspaceLibrarySecondaryEntryPoints, +} from './secondary-entry-points'; +import { getRootTsConfigPath, readTsPathMappings } from './typescript'; + +/** + * Build an object of functions to be used with the ModuleFederationPlugin to + * share Nx Workspace Libraries between Hosts and Remotes. + * + * @param workspaceLibs - The Nx Workspace Libraries to share + * @param tsConfigPath - The path to TS Config File that contains the Path Mappings for the Libraries + */ +export function shareWorkspaceLibraries( + workspaceLibs: WorkspaceLibrary[], + tsConfigPath = process.env.NX_TSCONFIG_PATH ?? getRootTsConfigPath() +): SharedWorkspaceLibraryConfig { + if (!workspaceLibs) { + return getEmptySharedLibrariesConfig(); + } + + const tsconfigPathAliases = readTsPathMappings(tsConfigPath); + if (!Object.keys(tsconfigPathAliases).length) { + return getEmptySharedLibrariesConfig(); + } + + // Nested projects must come first, sort them as such + const sortedTsConfigPathAliases = {}; + Object.keys(tsconfigPathAliases) + .sort((a, b) => { + return b.split('/').length - a.split('/').length; + }) + .forEach( + (key) => (sortedTsConfigPathAliases[key] = tsconfigPathAliases[key]) + ); + + const pathMappings: { name: string; path: string }[] = []; + for (const [key, paths] of Object.entries(sortedTsConfigPathAliases)) { + const library = workspaceLibs.find((lib) => lib.importKey === key); + if (!library) { + continue; + } + + // This is for Angular Projects that use ng-package.json + // It will do nothing for React Projects + collectWorkspaceLibrarySecondaryEntryPoints( + library, + sortedTsConfigPathAliases + ).forEach(({ name, path }) => + pathMappings.push({ + name, + path, + }) + ); + + pathMappings.push({ + name: key, + path: normalize(join(workspaceRoot, paths[0])), + }); + } + + return { + getAliases: () => + pathMappings.reduce( + (aliases, library) => ({ + ...aliases, + // If the library path ends in a wildcard, remove it as webpack can't handle this in resolve.alias + // e.g. path/to/my/lib/* -> path/to/my/lib + [library.name]: library.path.replace(/\/\*$/, ''), + }), + {} + ), + getLibraries: ( + projectRoot: string, + eager?: boolean + ): Record => { + let pkgJson: PackageJson = null; + if ( + projectRoot && + existsSync( + joinPathFragments(workspaceRoot, projectRoot, 'package.json') + ) + ) { + pkgJson = readJsonFile( + joinPathFragments(workspaceRoot, projectRoot, 'package.json') + ); + } + return pathMappings.reduce((libraries, library) => { + // Check to see if the library version is declared in the app's package.json + let version = pkgJson?.dependencies?.[library.name]; + if (!version && workspaceLibs.length > 0) { + const workspaceLib = workspaceLibs.find( + (lib) => lib.importKey === library.name + ); + + const libPackageJsonPath = workspaceLib + ? join(workspaceLib.root, 'package.json') + : null; + if (libPackageJsonPath && existsSync(libPackageJsonPath)) { + pkgJson = readJsonFile(libPackageJsonPath); + + if (pkgJson) { + version = pkgJson.version; + } + } + } + + return { + ...libraries, + [library.name]: { + ...(version + ? { + requiredVersion: version, + singleton: true, + } + : { requiredVersion: false }), + eager, + }, + }; + }, {} as Record); + }, + getReplacementPlugin: () => + new NormalModuleReplacementPlugin(/./, (req) => { + if (!req.request.startsWith('.')) { + return; + } + + const from = req.context; + const to = normalize(join(req.context, req.request)); + + for (const library of pathMappings) { + const libFolder = normalize(dirname(library.path)); + if (!from.startsWith(libFolder) && to.startsWith(libFolder)) { + const newReq = library.name.endsWith('/*') + ? /** + * req usually is in the form of "../../../path/to/file" + * library.path is usually in the form of "/Users/username/path/to/Workspace/path/to/library" + * + * When a wildcard is used in the TS path mappings, we want to get everything after the import to + * re-route the request correctly inline with the webpack resolve.alias + */ + join( + library.name, + req.request.split( + library.path.replace(workspaceRoot, '').replace('/*', '') + )[1] + ) + : library.name; + req.request = newReq; + } + } + }), + }; +} + +/** + * Build the Module Federation Share Config for a specific package and the + * specified version of that package. + * @param pkgName - Name of the package to share + * @param version - Version of the package to require by other apps in the Module Federation setup + */ +export function getNpmPackageSharedConfig( + pkgName: string, + version: string +): SharedLibraryConfig | undefined { + if (!version) { + logger.warn( + `Could not find a version for "${pkgName}" in the root "package.json" ` + + 'when collecting shared packages for the Module Federation setup. ' + + 'The package will not be shared.' + ); + + return undefined; + } + + return { singleton: true, strictVersion: true, requiredVersion: version }; +} + +/** + * Create a dictionary of packages and their Module Federation Shared Config + * from an array of package names. + * + * Lookup the versions of the packages from the root package.json file in the + * workspace. + * @param packages - Array of package names as strings + */ +export function sharePackages( + packages: string[] +): Record { + const pkgJson = readRootPackageJson(); + const allPackages: { name: string; version: string }[] = []; + packages.forEach((pkg) => { + const pkgVersion = + pkgJson.dependencies?.[pkg] ?? pkgJson.devDependencies?.[pkg]; + allPackages.push({ name: pkg, version: pkgVersion }); + collectPackageSecondaryEntryPoints(pkg, pkgVersion, allPackages); + }); + + return allPackages.reduce((shared, pkg) => { + const config = getNpmPackageSharedConfig(pkg.name, pkg.version); + if (config) { + shared[pkg.name] = config; + } + + return shared; + }, {} as Record); +} + +/** + * Apply a custom function provided by the user that will modify the Shared Config + * of the dependencies for the Module Federation build. + * + * @param sharedConfig - The original Shared Config to be modified + * @param sharedFn - The custom function to run + */ +export function applySharedFunction( + sharedConfig: Record, + sharedFn: SharedFunction | undefined +): void { + if (!sharedFn) { + return; + } + + for (const [libraryName, library] of Object.entries(sharedConfig)) { + const mappedDependency = sharedFn(libraryName, library); + if (mappedDependency === false) { + delete sharedConfig[libraryName]; + continue; + } else if (!mappedDependency) { + continue; + } + + sharedConfig[libraryName] = mappedDependency; + } +} + +/** + * Add additional dependencies to the shared package that may not have been + * discovered by the project graph. + * + * This can be useful for applications that use a Dependency Injection system + * that expects certain Singleton values to be present in the shared injection + * hierarchy. + * + * @param sharedConfig - The original Shared Config + * @param additionalShared - The additional dependencies to add + * @param projectGraph - The Nx project graph + */ +export function applyAdditionalShared( + sharedConfig: Record, + additionalShared: AdditionalSharedConfig | undefined, + projectGraph: ProjectGraph +): void { + if (!additionalShared) { + return; + } + + for (const shared of additionalShared) { + if (typeof shared === 'string') { + addStringDependencyToSharedConfig(sharedConfig, shared, projectGraph); + } else if (Array.isArray(shared)) { + sharedConfig[shared[0]] = shared[1]; + } else if (typeof shared === 'object') { + sharedConfig[shared.libraryName] = shared.sharedConfig; + } + } +} + +function addStringDependencyToSharedConfig( + sharedConfig: Record, + dependency: string, + projectGraph: ProjectGraph +): void { + if (projectGraph.nodes[dependency]) { + sharedConfig[dependency] = { requiredVersion: false }; + } else if (projectGraph.externalNodes?.[`npm:${dependency}`]) { + const pkgJson = readRootPackageJson(); + const config = getNpmPackageSharedConfig( + dependency, + pkgJson.dependencies?.[dependency] ?? + pkgJson.devDependencies?.[dependency] + ); + + if (!config) { + return; + } + + sharedConfig[dependency] = config; + } else { + throw new Error( + `The specified dependency "${dependency}" in the additionalShared configuration does not exist in the project graph. ` + + `Please check your additionalShared configuration and make sure you are including valid workspace projects or npm packages.` + ); + } +} + +function getEmptySharedLibrariesConfig() { + return { + getAliases: () => ({}), + getLibraries: () => ({}), + getReplacementPlugin: () => + new NormalModuleReplacementPlugin(/./, () => undefined), + }; +} diff --git a/packages/rspack/src/utils/module-federation/start-remote-proxies.ts b/packages/rspack/src/utils/module-federation/start-remote-proxies.ts new file mode 100644 index 0000000000000..a451f8ad7d716 --- /dev/null +++ b/packages/rspack/src/utils/module-federation/start-remote-proxies.ts @@ -0,0 +1,62 @@ +import { logger } from '@nx/devkit'; +import type { Express } from 'express'; +import { existsSync, readFileSync } from 'fs'; +import { StaticRemotesConfig } from './parse-static-remotes-config'; + +export function startRemoteProxies( + staticRemotesConfig: StaticRemotesConfig, + mappedLocationsOfRemotes: Record, + 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: typeof import('http') = require('http'); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const https: typeof import('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.createServer( + { + cert: sslCert, + key: sslKey, + }, + expressProxy + ) + : http.createServer(expressProxy) + ).listen(staticRemotesConfig.config[app].port); + process.on('SIGTERM', () => proxyServer.close()); + process.on('exit', () => proxyServer.close()); + } + logger.info(`NX Static remotes proxies started successfully`); +} diff --git a/packages/rspack/src/utils/module-federation/start-ssr-remote-proxies.ts b/packages/rspack/src/utils/module-federation/start-ssr-remote-proxies.ts new file mode 100644 index 0000000000000..83a85cc2883c5 --- /dev/null +++ b/packages/rspack/src/utils/module-federation/start-ssr-remote-proxies.ts @@ -0,0 +1,77 @@ +import { logger } from '@nx/devkit'; +import type { Express } from 'express'; +import { existsSync, readFileSync } from 'fs'; +import type { StaticRemotesConfig } from './parse-static-remotes-config'; + +export function startSsrRemoteProxies( + staticRemotesConfig: StaticRemotesConfig, + mappedLocationsOfRemotes: Record, + 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: typeof import('http') = require('http'); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const https: typeof import('https') = require('https'); + + logger.info(`NX Starting static remotes proxies...`); + for (const app of staticRemotesConfig.remotes) { + const expressProxy: Express = express(); + /** + * SSR remotes have two output paths: one for the browser and one for the server. + * We need to handle paths for both of them. + * The browser output path is used to serve the client-side code. + * The server output path is used to serve the server-side code. + */ + + expressProxy.use( + createProxyMiddleware({ + target: `${mappedLocationsOfRemotes[app]}`, + secure: sslCert ? false : undefined, + changeOrigin: true, + pathRewrite: (path) => { + if (path.includes('/server')) { + return path; + } else { + return `browser/${path}`; + } + }, + }) + ); + + const proxyServer = ( + sslCert + ? https.createServer( + { + cert: sslCert, + key: sslKey, + }, + expressProxy + ) + : http.createServer(expressProxy) + ).listen(staticRemotesConfig.config[app].port); + process.on('SIGTERM', () => proxyServer.close()); + process.on('exit', () => proxyServer.close()); + } + logger.info(`Nx SSR Static remotes proxies started successfully`); +} diff --git a/packages/rspack/src/utils/module-federation/typescript.spec.ts b/packages/rspack/src/utils/module-federation/typescript.spec.ts new file mode 100644 index 0000000000000..6c733405b65a3 --- /dev/null +++ b/packages/rspack/src/utils/module-federation/typescript.spec.ts @@ -0,0 +1,45 @@ +import * as fs from 'fs'; +import { readTsPathMappings } from './typescript'; + +let readConfigFileResult: any; +let parseJsonConfigFileContentResult: any; +jest.mock('typescript', () => ({ + ...jest.requireActual('typescript'), + readConfigFile: jest.fn().mockImplementation(() => readConfigFileResult), + parseJsonConfigFileContent: jest + .fn() + .mockImplementation(() => parseJsonConfigFileContentResult), +})); + +describe('readTsPathMappings', () => { + it('should normalize paths', () => { + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + readConfigFileResult = { + config: { + options: { + paths: { + '@myorg/lib1': ['./libs/lib1/src/index.ts'], + '@myorg/lib2': ['libs/lib2/src/index.ts'], + }, + }, + }, + }; + parseJsonConfigFileContentResult = { + options: { + paths: { + '@myorg/lib1': ['./libs/lib1/src/index.ts'], + '@myorg/lib2': ['libs/lib2/src/index.ts'], + }, + }, + fileNames: [], + errors: [], + }; + + const paths = readTsPathMappings('/path/to/tsconfig.json'); + + expect(paths).toEqual({ + '@myorg/lib1': ['libs/lib1/src/index.ts'], + '@myorg/lib2': ['libs/lib2/src/index.ts'], + }); + }); +}); diff --git a/packages/rspack/src/utils/module-federation/typescript.ts b/packages/rspack/src/utils/module-federation/typescript.ts new file mode 100644 index 0000000000000..7a9efc49ee19a --- /dev/null +++ b/packages/rspack/src/utils/module-federation/typescript.ts @@ -0,0 +1,75 @@ +import { existsSync } from 'fs'; +import { ParsedCommandLine } from 'typescript'; +import { dirname, join } from 'path'; +import { workspaceRoot } from '@nx/devkit'; + +const tsConfig: Map = new Map(); +const tsPathMappings: Map = + new Map(); + +export function readTsPathMappings( + tsConfigPath: string = process.env.NX_TSCONFIG_PATH ?? getRootTsConfigPath() +): ParsedCommandLine['options']['paths'] { + if (tsPathMappings.has(tsConfigPath)) { + return tsPathMappings.get(tsConfigPath); + } + + if (!tsConfig.has(tsConfigPath)) { + tsConfig.set(tsConfigPath, readTsConfiguration(tsConfigPath)); + } + tsPathMappings.set(tsConfigPath, {}); + Object.entries(tsConfig.get(tsConfigPath).options?.paths ?? {}).forEach( + ([alias, paths]) => { + tsPathMappings.set(tsConfigPath, { + ...tsPathMappings.get(tsConfigPath), + [alias]: paths.map((path) => path.replace(/^\.\//, '')), + }); + } + ); + + return tsPathMappings.get(tsConfigPath); +} + +function readTsConfiguration(tsConfigPath: string): ParsedCommandLine { + if (!existsSync(tsConfigPath)) { + throw new Error( + `NX MF: TsConfig Path for workspace libraries does not exist! (${tsConfigPath}).` + ); + } + + return readTsConfig(tsConfigPath); +} + +let tsModule: typeof import('typescript'); + +export function readTsConfig(tsConfigPath: string): ParsedCommandLine { + if (!tsModule) { + tsModule = require('typescript'); + } + const readResult = tsModule.readConfigFile( + tsConfigPath, + tsModule.sys.readFile + ); + return tsModule.parseJsonConfigFileContent( + readResult.config, + tsModule.sys, + dirname(tsConfigPath) + ); +} + +export function getRootTsConfigPath(): string | null { + const tsConfigFileName = getRootTsConfigFileName(); + + return tsConfigFileName ? join(workspaceRoot, tsConfigFileName) : null; +} + +function getRootTsConfigFileName(): string | null { + for (const tsConfigName of ['tsconfig.base.json', 'tsconfig.json']) { + const tsConfigPath = join(workspaceRoot, tsConfigName); + if (existsSync(tsConfigPath)) { + return tsConfigName; + } + } + + return null; +} diff --git a/packages/rspack/src/utils/module-federation/with-module-federation/package-json.ts b/packages/rspack/src/utils/module-federation/with-module-federation/package-json.ts new file mode 100644 index 0000000000000..64d424bf87c4f --- /dev/null +++ b/packages/rspack/src/utils/module-federation/with-module-federation/package-json.ts @@ -0,0 +1,16 @@ +import { joinPathFragments, readJsonFile, workspaceRoot } from '@nx/devkit'; +import { existsSync } from 'fs'; + +export function readRootPackageJson(): { + dependencies?: { [key: string]: string }; + devDependencies?: { [key: string]: string }; +} { + const pkgJsonPath = joinPathFragments(workspaceRoot, 'package.json'); + if (!existsSync(pkgJsonPath)) { + throw new Error( + 'NX MFE: Could not find root package.json to determine dependency versions.' + ); + } + + return readJsonFile(pkgJsonPath); +} diff --git a/packages/rspack/src/utils/module-federation/with-module-federation/utils.ts b/packages/rspack/src/utils/module-federation/with-module-federation/utils.ts new file mode 100644 index 0000000000000..41ac4f1b8f236 --- /dev/null +++ b/packages/rspack/src/utils/module-federation/with-module-federation/utils.ts @@ -0,0 +1,131 @@ +import { + createProjectGraphAsync, + ProjectGraph, + readCachedProjectGraph, +} from '@nx/devkit'; +import { readCachedProjectConfiguration } from 'nx/src/project-graph/project-graph'; +import { getDependentPackagesForProject } from '../dependencies'; +import { ModuleFederationConfig } from '../models'; +import { mapRemotes, mapRemotesForSSR } from '../remotes'; +import { + applyAdditionalShared, + applySharedFunction, + sharePackages, + shareWorkspaceLibraries, +} from '../share'; + +export function getFunctionDeterminateRemoteUrl(isServer = false) { + const target = 'serve'; + const remoteEntry = isServer ? 'server/remoteEntry.js' : 'remoteEntry.js'; + + return function (remote: string) { + const mappedStaticRemotesFromEnv = process.env + .NX_MF_DEV_SERVER_STATIC_REMOTES + ? JSON.parse(process.env.NX_MF_DEV_SERVER_STATIC_REMOTES) + : undefined; + if (mappedStaticRemotesFromEnv && mappedStaticRemotesFromEnv[remote]) { + return `${mappedStaticRemotesFromEnv[remote]}/${remoteEntry}`; + } + + let remoteConfiguration = null; + try { + remoteConfiguration = readCachedProjectConfiguration(remote); + } catch (e) { + throw new Error( + `Cannot find remote: "${remote}". Check that the remote name is correct in your module federation config file.\n` + ); + } + const serveTarget = remoteConfiguration?.targets?.[target]; + + if (!serveTarget) { + throw new Error( + `Cannot automatically determine URL of remote (${remote}). Looked for property "host" in the project's "${serveTarget}" target.\n + You can also use the tuple syntax in your webpack config to configure your remotes. e.g. \`remotes: [['remote1', 'http://localhost:4201']]\`` + ); + } + + const host = + serveTarget.options?.host ?? + `http${serveTarget.options.ssl ? 's' : ''}://localhost`; + const port = serveTarget.options?.port ?? 4201; + return `${ + host.endsWith('/') ? host.slice(0, -1) : host + }:${port}/${remoteEntry}`; + }; +} + +export async function getModuleFederationConfig( + mfConfig: ModuleFederationConfig, + options: { + isServer: boolean; + determineRemoteUrl?: (remote: string) => string; + } = { isServer: false } +) { + let projectGraph: ProjectGraph; + try { + projectGraph = readCachedProjectGraph(); + } catch (e) { + projectGraph = await createProjectGraphAsync(); + } + + const project = projectGraph.nodes[mfConfig.name]?.data; + + if (!project) { + throw Error( + `Cannot find project "${mfConfig.name}". Check that the name is correct in module-federation.config.js` + ); + } + + const dependencies = getDependentPackagesForProject( + projectGraph, + mfConfig.name + ); + + if (mfConfig.shared) { + dependencies.workspaceLibraries = dependencies.workspaceLibraries.filter( + (lib) => mfConfig.shared(lib.importKey, {}) !== false + ); + dependencies.npmPackages = dependencies.npmPackages.filter( + (pkg) => mfConfig.shared(pkg, {}) !== false + ); + } + + const sharedLibraries = shareWorkspaceLibraries( + dependencies.workspaceLibraries + ); + + const npmPackages = sharePackages(dependencies.npmPackages); + + const sharedDependencies = { + ...sharedLibraries.getLibraries(project.root), + ...npmPackages, + }; + + applySharedFunction(sharedDependencies, mfConfig.shared); + applyAdditionalShared( + sharedDependencies, + mfConfig.additionalShared, + projectGraph + ); + + // Choose the correct mapRemotes function based on the server state. + const mapRemotesFunction = options.isServer ? mapRemotesForSSR : mapRemotes; + + // Determine the URL function, either from provided options or by using a default. + const determineRemoteUrlFunction = options.determineRemoteUrl + ? options.determineRemoteUrl + : getFunctionDeterminateRemoteUrl(options.isServer); + + // Map the remotes if they exist, otherwise default to an empty object. + let mappedRemotes = {}; + + if (mfConfig.remotes && mfConfig.remotes.length > 0) { + mappedRemotes = mapRemotesFunction( + mfConfig.remotes, + 'js', + determineRemoteUrlFunction + ); + } + + return { sharedLibraries, sharedDependencies, mappedRemotes }; +} diff --git a/packages/rspack/src/utils/module-federation/with-module-federation/with-module-federation-ssr.ts b/packages/rspack/src/utils/module-federation/with-module-federation/with-module-federation-ssr.ts new file mode 100644 index 0000000000000..b6fa214ae4fce --- /dev/null +++ b/packages/rspack/src/utils/module-federation/with-module-federation/with-module-federation-ssr.ts @@ -0,0 +1,77 @@ +import { DefinePlugin } from '@rspack/core'; +import { SharedConfigContext } from '../../model'; +import { + ModuleFederationConfig, + NxModuleFederationConfigOverride, +} from '../models'; +import { getModuleFederationConfig } from './utils'; + +export async function withModuleFederationForSSR( + options: ModuleFederationConfig, + configOverride?: NxModuleFederationConfigOverride +) { + if (global.NX_GRAPH_CREATION) { + return (config) => config; + } + + const { sharedLibraries, sharedDependencies, mappedRemotes } = + await getModuleFederationConfig(options, { + isServer: true, + }); + + return (config, { context }: SharedConfigContext) => { + config.target = 'async-node'; + config.output.uniqueName = options.name; + config.optimization = { + ...(config.optimization ?? {}), + runtimeChunk: false, + }; + + config.plugins.push( + // eslint-disable-next-line @typescript-eslint/no-var-requires + new (require('@module-federation/enhanced/rspack').ModuleFederationPlugin)( + { + name: options.name, + filename: 'remoteEntry.js', + exposes: options.exposes, + remotes: mappedRemotes, + shared: { + ...sharedDependencies, + }, + isServer: true, + /** + * Apply user-defined config overrides + */ + ...(configOverride ? configOverride : {}), + runtimePlugins: + process.env.NX_MF_DEV_REMOTES && + !options.disableNxRuntimeLibraryControlPlugin + ? [ + ...(configOverride?.runtimePlugins ?? []), + require.resolve('@module-federation/node/runtimePlugin'), + require.resolve( + '@nx/rspack/src/utils/module-federation/plugins/runtime-library-control.plugin.js' + ), + ] + : [ + ...(configOverride?.runtimePlugins ?? []), + require.resolve('@module-federation/node/runtimePlugin'), + ], + virtualRuntimeEntry: true, + }, + {} + ), + sharedLibraries.getReplacementPlugin() + ); + + // The env var is only set from the module-federation-dev-server + // Attach the runtime plugin + config.plugins.push( + new DefinePlugin({ + 'process.env.NX_MF_DEV_REMOTES': process.env.NX_MF_DEV_REMOTES, + }) + ); + + return config; + }; +} diff --git a/packages/rspack/src/utils/module-federation/with-module-federation/with-module-federation.ts b/packages/rspack/src/utils/module-federation/with-module-federation/with-module-federation.ts new file mode 100644 index 0000000000000..7e3245a76652b --- /dev/null +++ b/packages/rspack/src/utils/module-federation/with-module-federation/with-module-federation.ts @@ -0,0 +1,101 @@ +import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack'; +import type { Configuration } from '@rspack/core'; +import { DefinePlugin } from '@rspack/core'; +import { SharedConfigContext } from '../../model'; +import { + ModuleFederationConfig, + NxModuleFederationConfigOverride, +} from '../models'; +import { getModuleFederationConfig } from './utils'; + +const isVarOrWindow = (libType?: string) => + libType === 'var' || libType === 'window'; + +/** + * @param {ModuleFederationConfig} options + * @param {NxModuleFederationConfigOverride} configOverride + */ +export async function withModuleFederation( + options: ModuleFederationConfig, + configOverride?: NxModuleFederationConfigOverride +) { + if (global.NX_GRAPH_CREATION) { + return function makeConfig(config: Configuration): Configuration { + return config; + }; + } + + const { sharedDependencies, sharedLibraries, mappedRemotes } = + await getModuleFederationConfig(options); + const isGlobal = isVarOrWindow(options.library?.type); + + return function makeConfig( + config: Configuration, + { context }: SharedConfigContext + ): Configuration { + config.output.uniqueName = options.name; + config.output.publicPath = 'auto'; + + if (isGlobal) { + config.output.scriptType = 'text/javascript'; + } + + config.optimization = { + ...(config.optimization ?? {}), + runtimeChunk: false, + }; + + if ( + config.mode === 'development' && + Object.keys(mappedRemotes).length > 1 && + !options.exposes + ) { + config.optimization.runtimeChunk = 'single'; + } + + config.plugins.push( + new ModuleFederationPlugin({ + name: options.name, + filename: 'remoteEntry.js', + exposes: options.exposes, + remotes: mappedRemotes, + shared: { + ...sharedDependencies, + }, + /** + * remoteType: 'script' is required for the remote to be loaded as a script tag. + * remotes will need to be defined as: + * { appX: 'appX@http://localhost:3001/remoteEntry.js' } + * { appY: 'appY@http://localhost:3002/remoteEntry.js' } + */ + ...(isGlobal ? { remoteType: 'script' } : {}), + /** + * Apply user-defined config overrides + */ + ...(configOverride ? configOverride : {}), + runtimePlugins: + process.env.NX_MF_DEV_REMOTES && + !options.disableNxRuntimeLibraryControlPlugin + ? [ + ...(configOverride?.runtimePlugins ?? []), + require.resolve( + '@nx/rspack/src/utils/module-federation/plugins/runtime-library-control.plugin.js' + ), + ] + : configOverride?.runtimePlugins, + virtualRuntimeEntry: true, + }), + sharedLibraries.getReplacementPlugin() + ); + + // The env var is only set from the module-federation-dev-server + // Attach the runtime plugin + config.plugins.push( + new DefinePlugin({ + 'process.env.NX_MF_DEV_REMOTES': process.env.NX_MF_DEV_REMOTES, + }) + ); + + return config; + }; +} diff --git a/packages/rspack/src/utils/normalize-assets.ts b/packages/rspack/src/utils/normalize-assets.ts new file mode 100644 index 0000000000000..2eae341f3efbd --- /dev/null +++ b/packages/rspack/src/utils/normalize-assets.ts @@ -0,0 +1,50 @@ +import { normalizePath } from '@nx/devkit'; +import { statSync } from 'fs'; +import { basename, dirname, relative, resolve } from 'path'; + +export function normalizeAssets( + assets: any[], + root: string, + sourceRoot: string +): any[] { + return assets.map((asset) => { + if (typeof asset === 'string') { + const assetPath = normalizePath(asset); + const resolvedAssetPath = resolve(root, assetPath); + const resolvedSourceRoot = resolve(root, sourceRoot); + + if (!resolvedAssetPath.startsWith(resolvedSourceRoot)) { + throw new Error( + `The ${resolvedAssetPath} asset path must start with the project source root: ${sourceRoot}` + ); + } + + const isDirectory = statSync(resolvedAssetPath).isDirectory(); + const input = isDirectory + ? resolvedAssetPath + : dirname(resolvedAssetPath); + const output = relative(resolvedSourceRoot, resolve(root, input)); + const glob = isDirectory ? '**/*' : basename(resolvedAssetPath); + return { + input, + output, + glob, + }; + } else { + if (asset.output.startsWith('..')) { + throw new Error( + 'An asset cannot be written to a location outside of the output path.' + ); + } + + const assetPath = normalizePath(asset.input); + const resolvedAssetPath = resolve(root, assetPath); + return { + ...asset, + input: resolvedAssetPath, + // Now we remove starting slash to make Webpack place it from the output root. + output: asset.output.replace(/^\//, ''), + }; + } + }); +} diff --git a/packages/rspack/src/utils/read-rspack-options.ts b/packages/rspack/src/utils/read-rspack-options.ts new file mode 100644 index 0000000000000..e526e1c1b6acc --- /dev/null +++ b/packages/rspack/src/utils/read-rspack-options.ts @@ -0,0 +1,42 @@ +import { workspaceRoot } from '@nx/devkit'; +import { Configuration } from '@rspack/core'; +import { isNxRspackComposablePlugin } from './config'; + +/** + * Reads the Rspack options from a give Rspack configuration. The configuration can be: + * 1. A standard config object + * 2. A standard function that returns a config object + * 3. A Nx-specific composable function that takes Nx context, rspack config, and returns the config object. + * + * @param rspackConfig + */ +export async function readRspackOptions( + rspackConfig: unknown +): Promise { + let config: Configuration; + if (isNxRspackComposablePlugin(rspackConfig)) { + config = await rspackConfig( + {}, + { + // These values are only used during build-time, so passing stubs here just to read out + // the returned config object. + options: { + root: workspaceRoot, + projectRoot: '', + sourceRoot: '', + }, + context: { root: workspaceRoot, cwd: undefined, isVerbose: false }, + } + ); + } else if (typeof rspackConfig === 'function') { + config = await rspackConfig( + { + production: true, // we want the production build options + }, + {} + ); + } else { + config = rspackConfig; + } + return config; +} diff --git a/packages/rspack/src/utils/resolve-user-defined-rspack-config.ts b/packages/rspack/src/utils/resolve-user-defined-rspack-config.ts new file mode 100644 index 0000000000000..e4145d9280d89 --- /dev/null +++ b/packages/rspack/src/utils/resolve-user-defined-rspack-config.ts @@ -0,0 +1,44 @@ +import { clearRequireCache } from '@nx/devkit/src/utils/config-utils'; +import { registerTsProject } from '@nx/js/src/internal'; + +export function resolveUserDefinedRspackConfig( + path: string, + tsConfig: string, + /** Skip require cache and return latest content */ + reload = false +) { + if (reload) { + // Clear cache if the path is in the cache + if (require.cache[path]) { + // Clear all entries because config may import other modules + clearRequireCache(); + } + } + + // Don't transpile non-TS files. This prevents workspaces libs from being registered via tsconfig-paths. + // There's an issue here with Nx workspace where loading plugins from source (via tsconfig-paths) can lead to errors. + if (!/\.(ts|mts|cts)$/.test(path)) { + return require(path); + } + + const cleanupTranspiler = registerTsProject(tsConfig); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const maybeCustomRspackConfig = require(path); + cleanupTranspiler(); + + // If the user provides a configuration in TS file + // then there are 3 cases for exporing an object. The first one is: + // `module.exports = { ... }`. And the second one is: + // `export default { ... }`. The ESM format is compiled into: + // `{ default: { ... } }` + // There is also a case of + // `{ default: { default: { ... } }` + const customRspackConfig = + 'default' in maybeCustomRspackConfig + ? 'default' in maybeCustomRspackConfig.default + ? maybeCustomRspackConfig.default.default + : maybeCustomRspackConfig.default + : maybeCustomRspackConfig; + + return customRspackConfig; +} diff --git a/packages/rspack/src/utils/versions.ts b/packages/rspack/src/utils/versions.ts new file mode 100644 index 0000000000000..3a9137a86232f --- /dev/null +++ b/packages/rspack/src/utils/versions.ts @@ -0,0 +1,26 @@ +export const rspackCoreVersion = '1.0.5'; +export const rspackDevServerVersion = '1.0.5'; + +export const rspackPluginMinifyVersion = '^0.7.5'; +export const rspackPluginReactRefreshVersion = '^1.0.0'; +export const lessLoaderVersion = '~11.1.3'; + +export const reactVersion = '~18.2.0'; +export const reactRefreshVersion = '~0.14.0'; +export const reactDomVersion = '~18.2.0'; +export const typesReactVersion = '~18.0.28'; +export const typesReactDomVersion = '~18.0.10'; + +export const nestjsCommonVersion = '~9.0.0'; +export const nestjsCoreVersion = '~9.0.0'; +export const nestjsPlatformExpressVersion = '~9.0.0'; +export const nestjsMicroservicesVersion = '~9.0.0'; + +export const lessVersion = '4.1.3'; +export const sassVersion = '^1.42.1'; +export const stylusVersion = '^0.55.0'; + +export const eslintPluginImportVersion = '2.27.5'; +export const eslintPluginJsxA11yVersion = '6.7.1'; +export const eslintPluginReactVersion = '7.32.2'; +export const eslintPluginReactHooksVersion = '4.6.0'; diff --git a/packages/rspack/src/utils/with-nx.ts b/packages/rspack/src/utils/with-nx.ts new file mode 100644 index 0000000000000..ff3cbd3fa0e83 --- /dev/null +++ b/packages/rspack/src/utils/with-nx.ts @@ -0,0 +1,229 @@ +import { + Configuration, + ExternalItem, + ResolveAlias, + RspackPluginInstance, + rspack, +} from '@rspack/core'; +import { existsSync, readFileSync } from 'fs'; +import { LicenseWebpackPlugin } from 'license-webpack-plugin'; +import * as path from 'path'; +import { join } from 'path'; +import { GeneratePackageJsonPlugin } from '../plugins/generate-package-json-plugin'; +import { getCopyPatterns } from './get-copy-patterns'; +import { SharedConfigContext } from './model'; +import { normalizeAssets } from './normalize-assets'; + +export function withNx(_opts = {}) { + return function makeConfig( + config: Configuration, + { options, context }: SharedConfigContext + ): Configuration { + const isProd = + process.env.NODE_ENV === 'production' || options.mode === 'production'; + + const project = context.projectGraph.nodes[context.projectName]; + const sourceRoot = path.join(context.root, project.data.sourceRoot); + + // eslint-disable-next-line @typescript-eslint/no-var-requires + const tsconfigPaths = require('tsconfig-paths'); + const { paths } = tsconfigPaths.loadConfig(options.tsConfig); + const alias: ResolveAlias = Object.keys(paths).reduce((acc, k) => { + acc[k] = path.join(context.root, paths[k][0]); + return acc; + }, {}); + + const plugins = config.plugins ?? []; + if (options.extractLicenses) { + /** + * Needed to prevent an issue with Rspack and Workspaces where the + * workspace's root package.json file is added to the dependency tree + */ + let rootPackageJsonName; + const pathToRootPackageJson = join(context.root, 'package.json'); + if (existsSync(pathToRootPackageJson)) { + try { + const rootPackageJson = JSON.parse( + readFileSync(pathToRootPackageJson, 'utf-8') + ); + rootPackageJsonName = rootPackageJson.name; + } catch { + // do nothing + } + } + plugins.push( + new LicenseWebpackPlugin({ + stats: { + warnings: false, + errors: false, + }, + outputFilename: `3rdpartylicenses.txt`, + /** + * Needed to prevent an issue with Rspack and Workspaces where the + * workspace's root package.json file is added to the dependency tree + */ + excludedPackageTest: (packageName) => { + if (!rootPackageJsonName) { + return false; + } + return packageName === rootPackageJsonName; + }, + }) as unknown as RspackPluginInstance + ); + } + + if (options.generatePackageJson) { + const mainOutputFile = + options.main.split('/').pop().split('.')[0] + '.js'; + + plugins.push( + new GeneratePackageJsonPlugin( + { + tsConfig: options.tsConfig, + outputFileName: options.outputFileName ?? mainOutputFile, + }, + context + ) + ); + } + + plugins.push( + new rspack.CopyRspackPlugin({ + patterns: getCopyPatterns( + normalizeAssets(options.assets, context.root, sourceRoot) + ), + }) + ); + plugins.push(new rspack.ProgressPlugin()); + + options.fileReplacements.forEach((item) => { + alias[item.replace] = item.with; + }); + + const externals: ExternalItem = {}; + let externalsType: Configuration['externalsType']; + if (options.target === 'node') { + const projectDeps = + context.projectGraph.dependencies[context.projectName]; + for (const dep of Object.values(projectDeps)) { + const externalNode = context.projectGraph.externalNodes[dep.target]; + if (externalNode) { + externals[externalNode.data.packageName] = + externalNode.data.packageName; + } + } + externalsType = 'commonjs'; + } + + const updated: Configuration = { + ...config, + target: options.target, + mode: options.mode, + entry: {}, + context: join( + context.root, + context.projectGraph.nodes[context.projectName].data.root + ), + devtool: + options.sourceMap === 'hidden' + ? ('hidden-source-map' as const) + : options.sourceMap + ? ('source-map' as const) + : (false as const), + output: { + path: path.join(context.root, options.outputPath), + publicPath: '/', + filename: + isProd && options.target !== 'node' + ? '[name].[contenthash:8].js' + : '[name].js', + chunkFilename: + isProd && options.target !== 'node' + ? '[name].[contenthash:8].js' + : '[name].js', + cssFilename: + isProd && options.target !== 'node' + ? '[name].[contenthash:8].css' + : '[name].css', + cssChunkFilename: + isProd && options.target !== 'node' + ? '[name].[contenthash:8].css' + : '[name].css', + assetModuleFilename: + isProd && options.target !== 'node' + ? '[name].[contenthash:8][ext]' + : '[name][ext]', + }, + devServer: { + ...(config.devServer ?? {}), + port: config.devServer?.port ?? 4200, + hot: config.devServer?.hot ?? true, + } as any, + module: { + rules: [ + { + test: /\.js$/, + loader: 'builtin:swc-loader', + exclude: /node_modules/, + options: { + jsc: { + parser: { + syntax: 'ecmascript', + }, + externalHelpers: true, + }, + }, + type: 'javascript/auto', + }, + { + test: /\.ts$/, + loader: 'builtin:swc-loader', + exclude: /node_modules/, + options: { + jsc: { + parser: { + syntax: 'typescript', + decorators: true, + }, + transform: { + legacyDecorator: true, + decoratorMetadata: true, + }, + externalHelpers: true, + }, + }, + type: 'javascript/auto', + }, + ], + }, + plugins: plugins, + resolve: { + // There are some issues resolving workspace libs in a monorepo. + // It looks to be an issue with rspack itself, but will check back after Nx 16 release + // once I can reproduce a small example repo with rspack only. + alias, + // We need to define the extensions that rspack can resolve + extensions: ['...', '.ts', '.tsx', '.jsx'], + // tsConfigPath: path.join(context.root, options.tsConfig), + }, + infrastructureLogging: { + debug: false, + }, + externals, + externalsType, + stats: { + colors: true, + preset: 'normal', + }, + }; + + const mainEntry = options.main + ? options.outputFileName + ? path.parse(options.outputFileName).name + : 'main' + : 'main'; + updated.entry[mainEntry] = path.resolve(context.root, options.main); + + return updated; + }; +} diff --git a/packages/rspack/src/utils/with-react.ts b/packages/rspack/src/utils/with-react.ts new file mode 100644 index 0000000000000..6afd0974c5a3c --- /dev/null +++ b/packages/rspack/src/utils/with-react.ts @@ -0,0 +1,77 @@ +import { Configuration } from '@rspack/core'; +import { SharedConfigContext } from './model'; +import { withWeb } from './with-web'; + +export function withReact(opts = {}) { + return function makeConfig( + config: Configuration, + { options, context }: SharedConfigContext + ): Configuration { + const isDev = + process.env.NODE_ENV === 'development' || options.mode === 'development'; + + config = withWeb({ ...opts, cssModules: true })(config, { + options, + context, + }); + + // eslint-disable-next-line @typescript-eslint/no-var-requires + const ReactRefreshPlugin = require('@rspack/plugin-react-refresh'); + + const react = { + runtime: 'automatic', + development: isDev, + refresh: isDev, + }; + + return { + ...config, + plugins: [ + ...(config.plugins || []), + isDev && new ReactRefreshPlugin(), + ].filter(Boolean), + module: { + ...config.module, + rules: [ + ...(config.module.rules || []), + { + test: /\.jsx$/, + loader: 'builtin:swc-loader', + exclude: /node_modules/, + options: { + jsc: { + parser: { + syntax: 'ecmascript', + jsx: true, + }, + transform: { + react, + }, + externalHelpers: true, + }, + }, + type: 'javascript/auto', + }, + { + test: /\.tsx$/, + loader: 'builtin:swc-loader', + exclude: /node_modules/, + options: { + jsc: { + parser: { + syntax: 'typescript', + tsx: true, + }, + transform: { + react, + }, + externalHelpers: true, + }, + }, + type: 'javascript/auto', + }, + ], + }, + }; + }; +} diff --git a/packages/rspack/src/utils/with-web.ts b/packages/rspack/src/utils/with-web.ts new file mode 100644 index 0000000000000..1d735a4d52744 --- /dev/null +++ b/packages/rspack/src/utils/with-web.ts @@ -0,0 +1,126 @@ +import { Configuration, RuleSetRule, rspack } from '@rspack/core'; +import * as path from 'path'; +import { SharedConfigContext } from './model'; + +export interface WithWebOptions { + stylePreprocessorOptions?: { + includePaths?: string[]; + }; + cssModules?: boolean; +} + +export function withWeb(opts: WithWebOptions = {}) { + return function makeConfig( + config: Configuration, + { options, context }: SharedConfigContext + ): Configuration { + const isProd = + process.env.NODE_ENV === 'production' || options.mode === 'production'; + + const projectRoot = path.join( + context.root, + context.projectGraph.nodes[context.projectName].data.root + ); + + const includePaths: string[] = []; + if (opts?.stylePreprocessorOptions?.includePaths?.length > 0) { + opts.stylePreprocessorOptions.includePaths.forEach( + (includePath: string) => + includePaths.push(path.resolve(context.root, includePath)) + ); + } + + let lessPathOptions: { paths?: string[] } = {}; + + if (includePaths.length > 0) { + lessPathOptions = { + paths: includePaths, + }; + } + + return { + ...config, + experiments: { + css: true, + }, + module: { + ...config.module, + rules: [ + ...(config.module.rules || []), + { + test: /\.css$/, + type: opts?.cssModules ? 'css/module' : undefined, + }, + { + test: /\.css$/, + type: 'css', + use: [ + { + loader: require.resolve('postcss-loader'), + }, + ], + }, + { + test: /\.scss$|\.sass$/, + type: opts?.cssModules ? 'css/module' : undefined, + use: [ + { + loader: require.resolve('sass-loader'), + options: { + sourceMap: !!options.sourceMap, + sassOptions: { + fiber: false, + // bootstrap-sass requires a minimum precision of 8 + precision: 8, + includePaths, + }, + }, + }, + ], + }, + { + test: /.less$/, + type: opts?.cssModules ? 'css/module' : undefined, + use: [ + { + loader: require.resolve('less-loader'), + options: { + sourceMap: !!options.sourceMap, + lessOptions: { + javascriptEnabled: true, + ...lessPathOptions, + }, + }, + }, + ], + }, + { + test: /\.styl$/, + use: [ + { + loader: require.resolve('stylus-loader'), + options: { + sourceMap: !!options.sourceMap, + stylusOptions: { + include: includePaths, + }, + }, + }, + ], + }, + ].filter((a): a is RuleSetRule => !!a), + }, + plugins: [ + ...config.plugins, + new rspack.HtmlRspackPlugin({ + template: options.indexHtml + ? path.join(context.root, options.indexHtml) + : path.join(projectRoot, 'src/index.html'), + }), + new rspack.DefinePlugin({ + 'process.env.NODE_ENV': isProd ? "'production'" : "'development'", + }), + ], + }; + }; +} diff --git a/packages/rspack/tsconfig.json b/packages/rspack/tsconfig.json new file mode 100644 index 0000000000000..19b9eece4df14 --- /dev/null +++ b/packages/rspack/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs" + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/rspack/tsconfig.lib.json b/packages/rspack/tsconfig.lib.json new file mode 100644 index 0000000000000..53e9f950df36d --- /dev/null +++ b/packages/rspack/tsconfig.lib.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "resolveJsonModule": true, + "outDir": "../../dist/out-tsc", + "declaration": true, + "esModuleInterop": true, + "types": ["node"] + }, + "include": ["**/*.ts"], + "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/packages/rspack/tsconfig.spec.json b/packages/rspack/tsconfig.spec.json new file mode 100644 index 0000000000000..546f12877f7f0 --- /dev/null +++ b/packages/rspack/tsconfig.spec.json @@ -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"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7bf4d455e561f..cc379276bfba6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -177,7 +177,7 @@ importers: version: 0.1802.5(chokidar@3.6.0) '@angular-devkit/build-angular': specifier: ~18.2.0 - version: 18.2.5(@angular/compiler-cli@18.2.5(@angular/compiler@18.2.5(@angular/core@18.2.5(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(chokidar@3.6.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@18.19.8)(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(typescript@5.5.4)))(ng-packagr@18.2.1(@angular/compiler-cli@18.2.5(@angular/compiler@18.2.5(@angular/core@18.2.5(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.5.4))(tailwindcss@3.4.4(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(typescript@5.5.4)))(tslib@2.7.0)(typescript@5.5.4))(stylus@0.59.0)(tailwindcss@3.4.4(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(typescript@5.5.4)))(typescript@5.5.4)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) + version: 18.2.5(vlaqpyq5c5jdro3rfvxymdqdnu) '@angular-devkit/core': specifier: ~18.2.0 version: 18.2.5(chokidar@3.6.0) @@ -300,7 +300,7 @@ importers: version: 3.13.2(rollup@4.22.0)(webpack-sources@3.2.3) '@nx/angular': specifier: 19.8.0-beta.2 - version: 19.8.0-beta.2(bia6o4nti5aiqh5ziiynwngwai) + version: 19.8.0-beta.2(@angular-devkit/build-angular@18.2.5(vlaqpyq5c5jdro3rfvxymdqdnu))(@angular-devkit/core@18.2.5(chokidar@3.6.0))(@angular-devkit/schematics@18.2.5(chokidar@3.6.0))(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@schematics/angular@18.2.5(chokidar@3.6.0))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(rxjs@7.8.1)(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) '@nx/cypress': specifier: 19.8.0-beta.2 version: 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(cypress@13.13.0)(eslint@8.57.0)(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0)) @@ -324,10 +324,10 @@ importers: version: 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0)) '@nx/next': specifier: 19.8.0-beta.2 - version: 19.8.0-beta.2(@babel/core@7.25.2)(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(next@14.2.5(@babel/core@7.25.2)(@playwright/test@1.47.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) + version: 19.8.0-beta.2(@babel/core@7.25.2)(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(next@14.2.5(@babel/core@7.25.2)(@playwright/test@1.47.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) '@nx/playwright': specifier: 19.8.0-beta.2 - version: 19.8.0-beta.2(@babel/traverse@7.25.6)(@playwright/test@1.47.1)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(vite@5.0.8(@types/node@18.19.8)(less@4.1.3)(sass@1.55.0)(stylus@0.59.0)(terser@5.31.6))(vitest@1.3.1(@types/node@18.19.8)(jsdom@20.0.3)(less@4.1.3)(sass@1.55.0)(stylus@0.59.0)(terser@5.31.6))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) + version: 19.8.0-beta.2(@babel/traverse@7.25.6)(@playwright/test@1.47.1)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(vite@5.0.8(@types/node@18.19.8)(less@4.1.3)(sass@1.55.0)(stylus@0.59.0)(terser@5.31.6))(vitest@1.3.1(@types/node@18.19.8)(jsdom@20.0.3)(less@4.1.3)(sass@1.55.0)(stylus@0.59.0)(terser@5.31.6))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) '@nx/powerpack-license': specifier: 0.0.2-alpha.4 version: 0.0.2-alpha.4 @@ -351,7 +351,7 @@ importers: version: 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0)) '@nx/webpack': specifier: 19.8.0-beta.2 - version: 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) + version: 19.8.0-beta.2(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) '@phenomnomnominal/tsquery': specifier: ~5.0.1 version: 5.0.1(typescript@5.5.4) @@ -391,6 +391,18 @@ importers: '@rollup/plugin-url': specifier: ^8.0.2 version: 8.0.2(rollup@4.22.0) + '@rspack/core': + specifier: 1.0.5 + version: 1.0.5(@swc/helpers@0.5.11) + '@rspack/dev-server': + specifier: 1.0.5 + version: 1.0.5(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@types/express@4.17.14)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) + '@rspack/plugin-minify': + specifier: ^0.7.5 + version: 0.7.5 + '@rspack/plugin-react-refresh': + specifier: ^1.0.0 + version: 1.0.0(react-refresh@0.10.0) '@schematics/angular': specifier: ~18.2.0 version: 18.2.5(chokidar@3.6.0) @@ -411,7 +423,7 @@ importers: version: 8.3.2(@storybook/test@8.3.2(storybook@8.3.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.22.0)(storybook@8.3.2)(typescript@5.5.4)(vite@5.0.8(@types/node@18.19.8)(less@4.1.3)(sass@1.55.0)(stylus@0.59.0)(terser@5.31.6))(webpack-sources@3.2.3) '@storybook/react-webpack5': specifier: ^8.2.8 - version: 8.3.2(@storybook/test@8.3.2(storybook@8.3.2))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2)(typescript@5.5.4)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) + version: 8.3.2(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@storybook/test@8.3.2(storybook@8.3.2))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2)(typescript@5.5.4)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) '@storybook/types': specifier: ^8.2.8 version: 8.3.2(storybook@8.3.2) @@ -2390,6 +2402,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.16.3': + resolution: {integrity: sha512-RolFVeinkeraDvN/OoRf1F/lP0KUfGNb5jxy/vkIMeRRChkrX/HTYN6TYZosRJs3a1+8wqpxAo5PI5hFmxyPRg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.17.6': resolution: {integrity: sha512-YnYSCceN/dUzUr5kdtUzB+wZprCafuD89Hs0Aqv9QSdwhYQybhXTaSTcrl6X/aWThn1a/j0eEpUBGOE7269REg==} engines: {node: '>=12'} @@ -2426,6 +2444,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm@0.16.3': + resolution: {integrity: sha512-mueuEoh+s1eRbSJqq9KNBQwI4QhQV6sRXIfTyLXSHGMpyew61rOK4qY21uKbXl1iBoMb0AdL1deWFCQVlN2qHA==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.17.6': resolution: {integrity: sha512-bSC9YVUjADDy1gae8RrioINU6e1lCkg3VGVwm0QQ2E1CWcC4gnMce9+B6RpxuSsrsXsk1yojn7sp1fnG8erE2g==} engines: {node: '>=12'} @@ -2462,6 +2486,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-x64@0.16.3': + resolution: {integrity: sha512-SFpTUcIT1bIJuCCBMCQWq1bL2gPTjWoLZdjmIhjdcQHaUfV41OQfho6Ici5uvvkMmZRXIUGpM3GxysP/EU7ifQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.17.6': resolution: {integrity: sha512-MVcYcgSO7pfu/x34uX9u2QIZHmXAB7dEiLQC5bBl5Ryqtpj9lT2sg3gNDEsrPEmimSJW2FXIaxqSQ501YLDsZQ==} engines: {node: '>=12'} @@ -2498,6 +2528,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.16.3': + resolution: {integrity: sha512-DO8WykMyB+N9mIDfI/Hug70Dk1KipavlGAecxS3jDUwAbTpDXj0Lcwzw9svkhxfpCagDmpaTMgxWK8/C/XcXvw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.17.6': resolution: {integrity: sha512-bsDRvlbKMQMt6Wl08nHtFz++yoZHsyTOxnjfB2Q95gato+Yi4WnRl13oC2/PJJA9yLCoRv9gqT/EYX0/zDsyMA==} engines: {node: '>=12'} @@ -2534,6 +2570,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.16.3': + resolution: {integrity: sha512-uEqZQ2omc6BvWqdCiyZ5+XmxuHEi1SPzpVxXCSSV2+Sh7sbXbpeNhHIeFrIpRjAs0lI1FmA1iIOxFozKBhKgRQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.17.6': resolution: {integrity: sha512-xh2A5oPrYRfMFz74QXIQTQo8uA+hYzGWJFoeTE8EvoZGHb+idyV4ATaukaUvnnxJiauhs/fPx3vYhU4wiGfosg==} engines: {node: '>=12'} @@ -2570,6 +2612,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.16.3': + resolution: {integrity: sha512-nJansp3sSXakNkOD5i5mIz2Is/HjzIhFs49b1tjrPrpCmwgBmH9SSzhC/Z1UqlkivqMYkhfPwMw1dGFUuwmXhw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.17.6': resolution: {integrity: sha512-EnUwjRc1inT4ccZh4pB3v1cIhohE2S4YXlt1OvI7sw/+pD+dIE4smwekZlEPIwY6PhU6oDWwITrQQm5S2/iZgg==} engines: {node: '>=12'} @@ -2606,6 +2654,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.16.3': + resolution: {integrity: sha512-TfoDzLw+QHfc4a8aKtGSQ96Wa+6eimljjkq9HKR0rHlU83vw8aldMOUSJTUDxbcUdcgnJzPaX8/vGWm7vyV7ug==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.17.6': resolution: {integrity: sha512-Uh3HLWGzH6FwpviUcLMKPCbZUAFzv67Wj5MTwK6jn89b576SR2IbEp+tqUHTr8DIl0iDmBAf51MVaP7pw6PY5Q==} engines: {node: '>=12'} @@ -2642,6 +2696,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.16.3': + resolution: {integrity: sha512-7I3RlsnxEFCHVZNBLb2w7unamgZ5sVwO0/ikE2GaYvYuUQs9Qte/w7TqWcXHtCwxvZx/2+F97ndiUQAWs47ZfQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.17.6': resolution: {integrity: sha512-bUR58IFOMJX523aDVozswnlp5yry7+0cRLCXDsxnUeQYJik1DukMY+apBsLOZJblpH+K7ox7YrKrHmJoWqVR9w==} engines: {node: '>=12'} @@ -2678,6 +2738,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.16.3': + resolution: {integrity: sha512-VwswmSYwVAAq6LysV59Fyqk3UIjbhuc6wb3vEcJ7HEJUtFuLK9uXWuFoH1lulEbE4+5GjtHi3MHX+w1gNHdOWQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.17.6': resolution: {integrity: sha512-7YdGiurNt7lqO0Bf/U9/arrPWPqdPqcV6JCZda4LZgEn+PTQ5SMEI4MGR52Bfn3+d6bNEGcWFzlIxiQdS48YUw==} engines: {node: '>=12'} @@ -2714,6 +2780,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.16.3': + resolution: {integrity: sha512-X8FDDxM9cqda2rJE+iblQhIMYY49LfvW4kaEjoFbTTQ4Go8G96Smj2w3BRTwA8IHGoi9dPOPGAX63dhuv19UqA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.17.6': resolution: {integrity: sha512-ujp8uoQCM9FRcbDfkqECoARsLnLfCUhKARTP56TFPog8ie9JG83D5GVKjQ6yVrEVdMie1djH86fm98eY3quQkQ==} engines: {node: '>=12'} @@ -2750,6 +2822,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.16.3': + resolution: {integrity: sha512-hIbeejCOyO0X9ujfIIOKjBjNAs9XD/YdJ9JXAy1lHA+8UXuOqbFe4ErMCqMr8dhlMGBuvcQYGF7+kO7waj2KHw==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.17.6': resolution: {integrity: sha512-y2NX1+X/Nt+izj9bLoiaYB9YXT/LoaQFYvCkVD77G/4F+/yuVXYCWz4SE9yr5CBMbOxOfBcy/xFL4LlOeNlzYQ==} engines: {node: '>=12'} @@ -2786,6 +2864,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.16.3': + resolution: {integrity: sha512-znFRzICT/V8VZQMt6rjb21MtAVJv/3dmKRMlohlShrbVXdBuOdDrGb+C2cZGQAR8RFyRe7HS6klmHq103WpmVw==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.17.6': resolution: {integrity: sha512-09AXKB1HDOzXD+j3FdXCiL/MWmZP0Ex9eR8DLMBVcHorrWJxWmY8Nms2Nm41iRM64WVx7bA/JVHMv081iP2kUA==} engines: {node: '>=12'} @@ -2822,6 +2906,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.16.3': + resolution: {integrity: sha512-EV7LuEybxhXrVTDpbqWF2yehYRNz5e5p+u3oQUS2+ZFpknyi1NXxr8URk4ykR8Efm7iu04//4sBg249yNOwy5Q==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.17.6': resolution: {integrity: sha512-AmLhMzkM8JuqTIOhxnX4ubh0XWJIznEynRnZAVdA2mMKE6FAfwT2TWKTwdqMG+qEaeyDPtfNoZRpJbD4ZBv0Tg==} engines: {node: '>=12'} @@ -2858,6 +2948,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.16.3': + resolution: {integrity: sha512-uDxqFOcLzFIJ+r/pkTTSE9lsCEaV/Y6rMlQjUI9BkzASEChYL/aSQjZjchtEmdnVxDKETnUAmsaZ4pqK1eE5BQ==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.17.6': resolution: {integrity: sha512-Y4Ri62PfavhLQhFbqucysHOmRamlTVK10zPWlqjNbj2XMea+BOs4w6ASKwQwAiqf9ZqcY9Ab7NOU4wIgpxwoSQ==} engines: {node: '>=12'} @@ -2894,6 +2990,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.16.3': + resolution: {integrity: sha512-NbeREhzSxYwFhnCAQOQZmajsPYtX71Ufej3IQ8W2Gxskfz9DK58ENEju4SbpIj48VenktRASC52N5Fhyf/aliQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.17.6': resolution: {integrity: sha512-SPUiz4fDbnNEm3JSdUW8pBJ/vkop3M1YwZAVwvdwlFLoJwKEZ9L98l3tzeyMzq27CyepDQ3Qgoba44StgbiN5Q==} engines: {node: '>=12'} @@ -2930,6 +3032,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.16.3': + resolution: {integrity: sha512-SDiG0nCixYO9JgpehoKgScwic7vXXndfasjnD5DLbp1xltANzqZ425l7LSdHynt19UWOcDjG9wJJzSElsPvk0w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.17.6': resolution: {integrity: sha512-a3yHLmOodHrzuNgdpB7peFGPx1iJ2x6m+uDvhP2CKdr2CwOaqEFMeSqYAHU7hG+RjCq8r2NFujcd/YsEsFgTGw==} engines: {node: '>=12'} @@ -2966,6 +3074,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/netbsd-x64@0.16.3': + resolution: {integrity: sha512-AzbsJqiHEq1I/tUvOfAzCY15h4/7Ivp3ff/o1GpP16n48JMNAtbW0qui2WCgoIZArEHD0SUQ95gvR0oSO7ZbdA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.17.6': resolution: {integrity: sha512-EanJqcU/4uZIBreTrnbnre2DXgXSa+Gjap7ifRfllpmyAU7YMvaXmljdArptTHmjrkkKm9BK6GH5D5Yo+p6y5A==} engines: {node: '>=12'} @@ -3014,6 +3128,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.16.3': + resolution: {integrity: sha512-gSABi8qHl8k3Cbi/4toAzHiykuBuWLZs43JomTcXkjMZVkp0gj3gg9mO+9HJW/8GB5H89RX/V0QP4JGL7YEEVg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.17.6': resolution: {integrity: sha512-xaxeSunhQRsTNGFanoOkkLtnmMn5QbA0qBhNet/XLVsc+OVkpIWPHcr3zTW2gxVU5YOHFbIHR9ODuaUdNza2Vw==} engines: {node: '>=12'} @@ -3050,6 +3170,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/sunos-x64@0.16.3': + resolution: {integrity: sha512-SF9Kch5Ete4reovvRO6yNjMxrvlfT0F0Flm+NPoUw5Z4Q3r1d23LFTgaLwm3Cp0iGbrU/MoUI+ZqwCv5XJijCw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.17.6': resolution: {integrity: sha512-gnMnMPg5pfMkZvhHee21KbKdc6W3GR8/JuE0Da1kjwpK6oiFU3nqfHuVPgUX2rsOx9N2SadSQTIYV1CIjYG+xw==} engines: {node: '>=12'} @@ -3086,6 +3212,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.16.3': + resolution: {integrity: sha512-u5aBonZIyGopAZyOnoPAA6fGsDeHByZ9CnEzyML9NqntK6D/xl5jteZUKm/p6nD09+v3pTM6TuUIqSPcChk5gg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.17.6': resolution: {integrity: sha512-G95n7vP1UnGJPsVdKXllAJPtqjMvFYbN20e8RK8LVLhlTiSOH1sd7+Gt7rm70xiG+I5tM58nYgwWrLs6I1jHqg==} engines: {node: '>=12'} @@ -3122,6 +3254,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.16.3': + resolution: {integrity: sha512-GlgVq1WpvOEhNioh74TKelwla9KDuAaLZrdxuuUgsP2vayxeLgVc+rbpIv0IYF4+tlIzq2vRhofV+KGLD+37EQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.17.6': resolution: {integrity: sha512-96yEFzLhq5bv9jJo5JhTs1gI+1cKQ83cUpyxHuGqXVwQtY5Eq54ZEsKs8veKtiKwlrNimtckHEkj4mRh4pPjsg==} engines: {node: '>=12'} @@ -3158,6 +3296,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.16.3': + resolution: {integrity: sha512-5/JuTd8OWW8UzEtyf19fbrtMJENza+C9JoPIkvItgTBQ1FO2ZLvjbPO6Xs54vk0s5JB5QsfieUEshRQfu7ZHow==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.17.6': resolution: {integrity: sha512-n6d8MOyUrNp6G4VSpRcgjs5xj4A91svJSaiwLIDWVWEsZtpN5FA9NlBbZHDmAJc2e8e6SF4tkBD3HAvPF+7igA==} engines: {node: '>=12'} @@ -3607,18 +3751,30 @@ packages: vue-tsc: optional: true + '@module-federation/runtime-tools@0.5.1': + resolution: {integrity: sha512-nfBedkoZ3/SWyO0hnmaxuz0R0iGPSikHZOAZ0N/dVSQaIzlffUo35B5nlC2wgWIc0JdMZfkwkjZRrnuuDIJbzg==} + '@module-federation/runtime-tools@0.6.1': resolution: {integrity: sha512-pnc0VZb3jjxrRI1t/K+QfSQ4Viim6VwoQQIeefBLzd2SIMD/LhrYfGzz/glrVHGnAFpxOE/pVANdqlknTMAerw==} + '@module-federation/runtime@0.5.1': + resolution: {integrity: sha512-xgiMUWwGLWDrvZc9JibuEbXIbhXg6z2oUkemogSvQ4LKvrl/n0kbqP1Blk669mXzyWbqtSp6PpvNdwaE1aN5xQ==} + '@module-federation/runtime@0.6.1': resolution: {integrity: sha512-57NvdAwrQagw7bEAjvIav8sU0srTT5O+ul8hl86j2a3I5gYBF6NN2QGx4w5d/exgXUcqmUQZBV6I3q5NrcRzfg==} + '@module-federation/sdk@0.5.1': + resolution: {integrity: sha512-exvchtjNURJJkpqjQ3/opdbfeT2wPKvrbnGnyRkrwW5o3FH1LaST1tkiNviT6OXTexGaVc2DahbdniQHVtQ7pA==} + '@module-federation/sdk@0.6.1': resolution: {integrity: sha512-OjDW2sYz4iaZnu4rpTlYDzjXAQiLLzCPwIFM+UFvRtdgWN08Bjn5sDRgMzP2x2ziLzonwmEFDEK9Sl2h85NtJg==} '@module-federation/third-party-dts-extractor@0.6.1': resolution: {integrity: sha512-65Xs4CRXkxLyGYhC4+XJ2Y7qcDsReI+xWt7bREe057PI/ZYp9FAbBO3M5E1eLVHeDdwcU5IQ9RigzGau527VbQ==} + '@module-federation/webpack-bundler-runtime@0.5.1': + resolution: {integrity: sha512-mMhRFH0k2VjwHt3Jol9JkUsmI/4XlrAoBG3E0o7HoyoPYv1UFOWyqAflfANcUPgbYpvqmyLzDcO+3IT36LXnrA==} + '@module-federation/webpack-bundler-runtime@0.6.1': resolution: {integrity: sha512-Gl8iSAS/xvdlicQPFA7URkwUU2HhjQ1L7tHL7s9TWecuDb12Pj0DMkRqE3QimYvitXtvgDqk9B7YUz+diYuCFA==} @@ -5555,6 +5711,84 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + '@rspack/binding-darwin-arm64@1.0.5': + resolution: {integrity: sha512-pEHj4AOluOa7FaR1DMACPUUZKO3qZI4/66xaTqk0BbclvMT7eheQAWtkmjdE9WJgeZ389TrwZeaMzzPdHhK/6Q==} + cpu: [arm64] + os: [darwin] + + '@rspack/binding-darwin-x64@1.0.5': + resolution: {integrity: sha512-xS5EDD9l3MHL54bnmxsndm61P9l3l7ZNuLSuPl2MbYJzDqPdnXhTdkIjdcDOLH2daFm8gfB634wa5knZhPGLOw==} + cpu: [x64] + os: [darwin] + + '@rspack/binding-linux-arm64-gnu@1.0.5': + resolution: {integrity: sha512-svPOFlem7s6T33tX8a28uD5Ngc7bdML96ioiH7Fhi0J/at+WAthor4GeUNwkwuzBQI/Nc9XCgiYPcE0pzP7c6w==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-arm64-musl@1.0.5': + resolution: {integrity: sha512-cysqogEUNc0TgzzXcK9bkv12eoCjqhLzOvGXQU1zSEU9Hov7tuzMDl3Z6R3A7NgOCmWu84/wOnTrkSOI28caew==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-x64-gnu@1.0.5': + resolution: {integrity: sha512-qIEMsWOzTKpVm0Sg553gKkua49Kd/sElLD1rZcXjjxjAsD97uq8AiNncArMfYdDKgkKbtwtW/Fb3uVuafTLnZg==} + cpu: [x64] + os: [linux] + + '@rspack/binding-linux-x64-musl@1.0.5': + resolution: {integrity: sha512-yulltMSQN3aBt3NMURYTmJcpAJBi4eEJ4i9qF0INE8f0885sJpI0j35/31POkCghG1ZOSZkYALFrheKKP9e8pg==} + cpu: [x64] + os: [linux] + + '@rspack/binding-win32-arm64-msvc@1.0.5': + resolution: {integrity: sha512-5oF/qN6TnUj28UAdaOgSIWKq7HG5QgI4p37zvQBBTXZHhrwN2kE6H+TaofWnSqWJynwmGIxJIx8bGo3lDfFbfA==} + cpu: [arm64] + os: [win32] + + '@rspack/binding-win32-ia32-msvc@1.0.5': + resolution: {integrity: sha512-y16IPjd/z6L7+r6RXLu7J/jlZDUenSnJDqo10HnnxtLjOJ+vna+pljI8sHcwu1ao0c3J3uMvbkF34dTiev7Opg==} + cpu: [ia32] + os: [win32] + + '@rspack/binding-win32-x64-msvc@1.0.5': + resolution: {integrity: sha512-PSBTbDSgT+ClYvyQTDtWBi/bxXW/xJmVjg9NOWe8KAEl5WNU+pToiCBLLPCGDSa+K7/zr2TDb6QakG/qYItPZw==} + cpu: [x64] + os: [win32] + + '@rspack/binding@1.0.5': + resolution: {integrity: sha512-SnVrzRWeKSosJ0/1e5taAeqJ1ISst6NAE1N8YK4ZdUEVWmE26tC2V/yTvZHSsqatc/0Cf+A18IZJx0q6H/DlRw==} + + '@rspack/core@1.0.5': + resolution: {integrity: sha512-UlydS2VupZ6yBx3jCqCHpeEUQNWCrBkTQhPIezK0eCAk13i745byjqXX4tcfN6jR5Kjh/1CIb8r07k9DgGON1w==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@swc/helpers': '>=0.5.1' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@rspack/dev-server@1.0.5': + resolution: {integrity: sha512-S1o1j9adjqNCiSWrIv1vmVHQPXFvcBa9JvPWIGxGjei72ejz0zvO6Fd948UkRlDgCPIoY4Cy+g1GLmBkJT5MKA==} + peerDependencies: + '@rspack/core': '*' + + '@rspack/lite-tapable@1.0.0': + resolution: {integrity: sha512-7MZf4lburSUZoEenwazwUDKHhqyfnLCGnQ/tKcUtztfmVzfjZfRn/EaiT0AKkYGnL2U8AGsw89oUeVyvaOLVCw==} + engines: {node: '>=16.0.0'} + + '@rspack/plugin-minify@0.7.5': + resolution: {integrity: sha512-BgsLdb4vUOQjOukMEBM/8NZZlC9MU/Rs6lt2ZQwZ1lF8vNyuLGPSTtGYM4+fcU3YWRmMgietIEHQDEGkdMlG0g==} + deprecated: this package is deprecated use terser-webpack-plugin instead + + '@rspack/plugin-react-refresh@1.0.0': + resolution: {integrity: sha512-WvXkLewW5G0Mlo5H1b251yDh5FFiH4NDAbYlFpvFjcuXX2AchZRf9zdw57BDE/ADyWsJgA8kixN/zZWBTN3iYA==} + peerDependencies: + react-refresh: '>=0.10.0 <1.0.0' + peerDependenciesMeta: + react-refresh: + optional: true + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -6626,6 +6860,9 @@ packages: '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@types/retry@0.12.2': resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} @@ -9362,6 +9599,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.16.3: + resolution: {integrity: sha512-71f7EjPWTiSguen8X/kxEpkAS7BFHwtQKisCDDV3Y4GLGWBaoSCyD5uXkaUew6JDzA9FEN1W23mdnSwW9kqCeg==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.17.6: resolution: {integrity: sha512-TKFRp9TxrJDdRWfSsSERKEovm6v30iHnrjlcGhLBOtReE28Yp1VSBRfO3GTaOFMoxsNerx4TjrhzSuma9ha83Q==} engines: {node: '>=12'} @@ -12883,6 +13125,10 @@ packages: resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} engines: {node: '>=8'} + p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + p-retry@6.2.0: resolution: {integrity: sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==} engines: {node: '>=16.17'} @@ -15510,6 +15756,11 @@ packages: uglify-js: optional: true + terser@5.16.1: + resolution: {integrity: sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==} + engines: {node: '>=10'} + hasBin: true + terser@5.31.6: resolution: {integrity: sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==} engines: {node: '>=10'} @@ -17083,7 +17334,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@18.2.5(@angular/compiler-cli@18.2.5(@angular/compiler@18.2.5(@angular/core@18.2.5(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(chokidar@3.6.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@18.19.8)(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(typescript@5.5.4)))(ng-packagr@18.2.1(@angular/compiler-cli@18.2.5(@angular/compiler@18.2.5(@angular/core@18.2.5(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.5.4))(tailwindcss@3.4.4(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(typescript@5.5.4)))(tslib@2.7.0)(typescript@5.5.4))(stylus@0.59.0)(tailwindcss@3.4.4(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(typescript@5.5.4)))(typescript@5.5.4)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))': + '@angular-devkit/build-angular@18.2.5(vlaqpyq5c5jdro3rfvxymdqdnu)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1802.5(chokidar@3.6.0) @@ -17109,7 +17360,7 @@ snapshots: browserslist: 4.23.3 copy-webpack-plugin: 12.0.2(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))) critters: 0.0.24 - css-loader: 7.1.2(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))) + css-loader: 7.1.2(@rspack/core@1.0.5(@swc/helpers@0.5.11))(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))) esbuild-wasm: 0.23.0 fast-glob: 3.3.2 http-proxy-middleware: 3.0.0 @@ -17118,7 +17369,7 @@ snapshots: jsonc-parser: 3.3.1 karma-source-map-support: 1.4.0 less: 4.2.0 - less-loader: 12.2.0(less@4.2.0)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))) + less-loader: 12.2.0(@rspack/core@1.0.5(@swc/helpers@0.5.11))(less@4.2.0)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))) license-webpack-plugin: 4.0.2(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))) loader-utils: 3.3.1 magic-string: 0.30.11 @@ -17130,11 +17381,11 @@ snapshots: picomatch: 4.0.2 piscina: 4.6.1 postcss: 8.4.41 - postcss-loader: 8.1.1(postcss@8.4.41)(typescript@5.5.4)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))) + postcss-loader: 8.1.1(@rspack/core@1.0.5(@swc/helpers@0.5.11))(postcss@8.4.41)(typescript@5.5.4)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.77.6 - sass-loader: 16.0.0(sass@1.77.6)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))) + sass-loader: 16.0.0(@rspack/core@1.0.5(@swc/helpers@0.5.11))(sass@1.77.6)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))) semver: 7.6.3 source-map-loader: 5.0.0(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))) source-map-support: 0.5.21 @@ -19187,6 +19438,9 @@ snapshots: '@esbuild/aix-ppc64@0.23.1': optional: true + '@esbuild/android-arm64@0.16.3': + optional: true + '@esbuild/android-arm64@0.17.6': optional: true @@ -19205,6 +19459,9 @@ snapshots: '@esbuild/android-arm64@0.23.1': optional: true + '@esbuild/android-arm@0.16.3': + optional: true + '@esbuild/android-arm@0.17.6': optional: true @@ -19223,6 +19480,9 @@ snapshots: '@esbuild/android-arm@0.23.1': optional: true + '@esbuild/android-x64@0.16.3': + optional: true + '@esbuild/android-x64@0.17.6': optional: true @@ -19241,6 +19501,9 @@ snapshots: '@esbuild/android-x64@0.23.1': optional: true + '@esbuild/darwin-arm64@0.16.3': + optional: true + '@esbuild/darwin-arm64@0.17.6': optional: true @@ -19259,6 +19522,9 @@ snapshots: '@esbuild/darwin-arm64@0.23.1': optional: true + '@esbuild/darwin-x64@0.16.3': + optional: true + '@esbuild/darwin-x64@0.17.6': optional: true @@ -19277,6 +19543,9 @@ snapshots: '@esbuild/darwin-x64@0.23.1': optional: true + '@esbuild/freebsd-arm64@0.16.3': + optional: true + '@esbuild/freebsd-arm64@0.17.6': optional: true @@ -19295,6 +19564,9 @@ snapshots: '@esbuild/freebsd-arm64@0.23.1': optional: true + '@esbuild/freebsd-x64@0.16.3': + optional: true + '@esbuild/freebsd-x64@0.17.6': optional: true @@ -19313,6 +19585,9 @@ snapshots: '@esbuild/freebsd-x64@0.23.1': optional: true + '@esbuild/linux-arm64@0.16.3': + optional: true + '@esbuild/linux-arm64@0.17.6': optional: true @@ -19331,6 +19606,9 @@ snapshots: '@esbuild/linux-arm64@0.23.1': optional: true + '@esbuild/linux-arm@0.16.3': + optional: true + '@esbuild/linux-arm@0.17.6': optional: true @@ -19349,6 +19627,9 @@ snapshots: '@esbuild/linux-arm@0.23.1': optional: true + '@esbuild/linux-ia32@0.16.3': + optional: true + '@esbuild/linux-ia32@0.17.6': optional: true @@ -19367,6 +19648,9 @@ snapshots: '@esbuild/linux-ia32@0.23.1': optional: true + '@esbuild/linux-loong64@0.16.3': + optional: true + '@esbuild/linux-loong64@0.17.6': optional: true @@ -19385,6 +19669,9 @@ snapshots: '@esbuild/linux-loong64@0.23.1': optional: true + '@esbuild/linux-mips64el@0.16.3': + optional: true + '@esbuild/linux-mips64el@0.17.6': optional: true @@ -19403,6 +19690,9 @@ snapshots: '@esbuild/linux-mips64el@0.23.1': optional: true + '@esbuild/linux-ppc64@0.16.3': + optional: true + '@esbuild/linux-ppc64@0.17.6': optional: true @@ -19421,6 +19711,9 @@ snapshots: '@esbuild/linux-ppc64@0.23.1': optional: true + '@esbuild/linux-riscv64@0.16.3': + optional: true + '@esbuild/linux-riscv64@0.17.6': optional: true @@ -19439,6 +19732,9 @@ snapshots: '@esbuild/linux-riscv64@0.23.1': optional: true + '@esbuild/linux-s390x@0.16.3': + optional: true + '@esbuild/linux-s390x@0.17.6': optional: true @@ -19457,6 +19753,9 @@ snapshots: '@esbuild/linux-s390x@0.23.1': optional: true + '@esbuild/linux-x64@0.16.3': + optional: true + '@esbuild/linux-x64@0.17.6': optional: true @@ -19475,6 +19774,9 @@ snapshots: '@esbuild/linux-x64@0.23.1': optional: true + '@esbuild/netbsd-x64@0.16.3': + optional: true + '@esbuild/netbsd-x64@0.17.6': optional: true @@ -19499,6 +19801,9 @@ snapshots: '@esbuild/openbsd-arm64@0.23.1': optional: true + '@esbuild/openbsd-x64@0.16.3': + optional: true + '@esbuild/openbsd-x64@0.17.6': optional: true @@ -19517,6 +19822,9 @@ snapshots: '@esbuild/openbsd-x64@0.23.1': optional: true + '@esbuild/sunos-x64@0.16.3': + optional: true + '@esbuild/sunos-x64@0.17.6': optional: true @@ -19535,6 +19843,9 @@ snapshots: '@esbuild/sunos-x64@0.23.1': optional: true + '@esbuild/win32-arm64@0.16.3': + optional: true + '@esbuild/win32-arm64@0.17.6': optional: true @@ -19553,6 +19864,9 @@ snapshots: '@esbuild/win32-arm64@0.23.1': optional: true + '@esbuild/win32-ia32@0.16.3': + optional: true + '@esbuild/win32-ia32@0.17.6': optional: true @@ -19571,6 +19885,9 @@ snapshots: '@esbuild/win32-ia32@0.23.1': optional: true + '@esbuild/win32-x64@0.16.3': + optional: true + '@esbuild/win32-x64@0.17.6': optional: true @@ -20196,15 +20513,26 @@ snapshots: - supports-color - utf-8-validate + '@module-federation/runtime-tools@0.5.1': + dependencies: + '@module-federation/runtime': 0.5.1 + '@module-federation/webpack-bundler-runtime': 0.5.1 + '@module-federation/runtime-tools@0.6.1': dependencies: '@module-federation/runtime': 0.6.1 '@module-federation/webpack-bundler-runtime': 0.6.1 + '@module-federation/runtime@0.5.1': + dependencies: + '@module-federation/sdk': 0.5.1 + '@module-federation/runtime@0.6.1': dependencies: '@module-federation/sdk': 0.6.1 + '@module-federation/sdk@0.5.1': {} + '@module-federation/sdk@0.6.1': {} '@module-federation/third-party-dts-extractor@0.6.1': @@ -20213,6 +20541,11 @@ snapshots: fs-extra: 9.1.0 resolve: 1.22.8 + '@module-federation/webpack-bundler-runtime@0.5.1': + dependencies: + '@module-federation/runtime': 0.5.1 + '@module-federation/sdk': 0.5.1 + '@module-federation/webpack-bundler-runtime@0.6.1': dependencies: '@module-federation/runtime': 0.6.1 @@ -20911,9 +21244,9 @@ snapshots: - bluebird - supports-color - '@nrwl/angular@19.8.0-beta.2(bia6o4nti5aiqh5ziiynwngwai)': + '@nrwl/angular@19.8.0-beta.2(@angular-devkit/build-angular@18.2.5(vlaqpyq5c5jdro3rfvxymdqdnu))(@angular-devkit/core@18.2.5(chokidar@3.6.0))(@angular-devkit/schematics@18.2.5(chokidar@3.6.0))(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@schematics/angular@18.2.5(chokidar@3.6.0))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(rxjs@7.8.1)(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))': dependencies: - '@nx/angular': 19.8.0-beta.2(bia6o4nti5aiqh5ziiynwngwai) + '@nx/angular': 19.8.0-beta.2(@angular-devkit/build-angular@18.2.5(vlaqpyq5c5jdro3rfvxymdqdnu))(@angular-devkit/core@18.2.5(chokidar@3.6.0))(@angular-devkit/schematics@18.2.5(chokidar@3.6.0))(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@schematics/angular@18.2.5(chokidar@3.6.0))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(rxjs@7.8.1)(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) tslib: 2.7.0 transitivePeerDependencies: - '@angular-devkit/build-angular' @@ -21063,9 +21396,9 @@ snapshots: - typescript - verdaccio - '@nrwl/next@19.8.0-beta.2(@babel/core@7.25.2)(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(next@14.2.5(@babel/core@7.25.2)(@playwright/test@1.47.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4))': + '@nrwl/next@19.8.0-beta.2(@babel/core@7.25.2)(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(next@14.2.5(@babel/core@7.25.2)(@playwright/test@1.47.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4))': dependencies: - '@nx/next': 19.8.0-beta.2(@babel/core@7.25.2)(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(next@14.2.5(@babel/core@7.25.2)(@playwright/test@1.47.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) + '@nx/next': 19.8.0-beta.2(@babel/core@7.25.2)(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(next@14.2.5(@babel/core@7.25.2)(@playwright/test@1.47.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) transitivePeerDependencies: - '@babel/core' - '@babel/traverse' @@ -21189,9 +21522,9 @@ snapshots: - typescript - verdaccio - '@nrwl/webpack@19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))': + '@nrwl/webpack@19.8.0-beta.2(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))': dependencies: - '@nx/webpack': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) + '@nx/webpack': 19.8.0-beta.2(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) transitivePeerDependencies: - '@babel/traverse' - '@parcel/css' @@ -21445,18 +21778,18 @@ snapshots: transitivePeerDependencies: - encoding - '@nx/angular@19.8.0-beta.2(bia6o4nti5aiqh5ziiynwngwai)': + '@nx/angular@19.8.0-beta.2(@angular-devkit/build-angular@18.2.5(vlaqpyq5c5jdro3rfvxymdqdnu))(@angular-devkit/core@18.2.5(chokidar@3.6.0))(@angular-devkit/schematics@18.2.5(chokidar@3.6.0))(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@schematics/angular@18.2.5(chokidar@3.6.0))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(rxjs@7.8.1)(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))': dependencies: - '@angular-devkit/build-angular': 18.2.5(@angular/compiler-cli@18.2.5(@angular/compiler@18.2.5(@angular/core@18.2.5(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(chokidar@3.6.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@18.19.8)(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(typescript@5.5.4)))(ng-packagr@18.2.1(@angular/compiler-cli@18.2.5(@angular/compiler@18.2.5(@angular/core@18.2.5(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.5.4))(tailwindcss@3.4.4(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(typescript@5.5.4)))(tslib@2.7.0)(typescript@5.5.4))(stylus@0.59.0)(tailwindcss@3.4.4(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(typescript@5.5.4)))(typescript@5.5.4)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) + '@angular-devkit/build-angular': 18.2.5(vlaqpyq5c5jdro3rfvxymdqdnu) '@angular-devkit/core': 18.2.5(chokidar@3.6.0) '@angular-devkit/schematics': 18.2.5(chokidar@3.6.0) '@module-federation/enhanced': 0.6.1(typescript@5.5.4)(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) - '@nrwl/angular': 19.8.0-beta.2(bia6o4nti5aiqh5ziiynwngwai) + '@nrwl/angular': 19.8.0-beta.2(@angular-devkit/build-angular@18.2.5(vlaqpyq5c5jdro3rfvxymdqdnu))(@angular-devkit/core@18.2.5(chokidar@3.6.0))(@angular-devkit/schematics@18.2.5(chokidar@3.6.0))(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@schematics/angular@18.2.5(chokidar@3.6.0))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(rxjs@7.8.1)(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) '@nx/devkit': 19.8.0-beta.2(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))) '@nx/eslint': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(eslint@8.57.0)(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0)) '@nx/js': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0)) '@nx/web': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0)) - '@nx/webpack': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) + '@nx/webpack': 19.8.0-beta.2(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) '@nx/workspace': 19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.5.4) '@schematics/angular': 18.2.5(chokidar@3.6.0) @@ -21783,16 +22116,16 @@ snapshots: - supports-color - verdaccio - '@nx/next@19.8.0-beta.2(@babel/core@7.25.2)(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(next@14.2.5(@babel/core@7.25.2)(@playwright/test@1.47.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4))': + '@nx/next@19.8.0-beta.2(@babel/core@7.25.2)(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(next@14.2.5(@babel/core@7.25.2)(@playwright/test@1.47.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4))': dependencies: '@babel/plugin-proposal-decorators': 7.24.7(@babel/core@7.25.2) - '@nrwl/next': 19.8.0-beta.2(@babel/core@7.25.2)(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(next@14.2.5(@babel/core@7.25.2)(@playwright/test@1.47.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) + '@nrwl/next': 19.8.0-beta.2(@babel/core@7.25.2)(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(next@14.2.5(@babel/core@7.25.2)(@playwright/test@1.47.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) '@nx/devkit': 19.8.0-beta.2(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))) '@nx/eslint': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(eslint@8.57.0)(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0)) '@nx/js': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0)) '@nx/react': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(eslint@8.57.0)(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) '@nx/web': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0)) - '@nx/webpack': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) + '@nx/webpack': 19.8.0-beta.2(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) '@nx/workspace': 19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.5.4) '@svgr/webpack': 8.1.0(typescript@5.5.4) @@ -21898,13 +22231,13 @@ snapshots: '@nx/nx-win32-x64-msvc@19.8.0-beta.2': optional: true - '@nx/playwright@19.8.0-beta.2(@babel/traverse@7.25.6)(@playwright/test@1.47.1)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(vite@5.0.8(@types/node@18.19.8)(less@4.1.3)(sass@1.55.0)(stylus@0.59.0)(terser@5.31.6))(vitest@1.3.1(@types/node@18.19.8)(jsdom@20.0.3)(less@4.1.3)(sass@1.55.0)(stylus@0.59.0)(terser@5.31.6))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))': + '@nx/playwright@19.8.0-beta.2(@babel/traverse@7.25.6)(@playwright/test@1.47.1)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(vite@5.0.8(@types/node@18.19.8)(less@4.1.3)(sass@1.55.0)(stylus@0.59.0)(terser@5.31.6))(vitest@1.3.1(@types/node@18.19.8)(jsdom@20.0.3)(less@4.1.3)(sass@1.55.0)(stylus@0.59.0)(terser@5.31.6))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))': dependencies: '@nx/devkit': 19.8.0-beta.2(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))) '@nx/eslint': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(eslint@8.57.0)(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0)) '@nx/js': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0)) '@nx/vite': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(vite@5.0.8(@types/node@18.19.8)(less@4.1.3)(sass@1.55.0)(stylus@0.59.0)(terser@5.31.6))(vitest@1.3.1(@types/node@18.19.8)(jsdom@20.0.3)(less@4.1.3)(sass@1.55.0)(stylus@0.59.0)(terser@5.31.6)) - '@nx/webpack': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) + '@nx/webpack': 19.8.0-beta.2(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.5.4) minimatch: 9.0.3 tslib: 2.7.0 @@ -22108,12 +22441,12 @@ snapshots: - typescript - verdaccio - '@nx/webpack@19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))': + '@nx/webpack@19.8.0-beta.2(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))': dependencies: '@babel/core': 7.25.2 '@module-federation/enhanced': 0.6.1(typescript@5.5.4)(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) '@module-federation/sdk': 0.6.1 - '@nrwl/webpack': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) + '@nrwl/webpack': 19.8.0-beta.2(@babel/traverse@7.25.6)(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(esbuild@0.19.5)(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) '@nx/devkit': 19.8.0-beta.2(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))) '@nx/js': 19.8.0-beta.2(@babel/traverse@7.25.6)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.4)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0)) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.5.4) @@ -22123,7 +22456,7 @@ snapshots: browserslist: 4.23.3 chalk: 4.1.2 copy-webpack-plugin: 10.2.4(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) - css-loader: 6.11.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) + css-loader: 6.11.0(@rspack/core@1.0.5(@swc/helpers@0.5.11))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) css-minimizer-webpack-plugin: 5.0.1(esbuild@0.19.5)(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) express: 4.21.0 fork-ts-checker-webpack-plugin: 7.2.13(typescript@5.5.4)(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) @@ -23005,6 +23338,90 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + '@rspack/binding-darwin-arm64@1.0.5': + optional: true + + '@rspack/binding-darwin-x64@1.0.5': + optional: true + + '@rspack/binding-linux-arm64-gnu@1.0.5': + optional: true + + '@rspack/binding-linux-arm64-musl@1.0.5': + optional: true + + '@rspack/binding-linux-x64-gnu@1.0.5': + optional: true + + '@rspack/binding-linux-x64-musl@1.0.5': + optional: true + + '@rspack/binding-win32-arm64-msvc@1.0.5': + optional: true + + '@rspack/binding-win32-ia32-msvc@1.0.5': + optional: true + + '@rspack/binding-win32-x64-msvc@1.0.5': + optional: true + + '@rspack/binding@1.0.5': + optionalDependencies: + '@rspack/binding-darwin-arm64': 1.0.5 + '@rspack/binding-darwin-x64': 1.0.5 + '@rspack/binding-linux-arm64-gnu': 1.0.5 + '@rspack/binding-linux-arm64-musl': 1.0.5 + '@rspack/binding-linux-x64-gnu': 1.0.5 + '@rspack/binding-linux-x64-musl': 1.0.5 + '@rspack/binding-win32-arm64-msvc': 1.0.5 + '@rspack/binding-win32-ia32-msvc': 1.0.5 + '@rspack/binding-win32-x64-msvc': 1.0.5 + + '@rspack/core@1.0.5(@swc/helpers@0.5.11)': + dependencies: + '@module-federation/runtime-tools': 0.5.1 + '@rspack/binding': 1.0.5 + '@rspack/lite-tapable': 1.0.0 + caniuse-lite: 1.0.30001662 + optionalDependencies: + '@swc/helpers': 0.5.11 + + '@rspack/dev-server@1.0.5(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@types/express@4.17.14)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4))': + dependencies: + '@rspack/core': 1.0.5(@swc/helpers@0.5.11) + chokidar: 3.6.0 + connect-history-api-fallback: 2.0.0 + express: 4.21.0 + http-proxy-middleware: 2.0.6(@types/express@4.17.14) + mime-types: 2.1.35 + p-retry: 4.6.2 + webpack-dev-middleware: 7.4.2(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) + webpack-dev-server: 5.0.4(webpack-cli@5.1.4)(webpack@5.88.0) + ws: 8.18.0 + transitivePeerDependencies: + - '@types/express' + - bufferutil + - debug + - supports-color + - utf-8-validate + - webpack + - webpack-cli + + '@rspack/lite-tapable@1.0.0': {} + + '@rspack/plugin-minify@0.7.5': + dependencies: + esbuild: 0.16.3 + terser: 5.16.1 + webpack-sources: 3.2.3 + + '@rspack/plugin-react-refresh@1.0.0(react-refresh@0.10.0)': + dependencies: + error-stack-parser: 2.1.4 + html-entities: 2.5.2 + optionalDependencies: + react-refresh: 0.10.0 + '@rtsao/scc@1.1.0': {} '@rushstack/eslint-patch@1.10.4': {} @@ -23527,7 +23944,7 @@ snapshots: - supports-color - webpack-sources - '@storybook/builder-webpack5@8.3.2(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(storybook@8.3.2)(typescript@5.5.4)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))': + '@storybook/builder-webpack5@8.3.2(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(storybook@8.3.2)(typescript@5.5.4)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))': dependencies: '@storybook/core-webpack': 8.3.2(storybook@8.3.2) '@types/node': 22.5.5 @@ -23536,7 +23953,7 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.1 constants-browserify: 1.0.0 - css-loader: 6.11.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) + css-loader: 6.11.0(@rspack/core@1.0.5(@swc/helpers@0.5.11))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) es-module-lexer: 1.5.4 express: 4.21.0 fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.5.4)(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) @@ -23714,9 +24131,9 @@ snapshots: - vite-plugin-glimmerx - webpack-sources - '@storybook/react-webpack5@8.3.2(@storybook/test@8.3.2(storybook@8.3.2))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2)(typescript@5.5.4)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))': + '@storybook/react-webpack5@8.3.2(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@storybook/test@8.3.2(storybook@8.3.2))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2)(typescript@5.5.4)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))': dependencies: - '@storybook/builder-webpack5': 8.3.2(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(storybook@8.3.2)(typescript@5.5.4)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) + '@storybook/builder-webpack5': 8.3.2(@rspack/core@1.0.5(@swc/helpers@0.5.11))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(storybook@8.3.2)(typescript@5.5.4)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) '@storybook/preset-react-webpack': 8.3.2(@storybook/test@8.3.2(storybook@8.3.2))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2)(typescript@5.5.4)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) '@storybook/react': 8.3.2(@storybook/test@8.3.2(storybook@8.3.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2)(typescript@5.5.4) '@types/node': 22.5.5 @@ -24454,6 +24871,8 @@ snapshots: dependencies: '@types/node': 18.19.8 + '@types/retry@0.12.0': {} + '@types/retry@0.12.2': {} '@types/semver@7.5.8': {} @@ -26901,7 +27320,7 @@ snapshots: postcss: 8.4.38 postcss-selector-parser: 6.1.2 - css-loader@6.11.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)): + css-loader@6.11.0(@rspack/core@1.0.5(@swc/helpers@0.5.11))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)): dependencies: icss-utils: 5.1.0(postcss@8.4.38) postcss: 8.4.38 @@ -26912,9 +27331,10 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.6.3 optionalDependencies: + '@rspack/core': 1.0.5(@swc/helpers@0.5.11) webpack: 5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4) - css-loader@7.1.2(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))): + css-loader@7.1.2(@rspack/core@1.0.5(@swc/helpers@0.5.11))(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))): dependencies: icss-utils: 5.1.0(postcss@8.4.38) postcss: 8.4.38 @@ -26925,6 +27345,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.6.3 optionalDependencies: + '@rspack/core': 1.0.5(@swc/helpers@0.5.11) webpack: 5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) css-minimizer-webpack-plugin@5.0.1(esbuild@0.19.5)(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)): @@ -27742,6 +28163,31 @@ snapshots: esbuild-wasm@0.23.0: {} + esbuild@0.16.3: + optionalDependencies: + '@esbuild/android-arm': 0.16.3 + '@esbuild/android-arm64': 0.16.3 + '@esbuild/android-x64': 0.16.3 + '@esbuild/darwin-arm64': 0.16.3 + '@esbuild/darwin-x64': 0.16.3 + '@esbuild/freebsd-arm64': 0.16.3 + '@esbuild/freebsd-x64': 0.16.3 + '@esbuild/linux-arm': 0.16.3 + '@esbuild/linux-arm64': 0.16.3 + '@esbuild/linux-ia32': 0.16.3 + '@esbuild/linux-loong64': 0.16.3 + '@esbuild/linux-mips64el': 0.16.3 + '@esbuild/linux-ppc64': 0.16.3 + '@esbuild/linux-riscv64': 0.16.3 + '@esbuild/linux-s390x': 0.16.3 + '@esbuild/linux-x64': 0.16.3 + '@esbuild/netbsd-x64': 0.16.3 + '@esbuild/openbsd-x64': 0.16.3 + '@esbuild/sunos-x64': 0.16.3 + '@esbuild/win32-arm64': 0.16.3 + '@esbuild/win32-ia32': 0.16.3 + '@esbuild/win32-x64': 0.16.3 + esbuild@0.17.6: optionalDependencies: '@esbuild/android-arm': 0.17.6 @@ -29422,6 +29868,18 @@ snapshots: transitivePeerDependencies: - supports-color + http-proxy-middleware@2.0.6(@types/express@4.17.14): + dependencies: + '@types/http-proxy': 1.17.15 + http-proxy: 1.18.1(debug@4.3.7) + is-glob: 4.0.3 + is-plain-obj: 3.0.0 + micromatch: 4.0.8 + optionalDependencies: + '@types/express': 4.17.14 + transitivePeerDependencies: + - debug + http-proxy-middleware@2.0.6(@types/express@4.17.21): dependencies: '@types/http-proxy': 1.17.15 @@ -30678,10 +31136,11 @@ snapshots: less: 4.1.3 webpack: 5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4) - less-loader@12.2.0(less@4.2.0)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))): + less-loader@12.2.0(@rspack/core@1.0.5(@swc/helpers@0.5.11))(less@4.2.0)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))): dependencies: less: 4.2.0 optionalDependencies: + '@rspack/core': 1.0.5(@swc/helpers@0.5.11) webpack: 5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) less@4.1.3: @@ -32735,6 +33194,11 @@ snapshots: eventemitter3: 4.0.7 p-timeout: 3.2.0 + p-retry@4.6.2: + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + p-retry@6.2.0: dependencies: '@types/retry': 0.12.2 @@ -33290,13 +33754,14 @@ snapshots: semver: 7.6.3 webpack: 5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4) - postcss-loader@8.1.1(postcss@8.4.41)(typescript@5.5.4)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))): + postcss-loader@8.1.1(@rspack/core@1.0.5(@swc/helpers@0.5.11))(postcss@8.4.41)(typescript@5.5.4)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))): dependencies: cosmiconfig: 9.0.0(typescript@5.5.4) jiti: 1.21.6 postcss: 8.4.41 semver: 7.6.3 optionalDependencies: + '@rspack/core': 1.0.5(@swc/helpers@0.5.11) webpack: 5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) transitivePeerDependencies: - typescript @@ -34655,10 +35120,11 @@ snapshots: optionalDependencies: sass: 1.55.0 - sass-loader@16.0.0(sass@1.77.6)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))): + sass-loader@16.0.0(@rspack/core@1.0.5(@swc/helpers@0.5.11))(sass@1.77.6)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))): dependencies: neo-async: 2.6.2 optionalDependencies: + '@rspack/core': 1.0.5(@swc/helpers@0.5.11) sass: 1.77.6 webpack: 5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.23.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) @@ -35641,6 +36107,13 @@ snapshots: '@swc/core': 1.5.7(@swc/helpers@0.5.11) esbuild: 0.23.0 + terser@5.16.1: + dependencies: + '@jridgewell/source-map': 0.3.6 + acorn: 8.12.1 + commander: 2.20.3 + source-map-support: 0.5.21 + terser@5.31.6: dependencies: '@jridgewell/source-map': 0.3.6 diff --git a/scripts/commitizen.js b/scripts/commitizen.js index c9e3e48004614..a591f1ba25395 100644 --- a/scripts/commitizen.js +++ b/scripts/commitizen.js @@ -20,6 +20,7 @@ const scopes = [ { value: 'react', name: 'react: anything React specific' }, { value: 'react-native', name: 'react-native: anything React Native specific' }, { value: 'remix', name: 'remix: anything Remix specific' }, + { value: 'rspack', name: 'rspack: anything Rspack specific' }, { value: 'expo', name: 'expo: anything Expo specific' }, { value: 'release', name: 'release: anything related to nx release' }, { value: 'repo', name: 'repo: anything related to managing the repo itself' }, diff --git a/tsconfig.base.json b/tsconfig.base.json index cf6f3a655e619..56ca653fb4c2a 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -127,6 +127,8 @@ "@nx/remix/*": ["packages/remix/*"], "@nx/rollup": ["packages/rollup"], "@nx/rollup/*": ["packages/rollup/*"], + "@nx/rspack": ["packages/rspack/src"], + "@nx/rspack/*": ["packages/rspack/src/*"], "@nx/storybook": ["packages/storybook"], "@nx/storybook/*": ["packages/storybook/*"], "@nx/typedoc-theme": ["typedoc-theme/src/index.ts"],