feat(core): support nested structure for cra-to-nx (#13253)

This commit is contained in:
Emily Xiong 2022-11-21 10:12:53 -05:00 committed by GitHub
parent 2772fab0e7
commit 590a3dc769
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 354 additions and 167 deletions

View File

@ -43,7 +43,7 @@ In this article, youll learn how to:
- Convert CRA scripts for use in Nx - Convert CRA scripts for use in Nx
- Create a library and use it in your application - Create a library and use it in your application
For this example, youll 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, youll 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. 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.

View File

@ -23,7 +23,7 @@ describe('cra-to-nx', () => {
const craToNxOutput = runCommand( const craToNxOutput = runCommand(
`${ `${
pmc.runUninstalledPackage pmc.runUninstalledPackage
} cra-to-nx@${getPublishedVersion()} --nxCloud=false` } cra-to-nx@${getPublishedVersion()} --nxCloud=false --integrated`
); );
expect(craToNxOutput).toContain('🎉 Done!'); expect(craToNxOutput).toContain('🎉 Done!');
@ -46,7 +46,7 @@ describe('cra-to-nx', () => {
const craToNxOutput = runCommand( const craToNxOutput = runCommand(
`${ `${
pmc.runUninstalledPackage pmc.runUninstalledPackage
} cra-to-nx@${getPublishedVersion()} --nxCloud=false --vite` } cra-to-nx@${getPublishedVersion()} --nxCloud=false --vite --integrated`
); );
expect(craToNxOutput).toContain('🎉 Done!'); expect(craToNxOutput).toContain('🎉 Done!');
@ -56,6 +56,9 @@ describe('cra-to-nx', () => {
runCLI(`build ${appName}`); runCLI(`build ${appName}`);
checkFilesExist(`dist/apps/${appName}/index.html`); 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', () => { it('should convert to an integrated workspace with Vite with custom port', () => {
@ -66,10 +69,53 @@ describe('cra-to-nx', () => {
runCommand( runCommand(
`${ `${
pmc.runUninstalledPackage 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`); const viteConfig = readFile(`apps/${appName}/vite.config.js`);
expect(viteConfig).toContain('port: 3000'); 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');
}); });
}); });

View File

@ -27,6 +27,11 @@ export const commandsObject = yargs
describe: 'Use Vite and Vitest (instead of Webpack and Jest)', describe: 'Use Vite and Vitest (instead of Webpack and Jest)',
default: false, default: false,
}) })
.option('integrated', {
type: 'boolean',
describe: 'Use integrated folder structure, with apps folder',
default: false,
})
.help(); .help();
createNxWorkspaceForReact(commandsObject.argv).catch((e) => { createNxWorkspaceForReact(commandsObject.argv).catch((e) => {

View File

@ -1,13 +1,20 @@
import { readJsonFile, writeJsonFile } from 'nx/src/utils/fileutils'; import { readJsonFile, writeJsonFile } from 'nx/src/utils/fileutils';
export function addCracoCommandsToPackageScripts(appName: string) { export function addCracoCommandsToPackageScripts(
const packageJson = readJsonFile(`apps/${appName}/package.json`); 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 = {
...packageJson.scripts, ...packageJson.scripts,
start: 'craco start', start: 'craco start',
serve: 'npm 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', test: 'craco test',
}; };
writeJsonFile(`apps/${appName}/package.json`, packageJson); writeJsonFile(packageJsonPath, packageJson);
} }

View File

@ -1,7 +1,13 @@
import { readJsonFile, writeJsonFile } from 'nx/src/utils/fileutils'; import { readJsonFile, writeJsonFile } from 'nx/src/utils/fileutils';
export function addViteCommandsToPackageScripts(appName: string) { export function addViteCommandsToPackageScripts(
const packageJson = readJsonFile(`apps/${appName}/package.json`); appName: string,
isNested: boolean
) {
const packageJsonPath = isNested
? 'package.json'
: `apps/${appName}/package.json`;
const packageJson = readJsonFile(packageJsonPath);
packageJson.scripts = { packageJson.scripts = {
...packageJson.scripts, ...packageJson.scripts,
start: 'vite', start: 'vite',
@ -9,5 +15,5 @@ export function addViteCommandsToPackageScripts(appName: string) {
build: `vite build`, build: `vite build`,
test: 'vitest', test: 'vitest',
}; };
writeJsonFile(`apps/${appName}/package.json`, packageJson, { spaces: 2 }); writeJsonFile(packageJsonPath, packageJson, { spaces: 2 });
} }

View File

@ -1,11 +1,25 @@
import { removeSync } from 'fs-extra'; import { removeSync } from 'fs-extra';
import { readJsonFile, writeJsonFile } from 'nx/src/utils/fileutils'; 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. // 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; 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'); removeSync('temp-workspace');
} }

View File

@ -20,27 +20,35 @@ import { renameJsToJsx } from './rename-js-to-jsx';
import { writeViteIndexHtml } from './write-vite-index-html'; import { writeViteIndexHtml } from './write-vite-index-html';
import { checkForCustomWebpackSetup } from './check-for-custom-webpack-setup'; 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[]) { function addDependencies(pmc: PackageManagerCommands, ...deps: string[]) {
const depsArg = deps.join(' '); const depsArg = deps.join(' ');
output.log({ title: `📦 Adding dependencies: ${depsArg}` }); output.log({ title: `📦 Adding dependencies: ${depsArg}` });
execSync(`${pmc.addDev} ${depsArg}`, { stdio: [0, 1, 2] }); execSync(`${pmc.addDev} ${depsArg}`, { stdio: [0, 1, 2] });
} }
export async function createNxWorkspaceForReact(options: Record<string, any>) { export function normalizeOptions(options: Options): NormalizedOptions {
if (!options.force) {
checkForUncommittedChanges();
checkForCustomWebpackSetup();
}
const packageManager = detectPackageManager(); const packageManager = detectPackageManager();
const pmc = getPackageManagerCommand(packageManager); const pmc = getPackageManagerCommand(packageManager);
output.log({ title: '✨ Nx initialization' }); const appIsJs = !fileExists(`tsconfig.json`);
let appIsJs = true;
if (fileExists(`tsconfig.json`)) {
appIsJs = false;
}
const reactAppName = readNameFromPackageJson(); const reactAppName = readNameFromPackageJson();
const packageJson = readJsonFile('package.json'); const packageJson = readJsonFile('package.json');
@ -52,110 +60,50 @@ export async function createNxWorkspaceForReact(options: Record<string, any>) {
const npmVersion = execSync('npm -v').toString(); const npmVersion = execSync('npm -v').toString();
// Should remove this check 04/2023 once Node 14 & npm 6 reach EOL // 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 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( return {
`npx ${ ...options,
npxYesFlagNeeded ? '-y' : '' packageManager,
} create-nx-workspace@latest temp-workspace --appName=${reactAppName} --preset=react --style=css --packageManager=${packageManager} ${ pmc,
options.nxCloud ? '--nxCloud' : '--nxCloud=false' appIsJs,
}`, reactAppName,
{ stdio: [0, 1, 2] } isCRA5,
); npxYesFlagNeeded,
isVite,
output.log({ title: '👋 Welcome to Nx!' }); isNested,
};
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;
} }
export async function createNxWorkspaceForReact(options: Record<string, any>) {
if (!options.force) {
checkForUncommittedChanges();
checkForCustomWebpackSetup();
} }
});
process.chdir('temp-workspace/'); output.log({ title: '✨ Nx initialization' });
if (isVite) { const normalizedOptions = normalizeOptions(options as Options);
output.log({ title: '🧑‍🔧 Setting up Vite' }); reorgnizeWorkspaceStructure(normalizedOptions);
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] });
} }
async function reorgnizeWorkspaceStructure(options: NormalizedOptions) {
createTempWorkspace(options);
moveFilesToTempWorkspace(options);
await addBundler(options);
output.log({ title: '🧶 Add all node_modules to .gitignore' }); output.log({ title: '🧶 Add all node_modules to .gitignore' });
execSync(`echo "node_modules" >> .gitignore`, { stdio: [0, 1, 2] }); execSync(`echo "node_modules" >> .gitignore`, { stdio: [0, 1, 2] });
process.chdir('../'); process.chdir('../');
output.log({ title: '🚚 Folder restructuring.' }); copyFromTempWorkspaceToRoot();
readdirSync('./temp-workspace').forEach((f) => { cleanUpUnusedFilesAndAddConfigFiles(options);
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`);
}
output.log({ title: '🙂 Please be patient, one final step remaining!' }); output.log({ title: '🙂 Please be patient, one final step remaining!' });
@ -164,17 +112,17 @@ export async function createNxWorkspaceForReact(options: Record<string, any>) {
}); });
addDependencies( addDependencies(
pmc, options.pmc,
'@testing-library/jest-dom', '@testing-library/jest-dom',
'eslint-config-react-app', 'eslint-config-react-app',
'web-vitals', 'web-vitals',
'jest-watch-typeahead' 'jest-watch-typeahead'
); );
if (isVite) { if (options.isVite) {
addDependencies(pmc, 'vite', 'vitest', '@vitejs/plugin-react'); addDependencies(options.pmc, 'vite', 'vitest', '@vitejs/plugin-react');
} else { } else {
addDependencies(pmc, '@craco/craco', 'cross-env', 'react-scripts'); addDependencies(options.pmc, '@craco/craco', 'cross-env', 'react-scripts');
} }
output.log({ title: '🎉 Done!' }); output.log({ title: '🎉 Done!' });
@ -188,20 +136,148 @@ export async function createNxWorkspaceForReact(options: Record<string, any>) {
], ],
}); });
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({ 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({ output.note({
title: 'Or, you can try the commands!', title: 'Or, you can try the commands!',
bodyLines: [ bodyLines: [
`npx nx serve ${reactAppName}`, `npx nx serve ${options.reactAppName}`,
`npx nx build ${reactAppName}`, `npx nx build ${options.reactAppName}`,
`npx nx test ${reactAppName}`, `npx nx test ${options.reactAppName}`,
` `, ` `,
`https://nx.dev/getting-started/intro#10-try-the-commands`, `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');
}
}

View File

@ -1,10 +1,9 @@
import { sync } from 'glob'; import { sync } from 'glob';
import { renameSync, readFileSync } from 'fs-extra'; import { renameSync, readFileSync } from 'fs-extra';
import { performance } from 'perf_hooks';
// Vite cannot process JSX like <div> or <Header> unless the file is named .jsx or .tsx // Vite cannot process JSX like <div> or <Header> unless the file is named .jsx or .tsx
export function renameJsToJsx(appName: string) { export function renameJsToJsx(appName: string, isNested: boolean) {
const files = sync(`apps/${appName}/src/**/*.js`); const files = sync(isNested ? 'src/**/*.js' : `apps/${appName}/src/**/*.js`);
files.forEach((file) => { files.forEach((file) => {
const content = readFileSync(file).toString(); const content = readFileSync(file).toString();

View File

@ -3,9 +3,10 @@ import {
readJsonFile, readJsonFile,
writeJsonFile, writeJsonFile,
} from 'nx/src/utils/fileutils'; } from 'nx/src/utils/fileutils';
import { join } from 'path';
const defaultTsConfig = { const defaultTsConfig = (relativePathToRoot: string) => ({
extends: '../../tsconfig.base.json', extends: join(relativePathToRoot, 'tsconfig.base.json'),
compilerOptions: { compilerOptions: {
jsx: 'react', jsx: 'react',
allowJs: true, allowJs: true,
@ -22,26 +23,26 @@ const defaultTsConfig = {
path: './tsconfig.spec.json', path: './tsconfig.spec.json',
}, },
], ],
}; });
const defaultTsConfigApp = { const defaultTsConfigApp = (relativePathToRoot: string) => ({
extends: './tsconfig.json', extends: './tsconfig.json',
compilerOptions: { compilerOptions: {
outDir: '../../dist/out-tsc', outDir: join(relativePathToRoot, 'dist/out-tsc'),
types: ['node'], types: ['node'],
}, },
files: [ files: [
'../../node_modules/@nrwl/react/typings/cssmodule.d.ts', join(relativePathToRoot, 'node_modules/@nrwl/react/typings/cssmodule.d.ts'),
'../../node_modules/@nrwl/react/typings/image.d.ts', join(relativePathToRoot, 'node_modules/@nrwl/react/typings/image.d.ts'),
], ],
exclude: ['**/*.spec.ts', '**/*.spec.tsx'], exclude: ['**/*.spec.ts', '**/*.spec.tsx'],
include: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'], include: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'],
}; });
const defaultTsConfigSpec = { const defaultTsConfigSpec = (relativePathToRoot: string) => ({
extends: './tsconfig.json', extends: './tsconfig.json',
compilerOptions: { compilerOptions: {
outDir: '../../dist/out-tsc', outDir: join(relativePathToRoot, 'dist/out-tsc'),
module: 'commonjs', module: 'commonjs',
types: ['jest', 'node'], types: ['jest', 'node'],
}, },
@ -53,15 +54,28 @@ const defaultTsConfigSpec = {
'**/*.d.ts', '**/*.d.ts',
], ],
files: [ files: [
'../../node_modules/@nrwl/react/typings/cssmodule.d.ts', join(relativePathToRoot, 'node_modules/@nrwl/react/typings/cssmodule.d.ts'),
'../../node_modules/@nrwl/react/typings/image.d.ts', join(relativePathToRoot, 'node_modules/@nrwl/react/typings/image.d.ts'),
], ],
}; });
export function setupTsConfig(appName: string) { export function setupTsConfig(appName: string, isNested: boolean) {
if (fileExists(`apps/${appName}/tsconfig.json`)) { const tsconfigPath = isNested
const json = readJsonFile(`apps/${appName}/tsconfig.json`); ? 'tsconfig.json'
json.extends = '../../tsconfig.base.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) { if (json.compilerOptions) {
json.compilerOptions.jsx = 'react'; json.compilerOptions.jsx = 'react';
} else { } else {
@ -72,24 +86,24 @@ export function setupTsConfig(appName: string) {
allowSyntheticDefaultImports: true, allowSyntheticDefaultImports: true,
}; };
} }
writeJsonFile(`apps/${appName}/tsconfig.json`, json); writeJsonFile(tsconfigPath, json);
} else { } else {
writeJsonFile(`apps/${appName}/tsconfig.json`, defaultTsConfig); writeJsonFile(tsconfigPath, defaultTsConfig(relativePathToRoot));
} }
if (fileExists(`apps/${appName}/tsconfig.app.json`)) { if (fileExists(tsconfigAppPath)) {
const json = readJsonFile(`apps/${appName}/tsconfig.app.json`); const json = readJsonFile(tsconfigAppPath);
json.extends = './tsconfig.json'; json.extends = './tsconfig.json';
writeJsonFile(`apps/${appName}/tsconfig.app.json`, json); writeJsonFile(tsconfigAppPath, json);
} else { } else {
writeJsonFile(`apps/${appName}/tsconfig.app.json`, defaultTsConfigApp); writeJsonFile(tsconfigAppPath, defaultTsConfigApp(relativePathToRoot));
} }
if (fileExists(`apps/${appName}/tsconfig.spec.json`)) { if (fileExists(tsconfiSpecPath)) {
const json = readJsonFile(`apps/${appName}/tsconfig.spec.json`); const json = readJsonFile(tsconfiSpecPath);
json.extends = './tsconfig.json'; json.extends = './tsconfig.json';
writeJsonFile(`apps/${appName}/tsconfig.spec.json`, json); writeJsonFile(tsconfiSpecPath, json);
} else { } else {
writeJsonFile(`apps/${appName}/tsconfig.spec.json`, defaultTsConfigSpec); writeJsonFile(tsconfiSpecPath, defaultTsConfigSpec(relativePathToRoot));
} }
} }

View File

@ -1,6 +1,10 @@
import { writeFileSync } from 'fs'; import { writeFileSync } from 'fs';
export function writeCracoConfig(appName: string, isCRA5: boolean) { export function writeCracoConfig(
appName: string,
isCRA5: boolean,
isNested: boolean
) {
const configOverride = ` const configOverride = `
const path = require('path'); const path = require('path');
const TsConfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); 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
);
} }

View File

@ -1,6 +1,10 @@
import * as fs from 'fs'; import * as fs from 'fs';
export function writeViteConfig(appName: string) { export function writeViteConfig(
appName: string,
isNested: boolean,
isJs: boolean
) {
let port = 4200; let port = 4200;
// Use PORT from .env file if it exists in project. // Use PORT from .env file if it exists in project.
@ -14,14 +18,14 @@ export function writeViteConfig(appName: string) {
} }
fs.writeFileSync( fs.writeFileSync(
`apps/${appName}/vite.config.js`, isNested ? 'vite.config.js' : `apps/${appName}/vite.config.js`,
`import { defineConfig } from 'vite' `import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
build: { build: {
outDir: '../../dist/apps/${appName}' outDir: ${isNested ? `'./dist'` : `'../../dist/apps/${appName}'`}
}, },
server: { server: {
port: ${port}, port: ${port},
@ -30,7 +34,7 @@ export default defineConfig({
test: { test: {
globals: true, globals: true,
environment: 'jsdom', environment: 'jsdom',
setupFiles: 'src/setupTests.js', setupFiles: 'src/setupTests.${isJs ? 'js' : 'ts'}',
css: true, css: true,
}, },
plugins: [react()], plugins: [react()],

View File

@ -1,8 +1,17 @@
import * as fs from 'fs'; 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( fs.writeFileSync(
`apps/${appName}/index.html`, indexPath,
`<!DOCTYPE html> `<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -13,7 +22,7 @@ export function writeViteIndexHtml(appName: string) {
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/index.jsx"></script> <script type="module" src="${indexFile}"></script>
</body> </body>
</html>` </html>`
); );