fix(js): generate pacakge.json for non-buildable nest and expo libs (#29891)

This PR fixes a couple of issues for TS solution setup:
1. Expo library should generate with correct `package.json` file (e.g.
`exports` maps either to source or dist). See [spec
file](https://github.com/nrwl/nx/pull/29891/files#diff-ae2eb3d10d58786c17aa21f5603043b68043faaebafaec77912f3d69ac0c5295).
2. Nest library should generate `package.json` when non-buildable. See
[spec
file](https://github.com/nrwl/nx/pull/29891/files#diff-368467bcd2215def98ef14aaff9dcb056a915b0a724d0eb857f3a0badef8b40a).

**Notes:**
- Also removed an unsupported `standaloneConfig` option from
`@nx/nest:lib` generator. This was removed a long time ago in other
generators.
- Expo lib generator isn't crystalized when using Rollup for build. This
is a separate issue and we'll handle it in another task.

## Current Behavior
- Non-buildable Expo libs generate without `exports`
- Buildable Expo libs fail to generate due to error
- Non-buildable Nest libs do not generate `package.json`

## Expected Behavior
Expo and Nest libs generate correct `package.json` files depending on
whether they are build or non-buildable.

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

Fixes #
This commit is contained in:
Jack Hsu 2025-02-05 16:27:56 -05:00 committed by GitHub
parent d62b94f6a7
commit 8bd0bcdd97
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 168 additions and 38 deletions

View File

@ -118,12 +118,6 @@
"type": "boolean", "type": "boolean",
"default": true "default": true
}, },
"standaloneConfig": {
"description": "Split the project configuration into <projectRoot>/project.json rather than including it inside workspace.json",
"type": "boolean",
"default": true,
"x-deprecated": "Nx only supports standaloneConfig"
},
"setParserOptionsProject": { "setParserOptionsProject": {
"type": "boolean", "type": "boolean",
"description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.", "description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.",

View File

@ -1,5 +1,5 @@
{ {
"name": "<%= name %>", "name": "<%= projectName %>",
"version": "0.0.1", "version": "0.0.1",
"main": "<%= appMain %>" "main": "<%= appMain %>"
} }

View File

@ -5,10 +5,12 @@ import {
} from '@nx/devkit/src/generators/project-name-and-root-utils'; } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { Schema } from '../schema'; import { Schema } from '../schema';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
export interface NormalizedSchema extends Schema { export interface NormalizedSchema extends Schema {
name: string; name: string;
fileName: string; fileName: string;
projectName: string;
projectRoot: string; projectRoot: string;
routePath: string; routePath: string;
parsedTags: string[]; parsedTags: string[];
@ -43,16 +45,20 @@ export async function normalizeOptions(
: []; : [];
const appMain = options.js ? 'src/index.js' : 'src/index.ts'; const appMain = options.js ? 'src/index.js' : 'src/index.ts';
const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host);
const normalized: NormalizedSchema = { const normalized: NormalizedSchema = {
...options, ...options,
fileName: projectName, fileName: projectName,
routePath: `/${projectNames.projectSimpleName}`, routePath: `/${projectNames.projectSimpleName}`,
name: projectName, name: projectName,
projectName: isUsingTsSolutionConfig
? getImportPath(host, projectName)
: projectName,
projectRoot, projectRoot,
parsedTags, parsedTags,
importPath, importPath,
appMain, appMain,
isUsingTsSolutionConfig: isUsingTsSolutionSetup(host), isUsingTsSolutionConfig,
}; };
return normalized; return normalized;

View File

@ -504,6 +504,22 @@ describe('lib', () => {
] ]
`); `);
// Make sure keys are in idiomatic order // Make sure keys are in idiomatic order
expect(readJson(appTree, 'my-lib/package.json')).toMatchInlineSnapshot(`
{
"exports": {
".": {
"default": "./src/index.ts",
"import": "./src/index.ts",
"types": "./src/index.ts",
},
"./package.json": "./package.json",
},
"main": "./src/index.ts",
"name": "@proj/my-lib",
"types": "./src/index.ts",
"version": "0.0.1",
}
`);
expect(Object.keys(readJson(appTree, 'my-lib/package.json'))) expect(Object.keys(readJson(appTree, 'my-lib/package.json')))
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
[ [
@ -511,7 +527,7 @@ describe('lib', () => {
"version", "version",
"main", "main",
"types", "types",
"nx", "exports",
] ]
`); `);
expect(readJson(appTree, 'my-lib/tsconfig.json')).toMatchInlineSnapshot(` expect(readJson(appTree, 'my-lib/tsconfig.json')).toMatchInlineSnapshot(`
@ -608,5 +624,69 @@ describe('lib', () => {
} }
`); `);
}); });
it('should generate buildable library', async () => {
await expoLibraryGenerator(appTree, {
...defaultSchema,
buildable: true,
strict: false,
});
expect(readJson(appTree, 'my-lib/package.json')).toMatchInlineSnapshot(`
{
"exports": {
".": {
"default": "./dist/index.esm.js",
"import": "./dist/index.esm.js",
"types": "./dist/index.esm.d.ts",
},
"./package.json": "./package.json",
},
"main": "./dist/index.esm.js",
"module": "./dist/index.esm.js",
"name": "@proj/my-lib",
"nx": {
"projectType": "library",
"sourceRoot": "my-lib/src",
"tags": [],
"targets": {
"build": {
"executor": "@nx/rollup:rollup",
"options": {
"assets": [
{
"glob": "my-lib/README.md",
"input": ".",
"output": ".",
},
],
"entryFile": "my-lib/src/index.ts",
"external": [
"react/jsx-runtime",
"react-native",
"react",
"react-dom",
],
"outputPath": "dist/my-lib",
"project": "my-lib/package.json",
"rollupConfig": "@nx/react/plugins/bundle-rollup",
"tsConfig": "my-lib/tsconfig.lib.json",
},
"outputs": [
"{options.outputPath}",
],
},
},
},
"peerDependencies": {
"react": "~18.3.1",
"react-native": "0.76.3",
},
"type": "module",
"types": "./dist/index.esm.d.ts",
"version": "0.0.1",
}
`);
});
}); });
}); });

