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",
"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": {
"type": "boolean",
"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",
"main": "<%= appMain %>"
}

View File

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

View File

@ -504,6 +504,22 @@ describe('lib', () => {
]
`);
// 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')))
.toMatchInlineSnapshot(`
[
@ -511,7 +527,7 @@ describe('lib', () => {
"version",
"main",
"types",
"nx",
"exports",
]
`);
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,
updateTsconfigFiles,
} 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';
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 });
tasks.push(initTask);
if (!options.skipPackageJson) {
@ -88,7 +91,7 @@ export async function expoLibraryGeneratorInternal(
const lintTask = await addLinting(host, {
...options,
projectName: options.name,
projectName: options.projectName,
tsConfigPaths: [
joinPathFragments(options.projectRoot, 'tsconfig.lib.json'),
],
@ -98,7 +101,7 @@ export async function expoLibraryGeneratorInternal(
const jestTask = await addJest(
host,
options.unitTestRunner,
options.name,
options.projectName,
options.projectRoot,
options.js,
options.skipPackageJson,
@ -134,10 +137,6 @@ export async function expoLibraryGeneratorInternal(
: undefined
);
if (options.isUsingTsSolutionConfig) {
addProjectToTsSolutionWorkspace(host, options.projectRoot);
}
sortPackageJsonFields(host, options.projectRoot);
if (!options.skipFormat) {
@ -169,23 +168,35 @@ async function addProject(
};
if (options.isUsingTsSolutionConfig) {
const packageName = getImportPath(host, options.name);
const sourceEntry = !options.buildable
? options.js
? './src/index.js'
: './src/index.ts'
: undefined;
writeJson(host, joinPathFragments(options.projectRoot, 'package.json'), {
name: packageName,
name: options.projectName,
version: '0.0.1',
main: sourceEntry,
types: sourceEntry,
nx: {
name: packageName === options.name ? undefined : options.name,
projectType: 'library',
sourceRoot: joinPathFragments(options.projectRoot, 'src'),
tags: options.parsedTags?.length ? options.parsedTags : undefined,
},
// For buildable libraries, the entries are configured by the bundler (i.e. Rollup).
exports: options.buildable
? 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 {
addProjectConfiguration(host, options.name, project);
@ -201,7 +212,7 @@ async function addProject(
);
const rollupConfigTask = await configurationGenerator(host, {
...options,
project: options.name,
project: options.projectName,
skipFormat: true,
});
@ -229,7 +240,7 @@ async function addProject(
},
};
updateProjectConfiguration(host, options.name, project);
updateProjectConfiguration(host, options.projectName, project);
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`);
}

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'));
}
}

View File

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

View File

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

View File

@ -118,12 +118,6 @@
"type": "boolean",
"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": {
"type": "boolean",
"description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.",