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) - [defaultTasksRunner](../../devkit/index#defaulttasksrunner)
- [detectPackageManager](../../devkit/index#detectpackagemanager) - [detectPackageManager](../../devkit/index#detectpackagemanager)
- [detectWorkspaceScope](../../devkit/index#detectworkspacescope) - [detectWorkspaceScope](../../devkit/index#detectworkspacescope)
- [ensurePackage](../../devkit/index#ensurepackage)
- [extractLayoutDirectory](../../devkit/index#extractlayoutdirectory) - [extractLayoutDirectory](../../devkit/index#extractlayoutdirectory)
- [formatFiles](../../devkit/index#formatfiles) - [formatFiles](../../devkit/index#formatfiles)
- [generateFiles](../../devkit/index#generatefiles) - [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
**extractLayoutDirectory**(`directory`): `Object` **extractLayoutDirectory**(`directory`): `Object`

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@ -1,6 +1,6 @@
export async function* combineAsyncIterableIterators( export async function* combineAsyncIterableIterators<T = any>(
...iterators: { 0: AsyncIterableIterator<any> } & AsyncIterableIterator<any>[] ...iterators: { 0: AsyncIterableIterator<T> } & AsyncIterableIterator<T>[]
) { ): AsyncGenerator<T> {
let [options] = iterators; let [options] = iterators;
if (typeof options.next === 'function') { if (typeof options.next === 'function') {
options = Object.create(null); 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, index?: number,
data?: AsyncIterable<T> | AsyncIterableIterator<T> data?: AsyncIterable<T> | AsyncIterableIterator<T>
) => O ) => O
) { ): AsyncIterable<O> | AsyncIterableIterator<O> {
async function* f() { async function* f() {
const generator = data[Symbol.asyncIterator] || data[Symbol.iterator]; const generator = data[Symbol.asyncIterator] || data[Symbol.iterator];
const iterator = generator.call(data); 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', () => { describe('tapAsyncIterator', () => {
it('should tap values', async () => { it('should tap values', async () => {
@ -11,7 +11,7 @@ describe('tapAsyncIterator', () => {
const tapped = []; const tapped = [];
const results = []; const results = [];
const c = tapAsyncIterator(f(), (x) => { const c = tapAsyncIterable(f(), (x) => {
tapped.push(`tap: ${x}`); tapped.push(`tap: ${x}`);
}); });

View File

@ -1,9 +1,9 @@
import { mapAsyncIterable } from './map-async-iteratable'; 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>, data: AsyncIterable<T> | AsyncIterableIterator<T>,
fn: (input: I) => void fn: (input: I) => void
) { ): AsyncIterable<T> | AsyncIterableIterator<T> {
return yield* mapAsyncIterable(data, (x) => { return yield* mapAsyncIterable(data, (x) => {
fn(x); fn(x);
return x; return x;

View File

@ -1,6 +1,6 @@
import type { Tree } from 'nx/src/generators/tree'; import type { Tree } from 'nx/src/generators/tree';
import { readJson, writeJson } from 'nx/src/generators/utils/json'; 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'; import { createTree } from 'nx/src/generators/testing-utils/create-tree';
describe('addDependenciesToPackageJson', () => { describe('addDependenciesToPackageJson', () => {
@ -310,3 +310,42 @@ describe('addDependenciesToPackageJson', () => {
expect(installTask).toBeDefined(); 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 { installPackagesTask } from '../tasks/install-packages-task';
import type { Tree } from 'nx/src/generators/tree'; import type { Tree } from 'nx/src/generators/tree';
import { GeneratorCallback } from 'nx/src/config/misc-interfaces'; 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 = { const NON_SEMVER_TAGS = {
'*': 2, '*': 2,
@ -274,3 +276,73 @@ function requiresRemovingOfPackages(
return needsDepsUpdate || needsDevDepsUpdate; 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 { EsBuildExecutorOptions } from './schema';
import { removeSync, writeJsonSync } from 'fs-extra'; 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 { buildEsbuildOptions } from './lib/build-esbuild-options';
import { getExtraDependencies } from './lib/get-extra-dependencies'; import { getExtraDependencies } from './lib/get-extra-dependencies';
import { DependentBuildableProjectNode } from '@nrwl/workspace/src/utilities/buildable-libs-utils'; import { DependentBuildableProjectNode } from '@nrwl/workspace/src/utilities/buildable-libs-utils';

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { cacheDir, ExecutorContext, logger } from '@nrwl/devkit'; import { cacheDir, ExecutorContext, logger } from '@nrwl/devkit';
import { exec, execSync } from 'child_process'; import { exec, execSync } from 'child_process';
import { removeSync } from 'fs-extra'; 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 { NormalizedSwcExecutorOptions, SwcCliOptions } from '../schema';
import { printDiagnostics } from '../typescript/print-diagnostics'; import { printDiagnostics } from '../typescript/print-diagnostics';
import { runTypeCheck, TypeCheckOptions } from '../typescript/run-type-check'; import { runTypeCheck, TypeCheckOptions } from '../typescript/run-type-check';

View File

@ -4,7 +4,7 @@ import {
TypeScriptCompilationOptions, TypeScriptCompilationOptions,
} from '@nrwl/workspace/src/utilities/typescript/compilation'; } from '@nrwl/workspace/src/utilities/typescript/compilation';
import type { Diagnostic } from 'typescript'; 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'; import { NormalizedExecutorOptions } from '../schema';
const TYPESCRIPT_FOUND_N_ERRORS_WATCHING_FOR_FILE_CHANGES = 6194; const TYPESCRIPT_FOUND_N_ERRORS_WATCHING_FOR_FILE_CHANGES = 6194;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -71,6 +71,12 @@
"version": "15.3.0-beta.0", "version": "15.3.0-beta.0",
"description": "Update projects using @nrwl/web:rollup to @nrwl/rollup:rollup for build.", "description": "Update projects using @nrwl/web:rollup to @nrwl/rollup:rollup for build.",
"factory": "./src/migrations/update-15-3-0/update-rollup-executor" "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": { "packageJsonUpdates": {

View File

@ -31,32 +31,12 @@
"migrations": "./migrations.json" "migrations": "./migrations.json"
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@nrwl/cypress": "file:../cypress",
"@nrwl/devkit": "file:../devkit", "@nrwl/devkit": "file:../devkit",
"@nrwl/jest": "file:../jest",
"@nrwl/js": "file:../js",
"@nrwl/linter": "file:../linter", "@nrwl/linter": "file:../linter",
"@nrwl/storybook": "file:../storybook",
"@nrwl/vite": "file:../vite",
"@nrwl/web": "file:../web",
"@nrwl/webpack": "file:../webpack",
"@nrwl/workspace": "file:../workspace", "@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", "chalk": "4.1.0",
"css-loader": "^6.4.0",
"minimatch": "3.0.5", "minimatch": "3.0.5",
"react-refresh": "^0.10.0", "semver": "7.3.4"
"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"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "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 { WebDevServerOptions } from '@nrwl/webpack/src/executors/dev-server/schema';
import { join } from 'path'; import { join } from 'path';
import * as chalk from 'chalk'; import * as chalk from 'chalk';
import { combineAsyncIterableIterators } from '@nrwl/js/src/utils/async-iterable/combine-async-iteratable-iterators'; import {
import { tapAsyncIterator } from '@nrwl/js/src/utils/async-iterable/tap-async-iteratable'; combineAsyncIterableIterators,
tapAsyncIterable,
} from '@nrwl/devkit/src/utils/async-iterable';
type ModuleFederationDevServerOptions = WebDevServerOptions & { type ModuleFederationDevServerOptions = WebDevServerOptions & {
devRemotes?: string | string[]; devRemotes?: string | string[];
@ -65,7 +67,7 @@ export default async function* moduleFederationDevServer(
} }
let numAwaiting = knownRemotes.length + 1; // remotes + host let numAwaiting = knownRemotes.length + 1; // remotes + host
return yield* tapAsyncIterator(iter, (x) => { return yield* tapAsyncIterable(iter, (x) => {
numAwaiting--; numAwaiting--;
if (numAwaiting === 0) { if (numAwaiting === 0) {
logger.info( logger.info(

View File

@ -15,6 +15,7 @@ import { addStyledModuleDependencies } from '../../rules/add-styled-dependencies
import { import {
addDependenciesToPackageJson, addDependenciesToPackageJson,
convertNxGenerator, convertNxGenerator,
ensurePackage,
formatFiles, formatFiles,
GeneratorCallback, GeneratorCallback,
joinPathFragments, joinPathFragments,
@ -24,10 +25,12 @@ import {
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import reactInitGenerator from '../init/init'; import reactInitGenerator from '../init/init';
import { Linter, 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';
import { viteConfigurationGenerator, vitestGenerator } from '@nrwl/vite';
import { mapLintPattern } from '@nrwl/linter/src/generators/lint-project/lint-project'; 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) { async function addLinting(host: Tree, options: NormalizedSchema) {
const tasks: GeneratorCallback[] = []; const tasks: GeneratorCallback[] = [];
@ -90,6 +93,8 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
addProject(host, options); addProject(host, options);
if (options.bundler === 'vite') { 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. // 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 // See: https://vitejs.dev/guide/env-and-mode.html
host.delete(joinPathFragments(options.appProjectRoot, 'src/environments')); host.delete(joinPathFragments(options.appProjectRoot, 'src/environments'));
@ -101,9 +106,20 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
includeVitest: true, includeVitest: true,
}); });
tasks.push(viteTask); 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') { if (options.bundler !== 'vite' && options.unitTestRunner === 'vitest') {
await ensurePackage(host, '@nrwl/vite', nxVersion);
const { vitestGenerator } = await import('@nrwl/vite');
const vitestTask = await vitestGenerator(host, { const vitestTask = await vitestGenerator(host, {
uiFramework: 'react', uiFramework: 'react',
project: options.projectName, project: options.projectName,

View File

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

View File

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

View File

@ -26,7 +26,7 @@ describe(componentTestGenerator.name, () => {
component: true, component: true,
}); });
componentTestGenerator(tree, { await componentTestGenerator(tree, {
project: 'some-lib', project: 'some-lib',
componentPath: 'lib/some-lib.tsx', componentPath: 'lib/some-lib.tsx',
}); });
@ -47,7 +47,7 @@ describe(componentTestGenerator.name, () => {
js: true, js: true,
}); });
componentTestGenerator(tree, { await componentTestGenerator(tree, {
project: 'some-lib', project: 'some-lib',
componentPath: 'lib/some-lib.js', componentPath: 'lib/some-lib.js',
}); });
@ -67,7 +67,7 @@ describe(componentTestGenerator.name, () => {
component: true, component: true,
}); });
tree.write('libs/some-lib/src/lib/some-lib.cy.tsx', 'existing content'); tree.write('libs/some-lib/src/lib/some-lib.cy.tsx', 'existing content');
componentTestGenerator(tree, { await componentTestGenerator(tree, {
project: 'some-lib', project: 'some-lib',
componentPath: 'lib/some-lib.tsx', componentPath: 'lib/some-lib.tsx',
}); });
@ -89,12 +89,12 @@ describe(componentTestGenerator.name, () => {
component: true, component: true,
}); });
expect(() => { await expect(
componentTestGenerator(tree, { componentTestGenerator(tree, {
project: 'some-lib', project: 'some-lib',
componentPath: 'lib/blah/abc-123.blah', componentPath: 'lib/blah/abc-123.blah',
}); })
}).not.toThrow(); ).resolves.not.toThrow();
}); });
it('should handle being provided the full path to the component', async () => { it('should handle being provided the full path to the component', async () => {
@ -109,7 +109,7 @@ describe(componentTestGenerator.name, () => {
component: true, component: true,
}); });
componentTestGenerator(tree, { await componentTestGenerator(tree, {
project: 'some-lib', project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx', componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
}); });
@ -144,11 +144,11 @@ export interface AnotherCmpProps {
} }
export function AnotherCmp(props: 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', project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx', componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
}); });
@ -180,7 +180,7 @@ export function AnotherCmp() {
} }
` `
); );
componentTestGenerator(tree, { await componentTestGenerator(tree, {
project: 'some-lib', project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx', componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
}); });
@ -214,7 +214,7 @@ export interface AnotherCmpProps {
} }
export default function AnotherCmp(props: AnotherCmpProps) { export default function AnotherCmp(props: AnotherCmpProps) {
return <button onClick="{handleClick}">{props.text}</button>; return <button onClick='{handleClick}'>{props.text}</button>;
} }
export function AnotherCmp2() { export function AnotherCmp2() {
@ -222,7 +222,7 @@ export function AnotherCmp2() {
} }
` `
); );
componentTestGenerator(tree, { await componentTestGenerator(tree, {
project: 'some-lib', project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx', componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
}); });
@ -257,7 +257,7 @@ export interface AnotherCmpProps {
} }
export function AnotherCmp(props: AnotherCmpProps) { export function AnotherCmp(props: AnotherCmpProps) {
return <button onClick="{handleClick}">{props.text}</button>; return <button onClick='{handleClick}'>{props.text}</button>;
} }
export function AnotherCmp2() { export function AnotherCmp2() {
@ -265,7 +265,7 @@ export function AnotherCmp2() {
} }
` `
); );
componentTestGenerator(tree, { await componentTestGenerator(tree, {
project: 'some-lib', project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx', componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
}); });
@ -302,11 +302,11 @@ export interface AnotherCmpProps {
} }
export function AnotherCmp(props: 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', project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx', componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
}); });
@ -328,7 +328,7 @@ export function AnotherCmp(props: AnotherCmpProps) {
unitTestRunner: 'none', unitTestRunner: 'none',
component: true, component: true,
}); });
componentTestGenerator(tree, { await componentTestGenerator(tree, {
project: 'some-lib', project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx', componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
}); });
@ -362,11 +362,11 @@ export interface AnotherCmpProps {
} }
export default function AnotherCmp(props: 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', project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx', componentPath: 'libs/some-lib/src/lib/some-lib.tsx',
}); });
@ -400,11 +400,11 @@ export interface AnotherCmpProps {
} }
export function AnotherCmp(props: 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', project: 'some-lib',
componentPath: 'libs/some-lib/src/lib/some-lib.tsx', 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 { import {
ensurePackage,
generateFiles, generateFiles,
joinPathFragments, joinPathFragments,
readProjectConfiguration, readProjectConfiguration,
@ -12,12 +12,17 @@ import {
getComponentNode, getComponentNode,
} from '../../utils/ast-utils'; } from '../../utils/ast-utils';
import { getDefaultsForComponent } from '../../utils/component-props'; import { getDefaultsForComponent } from '../../utils/component-props';
import { nxVersion } from '../../utils/versions';
import { ComponentTestSchema } from './schema'; import { ComponentTestSchema } from './schema';
export function componentTestGenerator( export async function componentTestGenerator(
tree: Tree, tree: Tree,
options: ComponentTestSchema options: ComponentTestSchema
) { ) {
await ensurePackage(tree, '@nrwl/cypress', nxVersion);
const { assertMinimumCypressVersion } = await import(
'@nrwl/cypress/src/utils/cypress-version'
);
assertMinimumCypressVersion(10); assertMinimumCypressVersion(10);
const projectConfig = readProjectConfiguration(tree, options.project); const projectConfig = readProjectConfiguration(tree, options.project);

View File

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

View File

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

View File

@ -1,10 +1,6 @@
import { findBuildConfig } from '@nrwl/cypress/src/utils/find-target-options';
import { import {
joinPathFragments,
ProjectConfiguration,
readProjectConfiguration, readProjectConfiguration,
Tree, Tree,
updateJson,
updateProjectConfiguration, updateProjectConfiguration,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { CypressComponentConfigurationSchema } from '../schema'; import { CypressComponentConfigurationSchema } from '../schema';
@ -13,6 +9,9 @@ export async function updateProjectConfig(
tree: Tree, tree: Tree,
options: CypressComponentConfigurationSchema options: CypressComponentConfigurationSchema
) { ) {
const { findBuildConfig } = await import(
'@nrwl/cypress/src/utils/find-target-options'
);
const found = await findBuildConfig(tree, { const found = await findBuildConfig(tree, {
project: options.project, project: options.project,
buildTarget: options.buildTarget, 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 { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import reactInitGenerator from './init'; import reactInitGenerator from './init';
import { InitSchema } from './schema'; import { InitSchema } from './schema';
@ -30,4 +30,9 @@ describe('init', () => {
await reactInitGenerator(tree, { ...schema, unitTestRunner: 'none' }); await reactInitGenerator(tree, { ...schema, unitTestRunner: 'none' });
expect(tree.exists('jest.config.js')).toEqual(false); 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 { import {
addDependenciesToPackageJson, addDependenciesToPackageJson,
convertNxGenerator, convertNxGenerator,
ensurePackage,
GeneratorCallback, GeneratorCallback,
readWorkspaceConfiguration, readWorkspaceConfiguration,
removeDependenciesFromPackageJson, removeDependenciesFromPackageJson,
Tree, Tree,
updateWorkspaceConfiguration, updateWorkspaceConfiguration,
writeJson,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { webInitGenerator } from '@nrwl/web';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { import {
babelPresetReactVersion,
nxVersion, nxVersion,
reactDomVersion, reactDomVersion,
reactTestRendererVersion, 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) { export async function reactInitGenerator(host: Tree, schema: InitSchema) {
const tasks: GeneratorCallback[] = []; const tasks: GeneratorCallback[] = [];
setDefault(host); setDefault(host);
if (!schema.e2eTestRunner || schema.e2eTestRunner === 'cypress') { if (!schema.e2eTestRunner || schema.e2eTestRunner === 'cypress') {
await ensurePackage(host, '@nrwl/cypress', nxVersion);
const { cypressInitGenerator } = await import('@nrwl/cypress');
const cypressTask = cypressInitGenerator(host, {}); const cypressTask = cypressInitGenerator(host, {});
tasks.push(cypressTask); tasks.push(cypressTask);
} }
// TODO(jack): We should be able to remove this generator and have react init everything. if (!schema.skipPackageJson && !schema.skipBabelConfig) {
const initTask = await webInitGenerator(host, { const installBabelTask = addDependenciesToPackageJson(
...schema, host,
skipPackageJson: true, {},
}); {
tasks.push(initTask); '@babel/preset-react': babelPresetReactVersion,
}
);
tasks.push(installBabelTask);
}
if (!schema.skipBabelConfig) {
initRootBabelConfig(host, schema);
}
if (!schema.skipPackageJson) { if (!schema.skipPackageJson) {
const installTask = updateDependencies(host, schema); const installTask = updateDependencies(host, schema);
tasks.push(installTask); 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, Tree,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { assertValidStyle } from '../../../utils/assertion'; import { assertValidStyle } from '../../../utils/assertion';
import { NormalizedSchema } from '../library'; import { NormalizedSchema, Schema } from '../schema';
import { Schema } from '../schema';
export function normalizeOptions( export function normalizeOptions(
host: Tree, host: Tree,
@ -40,7 +39,9 @@ export function normalizeOptions(
const normalized = { const normalized = {
...options, ...options,
compiler: options.compiler ?? 'babel', compiler: options.compiler ?? 'babel',
bundler: options.bundler ?? 'none', bundler:
options.bundler ??
(options.buildable || options.publishable ? 'rollup' : 'none'),
fileName, fileName,
routePath: `/${name}`, routePath: `/${name}`,
name: projectName, 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, createTreeWithEmptyWorkspace,
} from '@nrwl/devkit/testing'; } from '@nrwl/devkit/testing';
import { Linter } from '@nrwl/linter'; import { Linter } from '@nrwl/linter';
import { nxVersion } from '../../utils/versions';
import applicationGenerator from '../application/application'; import applicationGenerator from '../application/application';
import libraryGenerator from './library'; import libraryGenerator from './library';
import { Schema } from './schema'; import { Schema } from './schema';
@ -37,6 +38,16 @@ describe('lib', () => {
beforeEach(() => { beforeEach(() => {
mockedInstalledCypressVersion.mockReturnValue(10); mockedInstalledCypressVersion.mockReturnValue(10);
tree = createTreeWithEmptyV1Workspace(); 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', () => { describe('not nested', () => {
@ -726,7 +737,6 @@ describe('lib', () => {
describe('--skipPackageJson', () => { describe('--skipPackageJson', () => {
it('should not add dependencies to package.json when true', async () => { it('should not add dependencies to package.json when true', async () => {
// ARRANGE // ARRANGE
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
const packageJsonBeforeGenerator = tree.read('package.json', 'utf-8'); const packageJsonBeforeGenerator = tree.read('package.json', 'utf-8');
// ACT // ACT
await libraryGenerator(tree, { await libraryGenerator(tree, {

View File

@ -1,64 +1,32 @@
import { import {
addDependenciesToPackageJson, addDependenciesToPackageJson,
addProjectConfiguration, addProjectConfiguration,
applyChangesToString,
convertNxGenerator, convertNxGenerator,
ensurePackage,
formatFiles, formatFiles,
generateFiles,
GeneratorCallback, GeneratorCallback,
getWorkspaceLayout,
joinPathFragments, joinPathFragments,
names,
offsetFromRoot,
toJS,
Tree, Tree,
updateJson, updateJson,
} from '@nrwl/devkit'; } 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 { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { import {
getRelativePathToRootTsConfig, nxVersion,
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 {
reactDomVersion, reactDomVersion,
reactRouterDomVersion,
reactVersion, reactVersion,
typesReactRouterDomVersion, swcCoreVersion,
} from '../../utils/versions'; } from '../../utils/versions';
import componentGenerator from '../component/component'; import componentGenerator from '../component/component';
import initGenerator from '../init/init'; import initGenerator from '../init/init';
import { Schema } from './schema'; import { Schema } from './schema';
import { updateJestConfigContent } from '../../utils/jest-utils'; import { updateJestConfigContent } from '../../utils/jest-utils';
import { viteConfigurationGenerator, vitestGenerator } from '@nrwl/vite';
import { normalizeOptions } from './lib/normalize-options'; import { normalizeOptions } from './lib/normalize-options';
import { addRollupBuildTarget } from './lib/add-rollup-build-target';
export interface NormalizedSchema extends Schema { import { addLinting } from './lib/add-linting';
name: string; import { updateAppRoutes } from './lib/update-app-routes';
fileName: string; import { createFiles } from './lib/create-files';
projectRoot: string; import { updateBaseTsConfig } from './lib/update-base-tsconfig';
routePath: string;
projectDirectory: string;
parsedTags: string[];
appMain?: string;
appSourceRoot?: string;
libsDir?: string;
unitTestRunner: 'jest' | 'vitest' | 'none';
}
export async function libraryGenerator(host: Tree, schema: Schema) { export async function libraryGenerator(host: Tree, schema: Schema) {
const tasks: GeneratorCallback[] = []; const tasks: GeneratorCallback[] = [];
@ -82,7 +50,18 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
}); });
tasks.push(initTask); 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); const lintTask = await addLinting(host, options);
tasks.push(lintTask); tasks.push(lintTask);
@ -93,7 +72,10 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
updateBaseTsConfig(host, options); updateBaseTsConfig(host, options);
} }
// Set up build target
if (options.buildable && options.bundler === 'vite') { if (options.buildable && options.bundler === 'vite') {
await ensurePackage(host, '@nrwl/vite', nxVersion);
const { viteConfigurationGenerator } = await import('@nrwl/vite');
const viteTask = await viteConfigurationGenerator(host, { const viteTask = await viteConfigurationGenerator(host, {
uiFramework: 'react', uiFramework: 'react',
project: options.name, project: options.name,
@ -103,9 +85,16 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
includeVitest: true, includeVitest: true,
}); });
tasks.push(viteTask); 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') { if (options.unitTestRunner === 'jest') {
await ensurePackage(host, '@nrwl/jest', nxVersion);
const { jestProjectGenerator } = await import('@nrwl/jest');
const jestTask = await jestProjectGenerator(host, { const jestTask = await jestProjectGenerator(host, {
...options, ...options,
project: options.name, project: options.name,
@ -129,6 +118,8 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
options.unitTestRunner === 'vitest' && options.unitTestRunner === 'vitest' &&
options.bundler !== 'vite' // tests are already configured if bundler is vite 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, { const vitestTask = await vitestGenerator(host, {
uiFramework: 'react', uiFramework: 'react',
project: options.name, project: options.name,
@ -155,11 +146,14 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
} }
if (options.publishable || options.buildable) { if (options.publishable || options.buildable) {
updateLibPackageNpmScope(host, options); updateJson(host, `${options.projectRoot}/package.json`, (json) => {
json.name = options.importPath;
return json;
});
} }
if (!options.skipPackageJson) { if (!options.skipPackageJson) {
const installTask = await addDependenciesToPackageJson( const installReactTask = await addDependenciesToPackageJson(
host, host,
{ {
react: reactVersion, react: reactVersion,
@ -167,7 +161,7 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
}, },
options.compiler === 'swc' ? { '@swc/core': swcCoreVersion } : {} options.compiler === 'swc' ? { '@swc/core': swcCoreVersion } : {}
); );
tasks.push(installTask); tasks.push(installReactTask);
} }
const routeTask = updateAppRoutes(host, options); const routeTask = updateAppRoutes(host, options);
@ -180,281 +174,5 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
return runTasksInSerial(...tasks); 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 default libraryGenerator;
export const librarySchematic = convertNxGenerator(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'; import { SupportedStyles } from '../../../typings/style';
export interface Schema { export interface Schema {
@ -28,3 +28,17 @@ export interface Schema {
unitTestRunner?: 'jest' | 'vitest' | 'none'; unitTestRunner?: 'jest' | 'vitest' | 'none';
minimal?: boolean; 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, visitNotIgnoredFiles,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { basename, join } from 'path'; import { basename, join } from 'path';
import {
findStorybookAndBuildTargetsAndCompiler,
isTheFileAStory,
} from '@nrwl/storybook/src/utils/utilities';
import minimatch = require('minimatch'); import minimatch = require('minimatch');
export interface StorybookStoriesSchema { export interface StorybookStoriesSchema {
@ -29,7 +25,13 @@ export interface StorybookStoriesSchema {
ignorePaths?: string[]; 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; let projectDir: string;
if (config.projectType === 'application') { if (config.projectType === 'application') {
const { nextBuildTarget } = findStorybookAndBuildTargetsAndCompiler( const { nextBuildTarget } = findStorybookAndBuildTargetsAndCompiler(
@ -79,12 +81,16 @@ export async function createAllStories(
cypressProject?: string, cypressProject?: string,
ignorePaths?: string[] ignorePaths?: string[]
) { ) {
const { isTheFileAStory } = await import(
'@nrwl/storybook/src/utils/utilities'
);
const projects = getProjects(tree); const projects = getProjects(tree);
const projectConfiguration = projects.get(projectName); const projectConfiguration = projects.get(projectName);
const { sourceRoot, root } = projectConfiguration; const { sourceRoot, root } = projectConfiguration;
let componentPaths: string[] = []; let componentPaths: string[] = [];
visitNotIgnoredFiles(tree, projectRootPath(projectConfiguration), (path) => { const projectPath = await projectRootPath(tree, projectConfiguration);
visitNotIgnoredFiles(tree, projectPath, (path) => {
// Ignore private files starting with "_". // Ignore private files starting with "_".
if (basename(path).startsWith('_')) return; if (basename(path).startsWith('_')) return;

View File

@ -2,14 +2,18 @@ import { StorybookConfigureSchema } from './schema';
import storiesGenerator from '../stories/stories'; import storiesGenerator from '../stories/stories';
import { import {
convertNxGenerator, convertNxGenerator,
ensurePackage,
logger, logger,
readProjectConfiguration, readProjectConfiguration,
Tree, Tree,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { configurationGenerator } from '@nrwl/storybook'; import { nxVersion } from '../../utils/versions';
import { getE2eProjectName } from '@nrwl/cypress/src/utils/project-name';
async function generateStories(host: Tree, schema: StorybookConfigureSchema) { 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 projectConfig = readProjectConfiguration(host, schema.name);
const cypressProject = getE2eProjectName( const cypressProject = getE2eProjectName(
schema.name, schema.name,
@ -30,6 +34,9 @@ export async function storybookConfigurationGenerator(
host: Tree, host: Tree,
schema: StorybookConfigureSchema schema: StorybookConfigureSchema
) { ) {
await ensurePackage(host, '@nrwl/storybook', nxVersion);
const { configurationGenerator } = await import('@nrwl/storybook');
let bundler = schema.bundler ?? 'webpack'; let bundler = schema.bundler ?? 'webpack';
const projectConfig = readProjectConfiguration(host, schema.name); 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, readProjectConfiguration,
Tree, Tree,
} from '@nrwl/devkit'; } 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 { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils';
import { tsquery } from '@phenomnomnominal/tsquery'; import { tsquery } from '@phenomnomnominal/tsquery';
import { StringLiteral } from 'typescript'; 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 reactVersion = '18.2.0';
export const reactDomVersion = '18.2.0'; export const reactDomVersion = '18.2.0';
export const reactIsVersion = '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 typesReactVersion = '18.0.25';
export const typesReactDomVersion = '18.0.9'; export const typesReactDomVersion = '18.0.9';
export const typesReactIsVersion = '17.0.3'; export const typesReactIsVersion = '17.0.3';
export const typesNodeVersion = '18.11.9'; export const typesNodeVersion = '18.11.9';
export const babelPresetReactVersion = '^7.14.5';
export const styledComponentsVersion = '5.3.6'; export const styledComponentsVersion = '5.3.6';
export const typesStyledComponentsVersion = '5.1.26'; 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/schema';
export * from './src/executors/rollup/rollup.impl'; export * from './src/executors/rollup/rollup.impl';

View File

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

View File

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

View File

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

View File

@ -5,24 +5,31 @@ import {
GeneratorCallback, GeneratorCallback,
Tree, Tree,
} from '@nrwl/devkit'; } 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 { swcCoreVersion } from '@nrwl/js/src/utils/versions';
import { Schema } from './schema';
import { import {
reactRefreshVersion,
reactRefreshWebpackPluginVersion,
svgrWebpackVersion,
swcHelpersVersion, swcHelpersVersion,
swcLoaderVersion, swcLoaderVersion,
tsLibVersion, tsLibVersion,
tsQueryVersion,
urlLoaderVersion,
} from '../../utils/versions'; } from '../../utils/versions';
import { addBabelInputs } from '@nrwl/js/src/utils/add-babel-inputs'; import { addBabelInputs } from '@nrwl/js/src/utils/add-babel-inputs';
export async function webpackInitGenerator(tree: Tree, schema: Schema) { export async function webpackInitGenerator(tree: Tree, schema: Schema) {
let task: GeneratorCallback; const tasks: GeneratorCallback[] = [];
if (schema.compiler === 'babel') { if (schema.compiler === 'babel') {
addBabelInputs(tree); addBabelInputs(tree);
} }
if (schema.compiler === 'swc') { if (schema.compiler === 'swc') {
task = addDependenciesToPackageJson( const swcInstallTask = addDependenciesToPackageJson(
tree, tree,
{}, {},
{ {
@ -31,17 +38,39 @@ export async function webpackInitGenerator(tree: Tree, schema: Schema) {
'swc-loader': swcLoaderVersion, 'swc-loader': swcLoaderVersion,
} }
); );
tasks.push(swcInstallTask);
} }
if (schema.compiler === 'tsc') { 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) { if (!schema.skipFormat) {
await formatFiles(tree); await formatFiles(tree);
} }
return task; return runTasksInSerial(...tasks);
} }
export default webpackInitGenerator; export default webpackInitGenerator;

View File

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

View File

@ -6,6 +6,12 @@
"description": "Init Webpack Plugin.", "description": "Init Webpack Plugin.",
"type": "object", "type": "object",
"properties": { "properties": {
"uiFramework": {
"type": "string",
"description": "UI Framework to use for Vite.",
"enum": ["react", "none"],
"x-prompt": "What UI framework plugin should Webpack use?"
},
"compiler": { "compiler": {
"type": "string", "type": "string",
"enum": ["babel", "swc", "tsc"], "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 swcLoaderVersion = '0.1.15';
export const swcHelpersVersion = '~0.4.11'; export const swcHelpersVersion = '~0.4.11';
export const tsLibVersion = '^2.3.0'; 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/devkit"
}, },
{
"command": "node ./scripts/add-dependency-to-build.js workspace @nrwl/jest"
},
{ {
"command": "node ./scripts/add-dependency-to-build.js workspace @nrwl/linter" "command": "node ./scripts/add-dependency-to-build.js workspace @nrwl/linter"
} }

View File

@ -1,19 +1,20 @@
import { import {
Tree, addDependenciesToPackageJson,
addProjectConfiguration,
convertNxGenerator, convertNxGenerator,
ensurePackage,
extractLayoutDirectory,
formatFiles,
generateFiles,
GeneratorCallback,
getWorkspaceLayout,
joinPathFragments,
names, names,
offsetFromRoot, offsetFromRoot,
generateFiles,
toJS,
getWorkspaceLayout,
addProjectConfiguration,
formatFiles,
updateJson,
GeneratorCallback,
joinPathFragments,
ProjectConfiguration, ProjectConfiguration,
addDependenciesToPackageJson, toJS,
extractLayoutDirectory, Tree,
updateJson,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { getImportPath } from 'nx/src/utils/path'; import { getImportPath } from 'nx/src/utils/path';
import { join } from 'path'; import { join } from 'path';
@ -25,11 +26,6 @@ import {
import { nxVersion } from '../../utils/versions'; import { nxVersion } from '../../utils/versions';
import { Schema } from './schema'; 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 { export interface NormalizedSchema extends Schema {
name: string; name: string;
fileName: string; fileName: string;
@ -74,10 +70,12 @@ function addProject(tree: Tree, options: NormalizedSchema) {
); );
} }
export function addLint( export async function addLint(
tree: Tree, tree: Tree,
options: NormalizedSchema options: NormalizedSchema
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
await ensurePackage(tree, '@nrwl/linter', nxVersion);
const { lintProjectGenerator } = require('@nrwl/linter');
return lintProjectGenerator(tree, { return lintProjectGenerator(tree, {
project: options.name, project: options.name,
linter: options.linter, linter: options.linter,
@ -178,6 +176,8 @@ async function addJest(
tree: Tree, tree: Tree,
options: NormalizedSchema options: NormalizedSchema
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
await ensurePackage(tree, '@nrwl/jest', nxVersion);
const { jestProjectGenerator } = require('@nrwl/jest');
return await jestProjectGenerator(tree, { return await jestProjectGenerator(tree, {
...options, ...options,
project: options.name, project: options.name,
@ -232,7 +232,7 @@ function normalizeOptions(tree: Tree, options: Schema): NormalizedSchema {
} }
if (!options.linter) { if (!options.linter) {
options.linter = Linter.EsLint; options.linter = 'eslint';
} }
const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); 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 // Ignore packages that are defined here per package
const IGNORE_MATCHES_IN_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: [
'@angular-devkit/architect', '@angular-devkit/architect',
'@angular-devkit/build-angular', '@angular-devkit/build-angular',
@ -68,26 +82,30 @@ const IGNORE_MATCHES_IN_PACKAGE = {
'webpack', 'webpack',
], ],
react: [ react: [
'babel-plugin-emotion', // These are brought in by the webpack, rollup, or vite packages via init generators.
'babel-plugin-styled-components', '@babel/preset-react',
'rollup', '@module-federation/node',
'webpack', '@phenomnomnominal/tsquery',
'@pmmmwh/react-refresh-webpack-plugin',
'@svgr/webpack',
'@swc/jest', '@swc/jest',
'babel-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-loader',
'babel-plugin-emotion',
'babel-plugin-styled-components',
'css-loader', 'css-loader',
'less-loader', 'less-loader',
'react-refresh',
'rollup',
'sass', 'sass',
'sass-loader', 'sass-loader',
'style-loader', 'style-loader',
'stylus-loader', 'stylus-loader',
'swc-loader', 'swc-loader',
'tsconfig-paths-webpack-plugin', 'tsconfig-paths-webpack-plugin',
'@module-federation/node', 'url-loader',
'webpack',
'webpack-merge',
], ],
rollup: ['@swc/core'], rollup: ['@swc/core'],
storybook: [ storybook: [