Skip to content

Commit

Permalink
feat(bundling): add support for esbuild.config.js file (#16092)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaysoo authored Apr 4, 2023
1 parent e11e5d1 commit 807884c
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 31 deletions.
7 changes: 6 additions & 1 deletion docs/generated/packages/esbuild/executors/esbuild.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,14 @@
},
"esbuildOptions": {
"type": "object",
"description": "Additional options to pass to esbuild. See https://esbuild.github.io/api/.",
"description": "Additional options to pass to esbuild. See https://esbuild.github.io/api/. Cannot be used with 'esbuildConfig' option.",
"additionalProperties": true,
"x-priority": "important"
},
"esbuildConfig": {
"type": "string",
"description": "Path to a esbuild configuration file. See https://esbuild.github.io/api/. Cannot be used with 'esbuildOptions' option.",
"x-priority": "important"
}
},
"required": ["tsConfig", "main", "outputPath"],
Expand Down
25 changes: 21 additions & 4 deletions e2e/esbuild/src/esbuild.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import {
checkFilesExist,
cleanupProject,
newProject,
packageInstall,
readFile,
readJson,
rmDist,
runCLI,
runCommand,
runCommandUntil,
uniq,
updateFile,
updateProjectConfig,
packageInstall,
rmDist,
runCommandUntil,
waitUntil,
} from '@nrwl/e2e/utils';

Expand Down Expand Up @@ -229,5 +229,22 @@ describe('EsBuild Plugin', () => {

expect(runCommand(`node dist/libs/${myPkg}/main.js`)).toMatch(/main/);
expect(runCommand(`node dist/libs/${myPkg}/extra.js`)).toMatch(/extra/);
});
}, 120_000);

