Skip to content

Commit

Permalink
feat(react): add a preset to generate a workspace with a single app a…
Browse files Browse the repository at this point in the history
…t the root
  • Loading branch information
vsavkin committed Nov 12, 2022
1 parent fc8de9a commit c4ebef2
Show file tree
Hide file tree
Showing 31 changed files with 298 additions and 85 deletions.
2 changes: 1 addition & 1 deletion docs/generated/cli/create-nx-workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Package manager to use

Type: `string`

Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular", "angular-nest", "react", "react-express", "react-native", "expo", "next", "nest", "express"]. To build your own see https://nx.dev/packages/nx-plugin#preset
Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular", "angular-nest", "react", "react-experimental", "react-express", "react-native", "expo", "next", "nest", "express"]. To build your own see https://nx.dev/packages/nx-plugin#preset

### skipGit

Expand Down
6 changes: 6 additions & 0 deletions docs/generated/packages/cypress.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@
"type": "boolean",
"default": false,
"description": "Do not add dependencies to `package.json`."
},
"rootProject": {
"description": "Create a application at the root of the workspace",
"type": "boolean",
"default": false,
"hidden": true
}
},
"required": ["name"],
Expand Down
2 changes: 1 addition & 1 deletion docs/generated/packages/nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"name": "create-nx-workspace",
"id": "create-nx-workspace",
"file": "generated/cli/create-nx-workspace",
"content": "---\ntitle: 'create-nx-workspace - CLI command'\ndescription: 'Create a new Nx workspace'\n---\n\n# create-nx-workspace\n\nCreate a new Nx workspace\n\n## Usage\n\n```bash\ncreate-nx-workspace [name] [options]\n```\n\nInstall `create-nx-workspace` globally to invoke the command directly, or use `npx create-nx-workspace`, `yarn create nx-workspace`, or `pnpx create-nx-workspace`.\n\n## Options\n\n### allPrompts\n\nType: `boolean`\n\nDefault: `false`\n\nShow all prompts\n\n### appName\n\nType: `string`\n\nThe name of the application when a preset with pregenerated app is selected\n\n### ci\n\nType: `string`\n\nChoices: [github, circleci, azure]\n\nGenerate a CI workflow file\n\n### cli\n\nType: `string`\n\nChoices: [nx, angular]\n\nCLI to power the Nx workspace\n\n### commit.email\n\nType: `string`\n\nE-mail of the committer\n\n### commit.message\n\nType: `string`\n\nDefault: `Initial commit`\n\nCommit message\n\n### commit.name\n\nType: `string`\n\nName of the committer\n\n### defaultBase\n\nType: `string`\n\nDefault: `main`\n\nDefault base to use for new projects\n\n### help\n\nType: `boolean`\n\nShow help\n\n### interactive\n\nType: `boolean`\n\nEnable interactive mode with presets\n\n### name\n\nType: `string`\n\nWorkspace name (e.g. org name)\n\n### nxCloud\n\nType: `boolean`\n\nEnable distributed caching to make your CI faster\n\n### packageManager\n\nType: `string`\n\nChoices: [npm, pnpm, yarn]\n\nDefault: `npm`\n\nPackage manager to use\n\n### preset\n\nType: `string`\n\nCustomizes the initial content of your workspace. Default presets include: [\"apps\", \"empty\", \"core\", \"npm\", \"ts\", \"web-components\", \"angular\", \"angular-nest\", \"react\", \"react-express\", \"react-native\", \"expo\", \"next\", \"nest\", \"express\"]. To build your own see https://nx.dev/packages/nx-plugin#preset\n\n### skipGit\n\nType: `boolean`\n\nDefault: `false`\n\nSkip initializing a git repository.\n\n### style\n\nType: `string`\n\nStyle option to be used when a preset with pregenerated app is selected\n\n### version\n\nType: `boolean`\n\nShow version number\n"
"content": "---\ntitle: 'create-nx-workspace - CLI command'\ndescription: 'Create a new Nx workspace'\n---\n\n# create-nx-workspace\n\nCreate a new Nx workspace\n\n## Usage\n\n```bash\ncreate-nx-workspace [name] [options]\n```\n\nInstall `create-nx-workspace` globally to invoke the command directly, or use `npx create-nx-workspace`, `yarn create nx-workspace`, or `pnpx create-nx-workspace`.\n\n## Options\n\n### allPrompts\n\nType: `boolean`\n\nDefault: `false`\n\nShow all prompts\n\n### appName\n\nType: `string`\n\nThe name of the application when a preset with pregenerated app is selected\n\n### ci\n\nType: `string`\n\nChoices: [github, circleci, azure]\n\nGenerate a CI workflow file\n\n### cli\n\nType: `string`\n\nChoices: [nx, angular]\n\nCLI to power the Nx workspace\n\n### commit.email\n\nType: `string`\n\nE-mail of the committer\n\n### commit.message\n\nType: `string`\n\nDefault: `Initial commit`\n\nCommit message\n\n### commit.name\n\nType: `string`\n\nName of the committer\n\n### defaultBase\n\nType: `string`\n\nDefault: `main`\n\nDefault base to use for new projects\n\n### help\n\nType: `boolean`\n\nShow help\n\n### interactive\n\nType: `boolean`\n\nEnable interactive mode with presets\n\n### name\n\nType: `string`\n\nWorkspace name (e.g. org name)\n\n### nxCloud\n\nType: `boolean`\n\nEnable distributed caching to make your CI faster\n\n### packageManager\n\nType: `string`\n\nChoices: [npm, pnpm, yarn]\n\nDefault: `npm`\n\nPackage manager to use\n\n### preset\n\nType: `string`\n\nCustomizes the initial content of your workspace. Default presets include: [\"apps\", \"empty\", \"core\", \"npm\", \"ts\", \"web-components\", \"angular\", \"angular-nest\", \"react\", \"react-experimental\", \"react-express\", \"react-native\", \"expo\", \"next\", \"nest\", \"express\"]. To build your own see https://nx.dev/packages/nx-plugin#preset\n\n### skipGit\n\nType: `boolean`\n\nDefault: `false`\n\nSkip initializing a git repository.\n\n### style\n\nType: `string`\n\nStyle option to be used when a preset with pregenerated app is selected\n\n### version\n\nType: `boolean`\n\nShow version number\n"
},
{
"name": "init",
Expand Down
10 changes: 8 additions & 2 deletions docs/generated/packages/react.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint"],
"enum": ["eslint", "none"],
"default": "eslint"
},
"routing": {
Expand Down Expand Up @@ -228,6 +228,12 @@
"description": "Do not add dependencies to `package.json`.",
"type": "boolean",
"default": false
},
"rootProject": {
"description": "Create a application at the root of the workspace",
"type": "boolean",
"default": false,
"hidden": true
}
},
"required": [],
Expand Down Expand Up @@ -314,7 +320,7 @@
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint"],
"enum": ["eslint", "none"],
"default": "eslint"
},
"unitTestRunner": {
Expand Down
14 changes: 14 additions & 0 deletions e2e/workspace-create/src/create-nx-workspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ describe('create-nx-workspace', () => {

afterEach(() => cleanupProject());

it('should create a workspace with a single react app', () => {
const wsName = uniq('react');
const appName = uniq('app');

runCreateWorkspace(wsName, {
preset: 'react-experimental',
appName,
style: 'css',
packageManager,
});

checkFilesExist('package.json');
});

it('should be able to create an empty workspace built for apps', () => {
const wsName = uniq('apps');
runCreateWorkspace(wsName, {
Expand Down
11 changes: 10 additions & 1 deletion packages/create-nx-workspace/bin/create-nx-workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ enum Preset {
Angular = 'angular',
AngularWithNest = 'angular-nest',
React = 'react',
ReactExperimental = 'react-experimental',
ReactWithExpress = 'react-express',
ReactNative = 'react-native',
Expo = 'expo',
Expand Down Expand Up @@ -674,7 +675,14 @@ async function determineStyle(
});
}

if ([Preset.ReactWithExpress, Preset.React, Preset.NextJs].includes(preset)) {
if (
[
Preset.ReactWithExpress,
Preset.React,
Preset.ReactExperimental,
Preset.NextJs,
].includes(preset)
) {
choices.push(
{
name: 'styled-components',
Expand Down Expand Up @@ -1030,6 +1038,7 @@ function pointToTutorialAndCourse(preset: Preset) {
break;

case Preset.React:
case Preset.ReactExperimental:
case Preset.ReactWithExpress:
case Preset.NextJs:
output.addVerticalSeparator();
Expand Down
27 changes: 17 additions & 10 deletions packages/cypress/src/generators/cypress-project/cypress-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,16 +268,23 @@ export async function cypressProjectGenerator(host: Tree, schema: Schema) {

function normalizeOptions(host: Tree, options: Schema): CypressProjectSchema {
const { appsDir } = getWorkspaceLayout(host);
const projectName = filePathPrefix(
options.directory ? `${options.directory}-${options.name}` : options.name
);
const projectRoot = options.directory
? joinPathFragments(
appsDir,
names(options.directory).fileName,
options.name
)
: joinPathFragments(appsDir, options.name);
let projectName, projectRoot;

if (options.rootProject) {
projectName = options.name;
projectRoot = options.name;
} else {
projectName = filePathPrefix(
options.directory ? `${options.directory}-${options.name}` : options.name
);
projectRoot = options.directory
? joinPathFragments(
appsDir,
names(options.directory).fileName,
options.name
)
: joinPathFragments(appsDir, options.name);
}

options.linter = options.linter || Linter.EsLint;
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export interface Schema {
setParserOptionsProject?: boolean;
standaloneConfig?: boolean;
skipPackageJson?: boolean;
rootProject?: boolean;
}
6 changes: 6 additions & 0 deletions packages/cypress/src/generators/cypress-project/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@
"type": "boolean",
"default": false,
"description": "Do not add dependencies to `package.json`."
},
"rootProject": {
"description": "Create a application at the root of the workspace",
"type": "boolean",
"default": false,
"hidden": true
}
},
"required": ["name"],
Expand Down
5 changes: 5 additions & 0 deletions packages/devkit/src/utils/offset-from-root.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,9 @@ describe('offsetFromRoot', () => {
const result = offsetFromRoot('apps/dirname/appname/');
expect(result).toBe('../../../');
});

it('should work for root', () => {
const result = offsetFromRoot('.');
expect(result).toBe('./');
});
});
2 changes: 2 additions & 0 deletions packages/devkit/src/utils/offset-from-root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { normalize, sep } from 'path';
* @param fullPathToDir - directory path
*/
export function offsetFromRoot(fullPathToDir: string): string {
if (fullPathToDir === '.') return './';

const parts = normalize(fullPathToDir).split(sep);
let offset = '';
for (let i = 0; i < parts.length; ++i) {
Expand Down
19 changes: 19 additions & 0 deletions packages/react/src/generators/application/application.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -825,4 +825,23 @@ describe('app', () => {
});
});
});

describe('--root-project', () => {
it('should create files at the root', async () => {
await applicationGenerator(appTree, {
...schema,
rootProject: true,
});
expect(appTree.read('/src/main.tsx')).toBeDefined();
expect(appTree.read('/e2e/cypress.config.ts')).toBeDefined();
expect(readJson(appTree, '/tsconfig.json').extends).toEqual(
'./tsconfig.base.json'
);
expect(
readJson(appTree, '/workspace.json').projects['my-app'].architect[
'build'
].options['outputPath']
).toEqual('dist/my-app');
});
});
});
67 changes: 34 additions & 33 deletions packages/react/src/generators/application/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,47 +23,48 @@ import {
} from '@nrwl/devkit';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import reactInitGenerator from '../init/init';
import { lintProjectGenerator } from '@nrwl/linter';
import { Linter, lintProjectGenerator } from '@nrwl/linter';
import { swcCoreVersion } from '@nrwl/js/src/utils/versions';
import { swcLoaderVersion } from '@nrwl/webpack/src/utils/versions';

async function addLinting(host: Tree, options: NormalizedSchema) {
const tasks: GeneratorCallback[] = [];
const lintTask = await lintProjectGenerator(host, {
linter: options.linter,
project: options.projectName,
tsConfigPaths: [
joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
],
unitTestRunner: options.unitTestRunner,
eslintFilePatterns: [`${options.appProjectRoot}/**/*.{ts,tsx,js,jsx}`],
skipFormat: true,
});
tasks.push(lintTask);
if (options.linter === Linter.EsLint) {
const lintTask = await lintProjectGenerator(host, {
linter: options.linter,
project: options.projectName,
tsConfigPaths: [
joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
],
unitTestRunner: options.unitTestRunner,
eslintFilePatterns: [`${options.appProjectRoot}/**/*.{ts,tsx,js,jsx}`],
skipFormat: true,
});
tasks.push(lintTask);

const reactEslintJson = createReactEslintJson(
options.appProjectRoot,
options.setParserOptionsProject
);
const reactEslintJson = createReactEslintJson(
options.appProjectRoot,
options.setParserOptionsProject
);

updateJson(
host,
joinPathFragments(options.appProjectRoot, '.eslintrc.json'),
() => reactEslintJson
);

const installTask = await addDependenciesToPackageJson(
host,
extraEslintDependencies.dependencies,
{
...extraEslintDependencies.devDependencies,
...(options.compiler === 'swc'
? { '@swc/core': swcCoreVersion, 'swc-loader': swcLoaderVersion }
: {}),
}
);
tasks.push(installTask);
updateJson(
host,
joinPathFragments(options.appProjectRoot, '.eslintrc.json'),
() => reactEslintJson
);

const installTask = await addDependenciesToPackageJson(
host,
extraEslintDependencies.dependencies,
{
...extraEslintDependencies.devDependencies,
...(options.compiler === 'swc'
? { '@swc/core': swcCoreVersion, 'swc-loader': swcLoaderVersion }
: {}),
}
);
tasks.push(installTask);
}
return runTasksInSerial(...tasks);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export async function addCypress(host: Tree, options: NormalizedSchema) {

return await cypressProjectGenerator(host, {
...options,
name: `${options.name}-e2e`,
name: options.e2eProjectName,
directory: options.directory,
project: options.projectName,
});
Expand Down
7 changes: 6 additions & 1 deletion packages/react/src/generators/application/lib/add-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ function createBuildTarget(options: NormalizedSchema): TargetConfiguration {
defaultConfiguration: 'production',
options: {
compiler: options.compiler ?? 'babel',
outputPath: joinPathFragments('dist', options.appProjectRoot),
outputPath: joinPathFragments(
'dist',
options.appProjectRoot != '.'
? options.appProjectRoot
: options.projectName
),
index: joinPathFragments(options.appProjectRoot, 'src/index.html'),
baseHref: '/',
main: joinPathFragments(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ export function normalizeOptions(
): NormalizedSchema {
const appDirectory = normalizeDirectory(options);
const appProjectName = normalizeProjectName(options);
const e2eProjectName = `${appProjectName}-e2e`;
const e2eProjectName = options.rootProject
? 'e2e'
: `${names(options.name).fileName}-e2e`;

const { appsDir } = getWorkspaceLayout(host);
const appProjectRoot = normalizePath(`${appsDir}/${appDirectory}`);
const appProjectRoot = options.rootProject
? '.'
: normalizePath(`${appsDir}/${appDirectory}`);

const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim())
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/generators/application/lib/set-defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export function setDefaults(host: Tree, options: NormalizedSchema) {
...prev,
application: {
style: options.style,
unitTestRunner: options.unitTestRunner,
linter: options.linter,
...prev.application,
},
Expand All @@ -37,6 +38,7 @@ export function setDefaults(host: Tree, options: NormalizedSchema) {
},
library: {
style: options.style,
unitTestRunner: options.unitTestRunner,
linter: options.linter,
...prev.library,
},
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/generators/application/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface Schema {
devServerPort?: number;
skipDefaultProject?: boolean;
skipPackageJson?: boolean;
rootProject?: boolean;
}

export interface NormalizedSchema extends Schema {
Expand Down
8 changes: 7 additions & 1 deletion packages/react/src/generators/application/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint"],
"enum": ["eslint", "none"],
"default": "eslint"
},
"routing": {
Expand Down Expand Up @@ -169,6 +169,12 @@
"description": "Do not add dependencies to `package.json`.",
"type": "boolean",
"default": false
},
"rootProject": {
"description": "Create a application at the root of the workspace",
"type": "boolean",
"default": false,
"hidden": true
}
},
"required": []
Expand Down
Loading

1 comment on commit c4ebef2

@vercel
Copy link

@vercel vercel bot commented on c4ebef2 Nov 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx.dev
nx-dev-git-master-nrwl.vercel.app
nx-dev-nrwl.vercel.app
nx-five.vercel.app

Please sign in to comment.