Skip to content

Commit

Permalink
feat(js): add generateExportsField and additionalEntryPoints options …
Browse files Browse the repository at this point in the history
…to update exports field when building with tsc/swc (#18319)
  • Loading branch information
jaysoo committed Aug 1, 2023
1 parent 90e4e7e commit d63d357
Show file tree
Hide file tree
Showing 15 changed files with 550 additions and 167 deletions.
22 changes: 19 additions & 3 deletions docs/generated/packages/js/executors/swc.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,34 @@
"type": "string",
"description": "The name of the main entry-point file.",
"x-completion-type": "file",
"x-completion-glob": "main@(.js|.ts|.tsx)"
"x-completion-glob": "main@(.js|.ts|.tsx)",
"x-priority": "important"
},
"generateExportsField": {
"type": "boolean",
"alias": "exports",
"description": "Update the output package.json file's 'exports' field. This field is used by Node and bundles.",
"x-priority": "important",
"default": false
},
"additionalEntryPoints": {
"type": "array",
"description": "Additional entry-points to add to exports field in the package.json file.",
"items": { "type": "string" },
"x-priority": "important"
},
"outputPath": {
"type": "string",
"description": "The output path of the generated files.",
"x-completion-type": "directory"
"x-completion-type": "directory",
"x-priority": "important"
},
"tsConfig": {
"type": "string",
"description": "The path to the Typescript configuration file.",
"x-completion-type": "file",
"x-completion-glob": "tsconfig.*.json"
"x-completion-glob": "tsconfig.*.json",
"x-priority": "important"
},
"swcrc": {
"type": "string",
Expand Down
22 changes: 19 additions & 3 deletions docs/generated/packages/js/executors/tsc.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,21 @@
"type": "string",
"description": "The name of the main entry-point file.",
"x-completion-type": "file",
"x-completion-glob": "main@(.js|.ts|.jsx|.tsx)"
"x-completion-glob": "main@(.js|.ts|.jsx|.tsx)",
"x-priority": "important"
},
"generateExportsField": {
"type": "boolean",
"alias": "exports",
"description": "Update the output package.json file's 'exports' field. This field is used by Node and bundles.",
"default": false,
"x-priority": "important"
},
"additionalEntryPoints": {
"type": "array",
"description": "Additional entry-points to add to exports field in the package.json file.",
"items": { "type": "string" },
"x-priority": "important"
},
"rootDir": {
"type": "string",
Expand All @@ -23,13 +37,15 @@
"outputPath": {
"type": "string",
"description": "The output path of the generated files.",
"x-completion-type": "directory"
"x-completion-type": "directory",
"x-priority": "important"
},
"tsConfig": {
"type": "string",
"description": "The path to the Typescript configuration file.",
"x-completion-type": "file",
"x-completion-glob": "tsconfig.*.json"
"x-completion-glob": "tsconfig.*.json",
"x-priority": "important"
},
"assets": {
"type": "array",
Expand Down
110 changes: 106 additions & 4 deletions e2e/js/src/js-packaging.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
checkFilesExist,
updateJson,
updateProjectConfig,
cleanupProject,
newProject,
runCLI,
Expand All @@ -8,6 +9,8 @@ import {
createFile,
uniq,
getPackageManagerCommand,
readJson,
updateFile,
} from '@nx/e2e/utils';
import { join } from 'path';
import { ensureDirSync } from 'fs-extra';
Expand Down Expand Up @@ -123,17 +126,74 @@ describe('bundling libs', () => {
it('should support tsc and swc for building libs', () => {
const tscLib = uniq('tsclib');
const swcLib = uniq('swclib');
const tscEsmLib = uniq('tscesmlib');
const swcEsmLib = uniq('swcesmlib');

runCLI(`generate @nx/js:lib ${tscLib} --bundler=tsc --no-interactive`);
runCLI(`generate @nx/js:lib ${swcLib} --bundler=swc --no-interactive`);
runCLI(`generate @nx/js:lib ${tscEsmLib} --bundler=tsc --no-interactive`);
runCLI(`generate @nx/js:lib ${swcEsmLib} --bundler=swc --no-interactive`);

runCLI(`build ${tscLib}`);
runCLI(`build ${swcLib}`);
// Change module format to ESM
updateJson(`libs/${tscEsmLib}/tsconfig.json`, (json) => {
json.compilerOptions.module = 'esnext';
return json;
});
updateJson(`libs/${swcEsmLib}/.swcrc`, (json) => {
json.module.type = 'es6';
return json;
});
// Node ESM requires file extensions in imports so must add them before building
updateFile(
`libs/${tscEsmLib}/src/index.ts`,
`export * from './lib/${tscEsmLib}.js';`
);
updateFile(
`libs/${swcEsmLib}/src/index.ts`,
`export * from './lib/${swcEsmLib}.js';`
);

// Add additional entry points for `exports` field
updateProjectConfig(tscLib, (json) => {
json.targets.build.options.additionalEntryPoints = [
`libs/${tscLib}/src/foo/*.ts`,
];
return json;
});
updateFile(`libs/${tscLib}/src/foo/bar.ts`, `export const bar = 'bar';`);
updateFile(`libs/${tscLib}/src/foo/faz.ts`, `export const faz = 'faz';`);
updateProjectConfig(swcLib, (json) => {
json.targets.build.options.additionalEntryPoints = [
`libs/${swcLib}/src/foo/*.ts`,
];
return json;
});
updateFile(`libs/${swcLib}/src/foo/bar.ts`, `export const bar = 'bar';`);
updateFile(`libs/${swcLib}/src/foo/faz.ts`, `export const faz = 'faz';`);

runCLI(`build ${tscLib} --generateExportsField`);
runCLI(`build ${swcLib} --generateExportsField`);
runCLI(`build ${tscEsmLib} --generateExportsField`);
runCLI(`build ${swcEsmLib} --generateExportsField`);

expect(readJson(`dist/libs/${tscLib}/package.json`).exports).toEqual({
'./package.json': './package.json',
'.': './src/index.js',
'./foo/bar': './src/foo/bar.js',
'./foo/faz': './src/foo/faz.js',
});

expect(readJson(`dist/libs/${swcLib}/package.json`).exports).toEqual({
'./package.json': './package.json',
'.': './src/index.js',
'./foo/bar': './src/foo/bar.js',
'./foo/faz': './src/foo/faz.js',
});

const pmc = getPackageManagerCommand();
let output: string;

// Make sure outputs in commonjs project
// Make sure CJS output is correct
createFile(
'test-cjs/package.json',
JSON.stringify(
Expand All @@ -155,8 +215,13 @@ describe('bundling libs', () => {
`
const { ${tscLib} } = require('@proj/${tscLib}');
const { ${swcLib} } = require('@proj/${swcLib}');
// additional entry-points
const { bar } = require('@proj/${tscLib}/foo/bar');
const { faz } = require('@proj/${swcLib}/foo/faz');
console.log(${tscLib}());
console.log(${swcLib}());
console.log(bar);
console.log(faz);
`
);
runCommand(pmc.install, {
Expand All @@ -167,5 +232,42 @@ describe('bundling libs', () => {
});
expect(output).toContain(tscLib);
expect(output).toContain(swcLib);
expect(output).toContain('bar');
expect(output).toContain('faz');

// Make sure ESM output is correct
createFile(
'test-esm/package.json',
JSON.stringify(
{
name: 'test-esm',
private: true,
type: 'module',
dependencies: {
[`@proj/${tscEsmLib}`]: `file:../dist/libs/${tscEsmLib}`,
[`@proj/${swcEsmLib}`]: `file:../dist/libs/${swcEsmLib}`,
},
},
null,
2
)
);
createFile(
'test-esm/index.js',
`
import { ${tscEsmLib} } from '@proj/${tscEsmLib}';
import { ${swcEsmLib} } from '@proj/${swcEsmLib}';
console.log(${tscEsmLib}());
console.log(${swcEsmLib}());
`
);
runCommand(pmc.install, {
cwd: join(tmpProjPath(), 'test-esm'),
});
output = runCommand('node index.js', {
cwd: join(tmpProjPath(), 'test-esm'),
});
expect(output).toContain(tscEsmLib);
expect(output).toContain(swcEsmLib);
}, 500_000);
});
7 changes: 2 additions & 5 deletions packages/js/src/executors/node/node.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { InspectType, NodeExecutorOptions } from './schema';
import { calculateProjectBuildableDependencies } from '../../utils/buildable-libs-utils';
import { killTree } from './lib/kill-tree';
import { fileExists } from 'nx/src/utils/fileutils';
import { getMainFileDirRelativeToProjectRoot } from '../../utils/get-main-file-dir';
import { getRelativeDirectoryToProjectRoot } from '../../utils/get-main-file-dir';

interface ActiveTask {
id: string;
Expand Down Expand Up @@ -379,10 +379,7 @@ function getFileToRun(
buildTargetExecutor === '@nx/js:swc'
) {
outputFileName = path.join(
getMainFileDirRelativeToProjectRoot(
buildOptions.main,
project.data.root
),
getRelativeDirectoryToProjectRoot(buildOptions.main, project.data.root),
fileName
);
} else {
Expand Down
24 changes: 21 additions & 3 deletions packages/js/src/executors/swc/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,36 @@
"type": "string",
"description": "The name of the main entry-point file.",
"x-completion-type": "file",
"x-completion-glob": "main@(.js|.ts|.tsx)"
"x-completion-glob": "main@(.js|.ts|.tsx)",
"x-priority": "important"
},
"generateExportsField": {
"type": "boolean",
"alias": "exports",
"description": "Update the output package.json file's 'exports' field. This field is used by Node and bundles.",
"x-priority": "important",
"default": false
},
"additionalEntryPoints": {
"type": "array",
"description": "Additional entry-points to add to exports field in the package.json file.",
"items": {
"type": "string"
},
"x-priority": "important"
},
"outputPath": {
"type": "string",
"description": "The output path of the generated files.",
"x-completion-type": "directory"
"x-completion-type": "directory",
"x-priority": "important"
},
"tsConfig": {
"type": "string",
"description": "The path to the Typescript configuration file.",
"x-completion-type": "file",
"x-completion-glob": "tsconfig.*.json"
"x-completion-glob": "tsconfig.*.json",
"x-priority": "important"
},
"swcrc": {
"type": "string",
Expand Down
37 changes: 33 additions & 4 deletions packages/js/src/executors/swc/swc.impl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ExecutorContext } from '@nx/devkit';
import { ExecutorContext, readJsonFile } from '@nx/devkit';
import { assetGlobsToFiles, FileInputOutput } from '../../utils/assets/assets';
import { removeSync } from 'fs-extra';
import { sync as globSync } from 'fast-glob';
import { dirname, join, relative, resolve } from 'path';
import { copyAssets } from '../../utils/assets';
import { checkDependencies } from '../../utils/check-dependencies';
Expand Down Expand Up @@ -135,6 +136,13 @@ export async function* swcExecutor(
);
}

function determineModuleFormatFromSwcrc(
absolutePathToSwcrc: string
): 'cjs' | 'esm' {
const swcrc = readJsonFile(absolutePathToSwcrc);
return swcrc.module?.type?.startsWith('es') ? 'esm' : 'cjs';
}

if (options.watch) {
let disposeFn: () => void;
process.on('SIGINT', () => disposeFn());
Expand All @@ -145,7 +153,13 @@ export async function* swcExecutor(
const packageJsonResult = await copyPackageJson(
{
...options,
skipTypings: !options.skipTypeCheck,
additionalEntryPoints: createEntryPoints(options, context),
format: [
determineModuleFormatFromSwcrc(options.swcCliOptions.swcrcPath),
],
// As long as d.ts files match their .js counterparts, we don't need to emit them.
// TSC can match them correctly based on file names.
skipTypings: true,
},
context
);
Expand All @@ -161,8 +175,13 @@ export async function* swcExecutor(
await copyPackageJson(
{
...options,
generateExportsField: true,
skipTypings: !options.skipTypeCheck,
additionalEntryPoints: createEntryPoints(options, context),
format: [
determineModuleFormatFromSwcrc(options.swcCliOptions.swcrcPath),
],
// As long as d.ts files match their .js counterparts, we don't need to emit them.
// TSC can match them correctly based on file names.
skipTypings: true,
extraDependencies: swcHelperDependency ? [swcHelperDependency] : [],
},
context
Expand All @@ -183,4 +202,14 @@ function removeTmpSwcrc(swcrcPath: string) {
}
}

function createEntryPoints(
options: { additionalEntryPoints?: string[] },
context: ExecutorContext
): string[] {
if (!options.additionalEntryPoints?.length) return [];
return globSync(options.additionalEntryPoints, {
cwd: context.root,
});
}

export default swcExecutor;

1 comment on commit d63d357

@vercel
Copy link

@vercel vercel bot commented on d63d357 Aug 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx-dev-git-master-nrwl.vercel.app
nx-dev-nrwl.vercel.app
nx.dev
nx-five.vercel.app

Please sign in to comment.