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
- 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.

View File

@ -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');
});
});

View File

@ -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) => {

View File

@ -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);
}

View File

@ -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 });
}

View File

@ -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');
}

View File

@ -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<string, any>) {
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<string, any>) {
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] }
);
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;
return {
...options,
packageManager,
pmc,
appIsJs,
reactAppName,
isCRA5,
npxYesFlagNeeded,
isVite,
isNested,
};
}
export async function createNxWorkspaceForReact(options: Record<string, any>) {
if (!options.force) {
checkForUncommittedChanges();
checkForCustomWebpackSetup();
}
});
process.chdir('temp-workspace/');
output.log({ title: '✨ Nx initialization' });
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] });
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<string, any>) {
});
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<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({
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');
}
}

View File

@ -1,10 +1,9 @@
import { sync } from 'glob';
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
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();

View File

@ -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));
}
}

View File

@ -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
);
}

View File

@ -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()],

View File

@ -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,
`<!DOCTYPE html>
<html lang="en">
<head>
@ -13,7 +22,7 @@ export function writeViteIndexHtml(appName: string) {
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.jsx"></script>
<script type="module" src="${indexFile}"></script>
</body>
</html>`
);