Skip to content

Commit

Permalink
feat(bundling): generate matching d.ts files for rollup
Browse files Browse the repository at this point in the history
  • Loading branch information
jaysoo committed Jul 27, 2023
1 parent 1363bae commit 9765310
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 102 deletions.
19 changes: 15 additions & 4 deletions e2e/rollup/src/rollup.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
checkFilesExist,
cleanupProject,
newProject,
readJson,
rmDist,
runCLI,
runCommand,
Expand All @@ -23,8 +25,17 @@ describe('Rollup Plugin', () => {
`generate @nx/rollup:configuration ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts`
);
rmDist();
runCLI(`build ${myPkg}`);
let output = runCommand(`node dist/libs/${myPkg}/index.cjs`);
runCLI(`build ${myPkg} --format=cjs,esm --generateExportsField`);
checkFilesExist(`dist/libs/${myPkg}/index.cjs.d.ts`);
expect(readJson(`dist/libs/${myPkg}/package.json`).exports).toMatchObject({
exports: {
'.': {
import: './index.esm.js',
default: './index.cjs.js',
},
},
});
let output = runCommand(`node dist/libs/${myPkg}/index.cjs.js`);
expect(output).toMatch(/Hello/);

updateProjectConfig(myPkg, (config) => {
Expand All @@ -38,7 +49,7 @@ describe('Rollup Plugin', () => {
);
rmDist();
runCLI(`build ${myPkg}`);
output = runCommand(`node dist/libs/${myPkg}/index.cjs`);
output = runCommand(`node dist/libs/${myPkg}/index.cjs.js`);
expect(output).toMatch(/Hello/);

updateProjectConfig(myPkg, (config) => {
Expand All @@ -52,7 +63,7 @@ describe('Rollup Plugin', () => {
);
rmDist();
runCLI(`build ${myPkg}`);
output = runCommand(`node dist/libs/${myPkg}/index.cjs`);
output = runCommand(`node dist/libs/${myPkg}/index.cjs.js`);
expect(output).toMatch(/Hello/);
}, 500000);

Expand Down
60 changes: 60 additions & 0 deletions packages/js/src/plugins/rollup/type-definitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { OutputBundle } from 'rollup';
import { relative } from 'path';
import { stripIndents } from '@nx/devkit';

//NOTE: This is here so we can share between `@nx/rollup` and `@nx/vite`.

/*
* This plugin takes all entry-points from the generated bundle and creates a
* bundled version of corresponding d.ts files.
*
* For example, `src/index.ts` generates two corresponding files:
* - `dist/xyz/index.js`
* - `dist/xyz/src/index.d.ts`
*
* We want a third file: `dist/index.d.ts` that re-exports from `src/index.d.ts`.
* That way, when TSC or IDEs look for types, it will find them in the right place.
*/
export function typeDefinitions(options: {
projectRoot: string;
main: string;
}) {
return {
name: 'dts-bundle',
async generateBundle(_opts: unknown, bundle: OutputBundle): Promise<void> {
for (const [name, file] of Object.entries(bundle)) {
if (
file.type === 'asset' ||
!file.isEntry ||
file.facadeModuleId == null
) {
continue;
}

const hasDefaultExport = file.exports.includes('default');
const entrySourceFileName = relative(
options.projectRoot,
file.facadeModuleId
);
const entrySourceDtsName = entrySourceFileName.replace(
/\.[cm]?[jt]sx?$/,
''
);
const dtsFileName = file.fileName.replace(/\.[cm]?js$/, '.d.ts');
const relativeSourceDtsName = JSON.stringify('./' + entrySourceDtsName);
const dtsFileSource = hasDefaultExport
? stripIndents`
export * from ${relativeSourceDtsName};
export { default } from ${relativeSourceDtsName};
`
: `export * from ${relativeSourceDtsName};\n`;

this.emitFile({
type: 'asset',
fileName: dtsFileName,
source: dtsFileSource,
});
}
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,12 @@ describe('updatePackageJson', () => {
expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
exports: {
'.': {
types: './index.d.ts',
import: './index.js',
import: './index.esm.js',
},
},
main: './index.js',
module: './index.js',
main: './index.esm.js',
module: './index.esm.js',
type: 'module',
types: './index.d.ts',
});

spy.mockRestore();
Expand All @@ -73,13 +71,11 @@ describe('updatePackageJson', () => {
expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
exports: {
'.': {
types: './index.d.ts',
require: './index.cjs',
default: './index.cjs.js',
},
},
main: './index.cjs',
main: './index.cjs.js',
type: 'commonjs',
types: './index.d.ts',
});

spy.mockRestore();
Expand All @@ -103,15 +99,12 @@ describe('updatePackageJson', () => {
expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
exports: {
'.': {
types: './index.d.ts',
import: './index.js',
require: './index.cjs',
import: './index.esm.js',
default: './index.cjs.js',
},
},
main: './index.cjs',
module: './index.js',
type: 'module',
types: './index.d.ts',
main: './index.cjs.js',
module: './index.esm.js',
});

spy.mockRestore();
Expand All @@ -132,7 +125,7 @@ describe('updatePackageJson', () => {
{
exports: {
'./foo': {
import: './foo.js',
import: './foo.esm.js',
},
},
} as unknown as PackageJson
Expand All @@ -141,17 +134,15 @@ describe('updatePackageJson', () => {
expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
exports: {
'.': {
types: './index.d.ts',
import: './index.js',
import: './index.esm.js',
},
'./foo': {
import: './foo.js',
import: './foo.esm.js',
},
},
main: './index.js',
module: './index.js',
main: './index.esm.js',
module: './index.esm.js',
type: 'module',
types: './index.d.ts',
});

spy.mockRestore();
Expand All @@ -174,10 +165,9 @@ describe('updatePackageJson', () => {
);

expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
main: './index.js',
module: './index.js',
main: './index.esm.js',
module: './index.esm.js',
type: 'module',
types: './index.d.ts',
});

spy.mockRestore();
Expand All @@ -198,9 +188,8 @@ describe('updatePackageJson', () => {
);

expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
main: './index.cjs',
main: './index.cjs.js',
type: 'commonjs',
types: './index.d.ts',
});

spy.mockRestore();
Expand All @@ -221,10 +210,8 @@ describe('updatePackageJson', () => {
);

expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
main: './index.cjs',
module: './index.js',
type: 'module',
types: './index.d.ts',
main: './index.cjs.js',
module: './index.esm.js',
});

spy.mockRestore();
Expand All @@ -244,20 +231,19 @@ describe('updatePackageJson', () => {
{
exports: {
'./foo': {
import: './foo.js',
import: './foo.esm.js',
},
},
} as unknown as PackageJson
);

expect(utils.writeJsonFile).toHaveBeenCalledWith(expect.anything(), {
main: './index.js',
module: './index.js',
main: './index.esm.js',
module: './index.esm.js',
type: 'module',
types: './index.d.ts',
exports: {
'./foo': {
import: './foo.js',
import: './foo.esm.js',
},
},
});
Expand Down
28 changes: 10 additions & 18 deletions packages/rollup/src/executors/rollup/lib/update-package-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { writeJsonFile } from 'nx/src/utils/fileutils';
import { PackageJson } from 'nx/src/utils/package-json';
import { NormalizedRollupExecutorOptions } from './normalize';

// TODO(jack): Use updatePackageJson from @nx/js instead.
export function updatePackageJson(
options: NormalizedRollupExecutorOptions,
context: ExecutorContext,
Expand All @@ -19,40 +20,31 @@ export function updatePackageJson(
const hasEsmFormat = options.format.includes('esm');
const hasCjsFormat = options.format.includes('cjs');

const types = `./${relative(options.projectRoot, options.main).replace(
/\.[jt]sx?$/,
'.d.ts'
)}`;
const exports = {
// TS 4.5+
'.': {
types,
},
'.': {},
};

if (hasEsmFormat) {
// `module` field is used by bundlers like rollup and webpack to detect ESM.
// May not be required in the future if type is already "module".
packageJson.module = './index.js';
exports['.']['import'] = './index.js';
packageJson.module = './index.esm.js';
exports['.']['import'] = './index.esm.js';

if (!hasCjsFormat) {
packageJson.main = './index.js';
packageJson.main = './index.esm.js';
}
}

if (hasCjsFormat) {
packageJson.main = './index.cjs';
exports['.']['require'] = './index.cjs';
packageJson.main = './index.cjs.js';
exports['.']['default'] = './index.cjs.js';
}
if (!options.skipTypeField) {

// Dual format should not specify `type` field, the `exports` field resolves ESM vs CJS.
if (!options.skipTypeField && options.format.length === 1) {
packageJson.type = options.format.includes('esm') ? 'module' : 'commonjs';
}

// Support for older TS versions < 4.5
packageJson.types = types;

// TODO(jack): remove this for Nx 16
if (options.generateExportsField && typeof packageJson.exports !== 'string') {
packageJson.exports = {
...packageJson.exports,
Expand Down
20 changes: 0 additions & 20 deletions packages/rollup/src/executors/rollup/lib/validate-types.ts

This file was deleted.

8 changes: 4 additions & 4 deletions packages/rollup/src/executors/rollup/rollup.impl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ describe('rollupExecutor', () => {
dir: '/root/dist/ui',
format: 'esm',
name: 'Example',
chunkFileNames: '[name].js',
entryFileNames: '[name].js',
chunkFileNames: '[name].esm.js',
entryFileNames: '[name].esm.js',
},
{
dir: '/root/dist/ui',
format: 'cjs',
name: 'Example',
chunkFileNames: '[name].cjs',
entryFileNames: '[name].cjs',
chunkFileNames: '[name].cjs.js',
entryFileNames: '[name].cjs.js',
},
]);
});
Expand Down
Loading

0 comments on commit 9765310

Please sign in to comment.