View File

@ -40,7 +40,6 @@ import {
addProjectToTsSolutionWorkspace, addProjectToTsSolutionWorkspace,
updateTsconfigFiles, updateTsconfigFiles,
} from '@nx/js/src/utils/typescript/ts-solution-setup'; } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields'; import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields';
export async function expoLibraryGenerator( export async function expoLibraryGenerator(
@ -72,6 +71,10 @@ export async function expoLibraryGeneratorInternal(
); );
} }
if (options.isUsingTsSolutionConfig) {
addProjectToTsSolutionWorkspace(host, options.projectRoot);
}
const initTask = await init(host, { ...options, skipFormat: true }); const initTask = await init(host, { ...options, skipFormat: true });
tasks.push(initTask); tasks.push(initTask);
if (!options.skipPackageJson) { if (!options.skipPackageJson) {
@ -88,7 +91,7 @@ export async function expoLibraryGeneratorInternal(
const lintTask = await addLinting(host, { const lintTask = await addLinting(host, {
...options, ...options,
projectName: options.name, projectName: options.projectName,
tsConfigPaths: [ tsConfigPaths: [
joinPathFragments(options.projectRoot, 'tsconfig.lib.json'), joinPathFragments(options.projectRoot, 'tsconfig.lib.json'),
], ],
@ -98,7 +101,7 @@ export async function expoLibraryGeneratorInternal(
const jestTask = await addJest( const jestTask = await addJest(
host, host,
options.unitTestRunner, options.unitTestRunner,
options.name, options.projectName,
options.projectRoot, options.projectRoot,
options.js, options.js,
options.skipPackageJson, options.skipPackageJson,
@ -134,10 +137,6 @@ export async function expoLibraryGeneratorInternal(
: undefined : undefined
); );
if (options.isUsingTsSolutionConfig) {
addProjectToTsSolutionWorkspace(host, options.projectRoot);
}
sortPackageJsonFields(host, options.projectRoot); sortPackageJsonFields(host, options.projectRoot);
if (!options.skipFormat) { if (!options.skipFormat) {
@ -169,23 +168,35 @@ async function addProject(
}; };
if (options.isUsingTsSolutionConfig) { if (options.isUsingTsSolutionConfig) {
const packageName = getImportPath(host, options.name);
const sourceEntry = !options.buildable const sourceEntry = !options.buildable
? options.js ? options.js
? './src/index.js' ? './src/index.js'
: './src/index.ts' : './src/index.ts'
: undefined; : undefined;
writeJson(host, joinPathFragments(options.projectRoot, 'package.json'), { writeJson(host, joinPathFragments(options.projectRoot, 'package.json'), {
name: packageName, name: options.projectName,
version: '0.0.1', version: '0.0.1',
main: sourceEntry, main: sourceEntry,
types: sourceEntry, types: sourceEntry,
nx: { // For buildable libraries, the entries are configured by the bundler (i.e. Rollup).
name: packageName === options.name ? undefined : options.name, exports: options.buildable
projectType: 'library', ? undefined
sourceRoot: joinPathFragments(options.projectRoot, 'src'), : {
tags: options.parsedTags?.length ? options.parsedTags : undefined, './package.json': './package.json',
'.': options.js
? './src/index.js'
: {
types: './src/index.ts',
import: './src/index.ts',
default: './src/index.ts',
}, },
},
nx: options.parsedTags?.length
? {
tags: options.parsedTags,
}
: undefined,
}); });
} else { } else {
addProjectConfiguration(host, options.name, project); addProjectConfiguration(host, options.name, project);
@ -201,7 +212,7 @@ async function addProject(
); );
const rollupConfigTask = await configurationGenerator(host, { const rollupConfigTask = await configurationGenerator(host, {
...options, ...options,
project: options.name, project: options.projectName,
skipFormat: true, skipFormat: true,
}); });
@ -229,7 +240,7 @@ async function addProject(
}, },
}; };
updateProjectConfiguration(host, options.name, project); updateProjectConfiguration(host, options.projectName, project);
return rollupConfigTask; return rollupConfigTask;
} }
@ -271,7 +282,11 @@ function createFiles(host: Tree, options: NormalizedSchema) {
} }
); );
if (!options.publishable && !options.buildable) { if (
!options.publishable &&
!options.buildable &&
!options.isUsingTsSolutionConfig
) {
host.delete(`${options.projectRoot}/package.json`); host.delete(`${options.projectRoot}/package.json`);
} }

View File

@ -23,7 +23,11 @@ export function deleteFiles(tree: Tree, options: NormalizedOptions): void {
); );
} }
if (!options.buildable && !options.publishable) { if (
!options.buildable &&
!options.publishable &&
!options.isUsingTsSolutionsConfig
) {
tree.delete(joinPathFragments(options.projectRoot, 'package.json')); tree.delete(joinPathFragments(options.projectRoot, 'package.json'));
} }
} }

