diff --git a/docs/shared/migration/migration-cra.md b/docs/shared/migration/migration-cra.md index e2394187ef..22d1c4c2ea 100644 --- a/docs/shared/migration/migration-cra.md +++ b/docs/shared/migration/migration-cra.md @@ -43,7 +43,7 @@ In this article, youโ€™ll learn how to: - Convert CRA scripts for use in Nx - Create a library and use it in your application -For this example, youโ€™ll be migrating the default CRA typescript template app into an Nx workspace. This is the code that is generated when you run `yarn create react-app webapp --template typescript`. +For this example, youโ€™ll be migrating the default CRA typescript template app into an Nx workspace. This is the code that is generated when you run `npx create-react-app webapp --template typescript`. There is also a [repo](https://github.com/nrwl/cra-to-nx-migration) that shows the finished result of this guide and for each step a [diff](https://github.com/nrwl/cra-to-nx-migration/commits/main) will be provided to see the exact code changes that occur for that step. diff --git a/e2e/cra-to-nx/src/cra-to-nx.test.ts b/e2e/cra-to-nx/src/cra-to-nx.test.ts index f38c85b797..1f29a782d2 100644 --- a/e2e/cra-to-nx/src/cra-to-nx.test.ts +++ b/e2e/cra-to-nx/src/cra-to-nx.test.ts @@ -23,7 +23,7 @@ describe('cra-to-nx', () => { const craToNxOutput = runCommand( `${ pmc.runUninstalledPackage - } cra-to-nx@${getPublishedVersion()} --nxCloud=false` + } cra-to-nx@${getPublishedVersion()} --nxCloud=false --integrated` ); expect(craToNxOutput).toContain('๐ŸŽ‰ Done!'); @@ -46,7 +46,7 @@ describe('cra-to-nx', () => { const craToNxOutput = runCommand( `${ pmc.runUninstalledPackage - } cra-to-nx@${getPublishedVersion()} --nxCloud=false --vite` + } cra-to-nx@${getPublishedVersion()} --nxCloud=false --vite --integrated` ); expect(craToNxOutput).toContain('๐ŸŽ‰ Done!'); @@ -56,6 +56,9 @@ describe('cra-to-nx', () => { runCLI(`build ${appName}`); checkFilesExist(`dist/apps/${appName}/index.html`); + + const unitTestsOutput = runCLI(`test ${appName}`); + expect(unitTestsOutput).toContain('Successfully ran target test'); }); it('should convert to an integrated workspace with Vite with custom port', () => { @@ -66,10 +69,53 @@ describe('cra-to-nx', () => { runCommand( `${ pmc.runUninstalledPackage - } cra-to-nx@${getPublishedVersion()} --nxCloud=false --vite --force` + } cra-to-nx@${getPublishedVersion()} --nxCloud=false --vite --force --integrated` ); const viteConfig = readFile(`apps/${appName}/vite.config.js`); expect(viteConfig).toContain('port: 3000'); + + const unitTestsOutput = runCLI(`test ${appName}`); + expect(unitTestsOutput).toContain('Successfully ran target test'); + }); + + it('should convert to a nested workspace with craco (webpack)', () => { + const appName = 'my-app'; + createReactApp(appName); + + const craToNxOutput = runCommand( + `${ + pmc.runUninstalledPackage + } cra-to-nx@${getPublishedVersion()} --nxCloud=false` + ); + + expect(craToNxOutput).toContain('๐ŸŽ‰ Done!'); + + runCLI(`build ${appName}`); + checkFilesExist(`public/index.html`, `dist/asset-manifest.json`); + const manifest = readJson(`dist/asset-manifest.json`); + checkFilesExist(...manifest['entrypoints'].map((f) => `dist/${f}`)); + }); + + it('should convert to an nested workspace with Vite', () => { + const appName = 'my-app'; + createReactApp(appName); + + const craToNxOutput = runCommand( + `${ + pmc.runUninstalledPackage + } cra-to-nx@${getPublishedVersion()} --nxCloud=false --vite` + ); + + expect(craToNxOutput).toContain('๐ŸŽ‰ Done!'); + + const viteConfig = readFile(`vite.config.js`); + expect(viteConfig).toContain('port: 4200'); // default port + + runCLI(`build ${appName}`); + checkFilesExist(`dist/index.html`); + + const unitTestsOutput = runCLI(`test ${appName}`); + expect(unitTestsOutput).toContain('Successfully ran target test'); }); }); diff --git a/packages/cra-to-nx/src/index.ts b/packages/cra-to-nx/src/index.ts index 66be0664ed..f9fcf29805 100644 --- a/packages/cra-to-nx/src/index.ts +++ b/packages/cra-to-nx/src/index.ts @@ -27,6 +27,11 @@ export const commandsObject = yargs describe: 'Use Vite and Vitest (instead of Webpack and Jest)', default: false, }) + .option('integrated', { + type: 'boolean', + describe: 'Use integrated folder structure, with apps folder', + default: false, + }) .help(); createNxWorkspaceForReact(commandsObject.argv).catch((e) => { diff --git a/packages/cra-to-nx/src/lib/add-craco-commands-to-package-scripts.ts b/packages/cra-to-nx/src/lib/add-craco-commands-to-package-scripts.ts index d65498b933..c2d4b3ba36 100644 --- a/packages/cra-to-nx/src/lib/add-craco-commands-to-package-scripts.ts +++ b/packages/cra-to-nx/src/lib/add-craco-commands-to-package-scripts.ts @@ -1,13 +1,20 @@ import { readJsonFile, writeJsonFile } from 'nx/src/utils/fileutils'; -export function addCracoCommandsToPackageScripts(appName: string) { - const packageJson = readJsonFile(`apps/${appName}/package.json`); +export function addCracoCommandsToPackageScripts( + appName: string, + isNested: boolean +) { + const packageJsonPath = isNested + ? 'package.json' + : `apps/${appName}/package.json`; + const distPath = isNested ? 'dist' : `../../dist/apps/${appName}`; + const packageJson = readJsonFile(packageJsonPath); packageJson.scripts = { ...packageJson.scripts, start: 'craco start', serve: 'npm start', - build: `cross-env BUILD_PATH=../../dist/apps/${appName} craco build`, + build: `cross-env BUILD_PATH=${distPath} craco build`, test: 'craco test', }; - writeJsonFile(`apps/${appName}/package.json`, packageJson); + writeJsonFile(packageJsonPath, packageJson); } diff --git a/packages/cra-to-nx/src/lib/add-vite-commands-to-package-scripts.ts b/packages/cra-to-nx/src/lib/add-vite-commands-to-package-scripts.ts index 340b8f9a98..8771f63e72 100644 --- a/packages/cra-to-nx/src/lib/add-vite-commands-to-package-scripts.ts +++ b/packages/cra-to-nx/src/lib/add-vite-commands-to-package-scripts.ts @@ -1,7 +1,13 @@ import { readJsonFile, writeJsonFile } from 'nx/src/utils/fileutils'; -export function addViteCommandsToPackageScripts(appName: string) { - const packageJson = readJsonFile(`apps/${appName}/package.json`); +export function addViteCommandsToPackageScripts( + appName: string, + isNested: boolean +) { + const packageJsonPath = isNested + ? 'package.json' + : `apps/${appName}/package.json`; + const packageJson = readJsonFile(packageJsonPath); packageJson.scripts = { ...packageJson.scripts, start: 'vite', @@ -9,5 +15,5 @@ export function addViteCommandsToPackageScripts(appName: string) { build: `vite build`, test: 'vitest', }; - writeJsonFile(`apps/${appName}/package.json`, packageJson, { spaces: 2 }); + writeJsonFile(packageJsonPath, packageJson, { spaces: 2 }); } diff --git a/packages/cra-to-nx/src/lib/clean-up-files.ts b/packages/cra-to-nx/src/lib/clean-up-files.ts index 5a8623aa4b..6162b76ca4 100644 --- a/packages/cra-to-nx/src/lib/clean-up-files.ts +++ b/packages/cra-to-nx/src/lib/clean-up-files.ts @@ -1,11 +1,25 @@ import { removeSync } from 'fs-extra'; import { readJsonFile, writeJsonFile } from 'nx/src/utils/fileutils'; -export function cleanUpFiles(appName: string) { +export function cleanUpFiles(appName: string, isNested: boolean) { // Delete targets from project since we delegate to npm scripts. - const json = readJsonFile(`apps/${appName}/project.json`); + const projectJsonPath = isNested + ? 'project.json' + : `apps/${appName}/project.json`; + const json = readJsonFile(projectJsonPath); delete json.targets; - writeJsonFile(`apps/${appName}/project.json`, json); + if (isNested) { + if (json.sourceRoot) { + json.sourceRoot = json.sourceRoot.replace(`apps/${appName}/`, ''); + } + if (json['$schema']) { + json['$schema'] = json['$schema'].replace( + '../../node_modules', + 'node_modules' + ); + } + } + writeJsonFile(projectJsonPath, json); removeSync('temp-workspace'); } diff --git a/packages/cra-to-nx/src/lib/cra-to-nx.ts b/packages/cra-to-nx/src/lib/cra-to-nx.ts index 7a97c0b8f3..bf8facace6 100644 --- a/packages/cra-to-nx/src/lib/cra-to-nx.ts +++ b/packages/cra-to-nx/src/lib/cra-to-nx.ts @@ -20,27 +20,35 @@ import { renameJsToJsx } from './rename-js-to-jsx'; import { writeViteIndexHtml } from './write-vite-index-html'; import { checkForCustomWebpackSetup } from './check-for-custom-webpack-setup'; +export interface Options { + force: boolean; + e2e: boolean; + nxCloud: boolean; + vite: boolean; + integrated: boolean; +} +interface NormalizedOptions extends Options { + packageManager: string; + pmc: PackageManagerCommands; + appIsJs: boolean; + reactAppName: string; + isCRA5: boolean; + npxYesFlagNeeded: boolean; + isVite: boolean; + isNested: boolean; +} + function addDependencies(pmc: PackageManagerCommands, ...deps: string[]) { const depsArg = deps.join(' '); output.log({ title: `๐Ÿ“ฆ Adding dependencies: ${depsArg}` }); execSync(`${pmc.addDev} ${depsArg}`, { stdio: [0, 1, 2] }); } -export async function createNxWorkspaceForReact(options: Record) { - if (!options.force) { - checkForUncommittedChanges(); - checkForCustomWebpackSetup(); - } +export function normalizeOptions(options: Options): NormalizedOptions { const packageManager = detectPackageManager(); const pmc = getPackageManagerCommand(packageManager); - output.log({ title: 'โœจ Nx initialization' }); - - let appIsJs = true; - - if (fileExists(`tsconfig.json`)) { - appIsJs = false; - } + const appIsJs = !fileExists(`tsconfig.json`); const reactAppName = readNameFromPackageJson(); const packageJson = readJsonFile('package.json'); @@ -52,110 +60,50 @@ export async function createNxWorkspaceForReact(options: Record) { const npmVersion = execSync('npm -v').toString(); // Should remove this check 04/2023 once Node 14 & npm 6 reach EOL const npxYesFlagNeeded = !npmVersion.startsWith('6'); // npm 7 added -y flag to npx - const isVite = options.vite || options.bundler === 'vite'; + const isVite = options.vite; + const isNested = !options.integrated; - execSync( - `npx ${ - npxYesFlagNeeded ? '-y' : '' - } create-nx-workspace@latest temp-workspace --appName=${reactAppName} --preset=react --style=css --packageManager=${packageManager} ${ - options.nxCloud ? '--nxCloud' : '--nxCloud=false' - }`, - { stdio: [0, 1, 2] } - ); + return { + ...options, + packageManager, + pmc, + appIsJs, + reactAppName, + isCRA5, + npxYesFlagNeeded, + isVite, + isNested, + }; +} - output.log({ title: '๐Ÿ‘‹ Welcome to Nx!' }); - - output.log({ title: '๐Ÿงน Clearing unused files' }); - - copySync(`temp-workspace/apps/${reactAppName}/project.json`, 'project.json'); - removeSync(`temp-workspace/apps/${reactAppName}/`); - removeSync('node_modules'); - - output.log({ title: '๐Ÿšš Moving your React app in your new Nx workspace' }); - - const requiredCraFiles = [ - 'project.json', - 'package.json', - 'src', - 'public', - appIsJs ? null : 'tsconfig.json', - packageManager === 'yarn' ? 'yarn.lock' : null, - packageManager === 'pnpm' ? 'pnpm-lock.yaml' : null, - packageManager === 'npm' ? 'package-lock.json' : null, - ]; - - const optionalCraFiles = ['README.md']; - - const filesToMove = [...requiredCraFiles, ...optionalCraFiles].filter( - Boolean - ); - - filesToMove.forEach((f) => { - try { - moveSync(f, `temp-workspace/apps/${reactAppName}/${f}`, { - overwrite: true, - }); - } catch (error) { - if (requiredCraFiles.includes(f)) { - throw error; - } - } - }); - - process.chdir('temp-workspace/'); - - if (isVite) { - output.log({ title: '๐Ÿง‘โ€๐Ÿ”ง Setting up Vite' }); - const { addViteCommandsToPackageScripts } = await import( - './add-vite-commands-to-package-scripts' - ); - addViteCommandsToPackageScripts(reactAppName); - writeViteConfig(reactAppName); - writeViteIndexHtml(reactAppName); - renameJsToJsx(reactAppName); - } else { - output.log({ title: '๐Ÿง‘โ€๐Ÿ”ง Setting up craco + Webpack' }); - const { addCracoCommandsToPackageScripts } = await import( - './add-craco-commands-to-package-scripts' - ); - addCracoCommandsToPackageScripts(reactAppName); - - writeCracoConfig(reactAppName, isCRA5); - - output.log({ - title: '๐Ÿ›ฌ Skip CRA preflight check since Nx manages the monorepo', - }); - - execSync(`echo "SKIP_PREFLIGHT_CHECK=true" > .env`, { stdio: [0, 1, 2] }); +export async function createNxWorkspaceForReact(options: Record) { + if (!options.force) { + checkForUncommittedChanges(); + checkForCustomWebpackSetup(); } + output.log({ title: 'โœจ Nx initialization' }); + + const normalizedOptions = normalizeOptions(options as Options); + reorgnizeWorkspaceStructure(normalizedOptions); +} + +async function reorgnizeWorkspaceStructure(options: NormalizedOptions) { + createTempWorkspace(options); + + moveFilesToTempWorkspace(options); + + await addBundler(options); + output.log({ title: '๐Ÿงถ Add all node_modules to .gitignore' }); execSync(`echo "node_modules" >> .gitignore`, { stdio: [0, 1, 2] }); process.chdir('../'); - output.log({ title: '๐Ÿšš Folder restructuring.' }); + copyFromTempWorkspaceToRoot(); - readdirSync('./temp-workspace').forEach((f) => { - moveSync(`temp-workspace/${f}`, `./${f}`, { overwrite: true }); - }); - - output.log({ title: '๐Ÿงน Cleaning up.' }); - - cleanUpFiles(reactAppName); - - output.log({ title: "๐Ÿ“ƒ Extend the app's tsconfig.json from the base" }); - - setupTsConfig(reactAppName); - - if (options.e2e) { - output.log({ title: '๐Ÿ“ƒ Setup e2e tests' }); - setupE2eProject(reactAppName); - } else { - removeSync(`apps/${reactAppName}-e2e`); - execSync(`${pmc.rm} @nrwl/cypress eslint-plugin-cypress`); - } + cleanUpUnusedFilesAndAddConfigFiles(options); output.log({ title: '๐Ÿ™‚ Please be patient, one final step remaining!' }); @@ -164,17 +112,17 @@ export async function createNxWorkspaceForReact(options: Record) { }); addDependencies( - pmc, + options.pmc, '@testing-library/jest-dom', 'eslint-config-react-app', 'web-vitals', 'jest-watch-typeahead' ); - if (isVite) { - addDependencies(pmc, 'vite', 'vitest', '@vitejs/plugin-react'); + if (options.isVite) { + addDependencies(options.pmc, 'vite', 'vitest', '@vitejs/plugin-react'); } else { - addDependencies(pmc, '@craco/craco', 'cross-env', 'react-scripts'); + addDependencies(options.pmc, '@craco/craco', 'cross-env', 'react-scripts'); } output.log({ title: '๐ŸŽ‰ Done!' }); @@ -188,20 +136,148 @@ export async function createNxWorkspaceForReact(options: Record) { ], }); - if (isVite) { + if (options.isVite) { + const indexPath = options.isNested + ? 'index.html' + : `apps/${options.reactAppName}/index.html`; + const oldIndexPath = options.isNested + ? 'public/index.html' + : `apps/${options.reactAppName}/public/index.html`; output.note({ - title: `A new apps/${reactAppName}/index.html has been created. Compare it to the previous apps/${reactAppName}/public/index.html file and make any changes needed, then delete the previous file.`, + title: `A new ${indexPath} has been created. Compare it to the previous ${oldIndexPath} file and make any changes needed, then delete the previous file.`, }); } output.note({ title: 'Or, you can try the commands!', bodyLines: [ - `npx nx serve ${reactAppName}`, - `npx nx build ${reactAppName}`, - `npx nx test ${reactAppName}`, + `npx nx serve ${options.reactAppName}`, + `npx nx build ${options.reactAppName}`, + `npx nx test ${options.reactAppName}`, ` `, `https://nx.dev/getting-started/intro#10-try-the-commands`, ], }); } + +function createTempWorkspace(options: NormalizedOptions) { + execSync( + `npx ${ + options.npxYesFlagNeeded ? '-y' : '' + } create-nx-workspace@latest temp-workspace --appName=${ + options.reactAppName + } --preset=react --style=css --packageManager=${options.packageManager} ${ + options.nxCloud ? '--nxCloud' : '--nxCloud=false' + }`, + { stdio: [0, 1, 2] } + ); + + output.log({ title: '๐Ÿ‘‹ Welcome to Nx!' }); + + output.log({ title: '๐Ÿงน Clearing unused files' }); + + copySync( + `temp-workspace/apps/${options.reactAppName}/project.json`, + 'project.json' + ); + removeSync(`temp-workspace/apps/${options.reactAppName}/`); + removeSync('node_modules'); +} + +function moveFilesToTempWorkspace(options: NormalizedOptions) { + output.log({ title: '๐Ÿšš Moving your React app in your new Nx workspace' }); + + const requiredCraFiles = [ + 'project.json', + options.isNested ? null : 'package.json', + 'src', + 'public', + options.appIsJs ? null : 'tsconfig.json', + options.packageManager === 'yarn' ? 'yarn.lock' : null, + options.packageManager === 'pnpm' ? 'pnpm-lock.yaml' : null, + options.packageManager === 'npm' ? 'package-lock.json' : null, + ]; + + const optionalCraFiles = ['README.md']; + + const filesToMove = [...requiredCraFiles, ...optionalCraFiles].filter( + Boolean + ); + + filesToMove.forEach((f) => { + try { + moveSync( + f, + options.isNested + ? `temp-workspace/${f}` + : `temp-workspace/apps/${options.reactAppName}/${f}`, + { + overwrite: true, + } + ); + } catch (error) { + if (requiredCraFiles.includes(f)) { + throw error; + } + } + }); + + process.chdir('temp-workspace/'); +} + +async function addBundler(options: NormalizedOptions) { + if (options.isVite) { + output.log({ title: '๐Ÿง‘โ€๐Ÿ”ง Setting up Vite' }); + const { addViteCommandsToPackageScripts } = await import( + './add-vite-commands-to-package-scripts' + ); + addViteCommandsToPackageScripts(options.reactAppName, options.isNested); + writeViteConfig(options.reactAppName, options.isNested, options.appIsJs); + writeViteIndexHtml(options.reactAppName, options.isNested, options.appIsJs); + renameJsToJsx(options.reactAppName, options.isNested); + } else { + output.log({ title: '๐Ÿง‘โ€๐Ÿ”ง Setting up craco + Webpack' }); + const { addCracoCommandsToPackageScripts } = await import( + './add-craco-commands-to-package-scripts' + ); + addCracoCommandsToPackageScripts(options.reactAppName, options.isNested); + + writeCracoConfig(options.reactAppName, options.isCRA5, options.isNested); + + output.log({ + title: '๐Ÿ›ฌ Skip CRA preflight check since Nx manages the monorepo', + }); + + execSync(`echo "SKIP_PREFLIGHT_CHECK=true" > .env`, { stdio: [0, 1, 2] }); + } +} + +function copyFromTempWorkspaceToRoot() { + output.log({ title: '๐Ÿšš Folder restructuring.' }); + + readdirSync('./temp-workspace').forEach((f) => { + moveSync(`temp-workspace/${f}`, `./${f}`, { overwrite: true }); + }); +} + +function cleanUpUnusedFilesAndAddConfigFiles(options: NormalizedOptions) { + output.log({ title: '๐Ÿงน Cleaning up.' }); + + cleanUpFiles(options.reactAppName, options.isNested); + + output.log({ title: "๐Ÿ“ƒ Extend the app's tsconfig.json from the base" }); + + setupTsConfig(options.reactAppName, options.isNested); + + if (options.e2e && !options.isNested) { + output.log({ title: '๐Ÿ“ƒ Setup e2e tests' }); + setupE2eProject(options.reactAppName); + } else { + removeSync(`apps/${options.reactAppName}-e2e`); + execSync(`${options.pmc.rm} @nrwl/cypress eslint-plugin-cypress`); + } + + if (options.isNested) { + removeSync('apps'); + } +} diff --git a/packages/cra-to-nx/src/lib/rename-js-to-jsx.ts b/packages/cra-to-nx/src/lib/rename-js-to-jsx.ts index 951c8e8e0a..d3ee6cbcca 100644 --- a/packages/cra-to-nx/src/lib/rename-js-to-jsx.ts +++ b/packages/cra-to-nx/src/lib/rename-js-to-jsx.ts @@ -1,10 +1,9 @@ import { sync } from 'glob'; import { renameSync, readFileSync } from 'fs-extra'; -import { performance } from 'perf_hooks'; // Vite cannot process JSX like
or
unless the file is named .jsx or .tsx -export function renameJsToJsx(appName: string) { - const files = sync(`apps/${appName}/src/**/*.js`); +export function renameJsToJsx(appName: string, isNested: boolean) { + const files = sync(isNested ? 'src/**/*.js' : `apps/${appName}/src/**/*.js`); files.forEach((file) => { const content = readFileSync(file).toString(); diff --git a/packages/cra-to-nx/src/lib/tsconfig-setup.ts b/packages/cra-to-nx/src/lib/tsconfig-setup.ts index c8e89a2a62..a33a52f72a 100644 --- a/packages/cra-to-nx/src/lib/tsconfig-setup.ts +++ b/packages/cra-to-nx/src/lib/tsconfig-setup.ts @@ -3,9 +3,10 @@ import { readJsonFile, writeJsonFile, } from 'nx/src/utils/fileutils'; +import { join } from 'path'; -const defaultTsConfig = { - extends: '../../tsconfig.base.json', +const defaultTsConfig = (relativePathToRoot: string) => ({ + extends: join(relativePathToRoot, 'tsconfig.base.json'), compilerOptions: { jsx: 'react', allowJs: true, @@ -22,26 +23,26 @@ const defaultTsConfig = { path: './tsconfig.spec.json', }, ], -}; +}); -const defaultTsConfigApp = { +const defaultTsConfigApp = (relativePathToRoot: string) => ({ extends: './tsconfig.json', compilerOptions: { - outDir: '../../dist/out-tsc', + outDir: join(relativePathToRoot, 'dist/out-tsc'), types: ['node'], }, files: [ - '../../node_modules/@nrwl/react/typings/cssmodule.d.ts', - '../../node_modules/@nrwl/react/typings/image.d.ts', + join(relativePathToRoot, 'node_modules/@nrwl/react/typings/cssmodule.d.ts'), + join(relativePathToRoot, 'node_modules/@nrwl/react/typings/image.d.ts'), ], exclude: ['**/*.spec.ts', '**/*.spec.tsx'], include: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'], -}; +}); -const defaultTsConfigSpec = { +const defaultTsConfigSpec = (relativePathToRoot: string) => ({ extends: './tsconfig.json', compilerOptions: { - outDir: '../../dist/out-tsc', + outDir: join(relativePathToRoot, 'dist/out-tsc'), module: 'commonjs', types: ['jest', 'node'], }, @@ -53,15 +54,28 @@ const defaultTsConfigSpec = { '**/*.d.ts', ], files: [ - '../../node_modules/@nrwl/react/typings/cssmodule.d.ts', - '../../node_modules/@nrwl/react/typings/image.d.ts', + join(relativePathToRoot, 'node_modules/@nrwl/react/typings/cssmodule.d.ts'), + join(relativePathToRoot, 'node_modules/@nrwl/react/typings/image.d.ts'), ], -}; +}); -export function setupTsConfig(appName: string) { - if (fileExists(`apps/${appName}/tsconfig.json`)) { - const json = readJsonFile(`apps/${appName}/tsconfig.json`); - json.extends = '../../tsconfig.base.json'; +export function setupTsConfig(appName: string, isNested: boolean) { + const tsconfigPath = isNested + ? 'tsconfig.json' + : `apps/${appName}/tsconfig.json`; + const tsconfigAppPath = isNested + ? 'tsconfig.app.json' + : `apps/${appName}/tsconfig.app.json`; + const tsconfiSpecPath = isNested + ? 'tsconfig.spec.json' + : `apps/${appName}/tsconfig.spec.json`; + const tsconfigBasePath = isNested + ? './tsconfig.base.json' + : '../../tsconfig.base.json'; + const relativePathToRoot = isNested ? '.' : '../../'; + if (fileExists(tsconfigPath)) { + const json = readJsonFile(tsconfigPath); + json.extends = tsconfigBasePath; if (json.compilerOptions) { json.compilerOptions.jsx = 'react'; } else { @@ -72,24 +86,24 @@ export function setupTsConfig(appName: string) { allowSyntheticDefaultImports: true, }; } - writeJsonFile(`apps/${appName}/tsconfig.json`, json); + writeJsonFile(tsconfigPath, json); } else { - writeJsonFile(`apps/${appName}/tsconfig.json`, defaultTsConfig); + writeJsonFile(tsconfigPath, defaultTsConfig(relativePathToRoot)); } - if (fileExists(`apps/${appName}/tsconfig.app.json`)) { - const json = readJsonFile(`apps/${appName}/tsconfig.app.json`); + if (fileExists(tsconfigAppPath)) { + const json = readJsonFile(tsconfigAppPath); json.extends = './tsconfig.json'; - writeJsonFile(`apps/${appName}/tsconfig.app.json`, json); + writeJsonFile(tsconfigAppPath, json); } else { - writeJsonFile(`apps/${appName}/tsconfig.app.json`, defaultTsConfigApp); + writeJsonFile(tsconfigAppPath, defaultTsConfigApp(relativePathToRoot)); } - if (fileExists(`apps/${appName}/tsconfig.spec.json`)) { - const json = readJsonFile(`apps/${appName}/tsconfig.spec.json`); + if (fileExists(tsconfiSpecPath)) { + const json = readJsonFile(tsconfiSpecPath); json.extends = './tsconfig.json'; - writeJsonFile(`apps/${appName}/tsconfig.spec.json`, json); + writeJsonFile(tsconfiSpecPath, json); } else { - writeJsonFile(`apps/${appName}/tsconfig.spec.json`, defaultTsConfigSpec); + writeJsonFile(tsconfiSpecPath, defaultTsConfigSpec(relativePathToRoot)); } } diff --git a/packages/cra-to-nx/src/lib/write-craco-config.ts b/packages/cra-to-nx/src/lib/write-craco-config.ts index 90412e3d7c..b9c629360a 100644 --- a/packages/cra-to-nx/src/lib/write-craco-config.ts +++ b/packages/cra-to-nx/src/lib/write-craco-config.ts @@ -1,6 +1,10 @@ import { writeFileSync } from 'fs'; -export function writeCracoConfig(appName: string, isCRA5: boolean) { +export function writeCracoConfig( + appName: string, + isCRA5: boolean, + isNested: boolean +) { const configOverride = ` const path = require('path'); const TsConfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); @@ -57,5 +61,8 @@ export function writeCracoConfig(appName: string, isCRA5: boolean) { }, }; `; - writeFileSync(`apps/${appName}/craco.config.js`, configOverride); + writeFileSync( + isNested ? 'craco.config.js' : `apps/${appName}/craco.config.js`, + configOverride + ); } diff --git a/packages/cra-to-nx/src/lib/write-vite-config.ts b/packages/cra-to-nx/src/lib/write-vite-config.ts index 14a3bea837..b123d2e83c 100644 --- a/packages/cra-to-nx/src/lib/write-vite-config.ts +++ b/packages/cra-to-nx/src/lib/write-vite-config.ts @@ -1,6 +1,10 @@ import * as fs from 'fs'; -export function writeViteConfig(appName: string) { +export function writeViteConfig( + appName: string, + isNested: boolean, + isJs: boolean +) { let port = 4200; // Use PORT from .env file if it exists in project. @@ -14,14 +18,14 @@ export function writeViteConfig(appName: string) { } fs.writeFileSync( - `apps/${appName}/vite.config.js`, + isNested ? 'vite.config.js' : `apps/${appName}/vite.config.js`, `import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ export default defineConfig({ build: { - outDir: '../../dist/apps/${appName}' + outDir: ${isNested ? `'./dist'` : `'../../dist/apps/${appName}'`} }, server: { port: ${port}, @@ -30,7 +34,7 @@ export default defineConfig({ test: { globals: true, environment: 'jsdom', - setupFiles: 'src/setupTests.js', + setupFiles: 'src/setupTests.${isJs ? 'js' : 'ts'}', css: true, }, plugins: [react()], diff --git a/packages/cra-to-nx/src/lib/write-vite-index-html.ts b/packages/cra-to-nx/src/lib/write-vite-index-html.ts index 8f2ea874ee..28f9d38aca 100644 --- a/packages/cra-to-nx/src/lib/write-vite-index-html.ts +++ b/packages/cra-to-nx/src/lib/write-vite-index-html.ts @@ -1,8 +1,17 @@ import * as fs from 'fs'; -export function writeViteIndexHtml(appName: string) { +export function writeViteIndexHtml( + appName: string, + isNested: boolean, + isJs: boolean +) { + const indexPath = isNested ? 'index.html' : `apps/${appName}/index.html`; + if (fs.existsSync(indexPath)) { + fs.copyFileSync(indexPath, indexPath + '.old'); + } + const indexFile = isJs ? '/src/index.jsx' : '/src/index.tsx'; fs.writeFileSync( - `apps/${appName}/index.html`, + indexPath, ` @@ -13,7 +22,7 @@ export function writeViteIndexHtml(appName: string) {
- + ` );