feat(js): generate experimental simplified library with ts solution setup (#27910)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
This commit is contained in:
Leosvel Pérez Espinosa 2024-09-27 20:14:19 +02:00 committed by GitHub
parent 5724debed6
commit 49c5a73cd0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
80 changed files with 3002 additions and 1469 deletions

View File

@ -1,6 +1,6 @@
{
"name": "init",
"factory": "./src/generators/init/init#initGenerator",
"factory": "./src/generators/init/init#initGeneratorInternal",
"schema": {
"$schema": "https://json-schema.org/schema",
"$id": "NxTypescriptInit",
@ -8,6 +8,12 @@
"title": "Init nx/js",
"description": "Init generator placeholder for nx/js.",
"properties": {
"formatter": {
"description": "The tool to use for code formatting.",
"type": "string",
"enum": ["none", "prettier"],
"default": "none"
},
"js": {
"type": "boolean",
"default": false,
@ -40,12 +46,6 @@
"type": "string",
"description": "Customize the generated base tsconfig file name.",
"x-priority": "internal"
},
"setUpPrettier": {
"type": "boolean",
"description": "Add Prettier and corresponding configuration files.",
"x-priority": "internal",
"default": false
}
},
"presets": []
@ -54,7 +54,7 @@
"x-type": "init",
"description": "Initialize a TS/JS workspace.",
"hidden": true,
"implementation": "/packages/js/src/generators/init/init#initGenerator.ts",
"implementation": "/packages/js/src/generators/init/init#initGeneratorInternal.ts",
"path": "/packages/js/src/generators/init/schema.json",
"type": "generator"
}

View File

@ -21,22 +21,28 @@
"description": "A directory where the lib is placed.",
"x-priority": "important"
},
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"bundler": {
"description": "The bundler to use. Choosing 'none' means this library is not buildable.",
"type": "string",
"enum": ["as-provided", "derived"]
"enum": ["swc", "tsc", "rollup", "vite", "esbuild", "none"],
"x-priority": "important"
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "none"],
"default": "eslint"
"enum": ["none", "eslint"],
"x-priority": "important"
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "vitest", "none"],
"description": "Test runner to use for unit tests.",
"x-prompt": "Which unit test runner would you like to use?"
"type": "string",
"enum": ["none", "jest", "vitest"],
"x-priority": "important"
},
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"tags": {
"type": "string",
@ -112,18 +118,9 @@
"compiler": {
"type": "string",
"enum": ["tsc", "swc"],
"default": "tsc",
"description": "The compiler used by the build and test targets",
"x-deprecated": "Use the `bundler` option for greater control (swc, tsc, rollup, vite, esbuild, none)."
},
"bundler": {
"description": "The bundler to use. Choosing 'none' means this library is not buildable.",
"type": "string",
"enum": ["swc", "tsc", "rollup", "vite", "esbuild", "none"],
"default": "tsc",
"x-prompt": "Which bundler would you like to use to build the library? Choose 'none' to skip build setup.",
"x-priority": "important"
},
"skipTypeCheck": {
"type": "boolean",
"description": "Whether to skip TypeScript type checking for SWC compiler.",
@ -138,6 +135,10 @@
"description": "Don't include the directory in the generated file name.",
"type": "boolean",
"default": false
},
"useProjectJson": {
"type": "boolean",
"description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file."
}
},
"required": ["name"],

View File

@ -83,6 +83,12 @@
"prefix": {
"description": "The prefix to use for Angular component and directive selectors.",
"type": "string"
},
"formatter": {
"description": "The tool to use for code formatting.",
"type": "string",
"enum": ["none", "prettier"],
"default": "none"
}
},
"additionalProperties": true,

View File

@ -33,7 +33,7 @@ describe('Linter (legacy)', () => {
env: { NX_ADD_PLUGINS: 'false' },
}
);
runCLI(`generate @nx/js:lib ${mylib} --directory=apps/${mylib}`, {
runCLI(`generate @nx/js:lib ${mylib} --directory=libs/${mylib}`, {
env: { NX_ADD_PLUGINS: 'false' },
});
});

View File