View File

@ -40,6 +40,7 @@ export async function normalizeOptions(
? options.tags.split(',').map((s) => s.trim()) ? options.tags.split(',').map((s) => s.trim())
: []; : [];
const isUsingTsSolutionsConfig = isUsingTsSolutionSetup(tree);
const normalized: NormalizedOptions = { const normalized: NormalizedOptions = {
...options, ...options,
strict: options.strict ?? true, strict: options.strict ?? true,
@ -49,7 +50,7 @@ export async function normalizeOptions(
linter: options.linter ?? Linter.EsLint, linter: options.linter ?? Linter.EsLint,
parsedTags, parsedTags,
prefix: getNpmScope(tree), // we could also allow customizing this prefix: getNpmScope(tree), // we could also allow customizing this
projectName: isUsingTsSolutionSetup(tree) projectName: isUsingTsSolutionsConfig
? getImportPath(tree, projectName) ? getImportPath(tree, projectName)
: projectName, : projectName,
projectRoot, projectRoot,
@ -58,13 +59,14 @@ export async function normalizeOptions(
target: options.target ?? 'es6', target: options.target ?? 'es6',
testEnvironment: options.testEnvironment ?? 'node', testEnvironment: options.testEnvironment ?? 'node',
unitTestRunner: options.unitTestRunner ?? 'jest', unitTestRunner: options.unitTestRunner ?? 'jest',
isUsingTsSolutionsConfig,
}; };
return normalized; return normalized;
} }
export function toJsLibraryGeneratorOptions( export function toJsLibraryGeneratorOptions(
options: LibraryGeneratorOptions options: NormalizedOptions
): JsLibraryGeneratorSchema { ): JsLibraryGeneratorSchema {
return { return {
name: options.name, name: options.name,
@ -80,8 +82,8 @@ export function toJsLibraryGeneratorOptions(
tags: options.tags, tags: options.tags,
testEnvironment: options.testEnvironment, testEnvironment: options.testEnvironment,
unitTestRunner: options.unitTestRunner, unitTestRunner: options.unitTestRunner,
config: options.standaloneConfig ? 'project' : 'workspace',
setParserOptionsProject: options.setParserOptionsProject, setParserOptionsProject: options.setParserOptionsProject,
addPlugin: options.addPlugin, addPlugin: options.addPlugin,
useProjectJson: !options.isUsingTsSolutionsConfig,
}; };
} }

View File

@ -379,6 +379,41 @@ describe('lib', () => {
}, },
] ]
`); `);
expect(readJson(tree, 'mylib/package.json')).toMatchInlineSnapshot(`
{
"dependencies": {},
"exports": {
".": {
"default": "./src/index.ts",
"import": "./src/index.ts",
"types": "./src/index.ts",
},
"./package.json": "./package.json",
},
"main": "./src/index.ts",
"name": "@proj/mylib",
"nx": {
"targets": {
"lint": {
"executor": "@nx/eslint:lint",
},
"test": {
"executor": "@nx/jest:jest",
"options": {
"jestConfig": "mylib/jest.config.ts",
},
"outputs": [
"{projectRoot}/test-output/jest/coverage",
],
},
},
},
"private": true,
"type": "module",
"types": "./src/index.ts",
"version": "0.0.1",
}
`);
expect(readJson(tree, 'mylib/tsconfig.json')).toMatchInlineSnapshot(` expect(readJson(tree, 'mylib/tsconfig.json')).toMatchInlineSnapshot(`
{ {
"extends": "../tsconfig.base.json", "extends": "../tsconfig.base.json",

View File

@ -28,11 +28,11 @@ export interface LibraryGeneratorOptions {
| 'es2021'; | 'es2021';
testEnvironment?: 'jsdom' | 'node'; testEnvironment?: 'jsdom' | 'node';
unitTestRunner?: UnitTestRunner; unitTestRunner?: UnitTestRunner;
standaloneConfig?: boolean;
setParserOptionsProject?: boolean; setParserOptionsProject?: boolean;
skipPackageJson?: boolean; skipPackageJson?: boolean;
simpleName?: boolean; simpleName?: boolean;
addPlugin?: boolean; addPlugin?: boolean;
isUsingTsSolutionsConfig?: boolean;
} }
export interface NormalizedOptions extends LibraryGeneratorOptions { export interface NormalizedOptions extends LibraryGeneratorOptions {

View File

@ -118,12 +118,6 @@
"type": "boolean", "type": "boolean",
"default": true "default": true
}, },
"standaloneConfig": {
"description": "Split the project configuration into <projectRoot>/project.json rather than including it inside workspace.json",
"type": "boolean",
"default": true,
"x-deprecated": "Nx only supports standaloneConfig"
},
"setParserOptionsProject": { "setParserOptionsProject": {
"type": "boolean", "type": "boolean",
"description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.", "description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.",