it('should support external esbuild.config.js file', async () => {
const myPkg = uniq('my-pkg');
runCLI(`generate @nrwl/js:lib ${myPkg} --bundler=esbuild`);
updateFile(
`libs/${myPkg}/esbuild.config.js`,
`console.log('custom config loaded');\nmodule.exports = {};\n`
);
updateProjectConfig(myPkg, (json) => {
delete json.targets.build.options.esbuildOptions;
json.targets.build.options.esbuildConfig = `libs/${myPkg}/esbuild.config.js`;
return json;
});

const output = runCLI(`build ${myPkg}`);
expect(output).toContain('custom config loaded');
}, 120_000);
});
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ describe('buildEsbuildOptions', () => {
outputFileName: 'index.js',
singleEntry: true,
external: [],
userDefinedBuildOptions: {},
},
context
)
Expand Down Expand Up @@ -89,6 +90,7 @@ describe('buildEsbuildOptions', () => {
outputFileName: 'index.js',
singleEntry: false,
external: [],
userDefinedBuildOptions: {},
},
context
)
Expand Down Expand Up @@ -128,6 +130,7 @@ describe('buildEsbuildOptions', () => {
outputFileName: 'index.js',
singleEntry: true,
external: [],
userDefinedBuildOptions: {},
},
context
)
Expand Down Expand Up @@ -167,6 +170,7 @@ describe('buildEsbuildOptions', () => {
outputFileName: 'index.js',
singleEntry: true,
external: [],
userDefinedBuildOptions: {},
},
context
)
Expand Down Expand Up @@ -203,7 +207,7 @@ describe('buildEsbuildOptions', () => {
assets: [],
singleEntry: true,
external: [],
esbuildOptions: {
userDefinedBuildOptions: {
outExtension: {
'.js': '.mjs',
},
Expand Down Expand Up @@ -242,7 +246,7 @@ describe('buildEsbuildOptions', () => {
assets: [],
singleEntry: true,
external: [],
esbuildOptions: {
userDefinedBuildOptions: {
outExtension: {
'.js': '.js',
},
Expand Down Expand Up @@ -282,7 +286,7 @@ describe('buildEsbuildOptions', () => {
assets: [],
singleEntry: true,
external: [],
esbuildOptions: {
userDefinedBuildOptions: {
outExtension: {
'.js': '.cjs',
},
Expand Down Expand Up @@ -323,7 +327,7 @@ describe('buildEsbuildOptions', () => {
singleEntry: true,
outputFileName: 'index.js',
external: ['foo'],
esbuildOptions: {
userDefinedBuildOptions: {
external: ['bar'],
},
},
Expand Down Expand Up @@ -361,6 +365,7 @@ describe('buildEsbuildOptions', () => {
assets: [],
singleEntry: true,
external: ['foo'],
userDefinedBuildOptions: {},
},
context
)
Expand Down Expand Up @@ -398,6 +403,7 @@ describe('buildEsbuildOptions', () => {
singleEntry: true,
sourcemap: true,
external: [],
userDefinedBuildOptions: {},
},
context
)
Expand Down Expand Up @@ -434,6 +440,7 @@ describe('buildEsbuildOptions', () => {
assets: [],
singleEntry: true,
external: [],
userDefinedBuildOptions: {},
},
context
)
Expand Down Expand Up @@ -469,7 +476,7 @@ describe('buildEsbuildOptions', () => {
outputFileName: 'index.js',
assets: [],
singleEntry: true,
esbuildOptions: {
userDefinedBuildOptions: {
sourcemap: true,
},
external: [],
Expand Down Expand Up @@ -508,7 +515,7 @@ describe('buildEsbuildOptions', () => {
outputFileName: 'index.js',
assets: [],
singleEntry: true,
esbuildOptions: {
userDefinedBuildOptions: {
sourcemap: false,
},
sourcemap: true,
Expand Down
31 changes: 16 additions & 15 deletions packages/esbuild/src/executors/esbuild/lib/build-esbuild-options.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import * as esbuild from 'esbuild';
import * as path from 'path';
import { join, parse } from 'path';
import { existsSync, mkdirSync, writeFileSync } from 'fs';
import {
normalizePath,
ExecutorContext,
joinPathFragments,
normalizePath,
ProjectGraphProjectNode,
} from '@nrwl/devkit';
import { existsSync, mkdirSync, writeFileSync } from 'fs';

import { getClientEnvironment } from '../../../utils/environment-variables';
import {
EsBuildExecutorOptions,
NormalizedEsBuildExecutorOptions,
} from '../schema';
import { NormalizedEsBuildExecutorOptions } from '../schema';
import { getEntryPoints } from '../../../utils/get-entry-points';

const ESM_FILE_EXTENSION = '.js';
Expand All @@ -25,22 +21,27 @@ export function buildEsbuildOptions(
context: ExecutorContext
): esbuild.BuildOptions {
const outExtension = getOutExtension(format, options);

const esbuildOptions: esbuild.BuildOptions = {
...options.esbuildOptions,
...options.userDefinedBuildOptions,
entryNames:
options.outputHashing === 'all' ? '[dir]/[name].[hash]' : '[dir]/[name]',
bundle: options.bundle,
// Cannot use external with bundle option
external: options.bundle
? [...(options.esbuildOptions?.external ?? []), ...options.external]
? [
...(options.userDefinedBuildOptions?.external ?? []),
...options.external,
]
: undefined,
minify: options.minify,
platform: options.platform,
target: options.target,
metafile: options.metafile,
tsconfig: options.tsConfig,
sourcemap:
(options.sourcemap ?? options.esbuildOptions?.sourcemap) || false,
(options.sourcemap ?? options.userDefinedBuildOptions?.sourcemap) ||
false,
format,
outExtension: {
'.js': outExtension,
Expand Down Expand Up @@ -119,9 +120,9 @@ export function buildEsbuildOptions(

export function getOutExtension(
format: 'cjs' | 'esm',
options: EsBuildExecutorOptions
options: NormalizedEsBuildExecutorOptions
): '.cjs' | '.mjs' | '.js' {
const userDefinedExt = options.esbuildOptions?.outExtension?.['.js'];
const userDefinedExt = options.userDefinedBuildOptions?.outExtension?.['.js'];
// Allow users to change the output extensions from default CJS and ESM extensions.
// CJS -> .js
// ESM -> .mjs
Expand All @@ -136,15 +137,15 @@ export function getOutExtension(

export function getOutfile(
format: 'cjs' | 'esm',
options: EsBuildExecutorOptions,
options: NormalizedEsBuildExecutorOptions,
context: ExecutorContext
) {
const ext = getOutExtension(format, options);
const candidate = joinPathFragments(
context.target.options.outputPath,
options.outputFileName
);
const { dir, name } = parse(candidate);
const { dir, name } = path.parse(candidate);
return `${dir}/${name}${ext}`;
}

Expand Down Expand Up @@ -305,7 +306,7 @@ function getTsConfigCompilerPaths(context: ExecutorContext): {

function getRootTsConfigPath(context: ExecutorContext): string | null {
for (const tsConfigName of ['tsconfig.base.json', 'tsconfig.json']) {
const tsConfigPath = join(context.root, tsConfigName);
const tsConfigPath = path.join(context.root, tsConfigName);
if (existsSync(tsConfigPath)) {
return tsConfigPath;
}
Expand Down
28 changes: 25 additions & 3 deletions packages/esbuild/src/executors/esbuild/lib/normalize.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { parse } from 'path';
import * as fs from 'fs';
import * as path from 'path';
import {
EsBuildExecutorOptions,
NormalizedEsBuildExecutorOptions,
} from '../schema';
import { ExecutorContext, joinPathFragments, logger } from '@nrwl/devkit';
import chalk = require('chalk');
import * as esbuild from 'esbuild';

export function normalizeOptions(
options: EsBuildExecutorOptions,
Expand Down Expand Up @@ -36,6 +38,24 @@ export function normalizeOptions(
}

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

let userDefinedBuildOptions: esbuild.BuildOptions;
if (options.esbuildConfig) {
const userDefinedConfig = path.resolve(context.root, options.esbuildConfig);

if (options.esbuildOptions)
throw new Error(
`Cannot use both esbuildOptions and esbuildConfig options. Remove one of them and try again.`
);
if (!fs.existsSync(userDefinedConfig))
throw new Error(
`Path of esbuildConfig does not exist: ${userDefinedConfig}`
);
userDefinedBuildOptions = require(userDefinedConfig);
} else if (options.esbuildOptions) {
userDefinedBuildOptions = options.esbuildOptions;
}

if (options.additionalEntryPoints?.length > 0) {
const { outputFileName, ...rest } = options;
if (outputFileName) {
Expand All @@ -47,23 +67,25 @@ export function normalizeOptions(
...rest,
thirdParty,
assets,
userDefinedBuildOptions,
external: options.external ?? [],
singleEntry: false,
// Use the `main` file name as the output file name.
// This is needed for `@nrwl/js:node` to know the main file to execute.
// NOTE: The .js default extension may be replaced later in getOutfile() call.
outputFileName: `${parse(options.main).name}.js`,
outputFileName: `${path.parse(options.main).name}.js`,
};
} else {
return {
...options,
thirdParty,
assets,
userDefinedBuildOptions,
external: options.external ?? [],
singleEntry: true,
outputFileName:
// NOTE: The .js default extension may be replaced later in getOutfile() call.
options.outputFileName ?? `${parse(options.main).name}.js`,
options.outputFileName ?? `${path.parse(options.main).name}.js`,
};
}
}
5 changes: 4 additions & 1 deletion packages/esbuild/src/executors/esbuild/schema.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AssetGlob } from '@nrwl/js/src/utils/assets/assets';
import * as esbuild from 'esbuild';

type Compiler = 'babel' | 'swc';

Expand All @@ -10,6 +11,7 @@ export interface EsBuildExecutorOptions {
deleteOutputPath?: boolean;
dependenciesFieldType?: boolean;
esbuildOptions?: Record<string, any>;
esbuildConfig?: string;
external?: string[];
format?: Array<'esm' | 'cjs'>;
generatePackageJson?: boolean;
Expand All @@ -29,7 +31,8 @@ export interface EsBuildExecutorOptions {
}

export interface NormalizedEsBuildExecutorOptions
extends EsBuildExecutorOptions {
extends Omit<EsBuildExecutorOptions, 'esbuildOptions' | 'esbuildConfig'> {
singleEntry: boolean;
external: string[];
userDefinedBuildOptions: esbuild.BuildOptions;
}
7 changes: 6 additions & 1 deletion packages/esbuild/src/executors/esbuild/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,14 @@
},
"esbuildOptions": {
"type": "object",
"description": "Additional options to pass to esbuild. See https://esbuild.github.io/api/.",
"description": "Additional options to pass to esbuild. See https://esbuild.github.io/api/. Cannot be used with 'esbuildConfig' option.",
"additionalProperties": true,
"x-priority": "important"
},
"esbuildConfig": {
"type": "string",
"description": "Path to a esbuild configuration file. See https://esbuild.github.io/api/. Cannot be used with 'esbuildOptions' option.",
"x-priority": "important"
}
},
"required": ["tsConfig", "main", "outputPath"],
Expand Down

1 comment on commit 807884c

@vercel
Copy link

@vercel vercel bot commented on 807884c Apr 4, 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
nx-dev-git-master-nrwl.vercel.app
nx-dev-nrwl.vercel.app
nx-five.vercel.app

Please sign in to comment.