feat(react): remove unnecessary dependencies from @nrwl/react (#13525)

This commit is contained in:
Jack Hsu 2022-12-01 17:06:32 -05:00 committed by GitHub
parent 4c723de444
commit cded83b2c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 964 additions and 642 deletions

View File

@ -147,6 +147,7 @@ It only uses language primitives and immutable objects
- [defaultTasksRunner](../../devkit/index#defaulttasksrunner)
- [detectPackageManager](../../devkit/index#detectpackagemanager)
- [detectWorkspaceScope](../../devkit/index#detectworkspacescope)
- [ensurePackage](../../devkit/index#ensurepackage)
- [extractLayoutDirectory](../../devkit/index#extractlayoutdirectory)
- [formatFiles](../../devkit/index#formatfiles)
- [generateFiles](../../devkit/index#generatefiles)
@ -1164,6 +1165,39 @@ Detect workspace scope from the package.json name
---
### ensurePackage
**ensurePackage**(`tree`, `pkg`, `requiredVersion`, `options?`): `Promise`<`void`\>
Ensure that dependencies and devDependencies from package.json are installed at the required versions.
For example:
```typescript
ensureDependencies(tree, {}, { '@nrwl/jest': nxVersion });
```
This will check that @nrwl/jest@<nxVersion> exists in devDependencies.
If it exists then function returns, otherwise it will install the package before continuing.
When running with --dryRun, the function will throw when dependencies are missing.
#### Parameters
| Name | Type | Description |
| :------------------------ | :-------------------------------- | :------------------------------------- |
| `tree` | [`Tree`](../../devkit/index#tree) | the file system tree |
| `pkg` | `string` | the package to check (e.g. @nrwl/jest) |
| `requiredVersion` | `string` | the version to check |
| `options` | `Object` | |
| `options.dev?` | `boolean` | - |
| `options.throwOnMissing?` | `boolean` | - |
#### Returns
`Promise`<`void`\>
---
### extractLayoutDirectory
**extractLayoutDirectory**(`directory`): `Object`

File diff suppressed because one or more lines are too long

View File

@ -18,6 +18,12 @@
"description": "Init Webpack Plugin.",
"type": "object",
"properties": {
"uiFramework": {
"type": "string",
"description": "UI Framework to use for Vite.",
"enum": ["react", "none"],
"x-prompt": "What UI framework plugin should Webpack use?"
},
"compiler": {
"type": "string",
"enum": ["babel", "swc", "tsc"],

View File

@ -225,6 +225,7 @@ export { readJsonFile, writeJsonFile } from 'nx/src/utils/fileutils';
*/
export {
addDependenciesToPackageJson,
ensurePackage,
removeDependenciesFromPackageJson,
} from './src/utils/package-json';

View File

@ -1,6 +1,6 @@
export async function* combineAsyncIterableIterators(
...iterators: { 0: AsyncIterableIterator<any> } & AsyncIterableIterator<any>[]
) {
export async function* combineAsyncIterableIterators<T = any>(
...iterators: { 0: AsyncIterableIterator<T> } & AsyncIterableIterator<T>[]
): AsyncGenerator<T> {
let [options] = iterators;
if (typeof options.next === 'function') {
options = Object.create(null);

View File

@ -0,0 +1,4 @@
export * from './create-async-iterable';
export * from './combine-async-iteratable-iterators';
export * from './map-async-iteratable';
export * from './tap-async-iteratable';

View File

@ -5,7 +5,7 @@ export async function* mapAsyncIterable<T = any, I = any, O = any>(
index?: number,
data?: AsyncIterable<T> | AsyncIterableIterator<T>
) => O
) {
): AsyncIterable<O> | AsyncIterableIterator<O> {
async function* f() {
const generator = data[Symbol.asyncIterator] || data[Symbol.iterator];
const iterator = generator.call(data);

View File

@ -1,4 +1,4 @@
import { tapAsyncIterator } from './tap-async-iteratable';
import { tapAsyncIterable } from './tap-async-iteratable';
describe('tapAsyncIterator', () => {
it('should tap values', async () => {
@ -11,7 +11,7 @@ describe('tapAsyncIterator', () => {
const tapped = [];
const results = [];
const c = tapAsyncIterator(f(), (x) => {
const c = tapAsyncIterable(f(), (x) => {
tapped.push(`tap: ${x}`);
});

View File

@ -1,9 +1,9 @@
import { mapAsyncIterable } from './map-async-iteratable';
export async function* tapAsyncIterator<T = any, I = any, O = any>(
export async function* tapAsyncIterable<T = any, I = any, O = any>(
data: AsyncIterable<T> | AsyncIterableIterator<T>,
fn: (input: I) => void
) {
): AsyncIterable<T> | AsyncIterableIterator<T> {
return yield* mapAsyncIterable(data, (x) => {
fn(x);
return x;

View File

@ -1,6 +1,6 @@
import type { Tree } from 'nx/src/generators/tree';
import { readJson, writeJson } from 'nx/src/generators/utils/json';
import { addDependenciesToPackageJson } from './package-json';
import { addDependenciesToPackageJson, ensurePackage } from './package-json';
import { createTree } from 'nx/src/generators/testing-utils/create-tree';
describe('addDependenciesToPackageJson', () => {
@ -310,3 +310,42 @@ describe('addDependenciesToPackageJson', () => {
expect(installTask).toBeDefined();
});
});
describe('ensureDependencies', () => {
let tree: Tree;
beforeEach(() => {
tree = createTree();
});
it('should return without error when dependency is satisfied', async () => {
writeJson(tree, 'package.json', {
devDependencies: {
'@nrwl/vite': '15.0.0',
},
});
await expect(
ensurePackage(tree, '@nrwl/vite', '>=15.0.0', {
throwOnMissing: true,
})
).resolves.toBeUndefined();
});
it('should throw when dependencies are missing', async () => {
writeJson(tree, 'package.json', {});
await expect(() =>
ensurePackage(tree, '@nrwl/does-not-exist', '>=15.0.0', {
throwOnMissing: true,
})
).rejects.toThrow(/-D( -W)? @nrwl\/does-not-exist@>=15.0.0/);
await expect(() =>
ensurePackage(tree, '@nrwl/does-not-exist', '>=15.0.0', {
dev: false,
throwOnMissing: true,
})
).rejects.toThrow('@nrwl/does-not-exist@>=15.0.0');
});
});

View File

@ -2,7 +2,9 @@ import { readJson, updateJson } from 'nx/src/generators/utils/json';
import { installPackagesTask } from '../tasks/install-packages-task';
import type { Tree } from 'nx/src/generators/tree';
import { GeneratorCallback } from 'nx/src/config/misc-interfaces';
import { coerce, gt } from 'semver';
import { coerce, gt, satisfies } from 'semver';
import { getPackageManagerCommand } from 'nx/src/utils/package-manager';
import { execSync } from 'child_process';
const NON_SEMVER_TAGS = {
'*': 2,
@ -274,3 +276,73 @@ function requiresRemovingOfPackages(
return needsDepsUpdate || needsDevDepsUpdate;
}
/**
* @typedef EnsurePackageOptions
* @type {object}
* @property {boolean} dev indicate if the package is a dev dependency
* @property {throwOnMissing} boolean throws an error when the packag is missing
*/
/**
* Ensure that dependencies and devDependencies from package.json are installed at the required versions.
*
* For example:
* ```typescript
* ensureDependencies(tree, {}, { '@nrwl/jest': nxVersion })
* ```
* This will check that @nrwl/jest@<nxVersion> exists in devDependencies.
* If it exists then function returns, otherwise it will install the package before continuing.
* When running with --dryRun, the function will throw when dependencies are missing.
*
* @param tree the file system tree
* @param pkg the package to check (e.g. @nrwl/jest)
* @param requiredVersion the version to check
* @param {EnsurePackageOptions} options
* @returns {Promise<void>}
*/
export async function ensurePackage(
tree: Tree,
pkg: string,
requiredVersion: string,
options: {
dev?: boolean;
throwOnMissing?: boolean;
} = {}
): Promise<void> {
let version: string;
// Read package and version from root package.json file.
const packageJson = readJson(tree, 'package.json');
const dev = options.dev ?? true;
const throwOnMissing = options.throwOnMissing ?? !!process.env.NX_DRY_RUN; // NX_DRY_RUN is set in `packages/nx/src/command-line/generate.ts`
const pmc = getPackageManagerCommand();
const field = dev ? 'devDependencies' : 'dependencies';
version = packageJson[field]?.[pkg];
// If package not found, try to resolve it using Node and get its version.
if (!version) {
try {
version = require(`${pkg}/package.json`).version;
} catch {
// ignore
}
}
if (!satisfies(version, requiredVersion)) {
const installCmd = `${
dev ? pmc.addDev : pmc.add
} ${pkg}@${requiredVersion}`;
if (throwOnMissing) {
throw new Error(
`Required package ${pkg}@${requiredVersion} is missing. Run "${installCmd}", and then try again.`
);
} else {
execSync(installCmd, {
cwd: tree.root,
stdio: [0, 1, 2],
});
}
}
}

View File

@ -15,7 +15,7 @@ import { normalizeOptions } from './lib/normalize';
import { EsBuildExecutorOptions } from './schema';
import { removeSync, writeJsonSync } from 'fs-extra';
import { createAsyncIterable } from '@nrwl/js/src/utils/async-iterable/create-async-iterable';
import { createAsyncIterable } from '@nrwl/devkit/src/utils/async-iterable';
import { buildEsbuildOptions } from './lib/build-esbuild-options';
import { getExtraDependencies } from './lib/get-extra-dependencies';
import { DependentBuildableProjectNode } from '@nrwl/workspace/src/utilities/buildable-libs-utils';

View File

@ -33,7 +33,6 @@
"builders": "./executors.json",
"dependencies": {
"@nrwl/devkit": "file:../devkit",
"@nrwl/jest": "file:../jest",
"@nrwl/linter": "file:../linter",
"@nrwl/workspace": "file:../workspace",
"chalk": "4.1.0",

View File

@ -2,6 +2,7 @@ import {
addDependenciesToPackageJson,
addProjectConfiguration,
convertNxGenerator,
ensurePackage,
extractLayoutDirectory,
formatFiles,
generateFiles,
@ -18,7 +19,6 @@ import {
writeJson,
} from '@nrwl/devkit';
import { getImportPath } from 'nx/src/utils/path';
import { jestProjectGenerator } from '@nrwl/jest';
import { Linter, lintProjectGenerator } from '@nrwl/linter';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import {
@ -293,6 +293,8 @@ async function addJest(
tree: Tree,
options: NormalizedSchema
): Promise<GeneratorCallback> {
await ensurePackage(tree, '@nrwl/jest', nxVersion);
const { jestProjectGenerator } = await import('@nrwl/jest');
return await jestProjectGenerator(tree, {
...options,
project: options.name,

View File

@ -1,7 +1,7 @@
import { cacheDir, ExecutorContext, logger } from '@nrwl/devkit';
import { exec, execSync } from 'child_process';
import { removeSync } from 'fs-extra';
import { createAsyncIterable } from '../async-iterable/create-async-iterable';
import { createAsyncIterable } from '@nrwl/devkit/src/utils/async-iterable';
import { NormalizedSwcExecutorOptions, SwcCliOptions } from '../schema';
import { printDiagnostics } from '../typescript/print-diagnostics';
import { runTypeCheck, TypeCheckOptions } from '../typescript/run-type-check';

View File

@ -4,7 +4,7 @@ import {
TypeScriptCompilationOptions,
} from '@nrwl/workspace/src/utilities/typescript/compilation';
import type { Diagnostic } from 'typescript';
import { createAsyncIterable } from '../async-iterable/create-async-iterable';
import { createAsyncIterable } from '@nrwl/devkit/src/utils/async-iterable';
import { NormalizedExecutorOptions } from '../schema';
const TYPESCRIPT_FOUND_N_ERRORS_WATCHING_FOR_FILE_CHANGES = 6194;

View File

@ -34,7 +34,6 @@
},
"dependencies": {
"@nrwl/devkit": "file:../devkit",
"@nrwl/jest": "file:../jest",
"@phenomnomnominal/tsquery": "4.1.1",
"nx": "file:../nx",
"tmp": "~0.2.1",

View File

@ -2,6 +2,7 @@ import {
addDependenciesToPackageJson,
addProjectConfiguration,
convertNxGenerator,
ensurePackage,
formatFiles,
generateFiles,
joinPathFragments,
@ -11,17 +12,22 @@ import {
Tree,
updateWorkspaceConfiguration,
} from '@nrwl/devkit';
import { addPropertyToJestConfig, jestProjectGenerator } from '@nrwl/jest';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript';
import { join } from 'path';
import { workspaceLintPluginDir } from '../../utils/workspace-lint-rules';
import { swcCoreVersion, swcNodeVersion } from 'nx/src/utils/versions';
import { nxVersion } from '../../utils/versions';
export const WORKSPACE_RULES_PROJECT_NAME = 'eslint-rules';
export const WORKSPACE_PLUGIN_DIR = 'tools/eslint-rules';
export async function lintWorkspaceRulesProjectGenerator(tree: Tree) {
await ensurePackage(tree, '@nrwl/jest/', nxVersion);
const { addPropertyToJestConfig, jestProjectGenerator } = await import(
'@nrwl/jest'
);
// Noop if the workspace rules project already exists
try {
readProjectConfiguration(tree, WORKSPACE_RULES_PROJECT_NAME);

View File

@ -4,11 +4,11 @@ import {
Tree,
visitNotIgnoredFiles,
} from '@nrwl/devkit';
import { addPropertyToJestConfig } from '@nrwl/jest';
import { tsquery } from '@phenomnomnominal/tsquery';
export default async function eslint8Updates(tree: Tree) {
try {
const { addPropertyToJestConfig } = await import('@nrwl/jest');
const existingJestConfigPath = normalizePath(
'tools/eslint-rules/jest.config.js'
);

View File

@ -9,11 +9,12 @@ import {
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { exampleRootTslintJson } from '@nrwl/linter';
import { conversionGenerator } from './convert-tslint-to-eslint';
import * as devkit from '@nrwl/devkit';
/**
* Don't run actual child_process implementation of installPackagesTask()
*/
jest.mock('child_process');
// jest.mock('child_process');
const appProjectName = 'nest-app-1';
const appProjectRoot = `apps/${appProjectName}`;
@ -101,6 +102,7 @@ describe('convert-tslint-to-eslint', () => {
let host: Tree;
beforeEach(async () => {
jest.spyOn(devkit, 'installPackagesTask');
host = createTreeWithEmptyV1Workspace();
writeJson(host, 'tslint.json', exampleRootTslintJson.raw);

View File

@ -289,6 +289,9 @@ export async function generate(cwd: string, args: { [k: string]: any }) {
'generate',
projectsConfiguration
);
if (opts.dryRun) {
process.env.NX_DRY_RUN = 'true';
}
const { normalizedGeneratorName, schema, implementationFactory, aliases } =
ws.readGenerator(opts.collectionName, opts.generatorName);

View File

@ -22,8 +22,8 @@ export async function createAllStories(
const projects = getProjects(tree);
const projectConfiguration = projects.get(projectName);
const { sourceRoot, root } = projectConfiguration;
const projectPath = projectRootPath(projectConfiguration);
const { sourceRoot } = projectConfiguration;
const projectPath = await projectRootPath(tree, projectConfiguration);
let componentPaths: string[] = [];
visitNotIgnoredFiles(tree, projectPath, (path) => {

View File

@ -71,6 +71,12 @@
"version": "15.3.0-beta.0",
"description": "Update projects using @nrwl/web:rollup to @nrwl/rollup:rollup for build.",
"factory": "./src/migrations/update-15-3-0/update-rollup-executor"
},
"install-webpack-rollup-dependencies": {
"cli": "nx",
"version": "15.3.0-beta.0",
"description": "Install new dependencies for React projects using Webpack or Rollup.",
"factory": "./src/migrations/update-15-3-0/install-webpack-rollup-dependencies"
}
},
"packageJsonUpdates": {

View File

@ -31,32 +31,12 @@
"migrations": "./migrations.json"
},
"dependencies": {
"@babel/core": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@nrwl/cypress": "file:../cypress",
"@nrwl/devkit": "file:../devkit",
"@nrwl/jest": "file:../jest",
"@nrwl/js": "file:../js",
"@nrwl/linter": "file:../linter",
"@nrwl/storybook": "file:../storybook",
"@nrwl/vite": "file:../vite",
"@nrwl/web": "file:../web",
"@nrwl/webpack": "file:../webpack",
"@nrwl/workspace": "file:../workspace",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
"@phenomnomnominal/tsquery": "4.1.1",
"@svgr/webpack": "^6.1.2",
"chalk": "4.1.0",
"css-loader": "^6.4.0",
"minimatch": "3.0.5",
"react-refresh": "^0.10.0",
"semver": "7.3.4",
"style-loader": "^3.3.0",
"stylus": "^0.55.0",
"stylus-loader": "^7.1.0",
"url-loader": "^4.1.1",
"webpack": "^5.75.0",
"webpack-merge": "^5.8.0"
"semver": "7.3.4"
},
"publishConfig": {
"access": "public"

View File

@ -3,8 +3,10 @@ import devServerExecutor from '@nrwl/webpack/src/executors/dev-server/dev-server
import { WebDevServerOptions } from '@nrwl/webpack/src/executors/dev-server/schema';
import { join } from 'path';
import * as chalk from 'chalk';
import { combineAsyncIterableIterators } from '@nrwl/js/src/utils/async-iterable/combine-async-iteratable-iterators';
import { tapAsyncIterator } from '@nrwl/js/src/utils/async-iterable/tap-async-iteratable';
import {
combineAsyncIterableIterators,
tapAsyncIterable,
} from '@nrwl/devkit/src/utils/async-iterable';
type ModuleFederationDevServerOptions = WebDevServerOptions & {
devRemotes?: string | string[];
@ -65,7 +67,7 @@ export default async function* moduleFederationDevServer(
}
let numAwaiting = knownRemotes.length + 1; // remotes + host
return yield* tapAsyncIterator(iter, (x) => {
return yield* tapAsyncIterable(iter, (x) => {
numAwaiting--;
if (numAwaiting === 0) {
logger.info(

View File

@ -15,6 +15,7 @@ import { addStyledModuleDependencies } from '../../rules/add-styled-dependencies
import {
addDependenciesToPackageJson,
convertNxGenerator,
ensurePackage,
formatFiles,
GeneratorCallback,
joinPathFragments,
@ -24,10 +25,12 @@ import {
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import reactInitGenerator from '../init/init';
import { Linter, lintProjectGenerator } from '@nrwl/linter';
import { swcCoreVersion } from '@nrwl/js/src/utils/versions';
import { swcLoaderVersion } from '@nrwl/webpack/src/utils/versions';
import { viteConfigurationGenerator, vitestGenerator } from '@nrwl/vite';
import { mapLintPattern } from '@nrwl/linter/src/generators/lint-project/lint-project';
import {
nxVersion,
swcCoreVersion,
swcLoaderVersion,
} from '../../utils/versions';
async function addLinting(host: Tree, options: NormalizedSchema) {
const tasks: GeneratorCallback[] = [];
@ -90,6 +93,8 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
addProject(host, options);
if (options.bundler === 'vite') {
await ensurePackage(host, '@nrwl/vite', nxVersion);
const { viteConfigurationGenerator } = await import('@nrwl/vite');
// We recommend users use `import.meta.env.MODE` and other variables in their code to differentiate between production and development.
// See: https://vitejs.dev/guide/env-and-mode.html
host.delete(joinPathFragments(options.appProjectRoot, 'src/environments'));
@ -101,9 +106,20 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
includeVitest: true,
});
tasks.push(viteTask);
} else if (options.bundler === 'webpack') {
await ensurePackage(host, '@nrwl/webpack', nxVersion);
const { webpackInitGenerator } = await import('@nrwl/webpack');
const webpackInitTask = await webpackInitGenerator(host, {
uiFramework: 'react',
});
tasks.push(webpackInitTask);
}
if (options.bundler !== 'vite' && options.unitTestRunner === 'vitest') {
await ensurePackage(host, '@nrwl/vite', nxVersion);
const { vitestGenerator } = await import('@nrwl/vite');
const vitestTask = await vitestGenerator(host, {
uiFramework: 'react',
project: options.projectName,

View File

@ -1,11 +1,13 @@
import { cypressProjectGenerator } from '@nrwl/cypress';
import { Tree } from '@nrwl/devkit';
import { ensurePackage, Tree } from '@nrwl/devkit';
import { nxVersion } from '../../../utils/versions';
import { NormalizedSchema } from '../schema';
export async function addCypress(host: Tree, options: NormalizedSchema) {
if (options.e2eTestRunner !== 'cypress') {
return () => {};
}
await ensurePackage(host, '@nrwl/cypress', nxVersion);
const { cypressProjectGenerator } = await import('@nrwl/cypress');
return await cypressProjectGenerator(host, {
...options,

View File

@ -1,8 +1,11 @@
import { Tree } from '@nrwl/devkit';
import { jestProjectGenerator } from '@nrwl/jest';
import { ensurePackage, Tree } from '@nrwl/devkit';
import { NormalizedSchema } from '../schema';
import { nxVersion } from '../../../utils/versions';
export async function addJest(host: Tree, options: NormalizedSchema) {
await ensurePackage(host, '@nrwl/jest', nxVersion);
const { jestProjectGenerator } = await import('@nrwl/jest');
if (options.unitTestRunner !== 'jest') {
return () => {};
}

View File

@ -26,7 +26,7 @@ describe(componentTestGenerator.name, () => {
component: true,
});
componentTestGenerator(tree, {
await componentTestGenerator(tree, {
project: 'some-lib',
componentPath: 'lib/some-lib.tsx',
});
@ -47,7 +47,7 @@ describe(componentTestGenerator.name, () => {
js: true,
});
componentTestGenerator(tree, {
await componentTestGenerator(tree, {
project: 'some-lib',
componentPath: 'lib/some-lib.js',
});
@ -67,7 +67,7 @@ describe(componentTestGenerator.name, () => {
component: true,
});
tree.write('libs/some-lib/src/lib/some-lib.cy.tsx', 'existing content');
componentTestGenerator(tree, {
await componentTestGenerator(tree, {
project: 'some-lib',
componentPath: 'lib/some-lib.tsx',
});
@ -89,12 +89,12 @@ describe(componentTestGenerator.name, () => {
component: true,
});
expect(() => {
await expect(
componentTestGenerator(tree, {
project: 'some-lib',
componentPath: 'lib/blah/abc-123.blah',
});
}).not.toThrow();
})
).resolves.not.toThrow();
});
it('should handle being provided the full path to the component', async () => {
@ -109,7 +109,7 @@ describe(componentTestGenerator.name, () => {
component: true,
});
componentTestGenerator(tree, {
await componentTestGenerator(tree, {
project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
});
@ -144,11 +144,11 @@ export interface AnotherCmpProps {
}
export function AnotherCmp(props: AnotherCmpProps) {
return <button onClick="{handleClick}">{props.text}</button>;
return <button onClick='{handleClick}'>{props.text}</button>;
}
`
);
componentTestGenerator(tree, {
await componentTestGenerator(tree, {
project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
});
@ -180,7 +180,7 @@ export function AnotherCmp() {
}
`
);
componentTestGenerator(tree, {
await componentTestGenerator(tree, {
project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
});
@ -214,7 +214,7 @@ export interface AnotherCmpProps {
}
export default function AnotherCmp(props: AnotherCmpProps) {
return <button onClick="{handleClick}">{props.text}</button>;
return <button onClick='{handleClick}'>{props.text}</button>;
}
export function AnotherCmp2() {
@ -222,7 +222,7 @@ export function AnotherCmp2() {
}
`
);
componentTestGenerator(tree, {
await componentTestGenerator(tree, {
project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
});
@ -257,7 +257,7 @@ export interface AnotherCmpProps {
}
export function AnotherCmp(props: AnotherCmpProps) {
return <button onClick="{handleClick}">{props.text}</button>;
return <button onClick='{handleClick}'>{props.text}</button>;
}
export function AnotherCmp2() {
@ -265,7 +265,7 @@ export function AnotherCmp2() {
}
`
);
componentTestGenerator(tree, {
await componentTestGenerator(tree, {
project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
});
@ -302,11 +302,11 @@ export interface AnotherCmpProps {
}
export function AnotherCmp(props: AnotherCmpProps) {
return <button onClick="{handleClick}">{props.text}</button>;
return <button onClick='{handleClick}'>{props.text}</button>;
}
`
);
componentTestGenerator(tree, {
await componentTestGenerator(tree, {
project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
});
@ -328,7 +328,7 @@ export function AnotherCmp(props: AnotherCmpProps) {
unitTestRunner: 'none',
component: true,
});
componentTestGenerator(tree, {
await componentTestGenerator(tree, {
project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
});
@ -362,11 +362,11 @@ export interface AnotherCmpProps {
}
export default function AnotherCmp(props: AnotherCmpProps) {
return <button onClick="{handleClick}">{props.text}</button>;
return <button onClick='{handleClick}'>{props.text}</button>;
}
`
);
componentTestGenerator(tree, {
await componentTestGenerator(tree, {
project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
});
@ -400,11 +400,11 @@ export interface AnotherCmpProps {
}
export function AnotherCmp(props: AnotherCmpProps) {
return <button onClick="{handleClick}">{props.text}</button>;
return <button onClick='{handleClick}'>{props.text}</button>;
}
`
);
componentTestGenerator(tree, {
await componentTestGenerator(tree, {
project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
});

View File

@ -1,5 +1,5 @@
import { assertMinimumCypressVersion } from '@nrwl/cypress/src/utils/cypress-version';
import {
ensurePackage,
generateFiles,
joinPathFragments,
readProjectConfiguration,
@ -12,12 +12,17 @@ import {
getComponentNode,
} from '../../utils/ast-utils';
import { getDefaultsForComponent } from '../../utils/component-props';
import { nxVersion } from '../../utils/versions';
import { ComponentTestSchema } from './schema';
export function componentTestGenerator(
export async function componentTestGenerator(
tree: Tree,
options: ComponentTestSchema
) {
await ensurePackage(tree, '@nrwl/cypress', nxVersion);
const { assertMinimumCypressVersion } = await import(
'@nrwl/cypress/src/utils/cypress-version'
);
assertMinimumCypressVersion(10);
const projectConfig = readProjectConfiguration(tree, options.project);

View File

@ -1,5 +1,10 @@
import { cypressComponentProject } from '@nrwl/cypress';
import { formatFiles, readProjectConfiguration, Tree } from '@nrwl/devkit';
import {
ensurePackage,
formatFiles,
readProjectConfiguration,
Tree,
} from '@nrwl/devkit';
import { nxVersion } from '../../utils/versions';
import { addFiles } from './lib/add-files';
import { updateProjectConfig } from './lib/update-configs';
import { CypressComponentConfigurationSchema } from './schema.d';
@ -13,6 +18,8 @@ export async function cypressComponentConfigGenerator(
tree: Tree,
options: CypressComponentConfigurationSchema
) {
await ensurePackage(tree, '@nrwl/cypress', nxVersion);
const { cypressComponentProject } = await import('@nrwl/cypress');
const projectConfig = readProjectConfiguration(tree, options.project);
const installTask = await cypressComponentProject(tree, {
project: options.project,
@ -20,7 +27,7 @@ export async function cypressComponentConfigGenerator(
});
await updateProjectConfig(tree, options);
addFiles(tree, projectConfig, options);
await addFiles(tree, projectConfig, options);
if (options.skipFormat) {
await formatFiles(tree);
}

View File

@ -13,7 +13,7 @@ import { CypressComponentConfigurationSchema } from '../schema';
const allowedFileExt = new RegExp(/\.[jt]sx?/g);
const isSpecFile = new RegExp(/(spec|test)\./g);
export function addFiles(
export async function addFiles(
tree: Tree,
projectConfig: ProjectConfiguration,
options: CypressComponentConfigurationSchema
@ -36,14 +36,19 @@ export function addFiles(
);
if (options.generateTests) {
const filePaths = [];
visitNotIgnoredFiles(tree, projectConfig.sourceRoot, (filePath) => {
if (isComponent(tree, filePath)) {
componentTestGenerator(tree, {
filePaths.push(filePath);
}
});
for (const filePath of filePaths) {
await componentTestGenerator(tree, {
project: options.project,
componentPath: filePath,
});
}
});
}
}

View File

@ -1,10 +1,6 @@
import { findBuildConfig } from '@nrwl/cypress/src/utils/find-target-options';
import {
joinPathFragments,
ProjectConfiguration,
readProjectConfiguration,
Tree,
updateJson,
updateProjectConfiguration,
} from '@nrwl/devkit';
import { CypressComponentConfigurationSchema } from '../schema';
@ -13,6 +9,9 @@ export async function updateProjectConfig(
tree: Tree,
options: CypressComponentConfigurationSchema
) {
const { findBuildConfig } = await import(
'@nrwl/cypress/src/utils/find-target-options'
);
const found = await findBuildConfig(tree, {
project: options.project,
buildTarget: options.buildTarget,

View File

@ -1,4 +1,4 @@
import { NxJsonConfiguration, readJson, Tree } from '@nrwl/devkit';
import { readJson, Tree } from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import reactInitGenerator from './init';
import { InitSchema } from './schema';
@ -30,4 +30,9 @@ describe('init', () => {
await reactInitGenerator(tree, { ...schema, unitTestRunner: 'none' });
expect(tree.exists('jest.config.js')).toEqual(false);
});
it('should not add babel.config.json if skipBabelConfig is true', async () => {
await reactInitGenerator(tree, { ...schema, skipBabelConfig: true });
expect(tree.exists('babel.config.json')).toEqual(false);
});
});

View File

@ -1,16 +1,17 @@
import { cypressInitGenerator } from '@nrwl/cypress';
import {
addDependenciesToPackageJson,
convertNxGenerator,
ensurePackage,
GeneratorCallback,
readWorkspaceConfiguration,
removeDependenciesFromPackageJson,
Tree,
updateWorkspaceConfiguration,
writeJson,
} from '@nrwl/devkit';
import { webInitGenerator } from '@nrwl/web';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import {
babelPresetReactVersion,
nxVersion,
reactDomVersion,
reactTestRendererVersion,
@ -66,22 +67,54 @@ function updateDependencies(host: Tree, schema: InitSchema) {
});
}
function initRootBabelConfig(tree: Tree, schema: InitSchema) {
if (tree.exists('/babel.config.json') || tree.exists('/babel.config.js')) {
return;
}
if (!schema.skipBabelConfig) {
writeJson(tree, '/babel.config.json', {
babelrcRoots: ['*'], // Make sure .babelrc files other than root can be loaded in a monorepo
});
}
const workspaceConfiguration = readWorkspaceConfiguration(tree);
if (workspaceConfiguration.namedInputs?.sharedGlobals) {
workspaceConfiguration.namedInputs.sharedGlobals.push(
'{workspaceRoot}/babel.config.json'
);
}
updateWorkspaceConfiguration(tree, workspaceConfiguration);
}
export async function reactInitGenerator(host: Tree, schema: InitSchema) {
const tasks: GeneratorCallback[] = [];
setDefault(host);
if (!schema.e2eTestRunner || schema.e2eTestRunner === 'cypress') {
await ensurePackage(host, '@nrwl/cypress', nxVersion);
const { cypressInitGenerator } = await import('@nrwl/cypress');
const cypressTask = cypressInitGenerator(host, {});
tasks.push(cypressTask);
}
// TODO(jack): We should be able to remove this generator and have react init everything.
const initTask = await webInitGenerator(host, {
...schema,
skipPackageJson: true,
});
tasks.push(initTask);
if (!schema.skipPackageJson && !schema.skipBabelConfig) {
const installBabelTask = addDependenciesToPackageJson(
host,
{},
{
'@babel/preset-react': babelPresetReactVersion,
}
);
tasks.push(installBabelTask);
}
if (!schema.skipBabelConfig) {
initRootBabelConfig(host, schema);
}
if (!schema.skipPackageJson) {
const installTask = updateDependencies(host, schema);
tasks.push(installTask);

View File

@ -0,0 +1,47 @@
import { Tree } from 'nx/src/generators/tree';
import { Linter, lintProjectGenerator } from '@nrwl/linter';
import { joinPathFragments } from 'nx/src/utils/path';
import { updateJson } from 'nx/src/generators/utils/json';
import { addDependenciesToPackageJson } from '@nrwl/devkit';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { NormalizedSchema } from '../schema';
import {
extendReactEslintJson,
extraEslintDependencies,
} from '../../../utils/lint';
export async function addLinting(host: Tree, options: NormalizedSchema) {
if (options.linter === Linter.EsLint) {
const lintTask = await lintProjectGenerator(host, {
linter: options.linter,
project: options.name,
tsConfigPaths: [
joinPathFragments(options.projectRoot, 'tsconfig.lib.json'),
],
unitTestRunner: options.unitTestRunner,
eslintFilePatterns: [`${options.projectRoot}/**/*.{ts,tsx,js,jsx}`],
skipFormat: true,
skipPackageJson: options.skipPackageJson,
});
updateJson(
host,
joinPathFragments(options.projectRoot, '.eslintrc.json'),
extendReactEslintJson
);
let installTask = () => {};
if (!options.skipPackageJson) {
installTask = await addDependenciesToPackageJson(
host,
extraEslintDependencies.dependencies,
extraEslintDependencies.devDependencies
);
}
return runTasksInSerial(lintTask, installTask);
} else {
return () => {};
}
}

View File

@ -0,0 +1,65 @@
import { Tree } from 'nx/src/generators/tree';
import {
ensurePackage,
getWorkspaceLayout,
joinPathFragments,
readProjectConfiguration,
updateProjectConfiguration,
} from '@nrwl/devkit';
import { maybeJs } from './maybe-js';
import { NormalizedSchema } from '../schema';
import { nxVersion } from '../../../utils/versions';
export async function addRollupBuildTarget(
host: Tree,
options: NormalizedSchema
) {
await ensurePackage(host, '@nrwl/rollup', nxVersion);
const { rollupInitGenerator } = await import('@nrwl/rollup');
const { targets } = readProjectConfiguration(host, options.name);
const { libsDir } = getWorkspaceLayout(host);
const external: string[] = [];
if (options.style === '@emotion/styled') {
external.push('@emotion/react/jsx-runtime');
} else {
external.push('react/jsx-runtime');
}
targets.build = {
executor: '@nrwl/rollup:rollup',
outputs: ['{options.outputPath}'],
options: {
outputPath:
libsDir !== '.'
? `dist/${libsDir}/${options.projectDirectory}`
: `dist/${options.projectDirectory}`,
tsConfig: `${options.projectRoot}/tsconfig.lib.json`,
project: `${options.projectRoot}/package.json`,
entryFile: maybeJs(options, `${options.projectRoot}/src/index.ts`),
external,
rollupConfig: `@nrwl/react/plugins/bundle-rollup`,
compiler: options.compiler ?? 'babel',
assets: [
{
glob: `${options.projectRoot}/README.md`,
input: '.',
output: '.',
},
],
},
};
updateProjectConfiguration(host, options.name, {
root: options.projectRoot,
sourceRoot: joinPathFragments(options.projectRoot, 'src'),
projectType: 'library',
tags: options.parsedTags,
targets,
});
return rollupInitGenerator(host, options);
}

View File

@ -0,0 +1,74 @@
import { Tree } from 'nx/src/generators/tree';
import {
generateFiles,
joinPathFragments,
names,
offsetFromRoot,
toJS,
updateJson,
} from '@nrwl/devkit';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript';
import { NormalizedSchema } from '../schema';
export function createFiles(host: Tree, options: NormalizedSchema) {
const substitutions = {
...options,
...names(options.name),
tmpl: '',
offsetFromRoot: offsetFromRoot(options.projectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig(host, options.projectRoot),
};
generateFiles(
host,
joinPathFragments(__dirname, '../files/common'),
options.projectRoot,
substitutions
);
if (options.bundler === 'vite') {
generateFiles(
host,
joinPathFragments(__dirname, '../files/vite'),
options.projectRoot,
substitutions
);
if (host.exists(joinPathFragments(options.projectRoot, '.babelrc'))) {
host.delete(joinPathFragments(options.projectRoot, '.babelrc'));
}
}
if (!options.publishable && !options.buildable) {
host.delete(`${options.projectRoot}/package.json`);
}
if (options.js) {
toJS(host);
}
updateTsConfig(host, options);
}
function updateTsConfig(tree: Tree, options: NormalizedSchema) {
updateJson(
tree,
joinPathFragments(options.projectRoot, 'tsconfig.json'),
(json) => {
if (options.strict) {
json.compilerOptions = {
...json.compilerOptions,
forceConsistentCasingInFileNames: true,
strict: true,
noImplicitOverride: true,
noPropertyAccessFromIndexSignature: true,
noImplicitReturns: true,
noFallthroughCasesInSwitch: true,
};
}
return json;
}
);
}

View File

@ -0,0 +1,7 @@
import { NormalizedSchema } from '../schema';
export function maybeJs(options: NormalizedSchema, path: string): string {
return options.js && (path.endsWith('.ts') || path.endsWith('.tsx'))
? path.replace(/\.tsx?$/, '.js')
: path;
}

View File

@ -9,8 +9,7 @@ import {
Tree,
} from '@nrwl/devkit';
import { assertValidStyle } from '../../../utils/assertion';
import { NormalizedSchema } from '../library';
import { Schema } from '../schema';
import { NormalizedSchema, Schema } from '../schema';
export function normalizeOptions(
host: Tree,
@ -40,7 +39,9 @@ export function normalizeOptions(
const normalized = {
...options,
compiler: options.compiler ?? 'babel',
bundler: options.bundler ?? 'none',
bundler:
options.bundler ??
(options.buildable || options.publishable ? 'rollup' : 'none'),
fileName,
routePath: `/${name}`,
name: projectName,

View File

@ -0,0 +1,111 @@
import {
addDependenciesToPackageJson,
applyChangesToString,
getWorkspaceLayout,
names,
} from '@nrwl/devkit';
import { Tree } from 'nx/src/generators/tree';
import { getImportPath, joinPathFragments } from 'nx/src/utils/path';
import * as ts from 'typescript';
import { NormalizedSchema } from '../schema';
import {
addBrowserRouter,
addInitialRoutes,
addRoute,
findComponentImportPath,
} from '../../../utils/ast-utils';
import { maybeJs } from './maybe-js';
import {
reactRouterDomVersion,
typesReactRouterDomVersion,
} from '../../../utils/versions';
export function updateAppRoutes(host: Tree, options: NormalizedSchema) {
if (!options.appMain || !options.appSourceRoot) {
return () => {};
}
const { content, source } = readComponent(host, options.appMain);
const componentImportPath = findComponentImportPath('App', source);
if (!componentImportPath) {
throw new Error(
`Could not find App component in ${options.appMain} (Hint: you can omit --appProject, or make sure App exists)`
);
}
const appComponentPath = joinPathFragments(
options.appSourceRoot,
maybeJs(options, `${componentImportPath}.tsx`)
);
const routerTask = addDependenciesToPackageJson(
host,
{ 'react-router-dom': reactRouterDomVersion },
{ '@types/react-router-dom': typesReactRouterDomVersion }
);
// addBrowserRouterToMain
const isRouterPresent = content.match(/react-router-dom/);
if (!isRouterPresent) {
const changes = applyChangesToString(
content,
addBrowserRouter(options.appMain, source)
);
host.write(options.appMain, changes);
}
// addInitialAppRoutes
{
const { content: componentContent, source: componentSource } =
readComponent(host, appComponentPath);
const isComponentRouterPresent = componentContent.match(/react-router-dom/);
if (!isComponentRouterPresent) {
const changes = applyChangesToString(
componentContent,
addInitialRoutes(appComponentPath, componentSource)
);
host.write(appComponentPath, changes);
}
}
// addNewAppRoute
{
const { content: componentContent, source: componentSource } =
readComponent(host, appComponentPath);
const { npmScope } = getWorkspaceLayout(host);
const changes = applyChangesToString(
componentContent,
addRoute(appComponentPath, componentSource, {
routePath: options.routePath,
componentName: names(options.name).className,
moduleName: getImportPath(npmScope, options.projectDirectory),
})
);
host.write(appComponentPath, changes);
}
return routerTask;
}
function readComponent(
host: Tree,
path: string
): { content: string; source: ts.SourceFile } {
if (!host.exists(path)) {
throw new Error(`Cannot find ${path}`);
}
const content = host.read(path, 'utf-8');
const source = ts.createSourceFile(
path,
content,
ts.ScriptTarget.Latest,
true
);
return { content, source };
}

View File

@ -0,0 +1,32 @@
import { Tree } from 'nx/src/generators/tree';
import { updateJson } from 'nx/src/generators/utils/json';
import { getRootTsConfigPathInTree } from '@nrwl/workspace/src/utilities/typescript';
import { getWorkspaceLayout, joinPathFragments } from '@nrwl/devkit';
import { NormalizedSchema } from '../schema';
import { maybeJs } from './maybe-js';
export function updateBaseTsConfig(host: Tree, options: NormalizedSchema) {
updateJson(host, getRootTsConfigPathInTree(host), (json) => {
const c = json.compilerOptions;
c.paths = c.paths || {};
delete c.paths[options.name];
if (c.paths[options.importPath]) {
throw new Error(
`You already have a library using the import path "${options.importPath}". Make sure to specify a unique one.`
);
}
const { libsDir } = getWorkspaceLayout(host);
c.paths[options.importPath] = [
maybeJs(
options,
joinPathFragments(libsDir, `${options.projectDirectory}/src/index.ts`)
),
];
return json;
});
}

View File

@ -11,6 +11,7 @@ import {
createTreeWithEmptyWorkspace,
} from '@nrwl/devkit/testing';
import { Linter } from '@nrwl/linter';
import { nxVersion } from '../../utils/versions';
import applicationGenerator from '../application/application';
import libraryGenerator from './library';
import { Schema } from './schema';
@ -37,6 +38,16 @@ describe('lib', () => {
beforeEach(() => {
mockedInstalledCypressVersion.mockReturnValue(10);
tree = createTreeWithEmptyV1Workspace();
updateJson(tree, '/package.json', (json) => {
json.devDependencies = {
'@nrwl/cypress': nxVersion,
'@nrwl/jest': nxVersion,
'@nrwl/rollup': nxVersion,
'@nrwl/vite': nxVersion,
'@nrwl/webpack': nxVersion,
};
return json;
});
});
describe('not nested', () => {
@ -726,7 +737,6 @@ describe('lib', () => {
describe('--skipPackageJson', () => {
it('should not add dependencies to package.json when true', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
const packageJsonBeforeGenerator = tree.read('package.json', 'utf-8');
// ACT
await libraryGenerator(tree, {

View File

@ -1,64 +1,32 @@
import {
addDependenciesToPackageJson,
addProjectConfiguration,
applyChangesToString,
convertNxGenerator,
ensurePackage,
formatFiles,
generateFiles,
GeneratorCallback,
getWorkspaceLayout,
joinPathFragments,
names,
offsetFromRoot,
toJS,
Tree,
updateJson,
} from '@nrwl/devkit';
import { getImportPath } from 'nx/src/utils/path';
import { jestProjectGenerator } from '@nrwl/jest';
import { swcCoreVersion } from '@nrwl/js/src/utils/versions';
import { Linter, lintProjectGenerator } from '@nrwl/linter';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import {
getRelativePathToRootTsConfig,
getRootTsConfigPathInTree,
} from '@nrwl/workspace/src/utilities/typescript';
import * as ts from 'typescript';
import {
addBrowserRouter,
addInitialRoutes,
addRoute,
findComponentImportPath,
} from '../../utils/ast-utils';
import {
extendReactEslintJson,
extraEslintDependencies,
} from '../../utils/lint';
import {
nxVersion,
reactDomVersion,
reactRouterDomVersion,
reactVersion,
typesReactRouterDomVersion,
swcCoreVersion,
} from '../../utils/versions';
import componentGenerator from '../component/component';
import initGenerator from '../init/init';
import { Schema } from './schema';
import { updateJestConfigContent } from '../../utils/jest-utils';
import { viteConfigurationGenerator, vitestGenerator } from '@nrwl/vite';
import { normalizeOptions } from './lib/normalize-options';
export interface NormalizedSchema extends Schema {
name: string;
fileName: string;
projectRoot: string;
routePath: string;
projectDirectory: string;
parsedTags: string[];
appMain?: string;
appSourceRoot?: string;
libsDir?: string;
unitTestRunner: 'jest' | 'vitest' | 'none';
}
import { addRollupBuildTarget } from './lib/add-rollup-build-target';
import { addLinting } from './lib/add-linting';
import { updateAppRoutes } from './lib/update-app-routes';
import { createFiles } from './lib/create-files';
import { updateBaseTsConfig } from './lib/update-base-tsconfig';
export async function libraryGenerator(host: Tree, schema: Schema) {
const tasks: GeneratorCallback[] = [];
@ -82,7 +50,18 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
});
tasks.push(initTask);
addProject(host, options);
addProjectConfiguration(
host,
options.name,
{
root: options.projectRoot,
sourceRoot: joinPathFragments(options.projectRoot, 'src'),
projectType: 'library',
tags: options.parsedTags,
targets: {},
},
options.standaloneConfig
);
const lintTask = await addLinting(host, options);
tasks.push(lintTask);
@ -93,7 +72,10 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
updateBaseTsConfig(host, options);
}
// Set up build target
if (options.buildable && options.bundler === 'vite') {
await ensurePackage(host, '@nrwl/vite', nxVersion);
const { viteConfigurationGenerator } = await import('@nrwl/vite');
const viteTask = await viteConfigurationGenerator(host, {
uiFramework: 'react',
project: options.name,
@ -103,9 +85,16 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
includeVitest: true,
});
tasks.push(viteTask);
} else if (options.buildable && options.bundler === 'rollup') {
const rollupTask = await addRollupBuildTarget(host, options);
tasks.push(rollupTask);
}
// Set up test target
if (options.unitTestRunner === 'jest') {
await ensurePackage(host, '@nrwl/jest', nxVersion);
const { jestProjectGenerator } = await import('@nrwl/jest');
const jestTask = await jestProjectGenerator(host, {
...options,
project: options.name,
@ -129,6 +118,8 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
options.unitTestRunner === 'vitest' &&
options.bundler !== 'vite' // tests are already configured if bundler is vite
) {
await ensurePackage(host, '@nrwl/vite', nxVersion);
const { vitestGenerator } = await import('@nrwl/vite');
const vitestTask = await vitestGenerator(host, {
uiFramework: 'react',
project: options.name,
@ -155,11 +146,14 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
}
if (options.publishable || options.buildable) {
updateLibPackageNpmScope(host, options);
updateJson(host, `${options.projectRoot}/package.json`, (json) => {
json.name = options.importPath;
return json;
});
}
if (!options.skipPackageJson) {
const installTask = await addDependenciesToPackageJson(
const installReactTask = await addDependenciesToPackageJson(
host,
{
react: reactVersion,
@ -167,7 +161,7 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
},
options.compiler === 'swc' ? { '@swc/core': swcCoreVersion } : {}
);
tasks.push(installTask);
tasks.push(installReactTask);
}
const routeTask = updateAppRoutes(host, options);
@ -180,281 +174,5 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
return runTasksInSerial(...tasks);
}
async function addLinting(host: Tree, options: NormalizedSchema) {
if (options.linter === Linter.EsLint) {
const lintTask = await lintProjectGenerator(host, {
linter: options.linter,
project: options.name,
tsConfigPaths: [
joinPathFragments(options.projectRoot, 'tsconfig.lib.json'),
],
unitTestRunner: options.unitTestRunner,
eslintFilePatterns: [`${options.projectRoot}/**/*.{ts,tsx,js,jsx}`],
skipFormat: true,
skipPackageJson: options.skipPackageJson,
});
updateJson(
host,
joinPathFragments(options.projectRoot, '.eslintrc.json'),
extendReactEslintJson
);
let installTask = () => {};
if (!options.skipPackageJson) {
installTask = await addDependenciesToPackageJson(
host,
extraEslintDependencies.dependencies,
extraEslintDependencies.devDependencies
);
}
return runTasksInSerial(lintTask, installTask);
} else {
return () => {};
}
}
function addProject(host: Tree, options: NormalizedSchema) {
const targets: { [key: string]: any } = {};
if (options.publishable || options.buildable) {
const { libsDir } = getWorkspaceLayout(host);
const external: string[] = [];
if (options.style === '@emotion/styled') {
external.push('@emotion/react/jsx-runtime');
} else {
external.push('react/jsx-runtime');
}
targets.build = {
executor: '@nrwl/rollup:rollup',
outputs: ['{options.outputPath}'],
options: {
outputPath:
libsDir !== '.'
? `dist/${libsDir}/${options.projectDirectory}`
: `dist/${options.projectDirectory}`,
tsConfig: `${options.projectRoot}/tsconfig.lib.json`,
project: `${options.projectRoot}/package.json`,
entryFile: maybeJs(options, `${options.projectRoot}/src/index.ts`),
external,
rollupConfig: `@nrwl/react/plugins/bundle-rollup`,
compiler: options.compiler ?? 'babel',
assets: [
{
glob: `${options.projectRoot}/README.md`,
input: '.',
output: '.',
},
],
},
};
}
addProjectConfiguration(
host,
options.name,
{
root: options.projectRoot,
sourceRoot: joinPathFragments(options.projectRoot, 'src'),
projectType: 'library',
tags: options.parsedTags,
targets,
},
options.standaloneConfig
);
}
function updateTsConfig(tree: Tree, options: NormalizedSchema) {
updateJson(
tree,
joinPathFragments(options.projectRoot, 'tsconfig.json'),
(json) => {
if (options.strict) {
json.compilerOptions = {
...json.compilerOptions,
forceConsistentCasingInFileNames: true,
strict: true,
noImplicitOverride: true,
noPropertyAccessFromIndexSignature: true,
noImplicitReturns: true,
noFallthroughCasesInSwitch: true,
};
}
return json;
}
);
}
function updateBaseTsConfig(host: Tree, options: NormalizedSchema) {
updateJson(host, getRootTsConfigPathInTree(host), (json) => {
const c = json.compilerOptions;
c.paths = c.paths || {};
delete c.paths[options.name];
if (c.paths[options.importPath]) {
throw new Error(
`You already have a library using the import path "${options.importPath}". Make sure to specify a unique one.`
);
}
const { libsDir } = getWorkspaceLayout(host);
c.paths[options.importPath] = [
maybeJs(
options,
joinPathFragments(libsDir, `${options.projectDirectory}/src/index.ts`)
),
];
return json;
});
}
function createFiles(host: Tree, options: NormalizedSchema) {
const substitutions = {
...options,
...names(options.name),
tmpl: '',
offsetFromRoot: offsetFromRoot(options.projectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig(host, options.projectRoot),
};
generateFiles(
host,
joinPathFragments(__dirname, './files/common'),
options.projectRoot,
substitutions
);
if (options.bundler === 'vite') {
generateFiles(
host,
joinPathFragments(__dirname, './files/vite'),
options.projectRoot,
substitutions
);
if (host.exists(joinPathFragments(options.projectRoot, '.babelrc'))) {
host.delete(joinPathFragments(options.projectRoot, '.babelrc'));
}
}
if (!options.publishable && !options.buildable) {
host.delete(`${options.projectRoot}/package.json`);
}
if (options.js) {
toJS(host);
}
updateTsConfig(host, options);
}
function updateAppRoutes(host: Tree, options: NormalizedSchema) {
if (!options.appMain || !options.appSourceRoot) {
return () => {};
}
const { content, source } = readComponent(host, options.appMain);
const componentImportPath = findComponentImportPath('App', source);
if (!componentImportPath) {
throw new Error(
`Could not find App component in ${options.appMain} (Hint: you can omit --appProject, or make sure App exists)`
);
}
const appComponentPath = joinPathFragments(
options.appSourceRoot,
maybeJs(options, `${componentImportPath}.tsx`)
);
const routerTask = addDependenciesToPackageJson(
host,
{ 'react-router-dom': reactRouterDomVersion },
{ '@types/react-router-dom': typesReactRouterDomVersion }
);
// addBrowserRouterToMain
const isRouterPresent = content.match(/react-router-dom/);
if (!isRouterPresent) {
const changes = applyChangesToString(
content,
addBrowserRouter(options.appMain, source)
);
host.write(options.appMain, changes);
}
// addInitialAppRoutes
{
const { content: componentContent, source: componentSource } =
readComponent(host, appComponentPath);
const isComponentRouterPresent = componentContent.match(/react-router-dom/);
if (!isComponentRouterPresent) {
const changes = applyChangesToString(
componentContent,
addInitialRoutes(appComponentPath, componentSource)
);
host.write(appComponentPath, changes);
}
}
// addNewAppRoute
{
const { content: componentContent, source: componentSource } =
readComponent(host, appComponentPath);
const { npmScope } = getWorkspaceLayout(host);
const changes = applyChangesToString(
componentContent,
addRoute(appComponentPath, componentSource, {
routePath: options.routePath,
componentName: names(options.name).className,
moduleName: getImportPath(npmScope, options.projectDirectory),
})
);
host.write(appComponentPath, changes);
}
return routerTask;
}
function readComponent(
host: Tree,
path: string
): { content: string; source: ts.SourceFile } {
if (!host.exists(path)) {
throw new Error(`Cannot find ${path}`);
}
const content = host.read(path, 'utf-8');
const source = ts.createSourceFile(
path,
content,
ts.ScriptTarget.Latest,
true
);
return { content, source };
}
function updateLibPackageNpmScope(host: Tree, options: NormalizedSchema) {
return updateJson(host, `${options.projectRoot}/package.json`, (json) => {
json.name = options.importPath;
return json;
});
}
function maybeJs(options: NormalizedSchema, path: string): string {
return options.js && (path.endsWith('.ts') || path.endsWith('.tsx'))
? path.replace(/\.tsx?$/, '.js')
: path;
}
export default libraryGenerator;
export const librarySchematic = convertNxGenerator(libraryGenerator);

View File

@ -1,4 +1,4 @@
import { Linter } from '@nrwl/linter';
import type { Linter } from '@nrwl/linter';
import { SupportedStyles } from '../../../typings/style';
export interface Schema {
@ -28,3 +28,17 @@ export interface Schema {
unitTestRunner?: 'jest' | 'vitest' | 'none';
minimal?: boolean;
}
export interface NormalizedSchema extends Schema {
js: boolean;
name: string;
fileName: string;
projectRoot: string;
routePath: string;
projectDirectory: string;
parsedTags: string[];
appMain?: string;
appSourceRoot?: string;
libsDir?: string;
unitTestRunner: 'jest' | 'vitest' | 'none';
}

View File

@ -15,10 +15,6 @@ import {
visitNotIgnoredFiles,
} from '@nrwl/devkit';
import { basename, join } from 'path';
import {
findStorybookAndBuildTargetsAndCompiler,
isTheFileAStory,
} from '@nrwl/storybook/src/utils/utilities';
import minimatch = require('minimatch');
export interface StorybookStoriesSchema {
@ -29,7 +25,13 @@ export interface StorybookStoriesSchema {
ignorePaths?: string[];
}
export function projectRootPath(config: ProjectConfiguration): string {
export async function projectRootPath(
tree: Tree,
config: ProjectConfiguration
): Promise<string> {
const { findStorybookAndBuildTargetsAndCompiler } = await import(
'@nrwl/storybook/src/utils/utilities'
);
let projectDir: string;
if (config.projectType === 'application') {
const { nextBuildTarget } = findStorybookAndBuildTargetsAndCompiler(
@ -79,12 +81,16 @@ export async function createAllStories(
cypressProject?: string,
ignorePaths?: string[]
) {
const { isTheFileAStory } = await import(
'@nrwl/storybook/src/utils/utilities'
);
const projects = getProjects(tree);
const projectConfiguration = projects.get(projectName);
const { sourceRoot, root } = projectConfiguration;
let componentPaths: string[] = [];
visitNotIgnoredFiles(tree, projectRootPath(projectConfiguration), (path) => {
const projectPath = await projectRootPath(tree, projectConfiguration);
visitNotIgnoredFiles(tree, projectPath, (path) => {
// Ignore private files starting with "_".
if (basename(path).startsWith('_')) return;

View File

@ -2,14 +2,18 @@ import { StorybookConfigureSchema } from './schema';
import storiesGenerator from '../stories/stories';
import {
convertNxGenerator,
ensurePackage,
logger,
readProjectConfiguration,
Tree,
} from '@nrwl/devkit';
import { configurationGenerator } from '@nrwl/storybook';
import { getE2eProjectName } from '@nrwl/cypress/src/utils/project-name';
import { nxVersion } from '../../utils/versions';
async function generateStories(host: Tree, schema: StorybookConfigureSchema) {
await ensurePackage(host, '@nrwl/cypress', nxVersion);
const { getE2eProjectName } = await import(
'@nrwl/cypress/src/utils/project-name'
);
const projectConfig = readProjectConfiguration(host, schema.name);
const cypressProject = getE2eProjectName(
schema.name,
@ -30,6 +34,9 @@ export async function storybookConfigurationGenerator(
host: Tree,
schema: StorybookConfigureSchema
) {
await ensurePackage(host, '@nrwl/storybook', nxVersion);
const { configurationGenerator } = await import('@nrwl/storybook');
let bundler = schema.bundler ?? 'webpack';
const projectConfig = readProjectConfiguration(host, schema.name);

View File

@ -1,40 +0,0 @@
import { readJson, Tree } from '@nrwl/devkit';
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import * as path from 'path';
import { removeReactReduxTypesFromPackageJson } from './remove-react-redux-types-package';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
describe('Remove @types/react-redux Package from package.json 12.0.0', () => {
let tree: Tree;
let schematicRunner: SchematicTestRunner;
beforeEach(async () => {
tree = createTreeWithEmptyV1Workspace();
schematicRunner = new SchematicTestRunner(
'@nrwl/react',
path.join(__dirname, '../../../migrations.json')
);
});
it(`should remove @types/react-redux from deps and/or from devDeps in package.json`, async () => {
tree.write(
'package.json',
JSON.stringify({
dependencies: {
'@types/react-redux': '10.1.1',
},
devDependencies: {
'@types/react-redux': '10.1.1',
},
})
);
await removeReactReduxTypesFromPackageJson(tree);
const packageJson = readJson(tree, '/package.json');
expect(packageJson).toMatchObject({
dependencies: {},
devDependencies: {},
});
});
});

View File

@ -4,7 +4,7 @@ import {
readProjectConfiguration,
Tree,
} from '@nrwl/devkit';
import { JestExecutorOptions } from '@nrwl/jest/src/executors/jest/schema';
import type { JestExecutorOptions } from '@nrwl/jest/src/executors/jest/schema';
import { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils';
import { tsquery } from '@phenomnomnominal/tsquery';
import { StringLiteral } from 'typescript';

View File

@ -0,0 +1,62 @@
import { addProjectConfiguration, readJson } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { installWebpackRollupDependencies } from './install-webpack-rollup-dependencies';
describe('installWebpackRollupDependencies', () => {
it('should install packages if webpack is used', async () => {
const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'proj', {
root: 'proj',
targets: {
build: { executor: '@nrwl/webpack:webpack' },
},
});
await installWebpackRollupDependencies(tree);
expect(readJson(tree, 'package.json')).toMatchObject({
devDependencies: {
webpack: '^5.75.0',
},
});
});
it('should install packages if rollup is used', async () => {
const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'proj', {
root: 'proj',
targets: {
build: { executor: '@nrwl/rollup:rollup' },
},
});
await installWebpackRollupDependencies(tree);
expect(readJson(tree, 'package.json')).toMatchObject({
devDependencies: {
webpack: '^5.75.0',
},
});
});
it('should not install packages if neither webpack nor rollup are used', async () => {
const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'proj', {
root: 'proj',
targets: {
build: { executor: '@nrwl/vite:vite' },
},
});
await installWebpackRollupDependencies(tree);
expect(readJson(tree, 'package.json')).not.toMatchObject({
devDependencies: {
webpack: '^5.75.0',
},
});
});
});

View File

@ -0,0 +1,42 @@
import { addDependenciesToPackageJson, getProjects, Tree } from '@nrwl/devkit';
export function installWebpackRollupDependencies(tree: Tree) {
const projects = getProjects(tree);
let shouldInstall = false;
for (const [, project] of projects) {
if (
project.targets?.build?.executor === '@nrwl/webpack:webpack' ||
project.targets?.build?.executor === '@nrwl/rollup:rollup' ||
project.targets?.build?.executor === '@nrwl/web:rollup'
) {
shouldInstall = true;
break;
}
}
if (shouldInstall) {
// These were previously dependencies of `@nrwl/react` but we've removed them
// to accommodate different bundlers and test runners.
return addDependenciesToPackageJson(
tree,
{},
{
'@babel/preset-react': '^7.14.5',
'@pmmmwh/react-refresh-webpack-plugin': '^0.5.7',
'@phenomnomnominal/tsquery': '4.1.1',
'@svgr/webpack': '^6.1.2',
'css-loader': '^6.4.0',
'react-refresh': '^0.10.0',
'style-loader': '^3.3.0',
stylus: '^0.55.0',
'stylus-loader': '^7.1.0',
'url-loader': '^4.1.1',
webpack: '^5.75.0',
'webpack-merge': '^5.8.0',
}
);
}
}
export default installWebpackRollupDependencies;

View File

@ -1,19 +0,0 @@
import { Rule, Tree } from '@angular-devkit/schematics';
export function initRootBabelConfig(): Rule {
return (host: Tree) => {
if (host.exists('/babel.config.json') || host.exists('/babel.config.js'))
return;
host.create(
'/babel.config.json',
JSON.stringify(
{
presets: ['@nrwl/web/babel'],
babelrcRoots: ['*'], // Make sure .babelrc files other than root can be loaded in a monorepo
},
null,
2
)
);
};
}

View File

@ -1,110 +0,0 @@
import { join } from 'path';
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import { Rule, Tree } from '@angular-devkit/schematics';
import { updateWorkspace } from '@nrwl/workspace/src/utils/workspace';
import { readJsonInTree } from '@nrwl/workspace';
import { names } from '@nrwl/devkit';
const testRunner = new SchematicTestRunner(
'@nrwl/react',
join(__dirname, '../../../generators.json')
);
testRunner.registerCollection(
'@nrwl/jest',
join(__dirname, '../../../../jest/generators.json')
);
testRunner.registerCollection(
'@nrwl/cypress',
join(__dirname, '../../../../cypress/generators.json')
);
testRunner.registerCollection(
'@nrwl/storybook',
join(__dirname, '../../../../storybook/generators.json')
);
export function callRule(rule: Rule, tree: Tree) {
return testRunner.callRule(rule, tree).toPromise();
}
export function updateNxJson(tree, update: (json: any) => any) {
const updated = update(readJsonInTree(tree, '/nx.json'));
tree.overwrite('/nx.json', JSON.stringify(updated));
}
export function createApp(tree: Tree, appName: string): Promise<Tree> {
const { fileName } = names(appName);
tree.create(
`/apps/${fileName}/src/main.tsx`,
`import ReactDOM from 'react-dom';\n`
);
updateNxJson(tree, (json) => {
json.projects[appName] = { tags: [] };
return json;
});
return callRule(
updateWorkspace((workspace) => {
workspace.projects.add({
name: fileName,
root: `apps/${fileName}`,
projectType: 'application',
sourceRoot: `apps/${fileName}/src`,
targets: {},
});
}),
tree
);
}
export function createWebApp(tree: Tree, appName: string): Promise<Tree> {
const { fileName } = names(appName);
tree.create(`/apps/${fileName}/src/index.ts`, `\n`);
updateNxJson(tree, (json) => {
json.projects[appName] = { tags: [] };
return json;
});
return callRule(
updateWorkspace((workspace) => {
workspace.projects.add({
name: fileName,
root: `apps/${fileName}`,
projectType: 'application',
sourceRoot: `apps/${fileName}/src`,
targets: {},
});
}),
tree
);
}
export function createLib(tree: Tree, libName: string): Promise<Tree> {
const { fileName } = names(libName);
tree.create(`/libs/${fileName}/src/index.ts`, `import React from 'react';\n`);
updateNxJson(tree, (json) => {
json.projects[libName] = { tags: [] };
return json;
});
return callRule(
updateWorkspace((workspace) => {
workspace.projects.add({
name: fileName,
root: `libs/${fileName}`,
projectType: 'library',
sourceRoot: `libs/${fileName}/src`,
targets: {},
});
}),
tree
);
}

View File

@ -3,12 +3,16 @@ export const nxVersion = require('../../package.json').version;
export const reactVersion = '18.2.0';
export const reactDomVersion = '18.2.0';
export const reactIsVersion = '18.2.0';
export const swcLoaderVersion = '0.1.15';
export const swcCoreVersion = '^1.2.173';
export const typesReactVersion = '18.0.25';
export const typesReactDomVersion = '18.0.9';
export const typesReactIsVersion = '17.0.3';
export const typesNodeVersion = '18.11.9';
export const babelPresetReactVersion = '^7.14.5';
export const styledComponentsVersion = '5.3.6';
export const typesStyledComponentsVersion = '5.1.26';

View File

@ -1,2 +1,4 @@
export * from './src/generators/init/init';
export * from './src/generators/rollup-project/rollup-project';
export * from './src/executors/rollup/schema';
export * from './src/executors/rollup/rollup.impl';

View File

@ -27,9 +27,7 @@ export async function rollupInitGenerator(tree: Tree, schema: Schema) {
'swc-loader': swcLoaderVersion,
}
);
}
if (schema.compiler === 'tsc') {
} else {
task = addDependenciesToPackageJson(tree, {}, { tslib: tsLibVersion });
}

View File

@ -1,4 +1,5 @@
export * from './src/utils/config';
export * from './src/generators/init/init';
export * from './src/generators/webpack-project/webpack-project';
export type { WebDevServerOptions } from './src/executors/dev-server/schema';
export * from './src/executors/dev-server/dev-server.impl';

View File

@ -5,10 +5,10 @@ import {
runExecutor,
} from '@nrwl/devkit';
import * as chalk from 'chalk';
import { combineAsyncIterableIterators } from '@nrwl/js/src/utils/async-iterable/combine-async-iteratable-iterators';
import { combineAsyncIterableIterators } from '@nrwl/devkit/src/utils/async-iterable';
import { WebpackExecutorOptions } from '../webpack/schema';
import { WebSsrDevServerOptions } from './schema';
import { TargetOptions, WebSsrDevServerOptions } from './schema';
import { waitUntilServerIsListening } from './lib/wait-until-server-is-listening';
export async function* ssrDevServerExecutor(
@ -29,6 +29,7 @@ export async function* ssrDevServerExecutor(
const runBrowser = await runExecutor<{
success: boolean;
baseUrl?: string;
options: TargetOptions;
}>(
browserTarget,
{ ...browserOptions, ...options.browserTargetOptions },
@ -37,6 +38,7 @@ export async function* ssrDevServerExecutor(
const runServer = await runExecutor<{
success: boolean;
baseUrl?: string;
options: TargetOptions;
}>(
serverTarget,
{ ...serverOptions, ...options.serverTargetOptions },

View File

@ -5,24 +5,31 @@ import {
GeneratorCallback,
Tree,
} from '@nrwl/devkit';
import { Schema } from './schema';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { swcCoreVersion } from '@nrwl/js/src/utils/versions';
import { Schema } from './schema';
import {
reactRefreshVersion,
reactRefreshWebpackPluginVersion,
svgrWebpackVersion,
swcHelpersVersion,
swcLoaderVersion,
tsLibVersion,
tsQueryVersion,
urlLoaderVersion,
} from '../../utils/versions';
import { addBabelInputs } from '@nrwl/js/src/utils/add-babel-inputs';
export async function webpackInitGenerator(tree: Tree, schema: Schema) {
let task: GeneratorCallback;
const tasks: GeneratorCallback[] = [];
if (schema.compiler === 'babel') {
addBabelInputs(tree);
}
if (schema.compiler === 'swc') {
task = addDependenciesToPackageJson(
const swcInstallTask = addDependenciesToPackageJson(
tree,
{},
{
@ -31,17 +38,39 @@ export async function webpackInitGenerator(tree: Tree, schema: Schema) {
'swc-loader': swcLoaderVersion,
}
);
tasks.push(swcInstallTask);
}
if (schema.compiler === 'tsc') {
task = addDependenciesToPackageJson(tree, {}, { tslib: tsLibVersion });
const tscInstallTask = addDependenciesToPackageJson(
tree,
{},
{ tslib: tsLibVersion }
);
tasks.push(tscInstallTask);
}
if (schema.uiFramework === 'react') {
const reactInstallTask = addDependenciesToPackageJson(
tree,
{},
{
'@pmmmwh/react-refresh-webpack-plugin':
reactRefreshWebpackPluginVersion,
'@phenomnomnominal/tsquery': tsQueryVersion,
'@svgr/webpack': svgrWebpackVersion,
'react-refresh': reactRefreshVersion,
'url-loader': urlLoaderVersion,
}
);
tasks.push(reactInstallTask);
}
if (!schema.skipFormat) {
await formatFiles(tree);
}
return task;
return runTasksInSerial(...tasks);
}
export default webpackInitGenerator;

View File

@ -1,4 +1,5 @@
export interface Schema {
compiler?: 'babel' | 'swc' | 'tsc';
uiFramework?: 'react' | 'none';
skipFormat?: boolean;
}

View File

@ -6,6 +6,12 @@
"description": "Init Webpack Plugin.",
"type": "object",
"properties": {
"uiFramework": {
"type": "string",
"description": "UI Framework to use for Vite.",
"enum": ["react", "none"],
"x-prompt": "What UI framework plugin should Webpack use?"
},
"compiler": {
"type": "string",
"enum": ["babel", "swc", "tsc"],

View File

@ -3,3 +3,10 @@ export const nxVersion = require('../../package.json').version;
export const swcLoaderVersion = '0.1.15';
export const swcHelpersVersion = '~0.4.11';
export const tsLibVersion = '^2.3.0';
// React apps
export const reactRefreshWebpackPluginVersion = '^0.5.7';
export const tsQueryVersion = '4.1.1';
export const svgrWebpackVersion = '^6.1.2';
export const reactRefreshVersion = '^0.10.0';
export const urlLoaderVersion = '^4.1.1';

View File

@ -109,9 +109,6 @@
{
"command": "node ./scripts/add-dependency-to-build.js workspace @nrwl/devkit"
},
{
"command": "node ./scripts/add-dependency-to-build.js workspace @nrwl/jest"
},
{
"command": "node ./scripts/add-dependency-to-build.js workspace @nrwl/linter"
}

View File

@ -1,19 +1,20 @@
import {
Tree,
addDependenciesToPackageJson,
addProjectConfiguration,
convertNxGenerator,
ensurePackage,
extractLayoutDirectory,
formatFiles,
generateFiles,
GeneratorCallback,
getWorkspaceLayout,
joinPathFragments,
names,
offsetFromRoot,
generateFiles,
toJS,
getWorkspaceLayout,
addProjectConfiguration,
formatFiles,
updateJson,
GeneratorCallback,
joinPathFragments,
ProjectConfiguration,
addDependenciesToPackageJson,
extractLayoutDirectory,
toJS,
Tree,
updateJson,
} from '@nrwl/devkit';
import { getImportPath } from 'nx/src/utils/path';
import { join } from 'path';
@ -25,11 +26,6 @@ import {
import { nxVersion } from '../../utils/versions';
import { Schema } from './schema';
// nx-ignore-next-line
const { jestProjectGenerator } = require('@nrwl/jest');
// nx-ignore-next-line
const { lintProjectGenerator, Linter } = require('@nrwl/linter');
export interface NormalizedSchema extends Schema {
name: string;
fileName: string;
@ -74,10 +70,12 @@ function addProject(tree: Tree, options: NormalizedSchema) {
);
}
export function addLint(
export async function addLint(
tree: Tree,
options: NormalizedSchema
): Promise<GeneratorCallback> {
await ensurePackage(tree, '@nrwl/linter', nxVersion);
const { lintProjectGenerator } = require('@nrwl/linter');
return lintProjectGenerator(tree, {
project: options.name,
linter: options.linter,
@ -178,6 +176,8 @@ async function addJest(
tree: Tree,
options: NormalizedSchema
): Promise<GeneratorCallback> {
await ensurePackage(tree, '@nrwl/jest', nxVersion);
const { jestProjectGenerator } = require('@nrwl/jest');
return await jestProjectGenerator(tree, {
...options,
project: options.name,
@ -232,7 +232,7 @@ function normalizeOptions(tree: Tree, options: Schema): NormalizedSchema {
}
if (!options.linter) {
options.linter = Linter.EsLint;
options.linter = 'eslint';
}
const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');

View File

@ -3,7 +3,21 @@ import { join } from 'path';
// Ignore packages that are defined here per package
const IGNORE_MATCHES_IN_PACKAGE = {
'*': ['nx', '@nrwl/cli', '@nrwl/workspace', 'prettier', 'typescript', 'rxjs'],
'*': [
'nx',
'prettier',
'typescript',
'rxjs',
'@nrwl/cli',
'@nrwl/workspace',
// These are installed as needed and should not be added to package.json
'@nrwl/cypress',
'@nrwl/jest',
'@nrwl/rollup',
'@nrwl/storybook',
'@nrwl/vite',
'@nrwl/webpack',
],
angular: [
'@angular-devkit/architect',
'@angular-devkit/build-angular',
@ -68,26 +82,30 @@ const IGNORE_MATCHES_IN_PACKAGE = {
'webpack',
],
react: [
'babel-plugin-emotion',
'babel-plugin-styled-components',
'rollup',
'webpack',
// These are brought in by the webpack, rollup, or vite packages via init generators.
'@babel/preset-react',
'@module-federation/node',
'@phenomnomnominal/tsquery',
'@pmmmwh/react-refresh-webpack-plugin',
'@svgr/webpack',
'@swc/jest',
'babel-jest',
'@angular-devkit/core',
'@angular-devkit/schematics',
// TODO(caleb): remove when refactoring plugin to use @nrwl/web
// webpack plugins for cypress component testing dev server
'babel-loader',
'babel-plugin-emotion',
'babel-plugin-styled-components',
'css-loader',
'less-loader',
'react-refresh',
'rollup',
'sass',
'sass-loader',
'style-loader',
'stylus-loader',
'swc-loader',
'tsconfig-paths-webpack-plugin',
'@module-federation/node',
'url-loader',
'webpack',
'webpack-merge',
],
rollup: ['@swc/core'],
storybook: [