Skip to content

Commit

Permalink
fix(js): generate js libs with exports in package.json and ensure esm…
Browse files Browse the repository at this point in the history
… output when using rollup bundler (#29565)

- Ensure libs are generated with `exports` in `package.json`
- Generate `types` instead of `typings` in package.json
- Update js lib with rollup to only output esm
- Update `tsconfig.spec.json` for js libraries with rollup to set
`module: esnext` and `moduleResolution: bundler` (they use `@swc/jest`)
- Fix `@nx/js/typescript` issue with absolute paths when normalizing
inputs/outputs
- Fix `@nx/js/typescript` issue identifying buildable libs
- Fix express app generator not installing `@types/express`

<!-- 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 <[email protected]>
  • Loading branch information
leosvelperez and jaysoo authored Jan 10, 2025
1 parent cbfc6fe commit dd9b09f
Show file tree
Hide file tree
Showing 18 changed files with 235 additions and 86 deletions.
2 changes: 1 addition & 1 deletion e2e/esbuild/src/esbuild.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('EsBuild Plugin', () => {
private: true,
type: 'commonjs',
main: './index.cjs',
typings: './index.d.ts',
types: './index.d.ts',
dependencies: {},
});

Expand Down
2 changes: 1 addition & 1 deletion e2e/eslint/src/linter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ describe('Linter', () => {
name: `@proj/${mylib}`,
private: true,
type: 'commonjs',
typings: './src/index.d.ts',
types: './src/index.d.ts',
version: '0.0.1',
});

Expand Down
2 changes: 2 additions & 0 deletions e2e/node/src/node-ts-solution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,11 @@ packages:
expect(() => runCLI(`lint ${nodeapp}`)).not.toThrow();
expect(() => runCLI(`test ${nodeapp}`)).not.toThrow();
expect(() => runCLI(`build ${nodeapp}`)).not.toThrow();
expect(() => runCLI(`typecheck ${nodeapp}`)).not.toThrow();
expect(() => runCLI(`lint ${nodelib}`)).not.toThrow();
expect(() => runCLI(`test ${nodelib}`)).not.toThrow();
expect(() => runCLI(`build ${nodelib}`)).not.toThrow();
expect(() => runCLI(`typecheck ${nodelib}`)).not.toThrow();

const p = await runCommandUntil(
`serve ${nodeapp}`,
Expand Down
1 change: 1 addition & 0 deletions packages/express/src/generators/application/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export async function applicationGeneratorInternal(tree: Tree, schema: Schema) {
const applicationTask = await nodeApplicationGenerator(tree, {
...options,
bundler: 'webpack',
framework: 'express',
skipFormat: true,
});
tasks.push(applicationTask);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ export function createFiles(
? `${rootOffset}tsconfig.base.json`
: './tsconfig.json',
outDir: isTsSolutionSetup ? `./out-tsc/jest` : `${rootOffset}dist/out-tsc`,
module: !isTsSolutionSetup ? 'commonjs' : undefined,
module:
!isTsSolutionSetup || transformer === 'ts-jest' ? 'commonjs' : undefined,
});

if (options.setupFile === 'none') {
Expand Down
34 changes: 32 additions & 2 deletions packages/js/src/generators/library/library.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,14 @@ describe('lib', () => {
expect(readJson(tree, 'my-ts-lib/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/my-ts-lib",
"private": true,
Expand All @@ -1663,6 +1671,10 @@ describe('lib', () => {
expect(readJson(tree, 'my-js-lib/package.json')).toMatchInlineSnapshot(`
{
"dependencies": {},
"exports": {
".": "./src/index.js",
"./package.json": "./package.json",
},
"main": "./src/index.js",
"name": "@proj/my-js-lib",
"private": true,
Expand All @@ -1686,11 +1698,20 @@ describe('lib', () => {
"dependencies": {
"tslib": "^2.3.0",
},
"exports": {
".": {
"default": "./dist/index.js",
"import": "./dist/index.js",
"types": "./dist/index.d.ts",
},
"./package.json": "./package.json",
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"name": "@proj/my-ts-lib",
"private": true,
"type": "module",
"typings": "./dist/index.d.ts",
"types": "./dist/index.d.ts",
"version": "0.0.1",
}
`);
Expand All @@ -1710,11 +1731,20 @@ describe('lib', () => {
"dependencies": {
"@swc/helpers": "~0.5.11",
},
"exports": {
".": {
"default": "./dist/index.js",
"import": "./dist/index.js",
"types": "./dist/index.d.ts",
},
"./package.json": "./package.json",
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"name": "@proj/my-ts-lib",
"private": true,
"type": "module",
"typings": "./dist/index.d.ts",
"types": "./dist/index.d.ts",
"version": "0.0.1",
}
`);
Expand Down
180 changes: 115 additions & 65 deletions packages/js/src/generators/library/library.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {
addDependenciesToPackageJson,
installPackagesTask,
addProjectConfiguration,
ensurePackage,
formatFiles,
generateFiles,
GeneratorCallback,
getPackageManagerCommand,
installPackagesTask,
joinPathFragments,
names,
offsetFromRoot,
Expand Down Expand Up @@ -35,6 +35,7 @@ import { type PackageJson } from 'nx/src/utils/package-json';
import { join } from 'path';
import type { CompilerOptions } from 'typescript';
import { normalizeLinterOption } from '../../utils/generator-prompts';
import { getUpdatedPackageJsonContent } from '../../utils/package-json/update-package-json';
import {
getProjectPackageManagerWorkspaceState,
getProjectPackageManagerWorkspaceStateWarningTask,
Expand All @@ -43,6 +44,7 @@ import { addSwcConfig } from '../../utils/swc/add-swc-config';
import { getSwcDependencies } from '../../utils/swc/add-swc-dependencies';
import { getNeededCompilerOptionOverrides } from '../../utils/typescript/configuration';
import { tsConfigBaseOptions } from '../../utils/typescript/create-ts-config';
import { ensureTypescript } from '../../utils/typescript/ensure-typescript';
import { ensureProjectIsIncludedInPluginRegistrations } from '../../utils/typescript/plugin';
import {
addTsConfigPath,
Expand All @@ -68,7 +70,6 @@ import type {
LibraryGeneratorSchema,
NormalizedLibraryGeneratorOptions,
} from './schema';
import { ensureTypescript } from '../../utils/typescript/ensure-typescript';

const defaultOutputDirectory = 'dist';

Expand Down Expand Up @@ -118,7 +119,7 @@ export async function libraryGeneratorInternal(
await configurationGenerator(tree, {
project: options.name,
compiler: 'swc',
format: ['cjs', 'esm'],
format: options.isUsingTsSolutionConfig ? ['esm'] : ['cjs', 'esm'],
});
}

Expand Down Expand Up @@ -206,6 +207,12 @@ export async function libraryGeneratorInternal(
// add project reference to the runtime tsconfig.lib.json file
json.references ??= [];
json.references.push({ path: './tsconfig.lib.json' });

if (options.isUsingTsSolutionConfig && options.bundler === 'rollup') {
json.compilerOptions.module = 'esnext';
json.compilerOptions.moduleResolution = 'bundler';
}

return json;
}
);
Expand Down Expand Up @@ -503,8 +510,7 @@ function createFiles(tree: Tree, options: NormalizedLibraryGeneratorOptions) {
let fileNameImport = options.fileName;
if (
options.bundler === 'vite' ||
(options.isUsingTsSolutionConfig &&
['esbuild', 'swc', 'tsc'].includes(options.bundler))
(options.isUsingTsSolutionConfig && options.bundler !== 'none')
) {
const tsConfig = readTsConfigFromTree(
tree,
Expand Down Expand Up @@ -606,17 +612,35 @@ function createFiles(tree: Tree, options: NormalizedLibraryGeneratorOptions) {
// https://docs.npmjs.com/cli/v10/configuring-npm/package-json#files
json.files = ['dist', '!**/*.tsbuildinfo'];
}
return {

const updatedPackageJson = {
...json,
dependencies: {
...json.dependencies,
...determineDependencies(options),
},
...determineEntryFields(options),
};

if (
options.isUsingTsSolutionConfig &&
!['none', 'rollup', 'vite'].includes(options.bundler)
) {
return getUpdatedPackageJsonContent(updatedPackageJson, {
main: join(options.projectRoot, 'src/index.ts'),
outputPath: joinPathFragments(options.projectRoot, 'dist'),
projectRoot: options.projectRoot,
rootDir: join(options.projectRoot, 'src'),
generateExportsField: true,
packageJsonPath,
format: ['esm'],
});
}

return updatedPackageJson;
});
} else {
const packageJson: PackageJson = {
let packageJson: PackageJson = {
name: options.importPath,
version: '0.0.1',
dependencies: determineDependencies(options),
Expand All @@ -630,6 +654,22 @@ function createFiles(tree: Tree, options: NormalizedLibraryGeneratorOptions) {
// https://docs.npmjs.com/cli/v10/configuring-npm/package-json#files
packageJson.files = ['dist', '!**/*.tsbuildinfo'];
}

if (
options.isUsingTsSolutionConfig &&
!['none', 'rollup', 'vite'].includes(options.bundler)
) {
packageJson = getUpdatedPackageJsonContent(packageJson, {
main: join(options.projectRoot, 'src/index.ts'),
outputPath: joinPathFragments(options.projectRoot, 'dist'),
projectRoot: options.projectRoot,
rootDir: join(options.projectRoot, 'src'),
generateExportsField: true,
packageJsonPath,
format: ['esm'],
});
}

writeJson<PackageJson>(tree, packageJsonPath, packageJson);
}

Expand Down Expand Up @@ -1094,74 +1134,84 @@ function determineEntryFields(
): Record<string, EntryField> {
switch (options.bundler) {
case 'tsc':
return {
type: options.isUsingTsSolutionConfig ? 'module' : 'commonjs',
main: options.isUsingTsSolutionConfig
? './dist/index.js'
: './src/index.js',
typings: options.isUsingTsSolutionConfig
? './dist/index.d.ts'
: './src/index.d.ts',
};
case 'swc':
return {
type: options.isUsingTsSolutionConfig ? 'module' : 'commonjs',
main: options.isUsingTsSolutionConfig
? './dist/index.js'
: './src/index.js',
typings: options.isUsingTsSolutionConfig
? './dist/index.d.ts'
: './src/index.d.ts',
};
if (options.isUsingTsSolutionConfig) {
return {
type: 'module',
main: './dist/index.js',
types: './dist/index.d.ts',
};
} else {
return {
type: 'commonjs',
main: './src/index.js',
types: './src/index.d.ts',
};
}
case 'rollup':
return {
// Since we're publishing both formats, skip the type field.
// Bundlers or Node will determine the entry point to use.
main: options.isUsingTsSolutionConfig
? './dist/index.cjs'
: './index.cjs',
module: options.isUsingTsSolutionConfig
? './dist/index.js'
: './index.js',
};
if (options.isUsingTsSolutionConfig) {
// the rollup configuration generator already handles this
return {};
} else {
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',
};
}
case 'vite':
return {
type: 'module',
main: options.isUsingTsSolutionConfig
? './dist/index.js'
: './index.js',
typings: options.isUsingTsSolutionConfig
? './dist/index.d.ts'
: './index.d.ts',
};
if (options.isUsingTsSolutionConfig) {
// the vite configuration generator already handle this
return {};
} else {
return {
type: 'module',
main: './index.js',
types: './index.d.ts',
};
}
case 'esbuild':
return {
type: options.isUsingTsSolutionConfig ? 'module' : 'commonjs',
main: options.isUsingTsSolutionConfig
? './dist/index.js'
: './index.cjs',
typings: options.isUsingTsSolutionConfig
? './dist/index.d.ts'
: './index.d.ts',
};
default: {
if (options.isUsingTsSolutionConfig) {
return {
type: 'module',
main: './dist/index.js',
types: './dist/index.d.ts',
};
} else {
return {
type: 'commonjs',
main: './index.cjs',
types: './index.d.ts',
};
}
case 'none': {
if (options.isUsingTsSolutionConfig) {
return {
main: options.js ? './src/index.js' : './src/index.ts',
types: options.js ? './src/index.js' : './src/index.ts',
exports: {
'.': options.js
? './src/index.js'
: {
types: './src/index.ts',
import: './src/index.ts',
default: './src/index.ts',
},
'./package.json': './package.json',
},
};
}

return {
// Safest option is to not set a type field.
// Allow the user to decide which module format their library is using
type: undefined,
// For non-buildable libraries, point to source so we can still use them in apps via bundlers like Vite.
main: options.isUsingTsSolutionConfig
? options.js
? './src/index.js'
: './src/index.ts'
: undefined,
types: options.isUsingTsSolutionConfig
? options.js
? './src/index.js'
: './src/index.ts'
: undefined,
};
}
default: {
return {};
}
}
}

Expand Down
Loading

0 comments on commit dd9b09f

Please sign in to comment.