feat(pack-up): enable config via nodeAPI & allow bundles to have own tsconfig (#18440)

* feat: add ability to pass config & have tsconfig per bundle

* feat: use node:module builtinModules for node stuff

* chore: remove builtins from externals
This commit is contained in:
Josh 2023-10-16 08:55:43 +01:00 committed by GitHub
parent 8632eeaf18
commit 25d1ceaa44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 126 additions and 39 deletions

View File

@ -10,6 +10,5 @@ export default defineConfig({
},
],
dist: './dist',
externals: ['node:fs', 'node:path'],
runtime: 'node',
});

View File

@ -10,6 +10,5 @@ export default defineConfig({
},
],
dist: './dist',
externals: ['child_process', 'node:fs', 'node:path', 'os', 'path'],
runtime: 'node',
});

View File

@ -3,5 +3,4 @@ import { defineConfig } from '@strapi/pack-up';
export default defineConfig({
runtime: 'node',
externals: ['crypto', 'http', 'https', 'os', 'path', 'stream', 'zlib'],
});

View File

@ -3,13 +3,6 @@ import { defineConfig } from '@strapi/pack-up';
export default defineConfig({
externals: [
'crypto',
'node:async_hooks',
'node:path',
'path',
'stream',
'timers',
'tty',
/**
* Knex dependencies, if we don't mark these as external
* they will be included in the bundle which means they

View File

@ -2,6 +2,5 @@
import { defineConfig } from '@strapi/pack-up';
export default defineConfig({
externals: ['node:stream'],
runtime: 'node',
});

View File

@ -2,7 +2,6 @@
import { defineConfig } from '@strapi/pack-up';
export default defineConfig({
externals: ['crypto', 'fs', 'node:fs', 'node:os', 'node:path', 'node:readline', 'os', 'path'],
preserveModules: true,
runtime: 'node',
});

View File

@ -9,6 +9,5 @@ export default defineConfig({
import: './dist/plopfile.mjs',
},
],
externals: ['node:path', 'path'],
runtime: 'node',
});

View File

@ -2,6 +2,5 @@
import { defineConfig } from '@strapi/pack-up';
export default defineConfig({
externals: ['stream', 'fs', 'path'],
runtime: 'node',
});

View File

@ -10,7 +10,7 @@ export default defineConfig({
require: './dist/cli.js',
},
],
externals: ['fs', 'path', 'child_process', 'fs/promises', 'module', 'os'],
externals: ['node:module'],
runtime: 'node',
minify: false,
sourcemap: true,

View File

@ -1,7 +1,7 @@
import { build as nodeBuild, BuildOptions } from '../../node/build';
import { build as nodeBuild, BuildCLIOptions } from '../../node/build';
import { handleError } from '../errors';
export const build = async (options: Omit<BuildOptions, 'cwd'>) => {
export const build = async (options: BuildCLIOptions) => {
try {
await nodeBuild(options);
} catch (err) {

View File

@ -1,7 +1,7 @@
import { WatchOptions, watch as nodeWatch } from '../../node/watch';
import { WatchCLIOptions, watch as nodeWatch } from '../../node/watch';
import { handleError } from '../errors';
export const watch = async (options: WatchOptions) => {
export const watch = async (options: WatchCLIOptions) => {
try {
await nodeWatch(options);
} catch (err) {

View File

@ -4,7 +4,13 @@ export * from './node/check';
export * from './node/init';
export { defineConfig } from './node/core/config';
export type { Config } from './node/core/config';
export type {
Config,
ConfigBundle,
ConfigOptions,
ConfigProperty,
ConfigPropertyResolver,
} from './node/core/config';
export { defineTemplate, definePackageFeature, definePackageOption } from './node/templates/create';
export type {

View File

@ -4,7 +4,7 @@ import os from 'os';
import { CommonCLIOptions } from '../types';
import { loadConfig } from './core/config';
import { loadConfig, type Config } from './core/config';
import { isError } from './core/errors';
import { getExportExtensionMap, validateExportsOrdering } from './core/exports';
import { createLogger } from './core/logger';
@ -13,14 +13,34 @@ import { createBuildContext } from './createBuildContext';
import { BuildTask, createBuildTasks } from './createTasks';
import { TaskHandler, taskHandlers } from './tasks';
export interface BuildOptions extends CommonCLIOptions {
cwd?: string;
interface BuildCLIOptions extends CommonCLIOptions {
minify?: boolean;
sourcemap?: boolean;
}
export const build = async (opts: BuildOptions = {}) => {
const { silent, debug, cwd = process.cwd(), ...configOptions } = opts;
interface BuildWithConfigFile extends BuildCLIOptions {
configFile?: true;
config?: never;
cwd?: string;
}
interface BuildWithoutConfigFile extends BuildCLIOptions {
configFile: false;
config?: Config;
cwd?: string;
}
type BuildOptions = BuildWithConfigFile | BuildWithoutConfigFile;
const build = async (opts: BuildOptions = {}) => {
const {
silent,
debug,
cwd = process.cwd(),
configFile = true,
config: providedConfig,
...configOptions
} = opts;
const logger = createLogger({ silent, debug });
@ -66,13 +86,18 @@ export const build = async (opts: BuildOptions = {}) => {
packageJsonLoader.succeed('Verified package.json');
/**
* If configFile is true which is the default, atempt to load the config
* otherwise if it's explicitly false then we suspect there might be a config passed
* in the options, so we'll use that instead.
*/
const config = configFile ? await loadConfig({ cwd, logger }) : providedConfig;
/**
* We create tasks based on the exports of the package.json
* their handlers are then ran in the order of the exports map
* and results are logged to see gradual progress.
*/
const config = await loadConfig({ cwd, logger });
const buildContextLoader = ora(`Creating build context ${os.EOL}`).start();
const extMap = getExportExtensionMap();
@ -126,3 +151,6 @@ export const build = async (opts: BuildOptions = {}) => {
});
}
};
export { build };
export type { BuildOptions, BuildCLIOptions, BuildWithConfigFile, BuildWithoutConfigFile };

