Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 118 additions & 40 deletions src/cli/build/esbuild.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import { inspect } from 'util';

import tsconfigPaths from '@esbuild-plugins/tsconfig-paths';
import { build } from 'esbuild';
import { ModuleKind, ModuleResolutionKind, ScriptTarget } from 'typescript';

import { createLogger } from '../../utils/logging';
import { type BuildOptions, build } from 'esbuild';
import { type CompilerOptions, ModuleKind, ScriptTarget } from 'typescript';

import { parseTscArgs } from './args';
import { readTsconfig, tsc } from './tsc';
import type { RunnerParams } from './runner';
import { tsc } from './tsc';

interface EsbuildParameters {
debug: boolean;
}
export type EsbuildParameters = RunnerParams & {
mode: 'build' | 'build-package';
};

export const esbuild = async (
{ debug }: EsbuildParameters,
{ compilerOptions, debug, entryPoints, log, mode }: EsbuildParameters,
args = process.argv.slice(2),
) => {
const log = createLogger(debug);

const tscArgs = parseTscArgs(args);

if (tscArgs.build) {
Expand All @@ -29,15 +26,6 @@ export const esbuild = async (
return;
}

const parsedCommandLine = readTsconfig(args, log);

if (!parsedCommandLine || process.exitCode) {
return;
}

const { fileNames: entryPoints, options: compilerOptions } =
parsedCommandLine;

log.debug(log.bold('Files'));
entryPoints.forEach((filepath) => log.debug(filepath));

Expand All @@ -46,20 +34,122 @@ export const esbuild = async (

const start = process.hrtime.bigint();

// TODO: support `bundle`, `minify`, `splitting`, `treeShaking`
const bundle = false;
switch (mode) {
case 'build':
await runBuild({
compilerOptions,
debug,
entryPoints,
tsconfig: tscArgs.pathname,
});

break;

case 'build-package':
await runBuild({
bundle: true,
compilerOptions: {
...compilerOptions,
module: ModuleKind.ESNext,
},
debug,
entryPoints,
Comment on lines +49 to +56
Copy link
Member Author

Choose a reason for hiding this comment

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

I think these entryPoints are not correct for bundle: true. Strictly speaking it should only output the root lib/index.mjs unless more exports are defined.

outExtension: { '.js': '.mjs' },
tsconfig: tscArgs.pathname,
});

await runBuild({
bundle: true,
compilerOptions: {
...compilerOptions,
module: ModuleKind.NodeNext,
},
debug,
entryPoints,
outExtension: { '.js': '.js' },
tsconfig: tscArgs.pathname,
});

break;
}

const end = process.hrtime.bigint();

log.plain(`Built in ${log.timing(start, end)}.`);

if (compilerOptions.declaration || mode === 'build-package') {
const removeComments = compilerOptions.removeComments ?? false;

await tsc([
'--declaration',
'--emitDeclarationOnly',
...(tscArgs.project ? ['--project', tscArgs.project] : []),
'--removeComments',
removeComments.toString(),
]);
}
};

const ES_MODULE_KINDS = new Set<ModuleKind | undefined>([
ModuleKind.ES2015,
ModuleKind.ES2020,
ModuleKind.ES2022,
ModuleKind.ESNext,
]);

const NODE_MODULE_KINDS = new Set<ModuleKind | undefined>([
ModuleKind.CommonJS,
ModuleKind.Node16,
ModuleKind.NodeNext,
]);

const mapModule = (
compilerOptions: CompilerOptions,
): Pick<BuildOptions, 'format' | 'platform'> => {
if (NODE_MODULE_KINDS.has(compilerOptions.module)) {
return { format: 'cjs', platform: 'node' };
}

if (ES_MODULE_KINDS.has(compilerOptions.module)) {
return { format: 'esm', platform: 'neutral' };
}

return { format: undefined, platform: undefined };
};

type RunEsbuildOptions = {
bundle?: boolean;
compilerOptions: Pick<
CompilerOptions,
'baseUrl' | 'module' | 'outDir' | 'paths' | 'sourceMap' | 'target'
>;
debug: boolean;
entryPoints: string[];
outExtension?: BuildOptions['outExtension'];
tsconfig: string;
};

const runBuild = async ({
bundle = false,
compilerOptions,
debug,
entryPoints,
outExtension,
tsconfig,
}: RunEsbuildOptions) => {
const { format, platform } = mapModule(compilerOptions);

// TODO: support `minify`, `splitting`, `treeShaking`
await build({
bundle,
entryPoints,
format: compilerOptions.module === ModuleKind.CommonJS ? 'cjs' : undefined,
format,
outdir: compilerOptions.outDir,
logLevel: debug ? 'debug' : 'info',
logLimit: 0,
platform:
compilerOptions.moduleResolution === ModuleResolutionKind.NodeJs
? 'node'
: undefined,
outExtension,
packages: 'external',
platform,
plugins: bundle
? []
: [
Expand All @@ -76,18 +166,6 @@ export const esbuild = async (
target: compilerOptions.target
? ScriptTarget[compilerOptions.target].toLocaleLowerCase()
: undefined,
tsconfig: tscArgs.pathname,
tsconfig,
});

const end = process.hrtime.bigint();

log.plain(`Built in ${log.timing(start, end)}.`);

if (compilerOptions.declaration) {
await tsc([
'--declaration',
'--emitDeclarationOnly',
...(tscArgs.project ? ['--project', tscArgs.project] : []),
]);
}
};
80 changes: 24 additions & 56 deletions src/cli/build/index.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,26 @@
import chalk from 'chalk';

import { hasDebugFlag } from '../../utils/args';
import { log } from '../../utils/logging';
import { getStringPropFromConsumerManifest } from '../../utils/manifest';

import { copyAssets } from './assets';
import { esbuild } from './esbuild';
import { readTsconfig, tsc } from './tsc';

export const build = async (args = process.argv.slice(2)) => {
// TODO: define a unified `package.json#/skuba` schema and parser so we don't
// need all these messy lookups.
const tool = await getStringPropFromConsumerManifest('build');

switch (tool) {
case 'esbuild': {
const debug = hasDebugFlag(args);

log.plain(chalk.yellow('esbuild'));
await esbuild({ debug }, args);
break;
}

// TODO: flip the default case over to `esbuild` in skuba vNext.
case undefined:
case 'tsc': {
log.plain(chalk.blue('tsc'));
await tsc(args);
break;
}

default: {
log.err(
'We don’t support the build tool specified in your',
log.bold('package.json'),
'yet:',
);
log.err(log.subtle(JSON.stringify({ skuba: { build: tool } }, null, 2)));
process.exitCode = 1;
return;
}
}

const parsedCommandLine = readTsconfig(args, log);

if (!parsedCommandLine || process.exitCode) {
return;
}

const { options: compilerOptions } = parsedCommandLine;

if (!compilerOptions.outDir) {
return;
}

await copyAssets(compilerOptions.outDir);
};
import { runBuildTool } from './runner';
import { tsc } from './tsc';

export const build = async (args = process.argv.slice(2)) =>
runBuildTool(
{
esbuild: async ({ compilerOptions, ...params }) => {
await esbuild({ ...params, compilerOptions, mode: 'build' }, args);

if (compilerOptions.outDir) {
await copyAssets(compilerOptions.outDir);
}
},

tsc: async ({ compilerOptions }) => {
await tsc(args);

if (compilerOptions.outDir) {
await copyAssets(compilerOptions.outDir);
}
},
},
args,
);
68 changes: 68 additions & 0 deletions src/cli/build/runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import chalk from 'chalk';
import type { CompilerOptions } from 'typescript';

import { hasDebugFlag } from '../../utils/args';
import { type Logger, createLogger } from '../../utils/logging';
import { getStringPropFromConsumerManifest } from '../../utils/manifest';

import { readTsconfig } from './tsc';

export type RunnerParams = {
compilerOptions: CompilerOptions;
debug: boolean;
entryPoints: string[];
log: Logger;
};

type RunBuildToolParams = {
esbuild: (params: RunnerParams, args: string[]) => Promise<void>;
tsc: (params: RunnerParams, args: string[]) => Promise<void>;
};

export const runBuildTool = async (run: RunBuildToolParams, args: string[]) => {
const debug = hasDebugFlag(args);

const log = createLogger(debug);

const tsconfig = readTsconfig(args, log);

if (!tsconfig) {
process.exitCode = 1;
return;
}

const { compilerOptions, entryPoints } = tsconfig;

const params = { compilerOptions, debug, entryPoints, log };

// TODO: define a unified `package.json#/skuba` schema and parser so we don't
// need all these messy lookups.
const tool = await getStringPropFromConsumerManifest('build');

switch (tool) {
case 'esbuild': {
log.plain(chalk.yellow('esbuild'));
await run.esbuild(params, args);
break;
}

// TODO: flip the default case over to `esbuild` in skuba vNext.
case undefined:
case 'tsc': {
log.plain(chalk.blue('tsc'));
await run.tsc(params, args);
break;
}

default: {
log.err(
'We don’t support the build tool specified in your',
log.bold('package.json'),
'yet:',
);
log.err(log.subtle(JSON.stringify({ skuba: { build: tool } }, null, 2)));
process.exitCode = 1;
return;
}
}
};
8 changes: 4 additions & 4 deletions src/cli/build/tsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export const readTsconfig = (args = process.argv.slice(2), log: Logger) => {
);
if (!tsconfigFile) {
log.err(`Could not find ${tscArgs.pathname}.`);
process.exitCode = 1;
return;
}

Expand All @@ -57,7 +56,6 @@ export const readTsconfig = (args = process.argv.slice(2), log: Logger) => {
if (readConfigFile.error) {
log.err(`Could not read ${tscArgs.pathname}.`);
log.subtle(ts.formatDiagnostic(readConfigFile.error, formatHost));
process.exitCode = 1;
return;
}

Expand All @@ -72,9 +70,11 @@ export const readTsconfig = (args = process.argv.slice(2), log: Logger) => {
if (parsedCommandLine.errors.length) {
log.err(`Could not parse ${tscArgs.pathname}.`);
log.subtle(ts.formatDiagnostics(parsedCommandLine.errors, formatHost));
process.exitCode = 1;
return;
}

return parsedCommandLine;
return {
compilerOptions: parsedCommandLine.options,
entryPoints: parsedCommandLine.fileNames,
};
};
Loading