@ -37,9 +37,10 @@ interface BaseArguments extends CreateWorkspaceOptions {
interface NoneArguments extends BaseArguments {
stack: 'none';
workspaceType: 'package-based' | 'integrated' | 'standalone';
js: boolean;
appName: string | undefined;
workspaceType?: 'package-based' | 'integrated' | 'standalone';
js?: boolean;
appName?: string | undefined;
formatter?: 'none' | 'prettier';
}
interface ReactArguments extends BaseArguments {
@ -394,7 +395,11 @@ async function determineStack(
choices: [
{
name: `none`,
message: `None: Configures a TypeScript/JavaScript project with minimal structure.`,
message:
process.env.NX_ADD_PLUGINS !== 'false' &&
process.env.NX_ADD_TS_PLUGIN === 'true'
? `None: Configures a TypeScript/JavaScript monorepo.`
: `None: Configures a TypeScript/JavaScript project with minimal structure.`,
},
{
name: `react`,
@ -441,9 +446,38 @@ async function determinePresetOptions(
async function determineNoneOptions(
parsedArgs: yargs.Arguments<NoneArguments>
): Promise<Partial<NoneArguments>> {
if (
process.env.NX_ADD_PLUGINS !== 'false' &&
process.env.NX_ADD_TS_PLUGIN === 'true'
) {
const reply = await enquirer.prompt<{ prettier: 'Yes' | 'No' }>([
{
name: 'prettier',
message: `Would you like to use Prettier for code formatting?`,
type: 'autocomplete',
choices: [
{
name: 'Yes',
},
{
name: 'No',
},
],
initial: 1,
skip: !parsedArgs.interactive || isCI(),
},
]);
return {
preset: Preset.TS,
formatter: reply.prettier === 'Yes' ? 'prettier' : 'none',
};
} else {
let preset: Preset;
let workspaceType: 'package-based' | 'standalone' | 'integrated' | undefined =
undefined;
let workspaceType:
| 'package-based'
| 'standalone'
| 'integrated'
| undefined = undefined;
let appName: string | undefined = undefined;
let js: boolean | undefined;
@ -460,6 +494,10 @@ async function determineNoneOptions(
}
}
if (preset === Preset.TS) {
return { preset, formatter: 'prettier' };
}
if (parsedArgs.js !== undefined) {
js = parsedArgs.js;
} else if (preset === Preset.TsStandalone) {
@ -487,6 +525,7 @@ async function determineNoneOptions(
return { preset, js, appName };
}
}
async function determineReactOptions(
parsedArgs: yargs.Arguments<ReactArguments>

View File

@ -50,40 +50,47 @@ describe('@nx/eslint:lint-project', () => {
linter: Linter.EsLint,
project: 'test-lib',
setParserOptionsProject: false,
skipFormat: true,
});
expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(`
"const nx = require('@nx/eslint-plugin');
"const nx = require("@nx/eslint-plugin");
module.exports = [
...nx.configs['flat/base'],
...nx.configs['flat/typescript'],
...nx.configs['flat/javascript'],
...nx.configs["flat/base"],
...nx.configs["flat/typescript"],
...nx.configs["flat/javascript"],
{
ignores: ['**/dist'],
ignores: ["**/dist"]
},
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
rules: {
'@nx/enforce-module-boundaries': [
'error',
files: [
"**/*.ts",
"**/*.tsx",
"**/*.js",
"**/*.jsx"
],
rules: { "@nx/enforce-module-boundaries": [
"error",
{
enforceBuildableLibDependency: true,
allow: ['^.*/eslint(\\\\.base)?\\\\.config\\\\.[cm]?js$'],
depConstraints: [
{
sourceTag: '*',
onlyDependOnLibsWithTags: ['*'],
},
],
},
],
},
allow: ["^.*/eslint(\\\\.base)?\\\\.config\\\\.[cm]?js$"],
depConstraints: [{
sourceTag: "*",
onlyDependOnLibsWithTags: ["*"]
}]
}
] }
},
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
files: [
"**/*.ts",
"**/*.tsx",
"**/*.js",
"**/*.jsx"
],
// Override or add rules here
rules: {},
rules: {}
},
];
"

View File

@ -106,6 +106,7 @@ export async function lintProjectGeneratorInternal(
(p) => !['./src', '{projectRoot}', projectConfig.root].includes(p)
)
) {
projectConfig.targets ??= {};
projectConfig.targets['lint'] = {
command: `eslint ${lintFilePatterns
.join(' ')
@ -113,6 +114,7 @@ export async function lintProjectGeneratorInternal(
};
}
} else {
projectConfig.targets ??= {};
projectConfig.targets['lint'] = {
executor: '@nx/eslint:lint',
};

View File

@ -156,6 +156,7 @@ module.exports = [
module.exports = [
...compat.extends("plugin:playwright/recommend"),
...baseConfig,
{
files: [
@ -214,6 +215,7 @@ module.exports = [
module.exports = [
...fixupConfigRules(compat.extends("plugin:playwright/recommend")),
...baseConfig,
{
files: [
@ -276,10 +278,15 @@ module.exports = [
module.exports = [
...compat.extends("plugin:some-plugin1", "plugin:some-plugin2"),
...fixupConfigRules(compat.extends("incompatible-plugin1")),
...fixupConfigRules(compat.extends("incompatible-plugin2")),
...compat.extends("plugin:some-plugin3"),
...fixupConfigRules(compat.extends("incompatible-plugin3")),
...baseConfig,
{
files: [
@ -337,6 +344,7 @@ module.exports = [
module.exports = [
...compat.extends("plugin:playwright/recommend"),
...baseConfig,
{
files: [

View File

@ -213,6 +213,7 @@ describe('ast-utils', () => {
"const baseConfig = require("../../eslint.config.js");
module.exports = [
...config,
...baseConfig,
{
files: [

View File

@ -175,12 +175,17 @@ export function replaceOverride(
changes.push({
type: ChangeType.Insert,
index: start,
text: JSON.stringify(updatedData, null, 2)
// NOTE: Indentation added to format without formatting tools like Prettier.
text:
' ' +
JSON.stringify(updatedData, null, 2)
// restore any parser require calls that were stripped during JSON parsing
.replace(/"parser": "([^"]+)"/g, (_, parser) => {
return `"parser": require('${parser}')`;
})
.slice(2, -2), // remove curly braces and start/end line breaks since we are injecting just properties
.slice(2, -2) // remove curly braces and start/end line breaks since we are injecting just properties
// Append indentation so file is formatted without Prettier
.replaceAll(/\n/g, '\n '),
});
}
}
@ -394,7 +399,11 @@ export function addBlockToFlatConfigExport(
// base config was not generated by Nx.
if (!exportsArray) return content;
const insert = printer.printNode(ts.EmitHint.Expression, config, source);
const insert =
' ' +
printer
.printNode(ts.EmitHint.Expression, config, source)
.replaceAll(/\n/g, '\n ');
if (options.insertAtTheEnd) {
const index =
exportsArray.length > 0
@ -414,7 +423,7 @@ export function addBlockToFlatConfigExport(
{
type: ChangeType.Insert,
index,
text: `\n${insert},`,
text: `\n${insert},\n`,
},
]);
}

View File

@ -1,3 +1,19 @@
import {
formatFiles,
GeneratorCallback,
output,
readJson,
readNxJson,
readProjectConfiguration,
runTasksInSerial,
Tree,
} from '@nx/devkit';
import {
getRootTsConfigFileName,
initGenerator as jsInitGenerator,
} from '@nx/js';
import { JestPluginOptions } from '../../plugins/plugin';
import { getPresetExt } from '../../utils/config/config-file';
import { jestInitGenerator } from '../init/init';
import { checkForTestTarget } from './lib/check-for-test-target';
import { createFiles } from './lib/create-files';
@ -7,17 +23,6 @@ import { updateTsConfig } from './lib/update-tsconfig';
import { updateVsCodeRecommendedExtensions } from './lib/update-vscode-recommended-extensions';
import { updateWorkspace } from './lib/update-workspace';
import { JestProjectSchema, NormalizedJestProjectSchema } from './schema';
import {
formatFiles,
Tree,
GeneratorCallback,
readProjectConfiguration,
readNxJson,
runTasksInSerial,
} from '@nx/devkit';
import { initGenerator as jsInitGenerator } from '@nx/js';
import { JestPluginOptions } from '../../plugins/plugin';
import { getPresetExt } from '../../utils/config/config-file';
const schemaDefaults = {
setupFile: 'none',
@ -118,7 +123,39 @@ export async function configurationGeneratorInternal(
await formatFiles(tree);
}
tasks.push(getUnsupportedModuleResolutionWarningTask(tree));
return runTasksInSerial(...tasks);
}
/**
* For Jest < 30, there is no way to load jest.config.ts file if the tsconfig.json/tsconfig.base.json sets moduleResolution to bundler or nodenext.
* Jest uses ts-node in a way that is not compatible, so until this is fixed we need to log a warning.
* See: https://github.com/jestjs/jest/blob/main/packages/jest-config/src/readConfigFileAndSetRootDir.ts#L145-L153
*/
function getUnsupportedModuleResolutionWarningTask(
tree: Tree
): GeneratorCallback {
const tsConfigFileName = getRootTsConfigFileName(tree);
if (tsConfigFileName) {
const json = readJson(tree, tsConfigFileName);
if (
json.compilerOptions.moduleResolution !== 'node' &&
json.compilerOptions.moduleResolution !== 'node10'
) {
return () => {
output.warn({
title: `Compiler option 'moduleResolution' in ${tsConfigFileName} must be 'node' or 'node10'`,
bodyLines: [
`Jest requires 'moduleResolution' to be set to 'node' or 'node10' to work properly. It would need to be changed in the "${tsConfigFileName}" file. It's not enough to override the compiler option in the project's tsconfig file.`,
`Alternatively, you can use the environment variable \`TS_NODE_COMPILER_OPTIONS='{"moduleResolution": "node10"}'\` to override Jest's usage of ts-node.`,
],
});
};
}
}
return () => {};
}
export default configurationGenerator;

View File

@ -9,7 +9,6 @@ jest.mock('@nx/devkit', () => ({
import {
addProjectConfiguration as _addProjectConfiguration,
readProjectConfiguration,
stripIndents,
type ProjectConfiguration,
type ProjectGraph,
type Tree,
@ -49,24 +48,16 @@ describe('createJestConfig', () => {
await createJestConfig(tree, { js: true }, 'js');
expect(tree.exists('jest.config.js')).toBeTruthy();
expect(
stripIndents`${tree.read('jest.config.js', 'utf-8')}`
).toMatchSnapshot();
expect(
stripIndents`${tree.read('jest.preset.js', 'utf-8')}`
).toMatchSnapshot();
expect(tree.read('jest.config.js', 'utf-8')).toMatchSnapshot();
expect(tree.read('jest.preset.js', 'utf-8')).toMatchSnapshot();
});
it('should generate files ', async () => {
await createJestConfig(tree, {}, 'js');
expect(tree.exists('jest.config.ts')).toBeTruthy();
expect(
stripIndents`${tree.read('jest.config.ts', 'utf-8')}`
).toMatchSnapshot();
expect(
stripIndents`${tree.read('jest.preset.js', 'utf-8')}`
).toMatchSnapshot();
expect(tree.read('jest.config.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('jest.preset.js', 'utf-8')).toMatchSnapshot();
});
it('should not override existing files', async () => {
@ -83,7 +74,7 @@ describe('createJestConfig', () => {
},
},
});
const expected = stripIndents`
const expected = `
import { getJestProjects } from '@nx/jest';
export default {
projects: getJestProjects(),

View File

@ -139,14 +139,12 @@ module.exports = { ...nxPreset };`
function generateGlobalConfig(tree: Tree, isJS: boolean) {
const contents = isJS
? stripIndents`
const { getJestProjectsAsync } = require('@nx/jest');
? `const { getJestProjectsAsync } = require('@nx/jest');
module.exports = async () => ({
projects: await getJestProjectsAsync()
});`
: stripIndents`
import { getJestProjectsAsync } from '@nx/jest';
: `import { getJestProjectsAsync } from '@nx/jest';
export default async () => ({
projects: await getJestProjectsAsync()

View File

@ -10,7 +10,7 @@
"description": "Create a library"
},
"init": {
"factory": "./src/generators/init/init#initGenerator",
"factory": "./src/generators/init/init#initGeneratorInternal",
"schema": "./src/generators/init/schema.json",
"aliases": ["lib"],
"x-type": "init",

View File

@ -41,6 +41,7 @@
"@babel/runtime": "^7.22.6",
"@nx/devkit": "file:../devkit",
"@nx/workspace": "file:../workspace",
"@zkochan/js-yaml": "0.0.7",
"babel-plugin-const-enum": "^1.0.1",
"babel-plugin-macros": "^2.8.0",
"babel-plugin-transform-typescript-metadata": "^0.3.1",

View File

@ -3,7 +3,7 @@ import 'nx/src/internal-testing-utils/mock-project-graph';
import { readProjectConfiguration, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { join } from 'path';
import { LibraryGeneratorSchema } from '../../utils/schema';
import { LibraryGeneratorSchema } from '../library/schema';
import { libraryGenerator as jsLibraryGenerator } from '../library/library';
import { convertToSwcGenerator } from './convert-to-swc';

View File

@ -0,0 +1,34 @@
{
"compilerOptions": {
"allowJs": false,
"allowSyntheticDefaultImports": true,
"composite": true,
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"emitDecoratorMetadata": false,
"esModuleInterop": true,
"experimentalDecorators": false,
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"incremental": true,
"isolatedModules": true,
"lib": ["es2022"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"pretty": true,
"removeComments": false,
"resolveJsonModule": false,
"skipDefaultLibCheck": false,
"skipLibCheck": true,
"sourceMap": false,
"strict": true,
"target": "es2022",
"verbatimModuleSyntax": false
}
}

View File

@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"compileOnSave": false,
"files": [],
"references": []
}

View File

@ -131,7 +131,7 @@ describe('js init generator', () => {
it('should support skipping prettier setup', async () => {
await init(tree, {
setUpPrettier: false,
formatter: 'none',
});
const packageJson = readJson(tree, 'package.json');

View File

@ -1,19 +1,24 @@
import {
addDependenciesToPackageJson,
createProjectGraphAsync,
ensurePackage,
formatFiles,
generateFiles,
GeneratorCallback,
readJson,
readNxJson,
runTasksInSerial,
Tree,
} from '@nx/devkit';
import { addPlugin } from '@nx/devkit/src/utils/add-plugin';
import { checkAndCleanWithSemver } from '@nx/devkit/src/utils/semver';
import { readModulePackageJson } from 'nx/src/utils/package-json';
import { join } from 'path';
import { satisfies, valid } from 'semver';
import { createNodesV2 } from '../../plugins/typescript/plugin';
import { generatePrettierSetup } from '../../utils/prettier';
import { getRootTsConfigFileName } from '../../utils/typescript/ts-config';
import { isUsingTsSolutionSetup } from '../../utils/typescript/ts-solution-setup';
import {
nxVersion,
prettierVersion,
@ -64,9 +69,12 @@ export async function initGenerator(
tree: Tree,
schema: InitSchema
): Promise<GeneratorCallback> {
schema.addTsPlugin ??= false;
const isUsingNewTsSetup = schema.addTsPlugin || isUsingTsSolutionSetup(tree);
schema.formatter ??= isUsingNewTsSetup ? 'none' : 'prettier';
return initGeneratorInternal(tree, {
addTsConfigBase: true,
setUpPrettier: true,
...schema,
});
}
@ -76,12 +84,48 @@ export async function initGeneratorInternal(
schema: InitSchema
): Promise<GeneratorCallback> {
const tasks: GeneratorCallback[] = [];
// add tsconfig.base.json
const nxJson = readNxJson(tree);
schema.addPlugin ??=
process.env.NX_ADD_PLUGINS !== 'false' &&
nxJson.useInferencePlugins !== false;
schema.addTsPlugin ??=
schema.addPlugin && process.env.NX_ADD_TS_PLUGIN === 'true';
if (schema.addTsPlugin) {
await addPlugin(
tree,
await createProjectGraphAsync(),
'@nx/js/typescript',
createNodesV2,
{
typecheck: [
{ targetName: 'typecheck' },
{ targetName: 'tsc:typecheck' },
{ targetName: 'tsc-typecheck' },
],
build: [
{ targetName: 'build', configName: 'tsconfig.lib.json' },
{ targetName: 'tsc:build', configName: 'tsconfig.lib.json' },
{ targetName: 'tsc-build', configName: 'tsconfig.lib.json' },
],
},
schema.updatePackageScripts
);
}
if (schema.addTsConfigBase && !getRootTsConfigFileName(tree)) {
generateFiles(tree, join(__dirname, './files'), '.', {
if (schema.addTsPlugin) {
generateFiles(tree, join(__dirname, './files/ts-solution'), '.', {
tmpl: '',
});
} else {
generateFiles(tree, join(__dirname, './files/non-ts-solution'), '.', {
fileName: schema.tsConfigName ?? 'tsconfig.base.json',
});
}
}
const devDependencies = {
'@nx/js': nxVersion,
// When loading .ts config files (e.g. webpack.config.ts, jest.config.ts, etc.)
@ -104,7 +148,7 @@ export async function initGeneratorInternal(
}
}
if (schema.setUpPrettier) {
if (schema.formatter === 'prettier') {
const prettierTask = generatePrettierSetup(tree, {
skipPackageJson: schema.skipPackageJson,
});
@ -132,7 +176,12 @@ export async function initGeneratorInternal(
: () => {};
tasks.push(installTask);
if (!schema.skipPackageJson && schema.setUpPrettier) {
if (
!schema.skipPackageJson &&
// For `create-nx-workspace` or `nx g @nx/js:init`, we want to make sure users didn't set formatter to none.
// For programmatic usage, the formatter is normally undefined, and we want prettier to continue to be ensured, even if not ultimately installed.
schema.formatter !== 'none'
) {
ensurePackage('prettier', prettierVersion);
}

View File

@ -1,9 +1,12 @@
export interface InitSchema {
addTsConfigBase?: boolean;
formatter?: 'none' | 'prettier';
js?: boolean;
keepExistingVersions?: boolean;
setUpPrettier?: boolean;
skipFormat?: boolean;
skipPackageJson?: boolean;
tsConfigName?: string;
addPlugin?: boolean;
updatePackageScripts?: boolean;
addTsPlugin?: boolean;
}

View File

@ -5,6 +5,12 @@
"title": "Init nx/js",
"description": "Init generator placeholder for nx/js.",
"properties": {
"formatter": {
"description": "The tool to use for code formatting.",
"type": "string",
"enum": ["none", "prettier"],
"default": "none"
},
"js": {
"type": "boolean",
"default": false,
@ -37,12 +43,6 @@
"type": "string",
"description": "Customize the generated base tsconfig file name.",
"x-priority": "internal"
},
"setUpPrettier": {
"type": "boolean",
"description": "Add Prettier and corresponding configuration files.",
"x-priority": "internal",
"default": false
}
}
}

View File

@ -1,19 +1,11 @@
# <%= name %>
This library was generated with [Nx](https://nx.dev).
<% if (buildable) { %>
This library was generated with [Nx](https://nx.dev).<% if (buildable) { %>
## Building
Run `<%= cliCommand %> build <%= name %>` to build the library.
<% } %>
<% if (hasUnitTestRunner) { %>
Run `<%= cliCommand %> build <%= name %>` to build the library.<% } %><% if (unitTestRunner !== 'none') { %>
## Running unit tests
Run `<%= cliCommand %> test <%= name %>` to execute the unit tests via <% if(unitTestRunner === 'jest') { %>[Jest](https://jestjs.io)<% } else { %>[Vitest](https://vitest.dev/)<% } %>.
<% } %>
Run `<%= cliCommand %> test <%= name %>` to execute the unit tests via <% if(unitTestRunner === 'jest') { %>[Jest](https://jestjs.io)<% } else { %>[Vitest](https://vitest.dev/)<% } %>.<% } %>

View File

@ -0,0 +1,14 @@
{
"extends": "<%= offsetFromRoot %>tsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
"emitDeclarationOnly": false,<% if (compilerOptions.length) { %>
<%- compilerOptions %>,<% } %>
"types": ["node"]
},
"include": ["src/**/*.ts"<% if (js) { %>, "src/**/*.js"<% } %>],
"references": []
}

View File

@ -10,8 +10,8 @@ import {
updateJson,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { LibraryGeneratorSchema } from '../../utils/schema';
import libraryGenerator from './library';
import { libraryGenerator } from './library';
import type { LibraryGeneratorSchema } from './schema';
describe('lib', () => {
let tree: Tree;
@ -155,6 +155,7 @@ describe('lib', () => {
{
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"module": "commonjs",
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
@ -1616,4 +1617,73 @@ describe('lib', () => {
expect(content).toContain(`environment: 'jsdom'`);
});
});
describe('--useProjectJson', () => {
it('should generate the nx configuration in the package.json file when using --useProjectJson=false', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'my-lib',
bundler: 'none',
linter: 'none',
unitTestRunner: 'none',
useProjectJson: false,
projectNameAndRootFormat: 'as-provided',
});
expect(tree.exists('my-lib/project.json')).toBe(false);
expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(`
{
"dependencies": {},
"name": "@proj/my-lib",
"nx": {
"name": "my-lib",
},
"private": true,
"version": "0.0.1",
}
`);
});
it('should generate the nx configuration in the project.json file when using --useProjectJson=true', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'my-lib',
bundler: 'none',
useProjectJson: true,
projectNameAndRootFormat: 'as-provided',
});
expect(readJson(tree, 'my-lib/project.json')).toMatchInlineSnapshot(`
{
"$schema": "../node_modules/nx/schemas/project-schema.json",
"name": "my-lib",
"projectType": "library",
"sourceRoot": "my-lib/src",
"tags": [],
"targets": {
"lint": {
"executor": "@nx/eslint:lint",
},
"test": {
"executor": "@nx/jest:jest",
"options": {
"jestConfig": "my-lib/jest.config.ts",
},
"outputs": [
"{workspaceRoot}/coverage/{projectRoot}",
],
},
},
}
`);
expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(`
{
"dependencies": {},
"name": "@proj/my-lib",
"private": true,
"version": "0.0.1",
}
`);
});
});
});

View File

@ -18,26 +18,33 @@ import {
toJS,
Tree,
updateJson,
updateNxJson,
updateProjectConfiguration,
writeJson,
} from '@nx/devkit';
import {
determineProjectNameAndRootOptions,
type ProjectNameAndRootOptions,
} from '@nx/devkit/src/generators/project-name-and-root-utils';
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
import { prompt } from 'enquirer';
import { findMatchingProjects } from 'nx/src/utils/find-matching-projects';
import { isCI } from 'nx/src/utils/is-ci';
import { type PackageJson } from 'nx/src/utils/package-json';
import { join } from 'path';
import { Bundler, LibraryGeneratorSchema } from '../../utils/schema';
import type { CompilerOptions } from 'typescript';
import { getProjectPackageManagerWorkspaceState } from '../../utils/package-manager-workspaces';
import { addSwcConfig } from '../../utils/swc/add-swc-config';
import { addSwcDependencies } from '../../utils/swc/add-swc-dependencies';
import { getSwcDependencies } from '../../utils/swc/add-swc-dependencies';
import { getNeededCompilerOptionOverrides } from '../../utils/typescript/configuration';
import { tsConfigBaseOptions } from '../../utils/typescript/create-ts-config';
import {
addTsConfigPath,
getRelativePathToRootTsConfig,
getRootTsConfigFileName,
} from '../../utils/typescript/ts-config';
import {
isUsingTsSolutionSetup,
isUsingTypeScriptPlugin,
} from '../../utils/typescript/ts-solution-setup';
import {
esbuildVersion,
nxVersion,
@ -47,6 +54,16 @@ import {
} from '../../utils/versions';
import jsInitGenerator from '../init/init';
import setupVerdaccio from '../setup-verdaccio/generator';
import type {
Bundler,
LibraryGeneratorSchema,
NormalizedLibraryGeneratorOptions,
} from './schema';
import { getProjectPackageManagerWorkspaceStateWarningTask } from './utils/package-manager-workspaces';
import {
ensureProjectIsExcludedFromPluginRegistrations,
ensureProjectIsIncludedInPluginRegistrations,
} from './utils/plugin-registrations';
const defaultOutputDirectory = 'dist';
@ -56,6 +73,7 @@ export async function libraryGenerator(
) {
return await libraryGeneratorInternal(tree, {
addPlugin: false,
useProjectJson: true,
...schema,
});
}
@ -65,18 +83,22 @@ export async function libraryGeneratorInternal(
schema: LibraryGeneratorSchema
) {
const tasks: GeneratorCallback[] = [];
tasks.push(
await jsInitGenerator(tree, {
...schema,
skipFormat: true,
tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json',
addTsConfigBase: true,
// In the new setup, Prettier is prompted for and installed during `create-nx-workspace`.
formatter: isUsingTsSolutionSetup(tree) ? 'none' : 'prettier',
})
);
const options = await normalizeOptions(tree, schema);
createFiles(tree, options);
await addProject(tree, options);
await configureProject(tree, options);
if (!options.skipPackageJson) {
tasks.push(addProjectDependencies(tree, options));
@ -160,7 +182,7 @@ export async function libraryGeneratorInternal(
);
}
if (!schema.skipTsConfig) {
if (!schema.skipTsConfig && !options.isUsingTsSolutionConfig) {
addTsConfigPath(tree, options.importPath, [
joinPathFragments(
options.projectRoot,
@ -170,10 +192,45 @@ export async function libraryGeneratorInternal(
]);
}
if (options.isUsingTsSolutionConfig && options.unitTestRunner !== 'none') {
updateJson(
tree,
joinPathFragments(options.projectRoot, 'tsconfig.spec.json'),
(json) => {
const rootOffset = offsetFromRoot(options.projectRoot);
// ensure it extends from the root tsconfig.base.json
json.extends = joinPathFragments(rootOffset, 'tsconfig.base.json');
// ensure outDir is set to the correct value
json.compilerOptions ??= {};
json.compilerOptions.outDir = joinPathFragments(
rootOffset,
'dist/out-tsc',
options.projectRoot
);
// add project reference to the runtime tsconfig.lib.json file
json.references ??= [];
json.references.push({ path: './tsconfig.lib.json' });
return json;
}
);
}
if (!options.skipFormat) {
await formatFiles(tree);
}
if (
options.isUsingTsSolutionConfig &&
options.projectPackageManagerWorkspaceState !== 'included'
) {
tasks.push(
getProjectPackageManagerWorkspaceStateWarningTask(
options.projectPackageManagerWorkspaceState,
tree.root
)
);
}
if (options.publishable) {
tasks.push(() => {
logNxReleaseDocsInfo();
@ -187,16 +244,36 @@ export async function libraryGeneratorInternal(
return runTasksInSerial(...tasks);
}
export interface NormalizedSchema extends LibraryGeneratorSchema {
name: string;
projectNames: ProjectNameAndRootOptions['names'];
fileName: string;
projectRoot: string;
parsedTags: string[];
importPath?: string;
async function configureProject(
tree: Tree,
options: NormalizedLibraryGeneratorOptions
) {
if (options.hasPlugin) {
const nxJson = readNxJson(tree);
if (options.bundler === 'none') {
ensureProjectIsExcludedFromPluginRegistrations(
nxJson,
options.projectRoot
);
} else {
ensureProjectIsIncludedInPluginRegistrations(nxJson, options.projectRoot);
}
updateNxJson(tree, nxJson);
}
if (!options.useProjectJson) {
if (options.name !== options.importPath) {
// if the name is different than the package.json name, we need to set
// the proper name in the configuration
updateProjectConfiguration(tree, options.name, {
name: options.name,
root: options.projectRoot,
});
}
return;
}
async function addProject(tree: Tree, options: NormalizedSchema) {
const projectConfiguration: ProjectConfiguration = {
root: options.projectRoot,
sourceRoot: joinPathFragments(options.projectRoot, 'src'),
@ -275,21 +352,16 @@ async function addProject(tree: Tree, options: NormalizedSchema) {
if (options.config === 'workspace' || options.config === 'project') {
addProjectConfiguration(tree, options.name, projectConfiguration);
} else {
addProjectConfiguration(
tree,
options.name,
{
addProjectConfiguration(tree, options.name, {
root: projectConfiguration.root,
tags: projectConfiguration.tags,
targets: {},
},
true
);
});
}
}
export type AddLintOptions = Pick<
NormalizedSchema,
NormalizedLibraryGeneratorOptions,
| 'name'
| 'linter'
| 'projectRoot'
@ -407,25 +479,7 @@ export async function addLint(
return task;
}
function updateTsConfig(tree: Tree, options: NormalizedSchema) {
updateJson(tree, join(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 addBabelRc(tree: Tree, options: NormalizedSchema) {
function addBabelRc(tree: Tree, options: NormalizedLibraryGeneratorOptions) {
const filename = '.babelrc';
const babelrc = {
@ -435,12 +489,12 @@ function addBabelRc(tree: Tree, options: NormalizedSchema) {
writeJson(tree, join(options.projectRoot, filename), babelrc);
}
function createFiles(tree: Tree, options: NormalizedSchema) {
function createFiles(tree: Tree, options: NormalizedLibraryGeneratorOptions) {
const { className, name, propertyName } = names(
options.projectNames.projectFileName
);
createProjectTsConfigJson(tree, options);
createProjectTsConfigs(tree, options);
generateFiles(tree, join(__dirname, './files/lib'), options.projectRoot, {
...options,
@ -480,7 +534,6 @@ function createFiles(tree: Tree, options: NormalizedSchema) {
}
if (options.bundler === 'swc' || options.bundler === 'rollup') {
addSwcDependencies(tree);
addSwcConfig(
tree,
options.projectRoot,
@ -518,6 +571,11 @@ function createFiles(tree: Tree, options: NormalizedSchema) {
if (!options.publishable && !options.rootProject) {
json.private = true;
}
if (options.isUsingTsSolutionConfig && options.publishable) {
// package.json and README.md are always included by default
// https://docs.npmjs.com/cli/v10/configuring-npm/package-json#files
json.files = ['dist', '!**/*.tsbuildinfo'];
}
return {
...json,
dependencies: {
@ -537,6 +595,11 @@ function createFiles(tree: Tree, options: NormalizedSchema) {
if (!options.publishable && !options.rootProject) {
packageJson.private = true;
}
if (options.isUsingTsSolutionConfig && options.publishable) {
// package.json and README.md are always included by default
// https://docs.npmjs.com/cli/v10/configuring-npm/package-json#files
packageJson.files = ['dist', '!**/*.tsbuildinfo'];
}
writeJson<PackageJson>(tree, packageJsonPath, packageJson);
}
@ -548,23 +611,16 @@ function createFiles(tree: Tree, options: NormalizedSchema) {
};
return json;
});
} else if (
(!options.bundler || options.bundler === 'none') &&
!(options.projectRoot === '.')
) {
tree.delete(packageJsonPath);
}
if (options.minimal && !(options.projectRoot === '.')) {
tree.delete(join(options.projectRoot, 'README.md'));
}
updateTsConfig(tree, options);
}
async function addJest(
tree: Tree,
options: NormalizedSchema
options: NormalizedLibraryGeneratorOptions
): Promise<GeneratorCallback> {
const { configurationGenerator } = ensurePackage('@nx/jest', nxVersion);
return await configurationGenerator(tree, {
@ -585,7 +641,10 @@ async function addJest(
});
}
function replaceJestConfig(tree: Tree, options: NormalizedSchema) {
function replaceJestConfig(
tree: Tree,
options: NormalizedLibraryGeneratorOptions
) {
const filesDir = join(__dirname, './files/jest-config');
// the existing config has to be deleted otherwise the new config won't overwrite it
const existingJestConfig = joinPathFragments(
@ -609,16 +668,105 @@ function replaceJestConfig(tree: Tree, options: NormalizedSchema) {
});
}
function isNonInteractive(): boolean {
return (
isCI() || !process.stdout.isTTY || process.env.NX_INTERACTIVE !== 'true'
);
}
async function promptWhenInteractive<T>(
questions: Parameters<typeof prompt>[0],
defaultValue: T
): Promise<T> {
if (isNonInteractive()) {
return defaultValue;
}
return await prompt(questions);
}
async function normalizeOptions(
tree: Tree,
options: LibraryGeneratorSchema
): Promise<NormalizedSchema> {
): Promise<NormalizedLibraryGeneratorOptions> {
const nxJson = readNxJson(tree);
const addPlugin =
options.addPlugin ??=
process.env.NX_ADD_PLUGINS !== 'false' &&
nxJson.useInferencePlugins !== false;
options.addPlugin ??= addPlugin;
const hasPlugin = isUsingTypeScriptPlugin(tree);
const isUsingTsSolutionConfig = isUsingTsSolutionSetup(tree);
if (isUsingTsSolutionConfig) {
if (options.bundler === 'esbuild' || options.bundler === 'swc') {
throw new Error(
`Cannot use the "${options.bundler}" bundler when using the @nx/js/typescript plugin.`
);
}
if (options.bundler === undefined && options.compiler === undefined) {
options.bundler = await promptWhenInteractive<{ bundler: Bundler }>(
{
type: 'select',
name: 'bundler',
message: `Which bundler would you like to use to build the library? Choose 'none' to skip build setup.`,
choices: [
{ name: 'tsc' },
{ name: 'rollup' },
{ name: 'vite' },
{ name: 'none' },
],
initial: 0,
},
{ bundler: 'tsc' }
).then(({ bundler }) => bundler);
}
options.linter ??= await promptWhenInteractive<{
linter: 'none' | 'eslint';
}>(
{
type: 'select',
name: 'linter',
message: `Which linter would you like to use?`,
choices: [{ name: 'none' }, { name: 'eslint' }],
initial: 0,
},
{ linter: 'none' }
).then(({ linter }) => linter);
options.unitTestRunner ??= await promptWhenInteractive<{
unitTestRunner: 'none' | 'jest' | 'vitest';
}>(
{
type: 'select',
name: 'unitTestRunner',
message: `Which unit test runner would you like to use?`,
choices: [{ name: 'none' }, { name: 'vitest' }, { name: 'jest' }],
initial: 0,
},
{ unitTestRunner: 'none' }
).then(({ unitTestRunner }) => unitTestRunner);
} else {
if (options.bundler === undefined && options.compiler === undefined) {
options.bundler = await promptWhenInteractive<{ bundler: Bundler }>(
{
type: 'select',
name: 'bundler',
message: `Which bundler would you like to use to build the library? Choose 'none' to skip build setup.`,
choices: [
{ name: 'swc' },
{ name: 'tsc' },
{ name: 'rollup' },
{ name: 'vite' },
{ name: 'esbuild' },
{ name: 'none' },
],
initial: 1,
},
{ bundler: 'tsc' }
).then(({ bundler }) => bundler);
} else {
/**
* We are deprecating the compiler and the buildable options.
* However, we want to keep the existing behavior for now.
@ -641,7 +789,41 @@ async function normalizeOptions(
* By default, with nothing provided, libraries are buildable with `@nx/js:tsc`.
*/
options.bundler = options.bundler ?? options.compiler ?? 'tsc';
options.bundler ??= options.compiler;
}
options.linter ??= await promptWhenInteractive<{
linter: 'none' | 'eslint';
}>(
{
type: 'select',
name: 'linter',
message: `Which linter would you like to use?`,
choices: [{ name: 'eslint' }, { name: 'none' }],
initial: 0,
},
{ linter: 'eslint' }
).then(({ linter }) => linter);
options.unitTestRunner ??= await promptWhenInteractive<{
unitTestRunner: 'none' | 'jest' | 'vitest';
}>(
{
type: 'select',
name: 'unitTestRunner',
message: `Which unit test runner would you like to use?`,
choices: [{ name: 'jest' }, { name: 'vitest' }, { name: 'none' }],
initial: 0,
},
{ unitTestRunner: undefined }
).then(({ unitTestRunner }) => unitTestRunner);
if (!options.unitTestRunner && options.bundler === 'vite') {
options.unitTestRunner = 'vitest';
} else if (!options.unitTestRunner && options.config !== 'npm-scripts') {
options.unitTestRunner = 'jest';
}
}
// ensure programmatic runs have an expected default
if (!options.config) {
@ -665,10 +847,9 @@ async function normalizeOptions(
options.bundler = 'none';
}
const { Linter } = ensurePackage('@nx/eslint', nxVersion);
if (options.config === 'npm-scripts') {
options.unitTestRunner = 'none';
options.linter = Linter.None;
options.linter = 'none';
options.bundler = 'none';
}
@ -679,16 +860,6 @@ async function normalizeOptions(
options.skipTypeCheck = false;
}
if (!options.unitTestRunner && options.bundler === 'vite') {
options.unitTestRunner = 'vitest';
} else if (!options.unitTestRunner && options.config !== 'npm-scripts') {
options.unitTestRunner = 'jest';
}
if (!options.linter && options.config !== 'npm-scripts') {
options.linter = Linter.EsLint;
}
const {
projectName,
names: projectNames,
@ -715,6 +886,12 @@ async function normalizeOptions(
options.minimal ??= false;
const projectPackageManagerWorkspaceState =
getProjectPackageManagerWorkspaceState(tree, projectRoot);
// We default to generate a project.json file if the new setup is not being used
options.useProjectJson ??= !isUsingTsSolutionConfig;
return {
...options,
fileName,
@ -723,12 +900,15 @@ async function normalizeOptions(
projectRoot,
parsedTags,
importPath,
hasPlugin,
isUsingTsSolutionConfig,
projectPackageManagerWorkspaceState,
};
}
function addProjectDependencies(
tree: Tree,
options: NormalizedSchema
options: NormalizedLibraryGeneratorOptions
): GeneratorCallback {
if (options.bundler == 'esbuild') {
return addDependenciesToPackageJson(
@ -741,10 +921,28 @@ function addProjectDependencies(
}
);
} else if (options.bundler == 'rollup') {
const { dependencies, devDependencies } = getSwcDependencies();
return addDependenciesToPackageJson(
tree,
{ ...dependencies },
{
...devDependencies,
'@nx/rollup': nxVersion,
'@types/node': typesNodeVersion,
}
);
} else if (options.bundler === 'tsc') {
return addDependenciesToPackageJson(
tree,
{},
{ '@nx/rollup': nxVersion, '@types/node': typesNodeVersion }
{ tslib: tsLibVersion, '@types/node': typesNodeVersion }
);
} else if (options.bundler === 'swc') {
const { dependencies, devDependencies } = getSwcDependencies();
return addDependenciesToPackageJson(
tree,
{ ...dependencies },
{ ...devDependencies, '@types/node': typesNodeVersion }
);
} else {
return addDependenciesToPackageJson(
@ -776,7 +974,7 @@ function getBuildExecutor(bundler: Bundler) {
}
}
function getOutputPath(options: NormalizedSchema) {
function getOutputPath(options: NormalizedLibraryGeneratorOptions) {
const parts = [defaultOutputDirectory];
if (options.projectRoot === '.') {
parts.push(options.name);
@ -786,15 +984,117 @@ function getOutputPath(options: NormalizedSchema) {
return joinPathFragments(...parts);
}
function createProjectTsConfigJson(tree: Tree, options: NormalizedSchema) {
function createProjectTsConfigs(
tree: Tree,
options: NormalizedLibraryGeneratorOptions
) {
const rootOffset = offsetFromRoot(options.projectRoot);
let compilerOptionOverrides: Record<keyof CompilerOptions, any> = {
module: options.isUsingTsSolutionConfig
? options.bundler === 'rollup'
? 'esnext'
: 'nodenext'
: 'commonjs',
...(options.isUsingTsSolutionConfig
? options.bundler === 'rollup'
? { moduleResolution: 'bundler' }
: { moduleResolution: 'nodenext' }
: {}),
...(options.js ? { allowJs: true } : {}),
...(options.strict
? {
forceConsistentCasingInFileNames: true,
strict: true,
importHelpers: true,
noImplicitOverride: true,
noImplicitReturns: true,
noFallthroughCasesInSwitch: true,
...(!options.isUsingTsSolutionConfig
? { noPropertyAccessFromIndexSignature: true }
: {}),
}
: {}),
};
if (!options.rootProject || options.isUsingTsSolutionConfig) {
// filter out options already set with the same value in root tsconfig file that we're going to extend from
compilerOptionOverrides = getNeededCompilerOptionOverrides(
tree,
compilerOptionOverrides,
// must have been created by now
getRootTsConfigFileName(tree)!
);
}
// tsconfig.lib.json
generateFiles(
tree,
join(
__dirname,
'files/tsconfig-lib',
options.isUsingTsSolutionConfig ? 'ts-solution' : 'non-ts-solution'
),
options.projectRoot,
{
...options,
offsetFromRoot: rootOffset,
js: !!options.js,
compilerOptions: Object.entries(compilerOptionOverrides)
.map(([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`)
.join(',\n '),
tmpl: '',
}
);
// tsconfig.json
if (options.isUsingTsSolutionConfig) {
if (options.rootProject) {
// the root tsconfig.json is already created with the expected settings
// for the TS plugin, we just need to update it with the project-specific
// settings
updateJson(tree, 'tsconfig.json', (json) => {
json.references.push({
path: './tsconfig.lib.json',
});
return json;
});
} else {
// create a new tsconfig.json for the project
const tsconfig = {
extends: getRelativePathToRootTsConfig(tree, options.projectRoot),
files: [],
include: [],
references: [{ path: './tsconfig.lib.json' }],
};
writeJson(
tree,
joinPathFragments(options.projectRoot, 'tsconfig.json'),
tsconfig
);
// update root project tsconfig.json references with the new lib tsconfig
updateJson(tree, 'tsconfig.json', (json) => {
json.references ??= [];
json.references.push({
path: options.projectRoot.startsWith('./')
? options.projectRoot
: './' + options.projectRoot,
});
return json;
});
}
return;
}
const tsconfig = {
extends: options.rootProject
? undefined
: getRelativePathToRootTsConfig(tree, options.projectRoot),
compilerOptions: {
...(options.rootProject ? tsConfigBaseOptions : {}),
module: 'commonjs',
allowJs: options.js ? true : undefined,
...compilerOptionOverrides,
},
files: [],
include: [],
@ -835,14 +1135,18 @@ function determineDependencies(
type EntryField = string | { [key: string]: EntryField };
function determineEntryFields(
options: LibraryGeneratorSchema
options: NormalizedLibraryGeneratorOptions
): Record<string, EntryField> {
switch (options.bundler) {
case 'tsc':
return {
type: 'commonjs',
main: './src/index.js',
typings: './src/index.d.ts',
main: options.isUsingTsSolutionConfig
? './dist/index.js'
: './src/index.js',
typings: options.isUsingTsSolutionConfig
? './dist/index.d.ts'
: './src/index.d.ts',
};
case 'swc':
return {
@ -854,16 +1158,26 @@ function determineEntryFields(
return {
// Since we're publishing both formats, skip the type field.
// Bundlers or Node will determine the entry point to use.
main: './index.cjs',
module: './index.js',
main: options.isUsingTsSolutionConfig
? './dist/index.cjs'
: './index.cjs',
module: options.isUsingTsSolutionConfig
? './dist/index.js'
: './index.js',
};
case 'vite':
return {
// Since we're publishing both formats, skip the type field.
// Bundlers or Node will determine the entry point to use.
main: './index.js',
module: './index.mjs',
typings: './index.d.ts',
main: options.isUsingTsSolutionConfig
? './dist/index.js'
: './index.js',
module: options.isUsingTsSolutionConfig
? './dist/index.mjs'
: './index.mjs',
typings: options.isUsingTsSolutionConfig
? './dist/index.d.ts'
: './index.d.ts',
};
case 'esbuild':
// For libraries intended for Node, use CJS.
@ -905,7 +1219,7 @@ function projectsConfigMatchesProject(
async function addProjectToNxReleaseConfig(
tree: Tree,
options: NormalizedSchema,
options: NormalizedLibraryGeneratorOptions,
projectConfiguration: ProjectConfiguration
) {
const nxJson = readNxJson(tree);

View File

@ -0,0 +1,53 @@
import type {
ProjectNameAndRootFormat,
ProjectNameAndRootOptions,
} from '@nx/devkit/src/generators/project-name-and-root-utils';
// nx-ignore-next-line
const { Linter, LinterType } = require('@nx/eslint'); // use require to import to avoid circular dependency
import type { ProjectPackageManagerWorkspaceState } from '../../utils/package-manager-workspaces';
export type Compiler = 'tsc' | 'swc';
export type Bundler = 'swc' | 'tsc' | 'rollup' | 'vite' | 'esbuild' | 'none';
export interface LibraryGeneratorSchema {
name: string;
directory?: string;
projectNameAndRootFormat?: ProjectNameAndRootFormat;
skipFormat?: boolean;
tags?: string;
skipTsConfig?: boolean;
skipPackageJson?: boolean;
includeBabelRc?: boolean;
unitTestRunner?: 'jest' | 'vitest' | 'none';
linter?: Linter | LinterType;
testEnvironment?: 'jsdom' | 'node';
importPath?: string;
js?: boolean;
pascalCaseFiles?: boolean;
strict?: boolean;
publishable?: boolean;
buildable?: boolean;
setParserOptionsProject?: boolean;
config?: 'workspace' | 'project' | 'npm-scripts';
compiler?: Compiler;
bundler?: Bundler;
skipTypeCheck?: boolean;
minimal?: boolean;
rootProject?: boolean;
simpleName?: boolean;
addPlugin?: boolean;
useProjectJson?: boolean;
}
export interface NormalizedLibraryGeneratorOptions
extends LibraryGeneratorSchema {
name: string;
projectNames: ProjectNameAndRootOptions['names'];
fileName: string;
projectRoot: string;
parsedTags: string[];
importPath?: string;
hasPlugin: boolean;
isUsingTsSolutionConfig: boolean;
projectPackageManagerWorkspaceState: ProjectPackageManagerWorkspaceState;
}

View File

@ -21,22 +21,28 @@
"description": "A directory where the lib is placed.",
"x-priority": "important"
},
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"bundler": {
"description": "The bundler to use. Choosing 'none' means this library is not buildable.",
"type": "string",
"enum": ["as-provided", "derived"]
"enum": ["swc", "tsc", "rollup", "vite", "esbuild", "none"],
"x-priority": "important"
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "none"],
"default": "eslint"
"enum": ["none", "eslint"],
"x-priority": "important"
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "vitest", "none"],
"description": "Test runner to use for unit tests.",
"x-prompt": "Which unit test runner would you like to use?"
"type": "string",
"enum": ["none", "jest", "vitest"],
"x-priority": "important"
},
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"tags": {
"type": "string",
@ -112,18 +118,9 @@
"compiler": {
"type": "string",
"enum": ["tsc", "swc"],
"default": "tsc",
"description": "The compiler used by the build and test targets",
"x-deprecated": "Use the `bundler` option for greater control (swc, tsc, rollup, vite, esbuild, none)."
},
"bundler": {
"description": "The bundler to use. Choosing 'none' means this library is not buildable.",
"type": "string",
"enum": ["swc", "tsc", "rollup", "vite", "esbuild", "none"],
"default": "tsc",
"x-prompt": "Which bundler would you like to use to build the library? Choose 'none' to skip build setup.",
"x-priority": "important"
},
"skipTypeCheck": {
"type": "boolean",
"description": "Whether to skip TypeScript type checking for SWC compiler.",
@ -138,6 +135,10 @@
"description": "Don't include the directory in the generated file name.",
"type": "boolean",
"default": false
},
"useProjectJson": {
"type": "boolean",
"description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file."
}
},
"required": ["name"],

View File

@ -0,0 +1,58 @@
import {
detectPackageManager,
getPackageManagerVersion,
output,
type GeneratorCallback,
} from '@nx/devkit';
import { lt } from 'semver';
import type { ProjectPackageManagerWorkspaceState } from '../../../utils/package-manager-workspaces';
export function getProjectPackageManagerWorkspaceStateWarningTask(
projectPackageManagerWorkspaceState: ProjectPackageManagerWorkspaceState,
workspaceRoot: string
): GeneratorCallback {
return (): void => {
const packageManager = detectPackageManager(workspaceRoot);
let packageManagerWorkspaceSetupDocs: string;
if (packageManager === 'pnpm') {
packageManagerWorkspaceSetupDocs =
'https://pnpm.io/workspaces and https://pnpm.io/pnpm-workspace_yaml';
} else if (packageManager === 'yarn') {
const yarnVersion = getPackageManagerVersion(
packageManager,
workspaceRoot
);
if (lt(yarnVersion, '2.0.0')) {
packageManagerWorkspaceSetupDocs =
'https://classic.yarnpkg.com/lang/en/docs/workspaces/';
} else {
packageManagerWorkspaceSetupDocs =
'https://yarnpkg.com/features/workspaces';
}
} else if (packageManager === 'npm') {
packageManagerWorkspaceSetupDocs =
'https://docs.npmjs.com/cli/v10/using-npm/workspaces';
} else if (packageManager === 'bun') {
packageManagerWorkspaceSetupDocs =
'https://bun.sh/docs/install/workspaces';
}
if (projectPackageManagerWorkspaceState === 'no-workspaces') {
output.warn({
title: `The package manager workspaces feature is not enabled in the workspace`,
bodyLines: [
'You must enable the package manager workspaces feature to use the "@nx/js/typescript" plugin.',
`Read more about the ${packageManager} workspaces feature and how to set it up at ${packageManagerWorkspaceSetupDocs}.`,
],
});
} else if (projectPackageManagerWorkspaceState === 'excluded') {
output.warn({
title: `The project is not included in the package manager workspaces configuration`,
bodyLines: [
'Please add it to the workspace configuration to use the "@nx/js/typescript" plugin.',
`Read more about the ${packageManager} workspaces feature and how to set it up at ${packageManagerWorkspaceSetupDocs}.`,
],
});
}
};
}

View File

@ -0,0 +1,428 @@
import type { NxJsonConfiguration } from '@nx/devkit';
import {
ensureProjectIsExcludedFromPluginRegistrations,
ensureProjectIsIncludedInPluginRegistrations,
} from './plugin-registrations';
describe('ensureProjectIsIncludedInPluginRegistrations', () => {
it('should do nothing when there is no `plugin` entry', () => {
const nxJson: NxJsonConfiguration = {};
ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toStrictEqual({});
});
it('should do nothing when the there are no plugins', () => {
const nxJson: NxJsonConfiguration = { plugins: [] };
ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toStrictEqual({ plugins: [] });
});
it('should do nothing when the are no registrations for the `@nx/js/typescript` plugin', () => {
const nxJson: NxJsonConfiguration = { plugins: ['@foo/bar/plugin'] };
ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toStrictEqual({ plugins: ['@foo/bar/plugin'] });
});
it('should do nothing when `include`/`exclude` are not set in a plugin registration that infers both targets', () => {
const originalNxJson: NxJsonConfiguration = {
plugins: [
{
plugin: '@nx/js/typescript',
options: {
typecheck: { targetName: 'typecheck' },
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
],
};
const nxJson = structuredClone(originalNxJson);
ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toEqual(originalNxJson);
});
it('should do nothing when `include` is set in a plugin registration that infers both targets and the project is already included', () => {
const originalNxJson: NxJsonConfiguration = {
plugins: [
{
plugin: '@nx/js/typescript',
include: ['packages/pkg1/*'],
options: {
typecheck: { targetName: 'typecheck' },
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
],
};
const nxJson = structuredClone(originalNxJson);
ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toEqual(originalNxJson);
});
it('should do nothing when `exclude` is set in a plugin registration that infers both targets and the project is not excluded', () => {
const originalNxJson: NxJsonConfiguration = {
plugins: [
{
plugin: '@nx/js/typescript',
exclude: ['packages/pkg1/*'],
options: {
typecheck: { targetName: 'typecheck' },
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
],
};
const nxJson = structuredClone(originalNxJson);
ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg2');
expect(nxJson).toEqual(originalNxJson);
});
it('should exclude a project from a string plugin registration and add a new plugin registration that includes it', () => {
const nxJson: NxJsonConfiguration = { plugins: ['@nx/js/typescript'] };
ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toStrictEqual({
plugins: [
{
plugin: '@nx/js/typescript',
exclude: ['packages/pkg1/*'],
},
{
plugin: '@nx/js/typescript',
include: ['packages/pkg1/*'],
options: {
typecheck: { targetName: 'typecheck' },
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
],
});
});
it('should exclude a project from a plugin registration missing the `typecheck` target and add a new plugin registration that includes it', () => {
const nxJson: NxJsonConfiguration = {
plugins: [
{
plugin: '@nx/js/typescript',
options: {
typecheck: false,
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
],
};
ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toStrictEqual({
plugins: [
{
plugin: '@nx/js/typescript',
exclude: ['packages/pkg1/*'],
options: {
typecheck: false,
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
{
plugin: '@nx/js/typescript',
include: ['packages/pkg1/*'],
options: {
typecheck: { targetName: 'typecheck' },
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
],
});
});
it('should exclude a project from a plugin registration missing the `build` target and add a new plugin registration that includes it', () => {
const nxJson: NxJsonConfiguration = {
plugins: [
{
plugin: '@nx/js/typescript',
options: { typecheck: { targetName: 'typecheck' } },
},
],
};
ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toStrictEqual({
plugins: [
{
plugin: '@nx/js/typescript',
exclude: ['packages/pkg1/*'],
options: { typecheck: { targetName: 'typecheck' } },
},
{
plugin: '@nx/js/typescript',
include: ['packages/pkg1/*'],
options: {
typecheck: { targetName: 'typecheck' },
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
],
});
});
it('should include a project in a plugin registration that infers both targets and with `include` set but not including the project', () => {
const nxJson: NxJsonConfiguration = {
plugins: [
{
plugin: '@nx/js/typescript',
include: ['packages/pkg1/*'],
options: {
typecheck: { targetName: 'typecheck' },
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
],
};
ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg2');
expect(nxJson).toStrictEqual({
plugins: [
{
plugin: '@nx/js/typescript',
include: ['packages/pkg1/*', 'packages/pkg2/*'],
options: {
typecheck: { targetName: 'typecheck' },
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
],
});
});
it('should add a new plugin registration including the project when there is an existing plugin registration that infers both targets and with `exclude` set excluding the project', () => {
const nxJson: NxJsonConfiguration = {
plugins: [
{
plugin: '@nx/js/typescript',
exclude: ['packages/**/*'],
options: {
typecheck: { targetName: 'typecheck' },
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
],
};
ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toStrictEqual({
plugins: [
{
plugin: '@nx/js/typescript',
exclude: ['packages/**/*'],
options: {
typecheck: { targetName: 'typecheck' },
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
{
plugin: '@nx/js/typescript',
include: ['packages/pkg1/*'],
options: {
typecheck: { targetName: 'typecheck' },
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
],
});
});
});
describe('ensureProjectIsExcludedFromPluginRegistrations', () => {
it('should do nothing when there is no `plugin` entry', () => {
const nxJson: NxJsonConfiguration = {};
ensureProjectIsExcludedFromPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toStrictEqual({});
});
it('should do nothing when the there are no plugins', () => {
const nxJson: NxJsonConfiguration = { plugins: [] };
ensureProjectIsExcludedFromPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toStrictEqual({ plugins: [] });
});
it('should do nothing when the are no registrations for the `@nx/js/typescript` plugin', () => {
const nxJson: NxJsonConfiguration = { plugins: ['@foo/bar/plugin'] };
ensureProjectIsExcludedFromPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toStrictEqual({ plugins: ['@foo/bar/plugin'] });
});
it('should do nothing when the plugin registration does not infer any of the targets', () => {
const nxJson: NxJsonConfiguration = {
plugins: [
{
plugin: '@nx/js/typescript',
options: { typecheck: false },
},
],
};
ensureProjectIsExcludedFromPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toStrictEqual({
plugins: [
{
plugin: '@nx/js/typescript',
options: { typecheck: false },
},
],
});
});
it('should do nothing when `include` is set in a plugin registration that infers any of the targets and the project is not included', () => {
const originalNxJson: NxJsonConfiguration = {
plugins: [
{
plugin: '@nx/js/typescript',
include: ['packages/pkg1/*'],
options: {
typecheck: { targetName: 'typecheck' },
},
},
],
};
const nxJson = structuredClone(originalNxJson);
ensureProjectIsExcludedFromPluginRegistrations(nxJson, 'packages/pkg2');
expect(nxJson).toEqual(originalNxJson);
});
it('should do nothing when `exclude` is set in a plugin registration that infers any of the targets and the project is already excluded', () => {
const originalNxJson: NxJsonConfiguration = {
plugins: [
{
plugin: '@nx/js/typescript',
exclude: ['packages/pkg1/*'],
options: {
typecheck: false,
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
],
};
const nxJson = structuredClone(originalNxJson);
ensureProjectIsExcludedFromPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toEqual(originalNxJson);
});
it('should exclude a project from a string plugin registration', () => {
const nxJson: NxJsonConfiguration = { plugins: ['@nx/js/typescript'] };
ensureProjectIsExcludedFromPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toStrictEqual({
plugins: [
{
plugin: '@nx/js/typescript',
exclude: ['packages/pkg1/*'],
},
],
});
});
it('should exclude a project from a plugin registration that infers any of the targets', () => {
const nxJson: NxJsonConfiguration = {
plugins: [
{
plugin: '@nx/js/typescript',
options: {
typecheck: { targetName: 'typecheck' },
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
],
};
ensureProjectIsExcludedFromPluginRegistrations(nxJson, 'packages/pkg1');
expect(nxJson).toStrictEqual({
plugins: [
{
plugin: '@nx/js/typescript',
exclude: ['packages/pkg1/*'],
options: {
typecheck: { targetName: 'typecheck' },
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
},
],
});
});
});

View File

@ -0,0 +1,141 @@
import type {
ExpandedPluginConfiguration,
NxJsonConfiguration,
} from '@nx/devkit';
import { findMatchingConfigFiles } from 'nx/src/devkit-internals';
import type { TscPluginOptions } from '../../../plugins/typescript/plugin';
export function ensureProjectIsIncludedInPluginRegistrations(
nxJson: NxJsonConfiguration,
projectRoot: string
): void {
if (
!nxJson.plugins?.length ||
!nxJson.plugins.some(isTypeScriptPluginRegistration)
) {
return;
}
let isIncluded = false;
let index = 0;
for (const registration of nxJson.plugins) {
if (!isTypeScriptPluginRegistration(registration)) {
index++;
continue;
}
if (typeof registration === 'string') {
// if it's a string all projects are included but the are no user-specified options
// and the `build` task is not inferred by default, so we need to exclude it
nxJson.plugins[index] = {
plugin: '@nx/js/typescript',
exclude: [`${projectRoot}/*`],
};
} else {
// check if the project would be included by the plugin registration
const matchingConfigFiles = findMatchingConfigFiles(
[`${projectRoot}/tsconfig.json`],
'**/tsconfig.json',
registration.include,
registration.exclude
);
if (matchingConfigFiles.length) {
// it's included by the plugin registration, check if the user-specified options would result
// in a `build` task being inferred, if not, we need to exclude it
if (registration.options?.typecheck && registration.options?.build) {
// it has the desired options, do nothing
isIncluded = true;
} else {
// it would not have the `build` task inferred, so we need to exclude it
registration.exclude ??= [];
registration.exclude.push(`${projectRoot}/*`);
}
} else if (
registration.options?.typecheck &&
registration.options?.build &&
!registration.exclude?.length
) {
// negative pattern are not supported by the `exclude` option so we
// can't update it to not exclude the project, so we only update the
// plugin registration if there's no `exclude` option, in which case
// the plugin registration should have an `include` options that doesn't
// include the project
isIncluded = true;
registration.include ??= [];
registration.include.push(`${projectRoot}/*`);
}
}
index++;
}
if (!isIncluded) {
// the project is not included by any plugin registration with an inferred `build` task,
// so we create a new plugin registration for it
nxJson.plugins.push({
plugin: '@nx/js/typescript',
include: [`${projectRoot}/*`],
options: {
typecheck: { targetName: 'typecheck' },
build: {
targetName: 'build',
configName: 'tsconfig.lib.json',
},
},
});
}
}
export function ensureProjectIsExcludedFromPluginRegistrations(
nxJson: NxJsonConfiguration,
projectRoot: string
): void {
if (
!nxJson.plugins?.length ||
!nxJson.plugins.some(isTypeScriptPluginRegistration)
) {
return;
}
let index = 0;
for (const registration of nxJson.plugins) {
if (!isTypeScriptPluginRegistration(registration)) {
index++;
continue;
}
if (typeof registration === 'string') {
// if it's a string, it includes all projects, so we need to exclude it
nxJson.plugins[index] = {
plugin: '@nx/js/typescript',
exclude: [`${projectRoot}/*`],
};
} else {
// check if the project would be included by the plugin registration
const matchingConfigFiles = findMatchingConfigFiles(
[`${projectRoot}/tsconfig.json`],
'**/tsconfig.json',
registration.include,
registration.exclude
);
if (
matchingConfigFiles.length &&
(registration.options?.typecheck !== false ||
registration.options?.build)
) {
// the project is included by a plugin registration that infers any of the targets, so we need to exclude it
registration.exclude ??= [];
registration.exclude.push(`${projectRoot}/*`);
}
}
index++;
}
}
function isTypeScriptPluginRegistration(
plugin: string | ExpandedPluginConfiguration
): plugin is string | ExpandedPluginConfiguration<TscPluginOptions> {
return (
(typeof plugin === 'string' && plugin === '@nx/js/typescript') ||
(typeof plugin !== 'string' && plugin.plugin === '@nx/js/typescript')
);
}

View File

@ -11,6 +11,7 @@ import {
} from '@nx/devkit';
import * as path from 'path';
import { SetupVerdaccioGeneratorSchema } from './schema';
import { isUsingTsSolutionSetup } from '../../utils/typescript/ts-solution-setup';
import { verdaccioVersion } from '../../utils/versions';
import { execSync } from 'child_process';
@ -38,21 +39,25 @@ export async function setupVerdaccio(
},
};
if (!tree.exists('project.json')) {
const isUsingNewTsSetup = isUsingTsSolutionSetup(tree);
const { name } = readJson(tree, 'package.json');
updateJson(tree, 'package.json', (json) => {
if (!json.nx) {
json.nx = {
includedScripts: [],
};
json.nx ??= { includedScripts: [] };
if (isUsingNewTsSetup) {
json.nx.targets ??= {};
json.nx.targets['local-registry'] ??= verdaccioTarget;
}
return json;
});
if (!isUsingNewTsSetup) {
addProjectConfiguration(tree, name, {
root: '.',
targets: {
['local-registry']: verdaccioTarget,
},
});
}
} else {
// use updateJson instead of updateProjectConfiguration due to unknown project name
updateJson(tree, 'project.json', (json: ProjectConfiguration) => {

View File

@ -169,6 +169,70 @@ describe('findNpmDependencies', () => {
});
});
it.each`
fileName
${'tsconfig.base.json'}
${'tsconfig.json'}
`(
'should pick up helper npm dependencies when using tsc and run-commands',
({ fileName }) => {
vol.fromJSON(
{
[`./${fileName}`]: JSON.stringify({
compilerOptions: { importHelpers: true },
}),
'./nx.json': JSON.stringify(nxJson),
'./libs/my-lib/tsconfig.json': JSON.stringify({
compilerOptions: {
importHelpers: true,
},
}),
},
'/root'
);
const lib = {
name: 'my-lib',
type: 'lib' as const,
data: {
root: 'libs/my-lib',
targets: {
build: {
executor: 'nx:run-commands',
options: {
command: 'tsc --build tsconfig.lib.json --pretty --verbose',
},
},
},
},
};
const projectGraph = {
nodes: {
'my-lib': lib,
},
externalNodes: {
'npm:tslib': {
name: 'npm:tslib' as const,
type: 'npm' as const,
data: {
packageName: 'tslib',
version: '2.6.0',
},
},
},
dependencies: {},
};
const projectFileMap = {
'my-lib': [],
};
expect(
findNpmDependencies('/root', lib, projectGraph, projectFileMap, 'build')
).toEqual({
tslib: '2.6.0',
});
}
);
it('should handle missing ts/swc helper packages from externalNodes', () => {
vol.fromJSON(
{

View File

@ -10,7 +10,7 @@ import {
} from '@nx/devkit';
import { fileExists } from 'nx/src/utils/fileutils';
import { fileDataDepTarget } from 'nx/src/config/project-graph';
import { readTsConfig } from './typescript/ts-config';
import { getRootTsConfigFileName, readTsConfig } from './typescript/ts-config';
import {
filterUsingGlobPatterns,
getTargetInputs,
@ -223,4 +223,21 @@ function collectHelperDependencies(
projectGraph.externalNodes['npm:@swc/helpers'].data.version;
}
}
// For inferred targets or manually added run-commands, check if user is using `tsc` in build target.
if (
target.executor === 'nx:run-commands' &&
/\btsc\b/.test(target.options.command)
) {
const tsConfigFileName = getRootTsConfigFileName();
if (tsConfigFileName) {
const tsConfig = readTsConfig(join(workspaceRoot, tsConfigFileName));
if (
tsConfig?.options['importHelpers'] &&
projectGraph.externalNodes['npm:tslib']?.type === 'npm'
) {
npmDeps['tslib'] = projectGraph.externalNodes['npm:tslib'].data.version;
}
}
}
}

View File

@ -0,0 +1,37 @@
import {
detectPackageManager,
isWorkspacesEnabled,
readJson,
type Tree,
} from '@nx/devkit';
import { minimatch } from 'minimatch';
import { join } from 'node:path/posix';
import { getGlobPatternsFromPackageManagerWorkspaces } from 'nx/src/plugins/package-json';
export type ProjectPackageManagerWorkspaceState =
| 'included'
| 'excluded'
| 'no-workspaces';
export function getProjectPackageManagerWorkspaceState(
tree: Tree,
projectRoot: string
): ProjectPackageManagerWorkspaceState {
if (!isUsingPackageManagerWorkspaces(tree)) {
return 'no-workspaces';
}
const patterns = getGlobPatternsFromPackageManagerWorkspaces(
tree.root,
(path) => readJson(tree, path, { expectComments: true })
);
const isIncluded = patterns.some((p) =>
minimatch(join(projectRoot, 'package.json'), p)
);
return isIncluded ? 'included' : 'excluded';
}
export function isUsingPackageManagerWorkspaces(tree: Tree): boolean {
return isWorkspacesEnabled(detectPackageManager(tree.root), tree.root);
}

View File

@ -1,5 +1,6 @@
import {
addDependenciesToPackageJson,
readJson,
stripIndents,
updateJson,
writeJson,
@ -96,3 +97,64 @@ export function generatePrettierSetup(
? () => {}
: addDependenciesToPackageJson(tree, {}, { prettier: prettierVersion });
}
export async function resolvePrettierConfigPath(
tree: Tree
): Promise<string | null> {
let prettier: typeof import('prettier');
try {
prettier = require('prettier');
} catch {
return null;
}
if (prettier) {
const filePath = await prettier.resolveConfigFile();
if (filePath) {
return filePath;
}
}
if (!tree) {
return null;
}
// if we haven't find a config file in the file system, we try to find it in the virtual tree
// https://prettier.io/docs/en/configuration.html
const prettierrcNameOptions = [
'.prettierrc',
'.prettierrc.json',
'.prettierrc.yml',
'.prettierrc.yaml',
'.prettierrc.json5',
'.prettierrc.js',
'.prettierrc.cjs',
'.prettierrc.mjs',
'.prettierrc.toml',
'prettier.config.js',
'prettier.config.cjs',
'prettier.config.mjs',
];
const filePath = prettierrcNameOptions.find((file) => tree.exists(file));
if (filePath) {
return filePath;
}
// check the package.json file
const packageJson = readJson(tree, 'package.json');
if (packageJson.prettier) {
return 'package.json';
}
// check the package.yaml file
if (tree.exists('package.yaml')) {
const { load } = await import('@zkochan/js-yaml');
const packageYaml = load(tree.read('package.yaml', 'utf-8'));
if (packageYaml.prettier) {
return 'package.yaml';
}
}
return null;
}

View File

@ -1,39 +1,7 @@
import type { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
import type { AssetGlob, FileInputOutput } from './assets/assets';
import { TransformerEntry } from './typescript/types';
// nx-ignore-next-line
const { Linter, LinterType } = require('@nx/eslint'); // use require to import to avoid circular dependency
export type Compiler = 'tsc' | 'swc';
export type Bundler = 'swc' | 'tsc' | 'rollup' | 'vite' | 'esbuild' | 'none';
export interface LibraryGeneratorSchema {
name: string;
directory?: string;
projectNameAndRootFormat?: ProjectNameAndRootFormat;
skipFormat?: boolean;
tags?: string;
skipTsConfig?: boolean;
skipPackageJson?: boolean;
includeBabelRc?: boolean;
unitTestRunner?: 'jest' | 'vitest' | 'none';
linter?: Linter | LinterType;
testEnvironment?: 'jsdom' | 'node';
importPath?: string;
js?: boolean;
strict?: boolean;
publishable?: boolean;
buildable?: boolean;
setParserOptionsProject?: boolean;
config?: 'workspace' | 'project' | 'npm-scripts';
compiler?: Compiler;
bundler?: Bundler;
skipTypeCheck?: boolean;
minimal?: boolean;
rootProject?: boolean;
simpleName?: boolean;
addPlugin?: boolean;
}
export interface ExecutorOptions {
assets: Array<AssetGlob | string>;

View File

@ -6,17 +6,25 @@ import {
swcNodeVersion,
} from '../versions';
export function addSwcDependencies(tree: Tree) {
return addDependenciesToPackageJson(
tree,
{
export function getSwcDependencies(): {
dependencies: Record<string, string>;
devDependencies: Record<string, string>;
} {
const dependencies = {
'@swc/helpers': swcHelpersVersion,
},
{
};
const devDependencies = {
'@swc/core': swcCoreVersion,
'@swc/cli': swcCliVersion,
};
return { dependencies, devDependencies };
}
);
export function addSwcDependencies(tree: Tree) {
const { dependencies, devDependencies } = getSwcDependencies();
return addDependenciesToPackageJson(tree, dependencies, devDependencies);
}
export function addSwcRegisterDependencies(tree: Tree) {

View File

@ -0,0 +1,102 @@
import type { Tree } from '@nx/devkit';
import { dirname } from 'path';
import type { CompilerOptions, System } from 'typescript';
import { ensureTypescript } from './ensure-typescript';
type CompilerOptionsEnumProps = Pick<
CompilerOptions,
| 'importsNotUsedAsValues'
| 'jsx'
| 'module'
| 'moduleDetection'
| 'moduleResolution'
| 'newLine'
| 'target'
>;
const optionEnumTypeMap: {
[key in keyof CompilerOptionsEnumProps]: keyof typeof ts;
} = {
importsNotUsedAsValues: 'ImportsNotUsedAsValues',
jsx: 'JsxEmit',
module: 'ModuleKind',
moduleDetection: 'ModuleDetectionKind',
moduleResolution: 'ModuleResolutionKind',
newLine: 'NewLineKind',
target: 'ScriptTarget',
};
let ts: typeof import('typescript');
/**
* Cleans up the provided compiler options to only include the options that are
* different or not set in the provided tsconfig file.
*/
export function getNeededCompilerOptionOverrides(
tree: Tree,
compilerOptions: Record<keyof CompilerOptions, any>,
tsConfigPath: string
): Record<keyof CompilerOptions, any> {
if (!ts) {
ts = ensureTypescript();
}
const tsSysFromTree: System = {
...ts.sys,
readFile: (path) => tree.read(path, 'utf-8'),
};
const parsed = ts.parseJsonConfigFileContent(
ts.readConfigFile(tsConfigPath, tsSysFromTree.readFile).config,
tsSysFromTree,
dirname(tsConfigPath)
);
// ModuleKind: { CommonJS: 'commonjs', ... } => ModuleKind: { commonjs: 'CommonJS', ... }
const reversedCompilerOptionsEnumValues = {
JsxEmit: reverseEnum(ts.server.protocol.JsxEmit),
ModuleKind: reverseEnum(ts.server.protocol.ModuleKind),
ModuleResolutionKind: reverseEnum(ts.server.protocol.ModuleResolutionKind),
NewLineKind: reverseEnum(ts.server.protocol.NewLineKind),
ScriptTarget: reverseEnum(ts.server.protocol.ScriptTarget),
};
const matchesValue = (key: keyof CompilerOptions) => {
return (
parsed.options[key] ===
ts[optionEnumTypeMap[key]][compilerOptions[key]] ||
parsed.options[key] ===
ts[optionEnumTypeMap[key]][
reversedCompilerOptionsEnumValues[optionEnumTypeMap[key]][
compilerOptions[key]
]
]
);
};
let result = {};
for (const key of Object.keys(compilerOptions)) {
if (optionEnumTypeMap[key]) {
if (parsed.options[key] === undefined) {
result[key] = compilerOptions[key];
} else if (!matchesValue(key)) {
result[key] = compilerOptions[key];
}
} else if (parsed.options[key] !== compilerOptions[key]) {
result[key] = compilerOptions[key];
}
}
return result;
}
type Entries<T extends object> = { [K in keyof T]: [K, T[K]] }[keyof T];
function reverseEnum<
EnumObj extends Record<keyof EnumObj, string>,
Result = {
[K in EnumObj[keyof EnumObj]]: Extract<Entries<EnumObj>, [any, K]>[0];
}
>(enumObj: EnumObj): Result {
return Object.keys(enumObj).reduce((acc, key) => {
acc[enumObj[key]] = key;
return acc;
}, {} as Result);
}

View File

@ -0,0 +1,63 @@
import { readJson, readNxJson, type Tree } from '@nx/devkit';
import { isUsingPackageManagerWorkspaces } from '../package-manager-workspaces';
export function isUsingTypeScriptPlugin(tree: Tree): boolean {
const nxJson = readNxJson(tree);
return (
nxJson?.plugins?.some((p) =>
typeof p === 'string'
? p === '@nx/js/typescript'
: p.plugin === '@nx/js/typescript'
) ?? false
);
}
export function isUsingTsSolutionSetup(tree: Tree): boolean {
return (
isUsingPackageManagerWorkspaces(tree) &&
isWorkspaceSetupWithTsSolution(tree)
);
}
function isWorkspaceSetupWithTsSolution(tree: Tree): boolean {
if (!tree.exists('tsconfig.base.json') || !tree.exists('tsconfig.json')) {
return false;
}
const tsconfigJson = readJson(tree, 'tsconfig.json');
if (tsconfigJson.extends !== './tsconfig.base.json') {
return false;
}
/**
* New setup:
* - `files` is defined and set to an empty array
* - `references` is defined and set to an empty array
* - `include` is not defined or is set to an empty array
*/
if (
!tsconfigJson.files ||
tsconfigJson.files.length > 0 ||
!tsconfigJson.references ||
!!tsconfigJson.include?.length
) {
return false;
}
const baseTsconfigJson = readJson(tree, 'tsconfig.base.json');
if (
!baseTsconfigJson.compilerOptions ||
!baseTsconfigJson.compilerOptions.composite ||
!baseTsconfigJson.compilerOptions.declaration
) {
return false;
}
const { compilerOptions, ...rest } = baseTsconfigJson;
if (Object.keys(rest).length > 0) {
return false;
}
return true;
}

View File

@ -31,6 +31,7 @@ exports[`lib --unit-test-runner none should not generate test configuration 1`]
{
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"module": "commonjs",
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
@ -53,6 +54,7 @@ exports[`lib nested should create a local tsconfig.json 1`] = `
{
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"module": "commonjs",
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
@ -91,6 +93,7 @@ exports[`lib not nested should create a local tsconfig.json 1`] = `
{
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"module": "commonjs",
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,

View File

@ -1,7 +1,7 @@
import { Tree, readNxJson } from '@nx/devkit';
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope';
import type { LibraryGeneratorSchema as JsLibraryGeneratorSchema } from '@nx/js/src/utils/schema';
import type { LibraryGeneratorSchema as JsLibraryGeneratorSchema } from '@nx/js/src/generators/library/schema';
import { Linter } from '@nx/eslint';
import type { LibraryGeneratorOptions, NormalizedOptions } from '../schema';

View File

@ -625,6 +625,7 @@ describe('app', () => {
module.exports = [
...compat.extends('next', 'next/core-web-vitals'),
...baseConfig,
...nx.configs['flat/react-typescript'],
{ ignores: ['.next/**/*'] },

View File

@ -114,6 +114,7 @@ describe('updateEslint', () => {
module.exports = [
...compat.extends("next", "next/core-web-vitals"),
...baseConfig,
...nx.configs["flat/react-typescript"],
{ ignores: [".next/**/*"] }

View File

@ -131,20 +131,16 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-app',
plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/my-app',
@ -498,20 +494,16 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/myApp',
plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/myApp',

View File

@ -115,7 +115,8 @@ function updateProjectConfigurationInPackageJson(
const packageJson = readJson<PackageJson>(tree, packageJsonFile);
if (packageJson.name === projectConfiguration.name ?? projectName) {
projectConfiguration.name = projectName;
if (packageJson.name === projectConfiguration.name) {
delete projectConfiguration.name;
}

View File

@ -66,6 +66,7 @@ export interface PackageJson {
packages: string[];
};
publishConfig?: Record<string, string>;
files?: string[];
// Nx Project Configuration
nx?: NxProjectPackageJsonConfiguration;

View File

@ -34,14 +34,12 @@ export default defineConfig({
define: {
global: 'window',
},
resolve: {
extensions,
alias: {
'react-native': 'react-native-web',
},
},
build: {
reportCompressedSize: true,
commonjsOptions: { transformMixedEsModules: true },
@ -50,7 +48,6 @@ export default defineConfig({
plugins: [rollupPlugin([/react-native-vector-icons/])],
},
},
server: {
port: 4200,
host: 'localhost',
@ -59,12 +56,10 @@ export default defineConfig({
allow: ['..'],
},
},
preview: {
port: 4300,
host: 'localhost',
},
optimizeDeps: {
esbuildOptions: {
resolveExtensions: extensions,
@ -72,9 +67,7 @@ export default defineConfig({
loader: { '.js': 'jsx' },
},
},
plugins: [react(), nxViteTsPaths()],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],

View File

@ -1,8 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`react app generator (legacy) should setup vite 1`] = `
"
/// <reference types='vitest' />
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
@ -11,26 +10,19 @@ exports[`react app generator (legacy) should setup vite 1`] = `
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-vite-app',
server:{
port: 4200,
host: 'localhost',
},
preview:{
port: 4300,
host: 'localhost',
},
plugins: [react(),
nxViteTsPaths(),
nxCopyAssetsPlugin(['*.md'])],
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../dist/my-vite-app',
emptyOutDir: true,
@ -39,8 +31,6 @@ nxCopyAssetsPlugin(['*.md'])],
transformMixedEsModules: true,
},
},
});"
});
"
`;

View File

@ -94,8 +94,7 @@ module.exports = {
`;
exports[`app --style @emotion/styled should not break if bundler is vite 1`] = `
"
/// <reference types='vitest' />
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
@ -104,26 +103,19 @@ exports[`app --style @emotion/styled should not break if bundler is vite 1`] = `
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-app',
server:{
port: 4200,
host: 'localhost',
},
preview:{
port: 4300,
host: 'localhost',
},
plugins: [react(),
nxViteTsPaths(),
nxCopyAssetsPlugin(['*.md'])],
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../dist/my-app',
emptyOutDir: true,
@ -132,10 +124,8 @@ nxCopyAssetsPlugin(['*.md'])],
transformMixedEsModules: true,
},
},
});"
});
"
`;
exports[`app --style none should exclude styles 1`] = `
@ -180,8 +170,7 @@ module.exports = {
`;
exports[`app --style none should not break if bundler is vite 1`] = `
"
/// <reference types='vitest' />
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
@ -190,26 +179,19 @@ exports[`app --style none should not break if bundler is vite 1`] = `
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-app',
server:{
port: 4200,
host: 'localhost',
},
preview:{
port: 4300,
host: 'localhost',
},
plugins: [react(),
nxViteTsPaths(),
nxCopyAssetsPlugin(['*.md'])],
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../dist/my-app',
emptyOutDir: true,
@ -218,15 +200,12 @@ nxCopyAssetsPlugin(['*.md'])],
transformMixedEsModules: true,
},
},
});"
});
"
`;
exports[`app not nested should add vite types to tsconfigs 1`] = `
"
/// <reference types='vitest' />
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
@ -235,26 +214,19 @@ exports[`app not nested should add vite types to tsconfigs 1`] = `
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-app',
server:{
port: 4200,
host: 'localhost',
},
preview:{
port: 4300,
host: 'localhost',
},
plugins: [react(),
nxViteTsPaths(),
nxCopyAssetsPlugin(['*.md'])],
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../dist/my-app',
emptyOutDir: true,
@ -263,21 +235,19 @@ nxCopyAssetsPlugin(['*.md'])],
transformMixedEsModules: true,
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/my-app',
provider: 'v8',
}
},
});"
});
"
`;
exports[`app not nested should generate files 1`] = `
@ -299,8 +269,7 @@ export default App;
`;
exports[`app not nested should use preview vite types to tsconfigs 1`] = `
"
/// <reference types='vitest' />
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
@ -309,26 +278,19 @@ exports[`app not nested should use preview vite types to tsconfigs 1`] = `
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-app',
server:{
port: 4200,
host: 'localhost',
},
preview:{
port: 4300,
host: 'localhost',
},
plugins: [react(),
nxViteTsPaths(),
nxCopyAssetsPlugin(['*.md'])],
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../dist/my-app',
emptyOutDir: true,
@ -337,21 +299,19 @@ nxCopyAssetsPlugin(['*.md'])],
transformMixedEsModules: true,
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/my-app',
provider: 'v8',
}
},
});"
});
"
`;
exports[`app setup React app with --bundler=vite should setup targets with vite configuration 1`] = `null`;
@ -425,8 +385,7 @@ export default App;
`;
exports[`app should setup vite if bundler is vite 1`] = `
"
/// <reference types='vitest' />
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
@ -435,26 +394,19 @@ exports[`app should setup vite if bundler is vite 1`] = `
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-app',
server:{
port: 4200,
host: 'localhost',
},
preview:{
port: 4300,
host: 'localhost',
},
plugins: [react(),
nxViteTsPaths(),
nxCopyAssetsPlugin(['*.md'])],
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../dist/my-app',
emptyOutDir: true,
@ -463,10 +415,8 @@ nxCopyAssetsPlugin(['*.md'])],
transformMixedEsModules: true,
},
},
});"
});
"
`;
exports[`app should setup webpack 1`] = `

View File

@ -10,20 +10,11 @@ import { defineConfig } from 'vite';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-lib',
plugins: [react(),
nxViteTsPaths(),
nxCopyAssetsPlugin(['*.md']),
],
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md']), ],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
test: {
'watch': false,
'globals': true,
@ -31,14 +22,13 @@ nxCopyAssetsPlugin(['*.md']),
'include': ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
'reporters': ["default"],
'coverage': {"reportsDirectory":"../coverage/my-lib","provider":"v8"},
},
});"
});
"
`;
exports[`lib should add vite types to tsconfigs 1`] = `
"
/// <reference types='vitest' />
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
@ -49,19 +39,11 @@ import * as path from 'path';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-lib',
plugins: [react(),
nxViteTsPaths(),
nxCopyAssetsPlugin(['*.md']),
dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md']), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
@ -85,18 +67,17 @@ dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json')
external: ['react','react-dom','react/jsx-runtime']
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/my-lib',
provider: 'v8',
}
},
});"
});
"
`;

View File

@ -379,21 +379,17 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/test',
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
test: {
setupFiles: ['test-setup.ts'],
watch: false,
globals: true,
environment: 'jsdom',
include: ['./tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/test',
@ -702,21 +698,17 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: './node_modules/.vite/test',
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
test: {
setupFiles: ['test-setup.ts'],
watch: false,
globals: true,
environment: 'jsdom',
include: ['./tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: './coverage/test',

View File

@ -31,14 +31,11 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/test',
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
test: {
setupFiles: ['./src/test-setup.ts'],
watch: false,

View File

@ -95,14 +95,11 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/libs/storybook-test',
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
test: {
setupFiles: ['./src/test-setup.ts'],
watch: false,

View File

@ -12,6 +12,7 @@ import {
writeJson,
} from '@nx/devkit';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { rollupInitGenerator } from '../init/init';
import { RollupExecutorOptions } from '../../executors/rollup/schema';
@ -56,9 +57,12 @@ export async function configurationGenerator(
}
function createRollupConfig(tree: Tree, options: RollupProjectSchema) {
const isUsingTsPlugin = isUsingTsSolutionSetup(tree);
const project = readProjectConfiguration(tree, options.project);
const buildOptions: RollupWithNxPluginOptions = {
outputPath: joinPathFragments(
outputPath: isUsingTsPlugin
? './dist'
: joinPathFragments(
offsetFromRoot(project.root),
'dist',
project.root === '.' ? project.name : project.root
@ -70,21 +74,24 @@ function createRollupConfig(tree: Tree, options: RollupProjectSchema) {
tree.write(
joinPathFragments(project.root, 'rollup.config.js'),
stripIndents`
const { withNx } = require('@nx/rollup/with-nx');
`const { withNx } = require('@nx/rollup/with-nx');
module.exports = withNx({
module.exports = withNx(
{
main: '${buildOptions.main}',
outputPath: '${buildOptions.outputPath}',
tsConfig: '${buildOptions.tsConfig}',
compiler: '${buildOptions.compiler}',
format: ${JSON.stringify(options.format ?? ['esm'])},
assets: [{ input: '.', output: '.', glob:'*.md' }],
}, {
},
{
// Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options
// e.g.
// output: { sourcemap: true },
});`
}
);
`
);
}

View File

@ -11,7 +11,6 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-lib',
plugins: [
nxViteTsPaths(),
nxCopyAssetsPlugin(['*.md']),
@ -20,12 +19,10 @@ export default defineConfig({
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
}),
],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
@ -49,13 +46,11 @@ export default defineConfig({
external: [],
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/my-lib',
@ -184,7 +179,6 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-jest',
plugins: [
react(),
nxViteTsPaths(),
@ -194,12 +188,10 @@ export default defineConfig({
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
}),
],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
@ -239,7 +231,6 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-jest',
plugins: [
react(),
nxViteTsPaths(),
@ -249,12 +240,10 @@ export default defineConfig({
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
}),
],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
@ -278,13 +267,11 @@ export default defineConfig({
external: ['react', 'react-dom', 'react/jsx-runtime'],
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../../coverage/libs/react-lib-nonb-jest',
@ -361,24 +348,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/apps/my-test-react-app',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../../dist/apps/my-test-react-app',
emptyOutDir: true,
@ -420,24 +402,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/apps/my-test-web-app',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../../dist/apps/my-test-web-app',
emptyOutDir: true,
@ -479,24 +456,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/apps/my-test-react-app',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../../dist/apps/my-test-react-app',
emptyOutDir: true,
@ -505,13 +477,11 @@ export default defineConfig({
transformMixedEsModules: true,
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../../coverage/apps/my-test-react-app',

View File

@ -15,7 +15,7 @@ import {
} from '../../utils/test-utils';
import { libraryGenerator as jsLibraryGenerator } from '@nx/js/src/generators/library/library';
import { LibraryGeneratorSchema } from '@nx/js/src/utils/schema';
import { LibraryGeneratorSchema } from '@nx/js/src/generators/library/schema';
describe('@nx/vite:configuration', () => {
let tree: Tree;
@ -273,7 +273,7 @@ describe('@nx/vite:configuration', () => {
...defaultOptions,
name: 'my-lib',
bundler: 'vite',
unitTestRunner: undefined,
unitTestRunner: 'vitest',
projectNameAndRootFormat: 'as-provided',
});

View File

@ -10,14 +10,11 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/apps/my-test-react-app',
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
define: {
'import.meta.vitest': undefined,
},
@ -52,20 +49,16 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/apps/my-test-react-app',
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../../coverage/apps/my-test-react-app',
@ -86,20 +79,16 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-jest',
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../../coverage/libs/react-lib-nonb-jest',

View File

@ -1,153 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ensureViteConfigIsCorrect should add build and test options if defineConfig is empty 1`] = `
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
/// <reference types="vitest" />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
plugins: [react(),
nxViteTsPaths()],build: {
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: 'my-app',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es', 'cjs']
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react', 'react-dom', 'react/jsx-runtime']
}
},test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
},});
"
`;
exports[`ensureViteConfigIsCorrect should add build option but not update test option if test already setup 1`] = `
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: 'my-app',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es', 'cjs']
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react', 'react-dom', 'react/jsx-runtime']
}
},cacheDir: '../../node_modules/.vitest',
plugins: [react(),
nxViteTsPaths(),
],
test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
},
});
"
`;
exports[`ensureViteConfigIsCorrect should add build options if build options don't exist 1`] = `
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: 'my-app',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es', 'cjs']
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react', 'react-dom', 'react/jsx-runtime']
}
},cacheDir: '../../node_modules/.vitest',
plugins: [react(),
nxViteTsPaths(),
],
test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
},
});
"
`;
exports[`ensureViteConfigIsCorrect should add build options if defineConfig is not used 1`] = `
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default {
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: 'my-app',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es', 'cjs']
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react', 'react-dom', 'react/jsx-runtime']
}
},test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
},
plugins: [react(),
nxViteTsPaths(),
],
};
"
`;
exports[`ensureViteConfigIsCorrect should add build options if it is using conditional config - do nothing for test 1`] = `
"
/// <reference types="vitest" />
@ -171,105 +23,4 @@ exports[`ensureViteConfigIsCorrect should add build options if it is using condi
"
`;
exports[`ensureViteConfigIsCorrect should add new build options if some build options already exist 1`] = `
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
cacheDir: '../../node_modules/.vitest',
plugins: [react(),
nxViteTsPaths(),
],
test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
},
build: {
'my': 'option',
'lib': {"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es","cjs"]},
'rollupOptions': {"external":["react","react-dom","react/jsx-runtime"]},
}
});
"
`;
exports[`ensureViteConfigIsCorrect should not do anything if cannot understand syntax of vite config 1`] = `"console.log('Unknown syntax')"`;
exports[`ensureViteConfigIsCorrect should not do anything if project has everything setup already 1`] = `
"
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import dts from 'vite-plugin-dts';
import { joinPathFragments } from '@nx/devkit';
export default defineConfig({
cacheDir: '../../node_modules/.vitest',
plugins: [dts({ entryRoot: 'src', tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'), skipDiagnostics: true }),
react(),
nxViteTsPaths(),
],
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: 'pure-libs-react-vite',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es', 'cjs'],
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react', 'react-dom', 'react/jsx-runtime'],
},
},
test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
},
});
"
`;
exports[`ensureViteConfigIsCorrect should update both test and build options - keep existing settings 1`] = `
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
plugins: [react(),
nxViteTsPaths(),
],
test: {
'my': 'option',
'globals': true,
'environment': "jsdom",
'include': ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
},
build: {
'my': 'option',
'lib': {"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es","cjs"]},
'rollupOptions': {"external":["react","react-dom","react/jsx-runtime"]},
}
});
"
`;

View File

@ -1,4 +1,5 @@
import {
addProjectConfiguration,
readProjectConfiguration,
Tree,
updateProjectConfiguration,
@ -7,12 +8,14 @@ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import {
findExistingJsBuildTargetInProject,
getViteConfigPathForProject,
createOrEditViteConfig,
} from './generator-utils';
import {
mockReactAppGenerator,
mockViteReactAppGenerator,
mockAngularAppGenerator,
} from './test-utils';
describe('generator utils', () => {
let tree: Tree;
@ -114,4 +117,135 @@ describe('generator utils', () => {
});
});
});
describe('createOrEditViteConfig', () => {
it('should generate formatted config', () => {
addProjectConfiguration(tree, 'myproj', {
name: 'myproj',
root: 'myproj',
});
createOrEditViteConfig(
tree,
{
project: 'myproj',
inSourceTests: true,
includeVitest: true,
includeLib: true,
},
false
);
expect(tree.read('myproj/vite.config.ts', 'utf-8'))
.toMatchInlineSnapshot(`
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
import * as path from 'path';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/myproj',
plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md']), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: '../dist/myproj',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: 'myproj',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es', 'cjs']
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: []
},
},
define: {
'import.meta.vitest': undefined
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
includeSource: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/myproj',
provider: 'v8',
}
},
});
"
`);
});
it('should generate formatted config without library and in-source tests', () => {
addProjectConfiguration(tree, 'myproj', {
name: 'myproj',
root: 'myproj',
});
createOrEditViteConfig(
tree,
{
project: 'myproj',
inSourceTests: false,
includeVitest: false,
includeLib: false,
},
false
);
expect(tree.read('myproj/vite.config.ts', 'utf-8'))
.toMatchInlineSnapshot(`
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/myproj',
server:{
port: 4200,
host: 'localhost',
},
preview:{
port: 4300,
host: 'localhost',
},
plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../dist/myproj',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
},
});
"
`);
});
});
});

View File

@ -9,12 +9,13 @@ import {
updateProjectConfiguration,
writeJson,
} from '@nx/devkit';
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { ViteBuildExecutorOptions } from '../executors/build/schema';
import { VitePreviewServerExecutorOptions } from '../executors/preview-server/schema';
import { VitestExecutorOptions } from '../executors/test/schema';
import { ViteConfigurationGeneratorSchema } from '../generators/configuration/schema';
import { ensureViteConfigIsCorrect } from './vite-config-edit-utils';
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
export type Target = 'build' | 'serve' | 'test' | 'preview';
export type TargetFlags = Partial<Record<Target, boolean>>;
@ -362,16 +363,17 @@ export function createOrEditViteConfig(
? `${projectRoot}/vitest.config.ts`
: `${projectRoot}/vite.config.ts`;
const buildOutDir =
projectRoot === '.'
const isUsingTsPlugin = isUsingTsSolutionSetup(tree);
const buildOutDir = isUsingTsPlugin
? './dist'
: projectRoot === '.'
? `./dist/${options.project}`
: `${offsetFromRoot(projectRoot)}dist/${projectRoot}`;
const buildOption = onlyVitest
? ''
: options.includeLib
? `
// Configuration for building your library.
? ` // Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: '${buildOutDir}',
@ -394,16 +396,14 @@ export function createOrEditViteConfig(
external: [${options.rollupOptionsExternal ?? ''}]
},
},`
: `
build: {
: ` build: {
outDir: '${buildOutDir}',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
},
`;
},`;
const imports: string[] = options.imports ? options.imports : [];
@ -436,10 +436,10 @@ export function createOrEditViteConfig(
watch: false,
globals: true,
environment: '${options.testEnvironment ?? 'jsdom'}',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
${
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],${
options.inSourceTests
? `includeSource: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],`
? `
includeSource: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],`
: ''
}
reporters: ['default'],
@ -462,8 +462,7 @@ export function createOrEditViteConfig(
? ''
: options.includeLib
? ''
: `
server:{
: ` server:{
port: 4200,
host: 'localhost',
},`;
@ -472,14 +471,12 @@ export function createOrEditViteConfig(
? ''
: options.includeLib
? ''
: `
preview:{
: ` preview:{
port: 4300,
host: 'localhost',
},`;
const workerOption = `
// Uncomment this if you are using workers.
const workerOption = ` // Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },`;
@ -510,8 +507,7 @@ export function createOrEditViteConfig(
return;
}
viteConfigContent = `
/// <reference types='vitest' />
viteConfigContent = `/// <reference types='vitest' />
import { defineConfig } from 'vite';
${imports.join(';\n')}${imports.length ? ';' : ''}
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
@ -519,20 +515,26 @@ export function createOrEditViteConfig(
export default defineConfig({
root: __dirname,
${cacheDir}
${devServerOption}
${previewServerOption}
plugins: [${plugins.join(',\n')}],
${workerOption}
${buildOption}
${defineOption}
${testOption}
});`;
${printOptions(
cacheDir,
devServerOption,
previewServerOption,
` plugins: [${plugins.join(', ')}],`,
workerOption,
buildOption,
defineOption,
testOption
)}
});
`.replace(/\s+(?=(\n|$))/gm, '\n');
tree.write(viteConfigPath, viteConfigContent);
}
function printOptions(...options: string[]): string {
return options.filter(Boolean).join('\n');
}
export function normalizeViteConfigFilePathWithTree(
tree: Tree,
projectRoot: string,

View File

@ -47,7 +47,43 @@ describe('ensureViteConfigIsCorrect', () => {
'PropertyAssignment:has(Identifier[name="build"])'
);
expect(buildNode).toBeDefined();
expect(appFileContent).toMatchSnapshot();
expect(appFileContent).toMatchInlineSnapshot(`
"import dts from 'vite-plugin-dts';
import { joinPathFragments } from '@nx/devkit'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: 'my-app',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es', 'cjs']
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react', 'react-dom', 'react/jsx-runtime']
}
},cacheDir: '../../node_modules/.vitest',
plugins: [react(), nxViteTsPaths(), ],
test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
},
});
"
`);
});
it('should add new build options if some build options already exist', () => {
@ -71,7 +107,32 @@ describe('ensureViteConfigIsCorrect', () => {
'PropertyAssignment:has(Identifier[name="build"])'
);
expect(buildNode).toBeDefined();
expect(appFileContent).toMatchSnapshot();
expect(appFileContent).toMatchInlineSnapshot(`
"import dts from 'vite-plugin-dts';
import { joinPathFragments } from '@nx/devkit'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
cacheDir: '../../node_modules/.vitest',
plugins: [react(), nxViteTsPaths(), ],
test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
},
build: {
'my': 'option',
'lib': {"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es","cjs"]},
'rollupOptions': {"external":["react","react-dom","react/jsx-runtime"]},
}
});
"
`);
});
it('should add build and test options if defineConfig is empty', () => {
@ -95,7 +156,39 @@ describe('ensureViteConfigIsCorrect', () => {
'PropertyAssignment:has(Identifier[name="build"])'
);
expect(buildNode).toBeDefined();
expect(appFileContent).toMatchSnapshot();
expect(appFileContent).toMatchInlineSnapshot(`
"import dts from 'vite-plugin-dts';
import { joinPathFragments } from '@nx/devkit'
/// <reference types="vitest" />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
plugins: [react(), nxViteTsPaths()],build: {
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: 'my-app',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es', 'cjs']
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react', 'react-dom', 'react/jsx-runtime']
}
},test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
},});
"
`);
});
it('should add build options if it is using conditional config - do nothing for test', () => {
@ -143,7 +236,39 @@ describe('ensureViteConfigIsCorrect', () => {
'PropertyAssignment:has(Identifier[name="build"])'
);
expect(buildNode).toBeDefined();
expect(appFileContent).toMatchSnapshot();
expect(appFileContent).toMatchInlineSnapshot(`
"import dts from 'vite-plugin-dts';
import { joinPathFragments } from '@nx/devkit'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default {
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: 'my-app',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es', 'cjs']
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react', 'react-dom', 'react/jsx-runtime']
}
},test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
},
plugins: [react(), nxViteTsPaths(), ],
};
"
`);
});
it('should not do anything if cannot understand syntax of vite config', () => {
@ -179,7 +304,44 @@ describe('ensureViteConfigIsCorrect', () => {
{ build: true, test: true, serve: true }
);
const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8');
expect(appFileContent).toMatchSnapshot();
expect(appFileContent).toMatchInlineSnapshot(`
"
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import dts from 'vite-plugin-dts';
import { joinPathFragments } from '@nx/devkit';
export default defineConfig({
cacheDir: '../../node_modules/.vitest',
plugins: [dts({ entryRoot: 'src', tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'), skipDiagnostics: true }), react(), nxViteTsPaths(), ],
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: 'pure-libs-react-vite',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es', 'cjs'],
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react', 'react-dom', 'react/jsx-runtime'],
},
},
test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
},
});
"
`);
});
it('should add build option but not update test option if test already setup', () => {
@ -197,7 +359,44 @@ describe('ensureViteConfigIsCorrect', () => {
{ build: false, test: true, serve: true }
);
const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8');
expect(appFileContent).toMatchSnapshot();
expect(appFileContent).toMatchInlineSnapshot(`
"import dts from 'vite-plugin-dts';
import { joinPathFragments } from '@nx/devkit'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: 'my-app',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es', 'cjs']
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react', 'react-dom', 'react/jsx-runtime']
}
},cacheDir: '../../node_modules/.vitest',
plugins: [react(), nxViteTsPaths(), ],
test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
},
});
"
`);
});
it('should update both test and build options - keep existing settings', () => {
@ -215,6 +414,31 @@ describe('ensureViteConfigIsCorrect', () => {
{ build: false, test: false, serve: true }
);
const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8');
expect(appFileContent).toMatchSnapshot();
expect(appFileContent).toMatchInlineSnapshot(`
"import dts from 'vite-plugin-dts';
import { joinPathFragments } from '@nx/devkit'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
plugins: [react(), nxViteTsPaths(), ],
test: {
'my': 'option',
'globals': true,
'environment': "jsdom",
'include': ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
},
build: {
'my': 'option',
'lib': {"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es","cjs"]},
'rollupOptions': {"external":["react","react-dom","react/jsx-runtime"]},
}
});
"
`);
});
});

View File

@ -91,19 +91,20 @@ function handleBuildOrTestNode(
propName !== 'reportsDirectory' &&
propName !== 'provider'
) {
// NOTE: Watch for formatting.
updatedPropsString += ` '${propName}': ${prop.initializer.getText()},\n`;
}
}
for (const [propName, propValue] of Object.entries(
configContentObject
)) {
// NOTE: Watch for formatting.
updatedPropsString += ` '${propName}': ${JSON.stringify(
propValue
)},\n`;
}
return `${name}: {
${updatedPropsString}
}`;
${updatedPropsString} }`;
}
);
} else {
@ -324,7 +325,7 @@ function handlePluginNode(
const existingPluginNodes = found?.[0].elements ?? [];
for (const plugin of existingPluginNodes) {
updatedPluginsString += `${plugin.getText()},\n`;
updatedPluginsString += `${plugin.getText()}, `;
}
for (const plugin of plugins) {
@ -333,7 +334,7 @@ function handlePluginNode(
node.getText().includes(plugin)
)
) {
updatedPluginsString += `${plugin},\n`;
updatedPluginsString += `${plugin}, `;
}
}
@ -371,7 +372,7 @@ function handlePluginNode(
{
type: ChangeType.Insert,
index: propertyAssignments[0].getStart(),
text: `plugins: [${plugins.join(',\n')}],`,
text: `plugins: [${plugins.join(', ')}],`,
},
]);
writeFile = true;
@ -380,7 +381,7 @@ function handlePluginNode(
{
type: ChangeType.Insert,
index: foundDefineConfig[0].getStart() + 14,
text: `plugins: [${plugins.join(',\n')}],`,
text: `plugins: [${plugins.join(', ')}],`,
},
]);
writeFile = true;
@ -400,7 +401,7 @@ function handlePluginNode(
{
type: ChangeType.Insert,
index: startOfObject + 1,
text: `plugins: [${plugins.join(',\n')}],`,
text: `plugins: [${plugins.join(', ')}],`,
},
]);
writeFile = true;
@ -411,7 +412,7 @@ function handlePluginNode(
}
if (writeFile) {
const filteredImports = filterImport(appFileContent, imports);
return filteredImports.join(';') + '\n' + appFileContent;
return filteredImports.join(';\n') + '\n' + appFileContent;
}
}

View File

@ -49,24 +49,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/test',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../dist/test',
emptyOutDir: true,
@ -75,13 +70,11 @@ export default defineConfig({
transformMixedEsModules: true,
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/test',
@ -202,24 +195,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/test',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../dist/test',
emptyOutDir: true,
@ -228,13 +216,11 @@ export default defineConfig({
transformMixedEsModules: true,
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/test',

View File

@ -12,7 +12,6 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-lib',
plugins: [
vue(),
nxViteTsPaths(),
@ -22,12 +21,10 @@ export default defineConfig({
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
}),
],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
@ -51,13 +48,11 @@ export default defineConfig({
external: [],
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/my-lib',
@ -108,7 +103,6 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-lib',
plugins: [
vue(),
nxViteTsPaths(),
@ -118,12 +112,10 @@ export default defineConfig({
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
}),
],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
@ -147,13 +139,11 @@ export default defineConfig({
external: [],
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/my-lib',

View File

@ -1527,20 +1527,26 @@ exports[`@nx/workspace:generateWorkspaceFiles README.md Nx Cloud (github) should
[Click here to finish setting up your workspace!](https://test.nx.app/connect?source=readme&token=TEST_NX_CLOUD_TOKEN)
## Generate a library
\`\`\`sh
npx nx g @nx/js:lib packages/pkg1 --publishable --importPath=@my-org/pkg1
\`\`\`
## Run tasks
To run tasks with Nx use:
To build the library use:
\`\`\`sh
npx nx build pkg1
\`\`\`
To run any task with Nx use:
\`\`\`sh
npx nx <target> <project-name>
\`\`\`
For example:
\`\`\`sh
npx nx build myproject
\`\`\`
These targets are either [inferred automatically](https://nx.dev/concepts/inferred-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or defined in the \`project.json\` or \`package.json\` files.
[More about running tasks in the docs &raquo;](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
@ -1557,30 +1563,6 @@ Pass \`--dry-run\` to see what would happen without actually releasing the libra
[Learn more about Nx release &raquo;](hhttps://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Add new projects
While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature.
To install a new plugin you can use the \`nx add\` command. Here's an example of adding the React plugin:
\`\`\`sh
npx nx add @nx/react
\`\`\`
Use the plugin's generator to create new projects. For example, to create a new React app or library:
\`\`\`sh
# Genenerate an app
npx nx g @nx/react:app demo
# Generate a library
npx nx g @nx/react:lib some-lib
\`\`\`
You can use \`npx nx list\` to get a list of installed plugins. Then, run \`npx nx list <plugin-name>\` to learn about more specific capabilities of a particular plugin. Alternatively, [install Nx Console](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) to browse plugins and generators in your IDE.
[Learn more about Nx plugins &raquo;](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry &raquo;](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
[Learn more about Nx on CI](https://nx.dev/ci/intro/ci-with-nx#ready-get-started-with-your-provider?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Install Nx Console
@ -3814,20 +3796,26 @@ exports[`@nx/workspace:generateWorkspaceFiles README.md Nx Cloud (skip) should b
[Learn more about this workspace setup and its capabilities](https://nx.dev/nx-api/js?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or run \`npx nx graph\` to visually explore what was created. Now, let's get you up to speed!
## Generate a library
\`\`\`sh
npx nx g @nx/js:lib packages/pkg1 --publishable --importPath=@my-org/pkg1
\`\`\`
## Run tasks
To run tasks with Nx use:
To build the library use:
\`\`\`sh
npx nx build pkg1
\`\`\`
To run any task with Nx use:
\`\`\`sh
npx nx <target> <project-name>
\`\`\`
For example:
\`\`\`sh
npx nx build myproject
\`\`\`
These targets are either [inferred automatically](https://nx.dev/concepts/inferred-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or defined in the \`project.json\` or \`package.json\` files.
[More about running tasks in the docs &raquo;](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
@ -3844,30 +3832,6 @@ Pass \`--dry-run\` to see what would happen without actually releasing the libra
[Learn more about Nx release &raquo;](hhttps://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Add new projects
While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature.
To install a new plugin you can use the \`nx add\` command. Here's an example of adding the React plugin:
\`\`\`sh
npx nx add @nx/react
\`\`\`
Use the plugin's generator to create new projects. For example, to create a new React app or library:
\`\`\`sh
# Genenerate an app
npx nx g @nx/react:app demo
# Generate a library
npx nx g @nx/react:lib some-lib
\`\`\`
You can use \`npx nx list\` to get a list of installed plugins. Then, run \`npx nx list <plugin-name>\` to learn about more specific capabilities of a particular plugin. Alternatively, [install Nx Console](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) to browse plugins and generators in your IDE.
[Learn more about Nx plugins &raquo;](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry &raquo;](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Set up CI!
### Step 1
@ -5836,20 +5800,26 @@ exports[`@nx/workspace:generateWorkspaceFiles README.md Nx Cloud (yes) should be
[Click here to finish setting up your workspace!](https://test.nx.app/connect?source=readme&token=TEST_NX_CLOUD_TOKEN)
## Generate a library
\`\`\`sh
npx nx g @nx/js:lib packages/pkg1 --publishable --importPath=@my-org/pkg1
\`\`\`
## Run tasks
To run tasks with Nx use:
To build the library use:
\`\`\`sh
npx nx build pkg1
\`\`\`
To run any task with Nx use:
\`\`\`sh
npx nx <target> <project-name>
\`\`\`
For example:
\`\`\`sh
npx nx build myproject
\`\`\`
These targets are either [inferred automatically](https://nx.dev/concepts/inferred-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or defined in the \`project.json\` or \`package.json\` files.
[More about running tasks in the docs &raquo;](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
@ -5866,30 +5836,6 @@ Pass \`--dry-run\` to see what would happen without actually releasing the libra
[Learn more about Nx release &raquo;](hhttps://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Add new projects
While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature.
To install a new plugin you can use the \`nx add\` command. Here's an example of adding the React plugin:
\`\`\`sh
npx nx add @nx/react
\`\`\`
Use the plugin's generator to create new projects. For example, to create a new React app or library:
\`\`\`sh
# Genenerate an app
npx nx g @nx/react:app demo
# Generate a library
npx nx g @nx/react:lib some-lib
\`\`\`
You can use \`npx nx list\` to get a list of installed plugins. Then, run \`npx nx list <plugin-name>\` to learn about more specific capabilities of a particular plugin. Alternatively, [install Nx Console](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) to browse plugins and generators in your IDE.
[Learn more about Nx plugins &raquo;](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry &raquo;](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
[Learn more about Nx on CI](https://nx.dev/ci/intro/ci-with-nx#ready-get-started-with-your-provider?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Install Nx Console

View File

@ -12,9 +12,9 @@ Run `npx nx graph` to visually explore what got created. Now, let's get you up t
[Click here to finish setting up your workspace!](<%= nxCloudOnboardingUrl %>)
<% } %>
<% } %><% if (!isEmptyRepo) { %>
## Run tasks
<% if (!isEmptyRepo) { %><% if (isJsStandalone) { %>
<% if (isJsStandalone) { %>
To build the library use:
```sh
@ -38,7 +38,29 @@ To see all available targets to run for a project, run:
```sh
npx nx show project <%= appName %>
```
<% } %><% } else { %>
<% } %><% } else if (isTsPreset) { %>
## Generate a library
```sh
npx nx g @nx/js:lib packages/pkg1 --publishable --importPath=@my-org/pkg1
```
## Run tasks
To build the library use:
```sh
npx nx build pkg1
```
To run any task with Nx use:
```sh
npx nx <target> <project-name>
```
<% } else { %>
## Run tasks
To run tasks with Nx use:
```sh
@ -66,7 +88,25 @@ npx nx release
Pass `--dry-run` to see what would happen without actually releasing the library.
[Learn more about Nx release &raquo;](hhttps://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
<% } %><% if (!isJsStandalone) { %>
<% } %><% if (isTsPreset && isUsingNewTsSolutionSetup) { %>
## Keep TypeScript project references up to date
Nx automatically updates TypeScript [project references](https://www.typescriptlang.org/docs/handbook/project-references.html) in `tsconfig.json` files to ensure they remain accurate based on your project dependencies (`import` or `require` statements). This sync is automatically done when running tasks such as `build` or `typecheck`, which require updated references to function correctly.
To manually trigger the process to sync the project graph dependencies information to the TypeScript project references, run the following command:
```sh
npx nx sync
```
You can enforce that the TypeScript project references are always in the correct state when running in CI by adding a step to your CI job configuration that runs the following command:
```sh
npx nx sync:check
```
[Learn more about nx sync](https://nx.dev/reference/nx-commands#sync)
<% } %><% if (!isJsStandalone && !isTsPreset) { %>
## Add new projects
While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature.

View File

@ -84,6 +84,7 @@ export function generatePreset(host: Tree, opts: NormalizedSchema) {
opts.ssr ? `--ssr` : null,
opts.prefix !== undefined ? `--prefix=${opts.prefix}` : null,
opts.nxCloudToken ? `--nxCloudToken=${opts.nxCloudToken}` : null,
opts.formatter ? `--formatter=${opts.formatter}` : null,
].filter((e) => !!e);
}
}

View File

@ -213,13 +213,13 @@ export async function generateWorkspaceFiles(
}
function setPresetProperty(tree: Tree, options: NormalizedSchema) {
updateJson(tree, join(options.directory, 'nx.json'), (json) => {
if (options.preset === Preset.NPM) {
updateJson(tree, join(options.directory, 'nx.json'), (json) => {
addPropertyWithStableKeys(json, 'extends', 'nx/presets/npm.json');
}
return json;
});
}
}
function createNxJson(
tree: Tree,
@ -274,7 +274,10 @@ function createFiles(tree: Tree, options: NormalizedSchema) {
options.preset === Preset.RemixStandalone ||
options.preset === Preset.TsStandalone
? './files-root-app'
: options.preset === Preset.NPM
: (options.preset === Preset.TS &&
process.env.NX_ADD_PLUGINS !== 'false' &&
process.env.NX_ADD_TS_PLUGIN === 'true') ||
options.preset === Preset.NPM
? './files-package-based-repo'
: './files-integrated-repo';
generateFiles(tree, join(__dirname, filesDirName), options.directory, {
@ -309,6 +312,10 @@ async function createReadme(
generateFiles(tree, join(__dirname, './files-readme'), directory, {
formattedNames,
isJsStandalone: preset === Preset.TsStandalone,
isTsPreset: preset === Preset.TS,
isUsingNewTsSolutionSetup:
process.env.NX_ADD_PLUGINS !== 'false' &&
process.env.NX_ADD_TS_PLUGIN === 'true',
isEmptyRepo: !appName,
appName,
generateAppCmd: presetInfo.generateAppCmd,
@ -405,7 +412,12 @@ function normalizeOptions(options: NormalizedSchema) {
}
function setUpWorkspacesInPackageJson(tree: Tree, options: NormalizedSchema) {
if (options.preset === Preset.NPM) {
if (
options.preset === Preset.NPM ||
(options.preset === Preset.TS &&
process.env.NX_ADD_PLUGINS !== 'false' &&
process.env.NX_ADD_TS_PLUGIN === 'true')
) {
if (options.packageManager === 'pnpm') {
tree.write(
join(options.directory, 'pnpm-workspace.yaml'),

View File

@ -37,6 +37,7 @@ interface Schema {
prefix?: string;
useGitHub?: boolean;
nxCloud?: 'yes' | 'skip' | 'circleci' | 'github';
formatter?: 'none' | 'prettier';
}
export interface NormalizedSchema extends Schema {

View File

@ -86,6 +86,12 @@
"prefix": {
"description": "The prefix to use for Angular component and directive selectors.",
"type": "string"
},
"formatter": {
"description": "The tool to use for code formatting.",
"type": "string",
"enum": ["none", "prettier"],
"default": "none"
}
},
"additionalProperties": true

View File

@ -84,24 +84,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: './node_modules/.vite/proj',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: './dist/proj',
emptyOutDir: true,
@ -110,13 +105,11 @@ export default defineConfig({
transformMixedEsModules: true,
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: './coverage/proj',
@ -176,24 +169,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/apps/proj',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../../dist/apps/proj',
emptyOutDir: true,
@ -202,13 +190,11 @@ export default defineConfig({
transformMixedEsModules: true,
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../../coverage/apps/proj',
@ -229,24 +215,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: './node_modules/.vite/proj',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: './dist/proj',
emptyOutDir: true,
@ -255,13 +236,11 @@ export default defineConfig({
transformMixedEsModules: true,
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: './coverage/proj',

View File

@ -275,7 +275,12 @@ async function createPreset(tree: Tree, options: Schema) {
});
} else if (options.preset === Preset.TS) {
const { initGenerator } = require('@nx' + '/js');
return initGenerator(tree, {});
return initGenerator(tree, {
formatter: options.formatter,
addTsPlugin:
process.env.NX_ADD_PLUGINS !== 'false' &&
process.env.NX_ADD_TS_PLUGIN === 'true',
});
} else if (options.preset === Preset.TsStandalone) {
const { libraryGenerator } = require('@nx' + '/js');
return libraryGenerator(tree, {

View File

@ -6,6 +6,7 @@ export interface Schema {
preset: Preset;
style?: string;
linter?: string;
formatter?: 'none' | 'prettier';
standaloneConfig?: boolean;
framework?: string;
packageManager?: PackageManager;