View File

@ -70,6 +70,7 @@ interface ConfigBundle {
import?: string;
require?: string;
runtime?: Runtime;
tsconfig?: string;
types?: string;
}
@ -99,6 +100,12 @@ interface ConfigOptions {
preserveModules?: boolean;
sourcemap?: boolean;
runtime?: Runtime;
/**
* @description path to the tsconfig file to use for the bundle.
*
* @default tsconfig.build.json
*/
tsconfig?: string;
}
/**
@ -128,4 +135,4 @@ export function resolveConfigProperty<T>(prop: ConfigProperty<T> | undefined, in
}
export { loadConfig, defineConfig, CONFIG_FILE_NAMES };
export type { Config };
export type { Config, ConfigOptions, ConfigBundle, ConfigPropertyResolver, ConfigProperty };

View File

@ -65,7 +65,7 @@ const createBuildContext = async ({
}: BuildContextArgs): Promise<BuildContext> => {
const tsConfig = loadTsConfig({
cwd,
path: 'tsconfig.build.json',
path: resolveConfigProperty(config.tsconfig, 'tsconfig.build.json'),
logger,
});

View File

@ -143,6 +143,7 @@ const createTasks =
exportPath: bundle.source,
sourcePath: bundle.source,
targetPath: bundle.types,
tsconfig: bundle.tsconfig,
});
}
}

View File

@ -4,6 +4,7 @@ import { Observable } from 'rxjs';
import ts from 'typescript';
import { isError } from '../../core/errors';
import { loadTsConfig } from '../../core/tsconfig';
import { printDiagnostic } from './diagnostic';
import { DtsBaseTask } from './types';
@ -33,7 +34,18 @@ const dtsBuildTask: TaskHandler<DtsBuildTask> = {
return new Observable((subscriber) => {
Promise.all(
task.entries.map(async (entry) => {
if (!ctx.ts) {
/**
* Entry level tsconfig's take precedence
*/
const tsconfig = entry.tsconfig
? loadTsConfig({
cwd: ctx.cwd,
path: entry.tsconfig,
logger: ctx.logger,
})
: ctx.ts;
if (!tsconfig) {
ctx.logger.warn(
`You've added a types entry but no tsconfig.json was found for ${entry.targetPath}. Skipping...`
);
@ -41,7 +53,7 @@ const dtsBuildTask: TaskHandler<DtsBuildTask> = {
return;
}
const program = ts.createProgram(ctx.ts.config.fileNames, ctx.ts.config.options);
const program = ts.createProgram(tsconfig.config.fileNames, tsconfig.config.options);
const emitResult = program.emit();

View File

@ -3,6 +3,12 @@ interface DtsTaskEntry {
exportPath: string;
sourcePath: string;
targetPath: string;
/**
* Allow a particular task to have it's own tsconfig
* great for when you're creating a server & web bundle
* package.
*/
tsconfig?: string;
}
interface DtsBaseTask {

View File

@ -4,6 +4,7 @@ import { Observable } from 'rxjs';
import ts from 'typescript';
import { isError } from '../../core/errors';
import { loadTsConfig } from '../../core/tsconfig';
import { printDiagnostic } from './diagnostic';
import { DtsBaseTask } from './types';
@ -36,7 +37,18 @@ const dtsWatchTask: TaskHandler<DtsWatchTask, ts.Diagnostic> = {
return new Observable((subscriber) => {
Promise.all(
task.entries.map(async (entry) => {
if (!ctx.ts) {
/**
* Entry level tsconfig's take precedence
*/
const tsconfig = entry.tsconfig
? loadTsConfig({
cwd: ctx.cwd,
path: entry.tsconfig,
logger: ctx.logger,
})
: ctx.ts;
if (!tsconfig) {
ctx.logger.warn(
`You've added a types entry but no tsconfig.json was found for ${entry.targetPath}. Skipping...`
);
@ -46,7 +58,7 @@ const dtsWatchTask: TaskHandler<DtsWatchTask, ts.Diagnostic> = {
const compilerHost = ts.createWatchCompilerHost(
'tsconfig.build.json',
ctx.ts.config.options,
tsconfig.config.options,
ts.sys,
ts.createEmitAndSemanticDiagnosticsBuilderProgram,
(diagnostic) => {

View File

@ -1,4 +1,5 @@
import react from '@vitejs/plugin-react';
import { builtinModules } from 'node:module';
import path from 'path';
import { InlineConfig, createLogger } from 'vite';
@ -82,7 +83,15 @@ const resolveViteConfig = (ctx: BuildContext, task: ViteBaseTask) => {
const name = idParts[0].startsWith('@') ? `${idParts[0]}/${idParts[1]}` : idParts[0];
if (name && external.includes(name)) {
const builtinModulesWithNodePrefix = [
...builtinModules,
...builtinModules.map((modName) => `node:${modName}`),
];
if (
(name && external.includes(name)) ||
(name && builtinModulesWithNodePrefix.includes(name))
) {
return true;
}

View File

@ -4,7 +4,7 @@ import { Observable, distinctUntilChanged, scan, startWith, switchMap } from 'rx
import { CommonCLIOptions } from '../types';
import { CONFIG_FILE_NAMES, loadConfig } from './core/config';
import { CONFIG_FILE_NAMES, Config, loadConfig } from './core/config';
import { getExportExtensionMap, validateExportsOrdering } from './core/exports';
import { createLogger } from './core/logger';
import { loadPkg, validatePkg } from './core/pkg';
@ -12,12 +12,24 @@ import { createBuildContext } from './createBuildContext';
import { WatchTask, createWatchTasks } from './createTasks';
import { TaskHandler, taskHandlers } from './tasks';
export interface WatchOptions extends CommonCLIOptions {
interface WatchCLIOptions extends CommonCLIOptions {}
interface WatchOptionsWithoutConfig extends WatchCLIOptions {
configFile?: true;
config?: never;
cwd?: string;
}
export const watch = async (opts: WatchOptions) => {
const { silent, debug, cwd = process.cwd() } = opts;
interface WatchOptionsWithConfig extends WatchCLIOptions {
configFile: false;
config?: Config;
cwd?: string;
}
type WatchOptions = WatchOptionsWithConfig | WatchOptionsWithoutConfig;
const watch = async (opts: WatchOptions) => {
const { silent, debug, cwd = process.cwd(), configFile = true, config: providedConfig } = opts;
const logger = createLogger({ silent, debug });
@ -111,12 +123,17 @@ export const watch = async (opts: WatchOptions) => {
}
);
/**
* If configFile is true which is the default, atempt to load the config
* otherwise if it's explicitly false then we suspect there might be a config passed
* in the options, so we'll use that instead.
*/
const config = configFile ? await loadConfig({ cwd, logger }) : providedConfig;
/**
* We create tasks based on the exports of the package.json
* their handlers are then ran in the order of the exports map
* and results are logged to see gradual progress.
*/
const config = await loadConfig({ cwd, logger });
const extMap = getExportExtensionMap();
@ -154,3 +171,6 @@ export const watch = async (opts: WatchOptions) => {
}
});
};
export { watch };
export type { WatchOptions, WatchOptionsWithConfig, WatchOptionsWithoutConfig, WatchCLIOptions };