Skip to content

Commit

Permalink
feat(bundling): added support for declarations (*.d.ts)
Browse files Browse the repository at this point in the history
  • Loading branch information
castleadmin authored and mandarini committed May 9, 2024
1 parent 25574ae commit 5136a13
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 7 deletions.
10 changes: 9 additions & 1 deletion docs/generated/packages/esbuild/executors/esbuild.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"version": 2,
"outputCapture": "direct-nodejs",
"title": "esbuild (experimental)",
"description": "Bundle a package for different platforms. Note: declaration (*.d.ts) file are not currently generated.",
"description": "Bundle a package for different platforms.",
"cli": "nx",
"type": "object",
"properties": {
Expand Down Expand Up @@ -52,6 +52,14 @@
"items": { "type": "string", "enum": ["esm", "cjs"] },
"default": ["esm"]
},
"declaration": {
"type": "boolean",
"description": "Generate declaration (*.d.ts) files for every TypeScript or JavaScript file inside your project. Should be used for libraries that are published to an npm repository."
},
"declarationRootDir": {
"type": "string",
"description": "Sets the rootDir for the declaration (*.d.ts) files."
},
"watch": {
"type": "boolean",
"description": "Enable re-building when files change.",
Expand Down
41 changes: 40 additions & 1 deletion e2e/esbuild/src/esbuild.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
checkFilesDoNotExist,
checkFilesExist,
cleanupProject,
createFile,
detectPackageManager,
newProject,
packageInstall,
Expand Down Expand Up @@ -83,7 +84,9 @@ describe('EsBuild Plugin', () => {
`
);
expect(() => runCLI(`build ${myPkg}`)).toThrow();
expect(() => runCLI(`build ${myPkg} --skipTypeCheck`)).not.toThrow();
expect(() =>
runCLI(`build ${myPkg} --skipTypeCheck --no-declaration`)
).not.toThrow();
expect(runCommand(`node dist/libs/${myPkg}/index.cjs`)).toMatch(/Bye/);
// Reset file
updateFile(
Expand Down Expand Up @@ -258,4 +261,40 @@ describe('EsBuild Plugin', () => {
expect(output).not.toMatch(/secret/);
expect(output).toMatch(/foobar/);
});

it('should support declaration builds', () => {
const declarationPkg = uniq('declaration-pkg');
runCLI(`generate @nx/js:lib ${declarationPkg} --bundler=esbuild`);
createFile(
`libs/${declarationPkg}/src/lib/testDir/sub.ts`,
`
export function sub(): string {
return 'sub';
}
`
);
updateFile(
`libs/${declarationPkg}/src/lib/${declarationPkg}.ts`,
`
import { sub } from './testDir/sub';
console.log('${declarationPkg}-' + sub());
`
);

runCLI(
`build ${declarationPkg} --declaration=true --declarationRootDir='libs/${declarationPkg}/src'`
);

checkFilesExist(
`dist/libs/${declarationPkg}/index.cjs`,
`dist/libs/${declarationPkg}/index.d.ts`,
`dist/libs/${declarationPkg}/lib/${declarationPkg}.d.ts`,
`dist/libs/${declarationPkg}/lib/testDir/sub.d.ts`
);

expect(runCommand(`node dist/libs/${declarationPkg}`)).toMatch(
new RegExp(`${declarationPkg}-sub`)
);
}, 300_000);
});
13 changes: 9 additions & 4 deletions packages/esbuild/src/executors/esbuild/esbuild.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,17 @@ function getTypeCheckOptions(
const { watch, tsConfig, outputPath } = options;

const typeCheckOptions: TypeCheckOptions = {
// TODO(jack): Add support for d.ts declaration files -- once the `@nx/js:tsc` changes are in we can use the same logic.
mode: 'noEmit',
...(options.declaration
? {
mode: 'emitDeclarationOnly',
outDir: outputPath,
}
: {
mode: 'noEmit',
}),
tsConfigPath: tsConfig,
// outDir: outputPath,
workspaceRoot: context.root,
rootDir: context.root,
rootDir: options.declarationRootDir ?? context.root,
};

if (watch) {
Expand Down
64 changes: 64 additions & 0 deletions packages/esbuild/src/executors/esbuild/lib/normalize.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,34 @@
import { normalizeOptions } from './normalize';
import { ExecutorContext } from '@nx/devkit';
import { readTsConfig } from '@nx/js';

jest.mock<typeof import('@nx/js')>('@nx/js', () => {
const actualModule = jest.requireActual('@nx/js');

return {
...actualModule,
readTsConfig: jest.fn(() => ({
fileNames: [],
errors: [],
options: {},
})),
};
});

describe('normalizeOptions', () => {
const context: ExecutorContext = {
root: '/',
cwd: '/',
isVerbose: false,
projectName: 'myapp',
projectsConfigurations: {
version: 2,
projects: {
myapp: {
root: 'apps/myapp',
},
},
},
projectGraph: {
nodes: {
myapp: {
Expand Down Expand Up @@ -142,6 +164,31 @@ describe('normalizeOptions', () => {
});
});

it("should use the tsconfig declaration option if the declaration option isn't defined", () => {
(
readTsConfig as jest.MockedFunction<typeof readTsConfig>
).mockImplementationOnce(() => ({
fileNames: [],
errors: [],
options: {
declaration: true,
},
}));

expect(
normalizeOptions(
{
main: 'apps/myapp/src/index.ts',
outputPath: 'dist/apps/myapp',
tsConfig: 'apps/myapp/tsconfig.app.json',
generatePackageJson: true,
assets: [],
},
context
)
).toEqual(expect.objectContaining({ declaration: true }));
});

it('should override thirdParty if bundle:false', () => {
expect(
normalizeOptions(
Expand All @@ -158,4 +205,21 @@ describe('normalizeOptions', () => {
)
).toEqual(expect.objectContaining({ thirdParty: false }));
});

it('should override skipTypeCheck if declaration:true', () => {
expect(
normalizeOptions(
{
main: 'apps/myapp/src/index.ts',
outputPath: 'dist/apps/myapp',
tsConfig: 'apps/myapp/tsconfig.app.json',
generatePackageJson: true,
skipTypeCheck: true,
declaration: true,
assets: [],
},
context
)
).toEqual(expect.objectContaining({ skipTypeCheck: false }));
});
});
36 changes: 36 additions & 0 deletions packages/esbuild/src/executors/esbuild/lib/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import {
import { ExecutorContext, joinPathFragments, logger } from '@nx/devkit';
import chalk = require('chalk');
import * as esbuild from 'esbuild';
import { readTsConfig } from '@nx/js';

export function normalizeOptions(
options: EsBuildExecutorOptions,
context: ExecutorContext
): NormalizedEsBuildExecutorOptions {
const tsConfig = readTsConfig(options.tsConfig);

// If we're not generating package.json file, then copy it as-is as an asset.
const assets = options.generatePackageJson
? options.assets
Expand Down Expand Up @@ -39,6 +42,33 @@ export function normalizeOptions(

const thirdParty = !options.bundle ? false : options.thirdParty;

const { root: projectRoot } =
context.projectsConfigurations.projects[context.projectName];
const declarationRootDir = options.declarationRootDir
? path.join(context.root, options.declarationRootDir)
: undefined;

// if option declaration is defined, then it takes precedence over the tsconfig option
const declaration =
options.declaration ??
(tsConfig.options.declaration || tsConfig.options.composite);

if (options.skipTypeCheck && declaration) {
logger.info(
chalk.yellow(
`Your build has conflicting options, ${chalk.bold(
'skipTypeCheck:true'
)} and ${chalk.bold(
'declaration:true'
)}. Your declarations won't be generated so we added an update ${chalk.bold(
'skipTypeCheck:false'
)}`
)
);
}

const skipTypeCheck = declaration ? false : options.skipTypeCheck;

let userDefinedBuildOptions: esbuild.BuildOptions;
if (options.esbuildConfig) {
const userDefinedConfig = path.resolve(context.root, options.esbuildConfig);
Expand Down Expand Up @@ -67,6 +97,9 @@ export function normalizeOptions(
...rest,
thirdParty,
assets,
declaration,
declarationRootDir,
skipTypeCheck,
userDefinedBuildOptions,
external: options.external ?? [],
singleEntry: false,
Expand All @@ -80,6 +113,9 @@ export function normalizeOptions(
...options,
thirdParty,
assets,
declaration,
declarationRootDir,
skipTypeCheck,
userDefinedBuildOptions,
external: options.external ?? [],
singleEntry: true,
Expand Down
2 changes: 2 additions & 0 deletions packages/esbuild/src/executors/esbuild/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export interface EsBuildExecutorOptions {
additionalEntryPoints?: string[];
assets: (AssetGlob | string)[];
bundle?: boolean;
declaration?: boolean;
declarationRootDir?: string;
deleteOutputPath?: boolean;
esbuildOptions?: Record<string, any>;
esbuildConfig?: string;
Expand Down
10 changes: 9 additions & 1 deletion packages/esbuild/src/executors/esbuild/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"version": 2,
"outputCapture": "direct-nodejs",
"title": "esbuild (experimental)",
"description": "Bundle a package for different platforms. Note: declaration (*.d.ts) file are not currently generated.",
"description": "Bundle a package for different platforms.",
"cli": "nx",
"type": "object",
"properties": {
Expand Down Expand Up @@ -54,6 +54,14 @@
},
"default": ["esm"]
},
"declaration": {
"type": "boolean",
"description": "Generate declaration (*.d.ts) files for every TypeScript or JavaScript file inside your project. Should be used for libraries that are published to an npm repository."
},
"declarationRootDir": {
"type": "string",
"description": "Sets the rootDir for the declaration (*.d.ts) files."
},
"watch": {
"type": "boolean",
"description": "Enable re-building when files change.",
Expand Down

0 comments on commit 5136a13

Please sign in to comment.