From 63021dd72a6fe85d101ff6355a00ea1512fb554d Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Wed, 15 Feb 2023 15:30:52 -0500 Subject: [PATCH 01/92] feat: add rspack plugin (#143) --- .eslintrc.json | 25 +++ README.md | 11 ++ executors.json | 15 ++ generators.json | 24 +++ jest.config.ts | 15 ++ package.json | 24 +++ project.json | 61 +++++++ src/executors/dev-server/dev-server.impl.ts | 45 +++++ src/executors/dev-server/schema.d.ts | 5 + src/executors/dev-server/schema.json | 19 ++ src/executors/rspack/rspack.impl.ts | 20 +++ src/executors/rspack/schema.d.ts | 12 ++ src/executors/rspack/schema.json | 95 ++++++++++ src/generators/init/init.ts | 38 ++++ src/generators/init/schema.d.ts | 5 + src/generators/init/schema.json | 20 +++ .../preset/files/src/app/app.module.__style__ | 2 + .../files/src/app/app.spec.tsx__template__ | 11 ++ .../preset/files/src/app/app.tsx__template__ | 10 ++ .../preset/files/src/assets/.gitkeep | 0 src/generators/preset/files/src/favicon.ico | Bin 0 -> 15086 bytes .../preset/files/src/index.html__template__ | 14 ++ .../preset/files/src/main.tsx__template__ | 15 ++ .../preset/files/src/styles.__style__ | 1 + .../files/tsconfig.app.json__template__ | 13 ++ .../preset/files/tsconfig.json__template__ | 17 ++ src/generators/preset/lib/add-cypress.ts | 20 +++ src/generators/preset/lib/add-jest.ts | 56 ++++++ .../preset/lib/normalize-options.ts | 54 ++++++ src/generators/preset/preset.ts | 86 +++++++++ src/generators/preset/schema.d.ts | 15 ++ src/generators/preset/schema.json | 60 +++++++ .../rspack-project/rspack-project.ts | 168 ++++++++++++++++++ src/generators/rspack-project/schema.d.ts | 9 + src/generators/rspack-project/schema.json | 58 ++++++ src/index.ts | 6 + src/utils/config.ts | 15 ++ src/utils/create-compiler.ts | 21 +++ src/utils/create-copy-plugin.ts | 23 +++ src/utils/jest-utils.ts | 11 ++ src/utils/model.ts | 7 + src/utils/normalize-assets.ts | 50 ++++++ src/utils/versions.ts | 10 ++ src/utils/with-nx.ts | 122 +++++++++++++ src/utils/with-react.ts | 26 +++ src/utils/with-web.ts | 62 +++++++ tsconfig.json | 16 ++ tsconfig.lib.json | 11 ++ tsconfig.spec.json | 9 + 49 files changed, 1432 insertions(+) create mode 100644 .eslintrc.json create mode 100644 README.md create mode 100644 executors.json create mode 100644 generators.json create mode 100644 jest.config.ts create mode 100644 package.json create mode 100644 project.json create mode 100644 src/executors/dev-server/dev-server.impl.ts create mode 100644 src/executors/dev-server/schema.d.ts create mode 100644 src/executors/dev-server/schema.json create mode 100644 src/executors/rspack/rspack.impl.ts create mode 100644 src/executors/rspack/schema.d.ts create mode 100644 src/executors/rspack/schema.json create mode 100644 src/generators/init/init.ts create mode 100644 src/generators/init/schema.d.ts create mode 100644 src/generators/init/schema.json create mode 100644 src/generators/preset/files/src/app/app.module.__style__ create mode 100644 src/generators/preset/files/src/app/app.spec.tsx__template__ create mode 100644 src/generators/preset/files/src/app/app.tsx__template__ create mode 100644 src/generators/preset/files/src/assets/.gitkeep create mode 100644 src/generators/preset/files/src/favicon.ico create mode 100644 src/generators/preset/files/src/index.html__template__ create mode 100644 src/generators/preset/files/src/main.tsx__template__ create mode 100644 src/generators/preset/files/src/styles.__style__ create mode 100644 src/generators/preset/files/tsconfig.app.json__template__ create mode 100644 src/generators/preset/files/tsconfig.json__template__ create mode 100644 src/generators/preset/lib/add-cypress.ts create mode 100644 src/generators/preset/lib/add-jest.ts create mode 100644 src/generators/preset/lib/normalize-options.ts create mode 100644 src/generators/preset/preset.ts create mode 100644 src/generators/preset/schema.d.ts create mode 100644 src/generators/preset/schema.json create mode 100644 src/generators/rspack-project/rspack-project.ts create mode 100644 src/generators/rspack-project/schema.d.ts create mode 100644 src/generators/rspack-project/schema.json create mode 100644 src/index.ts create mode 100644 src/utils/config.ts create mode 100644 src/utils/create-compiler.ts create mode 100644 src/utils/create-copy-plugin.ts create mode 100644 src/utils/jest-utils.ts create mode 100644 src/utils/model.ts create mode 100644 src/utils/normalize-assets.ts create mode 100644 src/utils/versions.ts create mode 100644 src/utils/with-nx.ts create mode 100644 src/utils/with-react.ts create mode 100644 src/utils/with-web.ts create mode 100644 tsconfig.json create mode 100644 tsconfig.lib.json create mode 100644 tsconfig.spec.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000000000..556554a7956a5 --- /dev/null +++ b/.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": { + "@nrwl/nx/nx-plugin-checks": "error" + } + } + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000000000..1fda4cfdbbf06 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# rspack + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build rspack` to build the library. + +## Running unit tests + +Run `nx test rspack` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/executors.json b/executors.json new file mode 100644 index 0000000000000..4ec8c30095772 --- /dev/null +++ b/executors.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/schema", + "executors": { + "rspack": { + "implementation": "./src/executors/rspack/rspack.impl", + "schema": "./src/executors/rspack/schema.json", + "description": "rspack executor" + }, + "dev-server": { + "implementation": "./src/executors/dev-server/dev-server.impl", + "schema": "./src/executors/dev-server/schema.json", + "description": "dev-server executor" + } + } +} diff --git a/generators.json b/generators.json new file mode 100644 index 0000000000000..b263978a590b4 --- /dev/null +++ b/generators.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/schema", + "name": "rspack", + "version": "0.0.1", + "generators": { + "rspack-project": { + "factory": "./src/generators/rspack-project/rspack-project", + "schema": "./src/generators/rspack-project/schema.json", + "description": "rspack generator" + }, + "init": { + "factory": "./src/generators/init/init", + "schema": "./src/generators/init/schema.json", + "description": "init generator", + "hidden": true + }, + "preset": { + "factory": "./src/generators/preset/preset", + "schema": "./src/generators/preset/schema.json", + "description": "preset generator", + "hidden": true + } + } +} diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 0000000000000..f3a6740a8888d --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,15 @@ +/* eslint-disable */ +export default { + displayName: 'rspack', + preset: '../../jest.preset.js', + globals: { + 'ts-jest': { + tsconfig: '/tsconfig.spec.json', + }, + }, + transform: { + '^.+\\.[tj]s$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/packages/rspack', +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000000000..74a68be690dbe --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "@nrwl/rspack", + "version": "15.7.0-beta.1", + "type": "commonjs", + "repository": { + "type": "git", + "url": "https://github.com/nrwl/nx-labs.git", + "directory": "packages/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": { + "copy-webpack-plugin": "^10.2.4" + } +} diff --git a/project.json b/project.json new file mode 100644 index 0000000000000..54367c77ef2f9 --- /dev/null +++ b/project.json @@ -0,0 +1,61 @@ +{ + "name": "rspack", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/rspack/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nrwl/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/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": "generators.json", + "output": "." + }, + { + "input": "./packages/rspack", + "glob": "executors.json", + "output": "." + } + ] + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "packages/rspack/**/*.ts", + "packages/rspack/generators.json", + "packages/rspack/executors.json", + "packages/rspack/package.json" + ] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "packages/rspack/jest.config.ts", + "passWithNoTests": true + } + } + }, + "tags": [] +} diff --git a/src/executors/dev-server/dev-server.impl.ts b/src/executors/dev-server/dev-server.impl.ts new file mode 100644 index 0000000000000..f2f724c5aec9a --- /dev/null +++ b/src/executors/dev-server/dev-server.impl.ts @@ -0,0 +1,45 @@ +import { + ExecutorContext, + parseTargetString, + readTargetOptions, +} from '@nrwl/devkit'; +import { createAsyncIterable } from '@nrwl/devkit/src/utils/async-iterable'; +import { RspackDevServer } from '@rspack/dev-server'; +import { createCompiler } from '../../utils/create-compiler'; +import { DevServerExecutorSchema } from './schema'; + +export default async function* runExecutor( + options: DevServerExecutorSchema, + context: ExecutorContext +) { + process.env.NODE_ENV ??= 'development'; + const buildTarget = parseTargetString( + options.buildTarget, + context.projectGraph + ); + const devServerConfig = { + port: options.port ?? 4200, + hot: true, + }; + const buildOptions = readTargetOptions(buildTarget, context); + const compiler = await createCompiler( + { ...buildOptions, devServer: devServerConfig }, + context + ); + + yield* createAsyncIterable(({ next }) => { + const server: any = new RspackDevServer( + { + ...devServerConfig, + onListening: (server: any) => { + next({ + success: true, + baseUrl: `http://localhost:${options.port ?? 4200}`, + }); + }, + } as any, + compiler + ); + server.start(); + }); +} diff --git a/src/executors/dev-server/schema.d.ts b/src/executors/dev-server/schema.d.ts new file mode 100644 index 0000000000000..66b5708123e0a --- /dev/null +++ b/src/executors/dev-server/schema.d.ts @@ -0,0 +1,5 @@ +export interface DevServerExecutorSchema { + buildTarget: string; + + port?: number; +} diff --git a/src/executors/dev-server/schema.json b/src/executors/dev-server/schema.json new file mode 100644 index 0000000000000..0702e2be344c1 --- /dev/null +++ b/src/executors/dev-server/schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/schema", + "version": 2, + "cli": "nx", + "title": "Rspack dev-server executor", + "description": "", + "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." + } + }, + "required": ["buildTarget"] +} diff --git a/src/executors/rspack/rspack.impl.ts b/src/executors/rspack/rspack.impl.ts new file mode 100644 index 0000000000000..503abed25402f --- /dev/null +++ b/src/executors/rspack/rspack.impl.ts @@ -0,0 +1,20 @@ +import { ExecutorContext } from '@nrwl/devkit'; +import { createCompiler } from '../../utils/create-compiler'; +import { RspackExecutorSchema } from './schema'; + +export default async function runExecutor( + options: RspackExecutorSchema, + context: ExecutorContext +) { + process.env.NODE_ENV ??= 'production'; + + const compiler = await createCompiler(options, context); + + return new Promise<{ success: boolean }>((res) => { + compiler.run(() => { + compiler.close((err: any) => { + res({ success: !err }); + }); + }); + }); +} diff --git a/src/executors/rspack/schema.d.ts b/src/executors/rspack/schema.d.ts new file mode 100644 index 0000000000000..f0a7bb91a288a --- /dev/null +++ b/src/executors/rspack/schema.d.ts @@ -0,0 +1,12 @@ +export interface RspackExecutorSchema { + target: 'web' | 'node'; + main: string; + tsConfig: string; + outputPath: string; + indexHtml?: string; + + rspackConfig: string; + optimization?: boolean; + sourceMap?: boolean | string; + assets?: any[]; +} diff --git a/src/executors/rspack/schema.json b/src/executors/rspack/schema.json new file mode 100644 index 0000000000000..4ae0ace118527 --- /dev/null +++ b/src/executors/rspack/schema.json @@ -0,0 +1,95 @@ +{ + "$schema": "http://json-schema.org/schema", + "version": 2, + "cli": "nx", + "title": "Rspack build executor", + "description": "", + "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." + }, + "tsConfig": { + "type": "string", + "description": "The tsconfig file to build the project." + }, + "indexHtml": { + "type": "string", + "description": "The path to the index.html file." + }, + "rspackConfig": { + "type": "string", + "description": "The path to the rspack config file." + }, + "optimization": { + "type": "boolean", + "description": "Enables optimization of the build output." + }, + "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" + } + } + }, + "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." + } + }, + "additionalProperties": false, + "required": ["glob", "input", "output"] + }, + { + "type": "string" + } + ] + } + } +} diff --git a/src/generators/init/init.ts b/src/generators/init/init.ts new file mode 100644 index 0000000000000..a21bfa6bc1547 --- /dev/null +++ b/src/generators/init/init.ts @@ -0,0 +1,38 @@ +import { + addDependenciesToPackageJson, + convertNxGenerator, + Tree, +} from '@nrwl/devkit'; +import { + rspackCoreVersion, + rspackDevServerVersion, + rspackLessLoaderVersion, + rspackPluginMinifyVersion, +} from '../../utils/versions'; +import { InitGeneratorSchema } from './schema'; + +export async function rspackInitGenerator( + tree: Tree, + schema: InitGeneratorSchema +) { + const devDependencies = { + // eslint-disable-next-line @typescript-eslint/no-var-requires + '@nrwl/rspack': require('../../../package.json').version, + '@rspack/core': rspackCoreVersion, + '@rspack/plugin-minify': rspackPluginMinifyVersion, + }; + + if (schema.style === 'less') { + devDependencies['@rspack/less-loader'] = rspackLessLoaderVersion; + } + + if (schema.uiFramework !== 'none' || schema.devServer) { + devDependencies['@rspack/dev-server'] = rspackDevServerVersion; + } + + return addDependenciesToPackageJson(tree, {}, devDependencies); +} + +export default rspackInitGenerator; + +export const rspackInitSchematic = convertNxGenerator(rspackInitGenerator); diff --git a/src/generators/init/schema.d.ts b/src/generators/init/schema.d.ts new file mode 100644 index 0000000000000..6025907385ef8 --- /dev/null +++ b/src/generators/init/schema.d.ts @@ -0,0 +1,5 @@ +export interface InitGeneratorSchema { + uiFramework?: 'none' | 'react' | 'web'; + style?: 'none' | 'css' | 'scss' | 'less'; + devServer?: boolean; +} diff --git a/src/generators/init/schema.json b/src/generators/init/schema.json new file mode 100644 index 0000000000000..38e289c320fd9 --- /dev/null +++ b/src/generators/init/schema.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/schema", + "cli": "nx", + "$id": "Init", + "title": "", + "type": "object", + "properties": { + "uiFramework": { + "type": "string", + "description": "The UI framework used by the project.", + "enum": ["none", "react", "web"] + }, + "style": { + "type": "string", + "description": "The style solution to use.", + "enum": ["none", "css", "scss", "less"] + } + }, + "required": [] +} diff --git a/src/generators/preset/files/src/app/app.module.__style__ b/src/generators/preset/files/src/app/app.module.__style__ new file mode 100644 index 0000000000000..485140808b943 --- /dev/null +++ b/src/generators/preset/files/src/app/app.module.__style__ @@ -0,0 +1,2 @@ +.container { +} diff --git a/src/generators/preset/files/src/app/app.spec.tsx__template__ b/src/generators/preset/files/src/app/app.spec.tsx__template__ new file mode 100644 index 0000000000000..d229ddcff5f79 --- /dev/null +++ b/src/generators/preset/files/src/app/app.spec.tsx__template__ @@ -0,0 +1,11 @@ +import { render } from '@testing-library/react'; +import { App } from './app'; + +describe('App component', () => { + it('should render a header', () => { + const { getByTestId } = render(); + + expect(getByTestId('header')).toBeTruthy(); + }); + +}); diff --git a/src/generators/preset/files/src/app/app.tsx__template__ b/src/generators/preset/files/src/app/app.tsx__template__ new file mode 100644 index 0000000000000..42bd6a4df4933 --- /dev/null +++ b/src/generators/preset/files/src/app/app.tsx__template__ @@ -0,0 +1,10 @@ +import styles from './app.module.<%= style %>'; + +export function App() { + return ( +
+

Welcome <%= name %>

+

NODE_ENV: {process.env['NODE_ENV']}

+
+ ); +} diff --git a/src/generators/preset/files/src/assets/.gitkeep b/src/generators/preset/files/src/assets/.gitkeep new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/generators/preset/files/src/favicon.ico b/src/generators/preset/files/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..317ebcb2336e0833a22dddf0ab287849f26fda57 GIT binary patch literal 15086 zcmeI332;U^%p|z7g|#(P)qFEA@4f!_@qOK2 z_lJl}!lhL!VT_U|uN7%8B2iKH??xhDa;*`g{yjTFWHvXn;2s{4R7kH|pKGdy(7z!K zgftM+Ku7~24TLlh(!g)gz|foI94G^t2^IO$uvX$3(OR0<_5L2sB)lMAMy|+`xodJ{ z_Uh_1m)~h?a;2W{dmhM;u!YGo=)OdmId_B<%^V^{ovI@y`7^g1_V9G}*f# zNzAtvou}I!W1#{M^@ROc(BZ! z+F!!_aR&Px3_reO(EW+TwlW~tv*2zr?iP7(d~a~yA|@*a89IUke+c472NXM0wiX{- zl`UrZC^1XYyf%1u)-Y)jj9;MZ!SLfd2Hl?o|80Su%Z?To_=^g_Jt0oa#CT*tjx>BI z16wec&AOWNK<#i0Qd=1O$fymLRoUR*%;h@*@v7}wApDl^w*h}!sYq%kw+DKDY)@&A z@9$ULEB3qkR#85`lb8#WZw=@})#kQig9oqy^I$dj&k4jU&^2(M3q{n1AKeGUKPFbr z1^<)aH;VsG@J|B&l>UtU#Ejv3GIqERzYgL@UOAWtW<{p#zy`WyJgpCy8$c_e%wYJL zyGHRRx38)HyjU3y{-4z6)pzb>&Q1pR)B&u01F-|&Gx4EZWK$nkUkOI|(D4UHOXg_- zw{OBf!oWQUn)Pe(=f=nt=zkmdjpO^o8ZZ9o_|4tW1ni+Un9iCW47*-ut$KQOww!;u z`0q)$s6IZO!~9$e_P9X!hqLxu`fpcL|2f^I5d4*a@Dq28;@2271v_N+5HqYZ>x;&O z05*7JT)mUe&%S0@UD)@&8SmQrMtsDfZT;fkdA!r(S=}Oz>iP)w=W508=Rc#nNn7ym z1;42c|8($ALY8#a({%1#IXbWn9-Y|0eDY$_L&j{63?{?AH{);EzcqfydD$@-B`Y3<%IIj7S7rK_N}je^=dEk%JQ4c z!tBdTPE3Tse;oYF>cnrapWq*o)m47X1`~6@(!Y29#>-#8zm&LXrXa(3=7Z)ElaQqj z-#0JJy3Fi(C#Rx(`=VXtJ63E2_bZGCz+QRa{W0e2(m3sI?LOcUBx)~^YCqZ{XEPX)C>G>U4tfqeH8L(3|pQR*zbL1 zT9e~4Tb5p9_G}$y4t`i*4t_Mr9QYvL9C&Ah*}t`q*}S+VYh0M6GxTTSXI)hMpMpIq zD1ImYqJLzbj0}~EpE-aH#VCH_udYEW#`P2zYmi&xSPs_{n6tBj=MY|-XrA;SGA_>y zGtU$?HXm$gYj*!N)_nQ59%lQdXtQZS3*#PC-{iB_sm+ytD*7j`D*k(P&IH2GHT}Eh z5697eQECVIGQAUe#eU2I!yI&%0CP#>%6MWV z@zS!p@+Y1i1b^QuuEF*13CuB zu69dve5k7&Wgb+^s|UB08Dr3u`h@yM0NTj4h7MnHo-4@xmyr7(*4$rpPwsCDZ@2be zRz9V^GnV;;?^Lk%ynzq&K(Aix`mWmW`^152Hoy$CTYVehpD-S1-W^#k#{0^L`V6CN+E z!w+xte;2vu4AmVNEFUOBmrBL>6MK@!O2*N|2=d|Y;oN&A&qv=qKn73lDD zI(+oJAdgv>Yr}8(&@ZuAZE%XUXmX(U!N+Z_sjL<1vjy1R+1IeHt`79fnYdOL{$ci7 z%3f0A*;Zt@ED&Gjm|OFTYBDe%bbo*xXAQsFz+Q`fVBH!N2)kaxN8P$c>sp~QXnv>b zwq=W3&Mtmih7xkR$YA)1Yi?avHNR6C99!u6fh=cL|KQ&PwF!n@ud^n(HNIImHD!h87!i*t?G|p0o+eelJ?B@A64_9%SBhNaJ64EvKgD&%LjLCYnNfc; znj?%*p@*?dq#NqcQFmmX($wms@CSAr9#>hUR^=I+=0B)vvGX%T&#h$kmX*s=^M2E!@N9#m?LhMvz}YB+kd zG~mbP|D(;{s_#;hsKK9lbVK&Lo734x7SIFJ9V_}2$@q?zm^7?*XH94w5Qae{7zOMUF z^?%F%)c1Y)Q?Iy?I>knw*8gYW#ok|2gdS=YYZLiD=CW|Nj;n^x!=S#iJ#`~Ld79+xXpVmUK^B(xO_vO!btA9y7w3L3-0j-y4 z?M-V{%z;JI`bk7yFDcP}OcCd*{Q9S5$iGA7*E1@tfkyjAi!;wP^O71cZ^Ep)qrQ)N z#wqw0_HS;T7x3y|`P==i3hEwK%|>fZ)c&@kgKO1~5<5xBSk?iZV?KI6&i72H6S9A* z=U(*e)EqEs?Oc04)V-~K5AUmh|62H4*`UAtItO$O(q5?6jj+K^oD!04r=6#dsxp?~}{`?&sXn#q2 zGuY~7>O2=!u@@Kfu7q=W*4egu@qPMRM>(eyYyaIE<|j%d=iWNdGsx%c!902v#ngNg z@#U-O_4xN$s_9?(`{>{>7~-6FgWpBpqXb`Ydc3OFL#&I}Irse9F_8R@4zSS*Y*o*B zXL?6*Aw!AfkNCgcr#*yj&p3ZDe2y>v$>FUdKIy_2N~}6AbHc7gA3`6$g@1o|dE>vz z4pl(j9;kyMsjaw}lO?(?Xg%4k!5%^t#@5n=WVc&JRa+XT$~#@rldvN3S1rEpU$;XgxVny7mki3 z-Hh|jUCHrUXuLr!)`w>wgO0N%KTB-1di>cj(x3Bav`7v z3G7EIbU$z>`Nad7Rk_&OT-W{;qg)-GXV-aJT#(ozdmnA~Rq3GQ_3mby(>q6Ocb-RgTUhTN)))x>m&eD;$J5Bg zo&DhY36Yg=J=$Z>t}RJ>o|@hAcwWzN#r(WJ52^g$lh^!63@hh+dR$&_dEGu&^CR*< z!oFqSqO@>xZ*nC2oiOd0eS*F^IL~W-rsrO`J`ej{=ou_q^_(<$&-3f^J z&L^MSYWIe{&pYq&9eGaArA~*kA + + + + <%= name %> + + + + + + +
+ + diff --git a/src/generators/preset/files/src/main.tsx__template__ b/src/generators/preset/files/src/main.tsx__template__ new file mode 100644 index 0000000000000..8506fa7ab04d2 --- /dev/null +++ b/src/generators/preset/files/src/main.tsx__template__ @@ -0,0 +1,15 @@ +import { StrictMode } from 'react'; +import * as ReactDOM from 'react-dom/client'; + +import './styles.<%= style %>'; +import { App } from './app/app'; + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); + +root.render( + + + +); diff --git a/src/generators/preset/files/src/styles.__style__ b/src/generators/preset/files/src/styles.__style__ new file mode 100644 index 0000000000000..90d4ee0072ce3 --- /dev/null +++ b/src/generators/preset/files/src/styles.__style__ @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/src/generators/preset/files/tsconfig.app.json__template__ b/src/generators/preset/files/tsconfig.app.json__template__ new file mode 100644 index 0000000000000..52fbcc8213f9b --- /dev/null +++ b/src/generators/preset/files/tsconfig.app.json__template__ @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "<%= offsetFromRoot %>dist/out-tsc", + "types": ["node"] + }, + "files": [ + "<%= offsetFromRoot %>node_modules/@nrwl/react/typings/cssmodule.d.ts", + "<%= offsetFromRoot %>node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": ["jest.config.ts","src/**/*.spec.ts", "src/**/*.test.ts", "src/**/*.spec.tsx", "src/**/*.test.tsx", "src/**/*.spec.js", "src/**/*.test.js", "src/**/*.spec.jsx", "src/**/*.test.jsx"], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/src/generators/preset/files/tsconfig.json__template__ b/src/generators/preset/files/tsconfig.json__template__ new file mode 100644 index 0000000000000..0f7e44b9f262c --- /dev/null +++ b/src/generators/preset/files/tsconfig.json__template__ @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + } + ], + "extends": "<%= offsetFromRoot %>tsconfig.base.json" +} diff --git a/src/generators/preset/lib/add-cypress.ts b/src/generators/preset/lib/add-cypress.ts new file mode 100644 index 0000000000000..f11126aae7132 --- /dev/null +++ b/src/generators/preset/lib/add-cypress.ts @@ -0,0 +1,20 @@ +import { ensurePackage, Tree } from '@nrwl/devkit'; +import { version as nxVersion } from 'nx/package.json'; +import { PresetGeneratorSchema } from '../schema'; + +export async function addCypress(host: Tree, options: PresetGeneratorSchema) { + if (options.e2eTestRunner !== 'cypress') { + return () => { + //nothing + }; + } + await ensurePackage(host, '@nrwl/cypress', nxVersion); + const { cypressProjectGenerator } = await import('@nrwl/cypress'); + + return await cypressProjectGenerator(host, { + ...options, + name: options.rootProject ? `e2e` : `${options.name}-e2e`, + project: options.name, + rootProject: options.rootProject, + }); +} diff --git a/src/generators/preset/lib/add-jest.ts b/src/generators/preset/lib/add-jest.ts new file mode 100644 index 0000000000000..2bc6c0200d9e3 --- /dev/null +++ b/src/generators/preset/lib/add-jest.ts @@ -0,0 +1,56 @@ +import { + ensurePackage, + offsetFromRoot, + readProjectConfiguration, + Tree, + updateJson, +} from '@nrwl/devkit'; +import { version as nxVersion } from 'nx/package.json'; +import { updateJestConfigContent } from '../../../utils/jest-utils'; +import { PresetGeneratorSchema } from '../schema'; + +export async function addJest(host: Tree, options: PresetGeneratorSchema) { + if (options.unitTestRunner !== 'jest') { + return () => { + // nothing + }; + } + + await ensurePackage(host, '@nrwl/jest', nxVersion); + const { jestProjectGenerator } = await import('@nrwl/jest'); + + const task = await jestProjectGenerator(host, { + ...options, + project: options.name, + supportTsx: true, + skipSerializers: true, + setupFile: 'none', + compiler: 'babel', // have to use babel for React projects + rootProject: options.rootProject, + }); + + updateSpecConfig(host, options); + + return task; +} + +function updateSpecConfig(host: Tree, options: PresetGeneratorSchema) { + if (options.unitTestRunner !== 'jest') { + return; + } + const project = readProjectConfiguration(host, options.name); + + updateJson(host, `${project.root}/tsconfig.spec.json`, (json) => { + const offset = offsetFromRoot(project.root); + json.files = [ + `${offset}node_modules/@nrwl/react/typings/cssmodule.d.ts`, + `${offset}node_modules/@nrwl/react/typings/image.d.ts`, + ]; + return json; + }); + + const configPath = `${project.root}/jest.config.ts`; + const originalContent = host.read(configPath, 'utf-8'); + const content = updateJestConfigContent(originalContent); + host.write(configPath, content); +} diff --git a/src/generators/preset/lib/normalize-options.ts b/src/generators/preset/lib/normalize-options.ts new file mode 100644 index 0000000000000..e3e121acb93c9 --- /dev/null +++ b/src/generators/preset/lib/normalize-options.ts @@ -0,0 +1,54 @@ +import { + extractLayoutDirectory, + getWorkspaceLayout, + names, + normalizePath, + Tree, +} from '@nrwl/devkit'; +import { NormalizedSchema, PresetGeneratorSchema } from '../schema'; + +export function normalizeDirectory(options: PresetGeneratorSchema) { + const { projectDirectory } = extractLayoutDirectory(options.directory); + return projectDirectory + ? `${names(projectDirectory).fileName}/${names(options.name).fileName}` + : names(options.name).fileName; +} + +export function normalizeProjectName(options: PresetGeneratorSchema) { + return normalizeDirectory(options).replace(new RegExp('/', 'g'), '-'); +} + +export function normalizeOptions( + host: Tree, + options: PresetGeneratorSchema +): NormalizedSchema { + // --monorepo takes precedence over --rootProject + // This won't be needed once we add --bundler=rspack to the @nrwl/react:app preset + const rootProject = !options.monorepo && (options.rootProject ?? true); + 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/src/generators/preset/preset.ts b/src/generators/preset/preset.ts new file mode 100644 index 0000000000000..f6644ea24317c --- /dev/null +++ b/src/generators/preset/preset.ts @@ -0,0 +1,86 @@ +import { + addDependenciesToPackageJson, + addProjectConfiguration, + ensurePackage, + formatFiles, + generateFiles, + joinPathFragments, + names, + offsetFromRoot, + Tree, +} from '@nrwl/devkit'; +import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; +import { version as nxVersion } from 'nx/package.json'; +import * as path from 'path'; + +import { + reactDomVersion, + reactVersion, + typesReactDomVersion, + typesReactVersion, +} from '../../utils/versions'; +import projectGenerator from '../rspack-project/rspack-project'; +import { addCypress } from './lib/add-cypress'; +import { addJest } from './lib/add-jest'; +import { normalizeOptions } from './lib/normalize-options'; +import { PresetGeneratorSchema } from './schema'; + +export default async function (tree: Tree, _options: PresetGeneratorSchema) { + await ensurePackage(tree, '@nrwl/react', nxVersion); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { reactInitGenerator } = require('@nrwl/react'); + + const tasks = []; + const options = normalizeOptions(tree, _options); + + options.style ??= 'css'; + + addProjectConfiguration(tree, options.name, { + root: options.appProjectRoot, + projectType: 'application', + sourceRoot: `${options.appProjectRoot}/src`, + targets: {}, + }); + + generateFiles(tree, path.join(__dirname, 'files'), options.appProjectRoot, { + ...options, + ...names(options.name), + offsetFromRoot: offsetFromRoot(options.appProjectRoot), + template: '', + }); + + const projectTask = await projectGenerator(tree, { + project: options.name, + style: options.style, + devServer: true, + tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'), + uiFramework: 'react', + target: 'web', + main: joinPathFragments(options.appProjectRoot, 'src/main.tsx'), + }); + tasks.push(projectTask); + + const jestTask = await addJest(tree, options); + tasks.push(jestTask); + + const cypressTask = await addCypress(tree, options); + tasks.push(cypressTask); + + const installTask = addDependenciesToPackageJson( + tree, + { react: reactVersion, 'react-dom': reactDomVersion }, + { + '@nrwl/react': nxVersion, + '@types/react': typesReactVersion, + '@types/react-dom': typesReactDomVersion, + } + ); + tasks.push(installTask); + + const reactInitTask = await reactInitGenerator(tree, options); + tasks.push(reactInitTask); + + await formatFiles(tree); + + return runTasksInSerial(...tasks); +} diff --git a/src/generators/preset/schema.d.ts b/src/generators/preset/schema.d.ts new file mode 100644 index 0000000000000..06a197b0bc29c --- /dev/null +++ b/src/generators/preset/schema.d.ts @@ -0,0 +1,15 @@ +export interface PresetGeneratorSchema { + name: string; + style: 'css' | 'scss' | 'less'; + 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/src/generators/preset/schema.json b/src/generators/preset/schema.json new file mode 100644 index 0000000000000..fe47740445161 --- /dev/null +++ b/src/generators/preset/schema.json @@ -0,0 +1,60 @@ +{ + "$schema": "http://json-schema.org/schema", + "cli": "nx", + "$id": "Preset", + "title": "Standalone preset", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "", + "$default": { + "$source": "argv", + "index": 0 + }, + "x-priority": "important" + }, + "style": { + "type": "string", + "description": "The style solution to use.", + "enum": ["none", "css", "sass", "less"], + "default": "css" + }, + "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": "array", + "description": "The tags to assign to the project.", + "items": { + "type": "string" + }, + "default": [] + }, + "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/src/generators/rspack-project/rspack-project.ts b/src/generators/rspack-project/rspack-project.ts new file mode 100644 index 0000000000000..f1b07635cbc82 --- /dev/null +++ b/src/generators/rspack-project/rspack-project.ts @@ -0,0 +1,168 @@ +import { + formatFiles, + joinPathFragments, + readProjectConfiguration, + Tree, + updateProjectConfiguration, +} from '@nrwl/devkit'; +import { RspackExecutorSchema } from '../../executors/rspack/schema'; +import rspackInitGenerator from '../init/init'; +import { RspackProjectGeneratorSchema } from './schema'; + +export async function rspackProjectGenerator( + tree: Tree, + options: RspackProjectGeneratorSchema +) { + const task = await rspackInitGenerator(tree, options); + checkForTargetConflicts(tree, options); + addBuildTarget(tree, options); + if (options.uiFramework !== 'none' || options.devServer) { + addServeTarget(tree, options); + } + writeRspackConfigFile(tree, options); + await formatFiles(tree); + return task; +} + +function checkForTargetConflicts( + tree: Tree, + options: RspackProjectGeneratorSchema +) { + if (options.skipValidation) return; + + const project = readProjectConfiguration(tree, options.project); + + if (project.targets?.build) { + throw new Error( + `Project "${project.name}" already has a build target. Pass --skipValidation to ignore this error.` + ); + } + + if (options.devServer && project.targets?.serve) { + throw new Error( + `Project "${project.name}" already has a serve target. Pass --skipValidation to ignore this error.` + ); + } +} + +function determineMain(tree: Tree, options: RspackProjectGeneratorSchema) { + 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'); +} + +function determineTsConfig(tree: Tree, options: RspackProjectGeneratorSchema) { + 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'); +} + +function addBuildTarget(tree: Tree, options: RspackProjectGeneratorSchema) { + const project = readProjectConfiguration(tree, options.project); + const buildOptions: RspackExecutorSchema = { + target: options.target ?? 'web', + outputPath: joinPathFragments('dist', project.root), + main: determineMain(tree, options), + tsConfig: determineTsConfig(tree, options), + rspackConfig: joinPathFragments(project.root, 'rspack.config.js'), + assets: [ + joinPathFragments(project.root, 'src/favicon.ico'), + joinPathFragments(project.root, 'src/assets'), + ], + }; + + updateProjectConfiguration(tree, options.project, { + ...project, + targets: { + ...project.targets, + build: { + executor: '@nrwl/rspack:rspack', + outputs: ['{options.outputPath}'], + defaultConfiguration: 'production', + options: buildOptions, + configurations: { + production: { + optimization: true, + sourceMap: false, + }, + }, + }, + }, + }); +} + +function addServeTarget(tree: Tree, options: RspackProjectGeneratorSchema) { + const project = readProjectConfiguration(tree, options.project); + updateProjectConfiguration(tree, options.project, { + ...project, + targets: { + ...project.targets, + serve: { + executor: '@nrwl/rspack:dev-server', + options: { + buildTarget: `${options.project}:build`, + }, + configurations: { + production: { + buildTarget: `${options.project}:build:production`, + }, + }, + }, + }, + }); +} + +function writeRspackConfigFile( + tree: Tree, + options: RspackProjectGeneratorSchema +) { + const project = readProjectConfiguration(tree, options.project); + + tree.write( + joinPathFragments(project.root, 'rspack.config.js'), + createConfig(options) + ); +} + +function createConfig(options: RspackProjectGeneratorSchema) { + if (options.uiFramework === 'react') { + return ` + const { composePlugins, withNx, withReact } = require('@nrwl/rspack'); + + module.exports = composePlugins(withNx(), withReact({ style: '${options.style}' }), (config) => { + return config; + }); + `; + } else if (options.uiFramework === 'web') { + return ` + const { composePlugins, withNx, withWeb } = require('@nrwl/rspack'); + + module.exports = composePlugins(withNx(), withWeb({ style: '${options.style}' }), (config) => { + return config; + }); + `; + } else { + return ` + const { composePlugins, withNx } = require('@nrwl/rspack'); + + module.exports = composePlugins(withNx(), (config) => { + return config; + }); + `; + } +} + +export default rspackProjectGenerator; diff --git a/src/generators/rspack-project/schema.d.ts b/src/generators/rspack-project/schema.d.ts new file mode 100644 index 0000000000000..90b381dbbae76 --- /dev/null +++ b/src/generators/rspack-project/schema.d.ts @@ -0,0 +1,9 @@ +import { InitGeneratorSchema } from '../init/schema'; + +export interface RspackProjectGeneratorSchema extends InitGeneratorSchema { + project: string; + main?: string; + tsConfig?: string; + target?: 'node' | 'web'; + skipValidation?: boolean; +} diff --git a/src/generators/rspack-project/schema.json b/src/generators/rspack-project/schema.json new file mode 100644 index 0000000000000..15d64d7c4da00 --- /dev/null +++ b/src/generators/rspack-project/schema.json @@ -0,0 +1,58 @@ +{ + "$schema": "http://json-schema.org/schema", + "cli": "nx", + "$id": "Rspack", + "title": "", + "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" + }, + "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 + }, + "uiFramework": { + "type": "string", + "description": "The UI framework used by the project.", + "enum": ["none", "react"] + }, + "style": { + "type": "string", + "description": "The style solution to use.", + "enum": ["none", "css", "scss", "less"] + }, + "skipValidation": { + "type": "boolean", + "default": false, + "description": "Do not perform any validation on existing project.", + "x-priority": "internal" + } + }, + "required": ["project"] +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000000000..e7e3031a6e588 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,6 @@ +export * from './generators/init/init'; +export * from './generators/rspack-project/rspack-project'; +export * from './utils/config'; +export * from './utils/with-nx'; +export * from './utils/with-react'; +export * from './utils/with-web'; diff --git a/src/utils/config.ts b/src/utils/config.ts new file mode 100644 index 0000000000000..26deecabb97c7 --- /dev/null +++ b/src/utils/config.ts @@ -0,0 +1,15 @@ +import { Configuration } from '@rspack/core'; +import { SharedConfigContext } from './model'; + +export function composePlugins(...plugins: any[]) { + return async function combined( + config: Configuration, + ctx: SharedConfigContext + ): Promise { + for (const plugin of plugins) { + const fn = await plugin; + config = await fn(config, ctx); + } + return config; + }; +} diff --git a/src/utils/create-compiler.ts b/src/utils/create-compiler.ts new file mode 100644 index 0000000000000..5ba7ca1442f82 --- /dev/null +++ b/src/utils/create-compiler.ts @@ -0,0 +1,21 @@ +import { ExecutorContext } from '@nrwl/devkit'; +import { Compiler, createCompiler as _createCompiler } from '@rspack/core'; +import * as path from 'path'; +import { RspackExecutorSchema } from '../executors/rspack/schema'; + +export async function createCompiler( + options: RspackExecutorSchema, + context: ExecutorContext +): Promise { + let userDefinedConfig = await import( + path.join(context.root, options.rspackConfig) + ).then((x) => x.default || x); + + if (typeof userDefinedConfig.then === 'function') { + userDefinedConfig = await userDefinedConfig; + } + + const config = await userDefinedConfig({}, { options, context }); + + return _createCompiler(config); +} diff --git a/src/utils/create-copy-plugin.ts b/src/utils/create-copy-plugin.ts new file mode 100644 index 0000000000000..ba481cb2c024b --- /dev/null +++ b/src/utils/create-copy-plugin.ts @@ -0,0 +1,23 @@ +import * as CopyWebpackPlugin from 'copy-webpack-plugin'; + +export function createCopyPlugin(assets: any[]) { + return new CopyWebpackPlugin({ + patterns: 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/src/utils/jest-utils.ts b/src/utils/jest-utils.ts new file mode 100644 index 0000000000000..c44bc73429f70 --- /dev/null +++ b/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)$)': '@nrwl/react/plugins/jest'," + ) + .replace( + `'babel-jest'`, + `['babel-jest', { presets: ['@nrwl/react/babel'] }]` + ); +} diff --git a/src/utils/model.ts b/src/utils/model.ts new file mode 100644 index 0000000000000..999f4ef3d8421 --- /dev/null +++ b/src/utils/model.ts @@ -0,0 +1,7 @@ +import { ExecutorContext } from '@nrwl/devkit'; +import { RspackExecutorSchema } from '../executors/rspack/schema'; + +export interface SharedConfigContext { + options: RspackExecutorSchema; + context: ExecutorContext; +} diff --git a/src/utils/normalize-assets.ts b/src/utils/normalize-assets.ts new file mode 100644 index 0000000000000..3bb3c2d62379e --- /dev/null +++ b/src/utils/normalize-assets.ts @@ -0,0 +1,50 @@ +import { normalizePath } from '@nrwl/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/src/utils/versions.ts b/src/utils/versions.ts new file mode 100644 index 0000000000000..da07b4cc96b27 --- /dev/null +++ b/src/utils/versions.ts @@ -0,0 +1,10 @@ +export const rspackCoreVersion = '^0.0.22'; +export const rspackDevServerVersion = '^0.0.22'; + +export const rspackPluginMinifyVersion = '^0.0.22'; +export const rspackLessLoaderVersion = '^0.0.22'; + +export const reactVersion = '~18.2.0'; +export const reactDomVersion = '~18.2.0'; +export const typesReactVersion = '~18.0.28'; +export const typesReactDomVersion = '~18.0.10'; diff --git a/src/utils/with-nx.ts b/src/utils/with-nx.ts new file mode 100644 index 0000000000000..2b7f6ba8ba667 --- /dev/null +++ b/src/utils/with-nx.ts @@ -0,0 +1,122 @@ +import { Configuration } from '@rspack/core'; +import * as path from 'path'; +import { createCopyPlugin } from './create-copy-plugin'; +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'; + const isDev = process.env.NODE_ENV === 'development'; + const projectRoot = path.join( + context.root, + context.projectGraph.nodes[context.projectName].data.root + ); + const sourceRoot = path.join( + context.root, + context.projectGraph.nodes[context.projectName].data.sourceRoot + ); + + // eslint-disable-next-line @typescript-eslint/no-var-requires + const tsconfigPaths = require('tsconfig-paths'); + const { paths } = tsconfigPaths.loadConfig(options.tsConfig); + const alias = Object.keys(paths).reduce((acc, k) => { + acc[k] = path.join(context.root, paths[k][0]); + return acc; + }, {}); + + const updated = { + ...config, + target: options.target, + mode: 'development' as const, + context: context.root, + devtool: + options.sourceMap === 'hidden' + ? ('hidden-source-map' as const) + : options.sourceMap + ? ('source-map' as const) + : (false as const), + entry: { + main: { + import: [path.join(context.root, options.main)], + }, + }, + output: { + path: path.join(context.root, options.outputPath), + publicPath: '/', + filename: isProd ? '[name].[contenthash:8][ext]' : '[name][ext]', + }, + devServer: { + port: 4200, + hot: true, + } as any, + module: { + rules: [ + { + test: /\.css$/, + type: 'css/module' as any, + }, + ], + }, + plugins: config.plugins ?? [], + resolve: { + alias, + // This is not working it seems. + //plugins: [new TsconfigPathsPlugin({ + // configFile: path.resolve(__dirname, 'tsconfig.app.json') + //})] + }, + infrastructureLogging: { + debug: false, + }, + builtins: { + html: [ + { + template: options.indexHtml + ? path.join(context.root, options.indexHtml) + : path.join(projectRoot, 'src/index.html'), + }, + ], + define: { + 'process.env.NODE_ENV': isProd ? "'production'" : "'development'", + }, + progress: {}, + // TODO(jack): This should go to withReact. + react: { + runtime: 'automatic', + development: isDev, + refresh: isDev, + }, + }, + }; + + if (Array.isArray(options.assets) && options.assets.length > 0) { + // TODO(jack): uncomment this once copy-webpack-plugin is working + //updated.plugins.push( + // createCopyPlugin( + // normalizeAssets(options.assets, context.root, sourceRoot) + // ) + //); + } + + if (options.optimization) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const minifyPlugin = require('@rspack/plugin-minify'); + updated.optimization = { + ...config.optimization, + minimize: true, + minimizer: [ + ...(config.optimization?.minimizer || []), + new minifyPlugin({ + minifier: 'terser', + }), + ], + }; + } + + return updated; + }; +} diff --git a/src/utils/with-react.ts b/src/utils/with-react.ts new file mode 100644 index 0000000000000..e99f2b43d9b76 --- /dev/null +++ b/src/utils/with-react.ts @@ -0,0 +1,26 @@ +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'; + + config = withWeb(opts)(config, { options, context }); + + return { + ...config, + builtins: { + ...config.builtins, + react: { + runtime: 'automatic', + development: isDev, + refresh: isDev, + }, + }, + }; + }; +} diff --git a/src/utils/with-web.ts b/src/utils/with-web.ts new file mode 100644 index 0000000000000..e04df5898879c --- /dev/null +++ b/src/utils/with-web.ts @@ -0,0 +1,62 @@ +import { Configuration } from '@rspack/core'; +import { ModuleRule } from '@rspack/core/dist/config/module'; +import * as path from 'path'; +import { SharedConfigContext } from './model'; + +export interface WithWebOptions { + style?: 'css' | 'scss' | 'less'; +} + +export function withWeb(opts: WithWebOptions = {}) { + return function makeConfig( + config: Configuration, + { options, context }: SharedConfigContext + ): Configuration { + const isProd = process.env.NODE_ENV === 'production'; + + const projectRoot = path.join( + context.root, + context.projectGraph.nodes[context.projectName].data.root + ); + + return { + ...config, + module: { + ...config.module, + rules: [ + ...(config.module.rules || []), + { + test: /\.css$/, + type: 'css/module' as any, + }, + // TODO(jack): uncomment once supported. + // { + // test: /\.s[ac]ss$/, + // use: [{ builtinLoader: 'builtin:sass-loader' }], + // type: 'css', + // }, + opts.style === 'less' && { + test: /.less$/, + use: [{ loader: require('@rspack/less-loader') }], + type: 'css', + }, + ].filter((a): a is ModuleRule => !!a), + }, + builtins: { + ...config.builtins, + html: [ + ...(config.builtins.html || []), + { + template: options.indexHtml + ? path.join(context.root, options.indexHtml) + : path.join(projectRoot, 'src/index.html'), + }, + ], + define: { + ...config.builtins.define, + 'process.env.NODE_ENV': isProd ? "'production'" : "'development'", + }, + }, + }; + }; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000000..19b9eece4df14 --- /dev/null +++ b/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/tsconfig.lib.json b/tsconfig.lib.json new file mode 100644 index 0000000000000..edecad0e0fc40 --- /dev/null +++ b/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "resolveJsonModule": true, + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["**/*.ts"], + "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 0000000000000..546f12877f7f0 --- /dev/null +++ b/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"] +} From c8df947e325d17387c2840a44d06a906fde1c5d9 Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Wed, 15 Feb 2023 15:30:52 -0500 Subject: [PATCH 02/92] feat: add rspack plugin (#143) --- jest.config.ts | 15 +++++++++++++++ project.json | 17 +++++++++++++++++ tests/rspack.spec.ts | 35 +++++++++++++++++++++++++++++++++++ tsconfig.json | 10 ++++++++++ tsconfig.spec.json | 9 +++++++++ 5 files changed, 86 insertions(+) create mode 100644 jest.config.ts create mode 100644 project.json create mode 100644 tests/rspack.spec.ts create mode 100644 tsconfig.json create mode 100644 tsconfig.spec.json diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 0000000000000..893fb612ef641 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,15 @@ +/* eslint-disable */ +export default { + displayName: 'rspack-e2e', + preset: '../../jest.preset.js', + globals: { + 'ts-jest': { + tsconfig: '/tsconfig.spec.json', + }, + }, + transform: { + '^.+\\.[tj]s$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/e2e/rspack-e2e', +}; diff --git a/project.json b/project.json new file mode 100644 index 0000000000000..d013186ec1ac3 --- /dev/null +++ b/project.json @@ -0,0 +1,17 @@ +{ + "name": "rspack-e2e", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "application", + "sourceRoot": "e2e/rspack-e2e/src", + "targets": { + "e2e": { + "executor": "@nrwl/nx-plugin:e2e", + "options": { + "target": "rspack:build", + "jestConfig": "e2e/rspack-e2e/jest.config.ts" + } + } + }, + "tags": [], + "implicitDependencies": ["rspack"] +} diff --git a/tests/rspack.spec.ts b/tests/rspack.spec.ts new file mode 100644 index 0000000000000..801a7046d2c6c --- /dev/null +++ b/tests/rspack.spec.ts @@ -0,0 +1,35 @@ +import { + ensureNxProject, + runNxCommandAsync, + uniq, +} from '@nrwl/nx-plugin/testing'; + +describe('rspack e2e', () => { + // 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(() => { + ensureNxProject('@nrwl/rspack', 'dist/packages/rspack'); + }); + + afterAll(() => { + // `nx reset` kills the daemon, and performs + // some work which can help clean up e2e leftovers + runNxCommandAsync('reset'); + }); + + it('should create rspack project', async () => { + const project = uniq('myapp'); + await runNxCommandAsync( + `generate @nrwl/rspack:preset ${project} --style=css --unitTestRunner=jest --e2eTestRunner=cypress` + ); + let result = await runNxCommandAsync(`build ${project}`); + expect(result.stdout).toContain('Successfully ran target build'); + + result = await runNxCommandAsync(`e2e e2e`); + expect(result.stdout).toContain('Successfully ran target e2e'); + }, 120_000); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000000..b9c9d95376eb4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 0000000000000..546f12877f7f0 --- /dev/null +++ b/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"] +} From 0850023f710624af095147de8b4362741ad44570 Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Tue, 28 Feb 2023 09:41:22 -0500 Subject: [PATCH 03/92] feat(rspack): update to latest rspack version (#159) --- package.json | 1 + src/generators/preset/schema.json | 2 +- src/utils/versions.ts | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 74a68be690dbe..692e5e27ac2ae 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "generators": "./generators.json", "executors": "./executors.json", "dependencies": { + "@nrwl/devkit": "^15.7.2", "copy-webpack-plugin": "^10.2.4" } } diff --git a/src/generators/preset/schema.json b/src/generators/preset/schema.json index fe47740445161..217ad44f5f8da 100644 --- a/src/generators/preset/schema.json +++ b/src/generators/preset/schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/schema", "cli": "nx", "$id": "Preset", - "title": "Standalone preset", + "title": "Standalone React and rspack preset", "type": "object", "properties": { "name": { diff --git a/src/utils/versions.ts b/src/utils/versions.ts index da07b4cc96b27..ea0a5d07eaa33 100644 --- a/src/utils/versions.ts +++ b/src/utils/versions.ts @@ -1,8 +1,8 @@ -export const rspackCoreVersion = '^0.0.22'; -export const rspackDevServerVersion = '^0.0.22'; +export const rspackCoreVersion = '^0.0.23'; +export const rspackDevServerVersion = '^0.0.23'; -export const rspackPluginMinifyVersion = '^0.0.22'; -export const rspackLessLoaderVersion = '^0.0.22'; +export const rspackPluginMinifyVersion = '^0.0.23'; +export const rspackLessLoaderVersion = '^0.0.23'; export const reactVersion = '~18.2.0'; export const reactDomVersion = '~18.2.0'; From 133bd2c2730a19bccd5c9a29c791aeebd7aa6fd2 Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Thu, 2 Mar 2023 08:11:46 -0500 Subject: [PATCH 04/92] feat(rspack): add missing features (less/sass/stylus, assets, etc.) (#160) --- README.md | 82 +- generators.json | 13 +- package.json | 10 +- src/generators/application/application.ts | 110 +++ .../files/src/app/app.module.__style__ | 0 .../files/src/app/app.spec.tsx__template__ | 4 +- .../files/src/app/app.tsx__template__ | 10 + .../files/src/app/nx-welcome.tsx__template__ | 820 ++++++++++++++++++ .../files/src/assets/.gitkeep | 0 .../files/src/favicon.ico | Bin .../files/src/index.html__template__ | 0 .../files/src/main.tsx__template__ | 0 .../files/src/styles.__style__ | 0 .../files/tsconfig.app.json__template__ | 0 .../lib/add-cypress.ts | 4 +- .../{preset => application}/lib/add-jest.ts | 8 +- src/generators/application/lib/add-linting.ts | 69 ++ .../application/lib/create-ts-config.ts | 47 + .../lib/normalize-options.ts | 8 +- src/generators/application/schema.d.ts | 15 + src/generators/application/schema.json | 92 ++ src/generators/init/init.ts | 9 +- .../preset/files/src/app/app.tsx__template__ | 10 - .../preset/files/tsconfig.json__template__ | 17 - src/generators/preset/preset.ts | 102 +-- src/generators/preset/schema.d.ts | 4 +- src/generators/preset/schema.json | 16 +- .../rspack-project/rspack-project.ts | 4 +- src/utils/create-copy-plugin.ts | 23 - src/utils/get-copy-patterns.ts | 19 + src/utils/versions.ts | 9 + src/utils/with-nx.ts | 16 +- src/utils/with-web.ts | 77 +- 33 files changed, 1417 insertions(+), 181 deletions(-) create mode 100644 src/generators/application/application.ts rename src/generators/{preset => application}/files/src/app/app.module.__style__ (100%) rename src/generators/{preset => application}/files/src/app/app.spec.tsx__template__ (63%) create mode 100644 src/generators/application/files/src/app/app.tsx__template__ create mode 100644 src/generators/application/files/src/app/nx-welcome.tsx__template__ rename src/generators/{preset => application}/files/src/assets/.gitkeep (100%) rename src/generators/{preset => application}/files/src/favicon.ico (100%) rename src/generators/{preset => application}/files/src/index.html__template__ (100%) rename src/generators/{preset => application}/files/src/main.tsx__template__ (100%) rename src/generators/{preset => application}/files/src/styles.__style__ (100%) rename src/generators/{preset => application}/files/tsconfig.app.json__template__ (100%) rename src/generators/{preset => application}/lib/add-cypress.ts (80%) rename src/generators/{preset => application}/lib/add-jest.ts (87%) create mode 100644 src/generators/application/lib/add-linting.ts create mode 100644 src/generators/application/lib/create-ts-config.ts rename src/generators/{preset => application}/lib/normalize-options.ts (84%) create mode 100644 src/generators/application/schema.d.ts create mode 100644 src/generators/application/schema.json delete mode 100644 src/generators/preset/files/src/app/app.tsx__template__ delete mode 100644 src/generators/preset/files/tsconfig.json__template__ delete mode 100644 src/utils/create-copy-plugin.ts create mode 100644 src/utils/get-copy-patterns.ts diff --git a/README.md b/README.md index 1fda4cfdbbf06..0e43fea55a70d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,81 @@ -# rspack +

Nx - Smart, Fast and Extensible Build System

-This library was generated with [Nx](https://nx.dev). +
-## Building +# Nx: Smart, Fast and Extensible Build System -Run `nx build rspack` to build the library. +Nx is a next generation build system with first class monorepo support and powerful integrations. -## Running unit tests +This package is a Rspack plugin for Nx. -Run `nx test rspack` to execute the unit tests via [Jest](https://jestjs.io). +## Getting Started + +Use `--preset=@nrwl/rspack` when creating new workspace. + +e.g. + +```bash +npx create-nx-workspace@latest rspack-demo --preset=@nrwl/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 @nrwl/rspack +``` + +Then, run the `rspack-project` generator: + +```bash +npx nx g @nrwl/rspack:rspack-project --skipValidation +``` + +**Note:** The `--skipValidation` option allows you to overwrite existing build targets. + +## Workspace libraries + +The `@nrwl/rspack` executor support importing workspace libs into the app. + +```bash +npx nx g @nrwl/react:lib mylib +``` + +Import the new library in your app. + +```typescript jsx +// src/app/app.tsx +import { Mylib } from '@rspack-demo/mylib'; + +// ... + +export default function App() { + return ; +} +``` + +Now, run the dev server again to see the new library in action. + +```bash +npm start +``` + +**Note:** You must restart the server if you make any changes to your library. diff --git a/generators.json b/generators.json index b263978a590b4..94d24a47ff309 100644 --- a/generators.json +++ b/generators.json @@ -6,19 +6,26 @@ "rspack-project": { "factory": "./src/generators/rspack-project/rspack-project", "schema": "./src/generators/rspack-project/schema.json", - "description": "rspack generator" + "description": "Rspack project generator." }, "init": { "factory": "./src/generators/init/init", "schema": "./src/generators/init/schema.json", - "description": "init generator", + "description": "Rspack init generator.", "hidden": true }, "preset": { "factory": "./src/generators/preset/preset", "schema": "./src/generators/preset/schema.json", - "description": "preset generator", + "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/package.json b/package.json index 692e5e27ac2ae..de4bf5c09bb07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nrwl/rspack", - "version": "15.7.0-beta.1", + "version": "0.0.1", "type": "commonjs", "repository": { "type": "git", @@ -19,7 +19,11 @@ "generators": "./generators.json", "executors": "./executors.json", "dependencies": { - "@nrwl/devkit": "^15.7.2", - "copy-webpack-plugin": "^10.2.4" + "@nrwl/linter": "^15.8.1", + "@nrwl/devkit": "^15.8.1", + "ajv": "^8.12.0", + "sass-loader": "^12.2.0", + "stylus-loader": "^7.1.0", + "less-loader": "11.1.0" } } diff --git a/src/generators/application/application.ts b/src/generators/application/application.ts new file mode 100644 index 0000000000000..00c86601eb0a6 --- /dev/null +++ b/src/generators/application/application.ts @@ -0,0 +1,110 @@ +import { + addDependenciesToPackageJson, + addProjectConfiguration, + ensurePackage, + formatFiles, + generateFiles, + joinPathFragments, + names, + offsetFromRoot as _offsetFromRoot, + Tree, +} from '@nrwl/devkit'; +import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; +import { version as nxVersion } from 'nx/package.json'; +import * as path from 'path'; +import { + lessVersion, + reactDomVersion, + reactVersion, + sassVersion, + stylusVersion, + typesReactDomVersion, + typesReactVersion, +} from '../../utils/versions'; +import projectGenerator from '../rspack-project/rspack-project'; +import { addCypress } from './lib/add-cypress'; +import { addJest } from './lib/add-jest'; +import { addLinting } from './lib/add-linting'; +import { createTsConfig } from './lib/create-ts-config'; +import { normalizeOptions } from './lib/normalize-options'; +import { ApplicationGeneratorSchema, NormalizedSchema } from './schema'; + +export default async function ( + tree: Tree, + _options: ApplicationGeneratorSchema +) { + await ensurePackage(tree, '@nrwl/react', nxVersion); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { reactInitGenerator } = require('@nrwl/react'); + + const tasks = []; + const options = normalizeOptions(tree, _options); + + options.style ??= 'css'; + + addProjectConfiguration(tree, options.name, { + root: options.appProjectRoot, + projectType: 'application', + sourceRoot: `${options.appProjectRoot}/src`, + targets: {}, + }); + + const offsetFromRoot = _offsetFromRoot(options.appProjectRoot); + generateFiles(tree, path.join(__dirname, 'files'), options.appProjectRoot, { + ...options, + ...names(options.name), + offsetFromRoot, + template: '', + }); + + createTsConfig( + tree, + options, + joinPathFragments(offsetFromRoot, 'tsconfig.base.json') + ); + + const projectTask = await projectGenerator(tree, { + project: options.name, + devServer: true, + tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'), + uiFramework: 'react', + target: 'web', + main: joinPathFragments(options.appProjectRoot, 'src/main.tsx'), + }); + tasks.push(projectTask); + + const jestTask = await addJest(tree, options); + tasks.push(jestTask); + + const cypressTask = await addCypress(tree, options); + tasks.push(cypressTask); + + const lintTask = await addLinting(tree, options); + tasks.push(lintTask); + + const installTask = addDependenciesToPackageJson( + tree, + { react: reactVersion, 'react-dom': reactDomVersion }, + { + ...getStyleDependency(options), + '@nrwl/react': nxVersion, + '@types/react': typesReactVersion, + '@types/react-dom': typesReactDomVersion, + } + ); + tasks.push(installTask); + + const reactInitTask = await reactInitGenerator(tree, options); + tasks.push(reactInitTask); + + await formatFiles(tree); + + return runTasksInSerial(...tasks); +} + +function getStyleDependency(options: NormalizedSchema): Record { + if (options.style === 'scss') return { sass: sassVersion }; + if (options.style === 'less') return { less: lessVersion }; + if (options.style === 'styl') return { stylus: stylusVersion }; + return {}; +} diff --git a/src/generators/preset/files/src/app/app.module.__style__ b/src/generators/application/files/src/app/app.module.__style__ similarity index 100% rename from src/generators/preset/files/src/app/app.module.__style__ rename to src/generators/application/files/src/app/app.module.__style__ diff --git a/src/generators/preset/files/src/app/app.spec.tsx__template__ b/src/generators/application/files/src/app/app.spec.tsx__template__ similarity index 63% rename from src/generators/preset/files/src/app/app.spec.tsx__template__ rename to src/generators/application/files/src/app/app.spec.tsx__template__ index d229ddcff5f79..b67b70fea41f8 100644 --- a/src/generators/preset/files/src/app/app.spec.tsx__template__ +++ b/src/generators/application/files/src/app/app.spec.tsx__template__ @@ -2,10 +2,10 @@ import { render } from '@testing-library/react'; import { App } from './app'; describe('App component', () => { - it('should render a header', () => { + it('should render a welcome message', () => { const { getByTestId } = render(); - expect(getByTestId('header')).toBeTruthy(); + expect(getByTestId('welcome')).toBeTruthy(); }); }); diff --git a/src/generators/application/files/src/app/app.tsx__template__ b/src/generators/application/files/src/app/app.tsx__template__ new file mode 100644 index 0000000000000..54cc8cf2e4fbd --- /dev/null +++ b/src/generators/application/files/src/app/app.tsx__template__ @@ -0,0 +1,10 @@ +import styles from './app.module.<%= style %>'; +import { NxWelcome } from './nx-welcome'; + +export function App() { + return ( +
+ +
+ ); +} diff --git a/src/generators/application/files/src/app/nx-welcome.tsx__template__ b/src/generators/application/files/src/app/nx-welcome.tsx__template__ new file mode 100644 index 0000000000000..761a47a0e2978 --- /dev/null +++ b/src/generators/application/files/src/app/nx-welcome.tsx__template__ @@ -0,0 +1,820 @@ +/* + * * * * * * * * * * * * * * * * * * * * * * * * * * * * + This is a starter component and can be deleted. + * * * * * * * * * * * * * * * * * * * * * * * * * * * * + Delete this file and get started with your project! + * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ +export function NxWelcome({ title }: { title: string }) { + return ( + <> +