tsdown 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- import type { ModuleFormat } from "rolldown";
2
- export declare function resolveOutputExtension(pkg: any, format: ModuleFormat): "mjs" | "cjs" | "js";
1
+ import type { NormalizedFormat } from "../options.js";
2
+ export declare function resolveOutputExtension(pkg: any, format: NormalizedFormat): "mjs" | "cjs" | "js";
@@ -0,0 +1,2 @@
1
+ import type { NormalizedFormat, ResolvedOptions } from "../options.js";
2
+ export declare function getShimsInject(format: NormalizedFormat, platform: ResolvedOptions["platform"]): Record<string, [string, string]> | undefined;
package/dist/index.d.ts CHANGED
@@ -4,15 +4,13 @@ import { logger } from "./utils/logger.js";
4
4
  * Build with tsdown.
5
5
  */
6
6
  export declare function build(userOptions?: Omit<Options, "silent">): Promise<void>;
7
+ export declare const pkgRoot: string;
7
8
  /**
8
9
  * Build a single configuration, without watch and shortcuts features.
9
10
  *
10
11
  * @param resolved Resolved options
11
12
  */
12
- export declare function buildSingle(resolved: ResolvedOptions): Promise<{
13
- rebuild: () => Promise<void>;
14
- close: () => Promise<void>;
15
- } | undefined>;
13
+ export declare function buildSingle(resolved: ResolvedOptions): Promise<(() => Promise<void>) | undefined>;
16
14
  /**
17
15
  * Defines the configuration for tsdown.
18
16
  */
package/dist/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { debug, logger } from "./logger-Pk57TMWT.js";
2
2
  import { ExternalPlugin } from "./external-9r3oq3tH.js";
3
+ import path, { dirname as dirname$1, normalize, sep, default as path$1, default as path$2, default as path$3, default as path$4, default as path$5 } from "node:path";
3
4
  import process, { default as process$1, default as process$2, default as process$3 } from "node:process";
4
- import { rolldown } from "rolldown";
5
+ import { fileURLToPath } from "node:url";
6
+ import { build as rolldownBuild } from "rolldown";
5
7
  import { transformPlugin } from "rolldown/experimental";
6
8
  import { IsolatedDecl } from "unplugin-isolated-decl";
7
9
  import { Unused } from "unplugin-unused";
8
10
  import { access, readdir, rm, stat } from "node:fs/promises";
9
- import path, { default as path$1, default as path$2 } from "node:path";
10
11
  import { glob, glob as glob$1 } from "tinyglobby";
11
12
  import { readPackageJSON } from "pkg-types";
12
13
  import readline from "node:readline";
@@ -14,15 +15,33 @@ import pc, { default as pc$1, default as pc$2 } from "picocolors";
14
15
  import { loadConfig } from "unconfig";
15
16
 
16
17
  //#region src/utils/fs.ts
17
- function fsExists(path$3) {
18
- return access(path$3).then(() => true, () => false);
18
+ function fsExists(path$6) {
19
+ return access(path$6).then(() => true, () => false);
20
+ }
21
+ function lowestCommonAncestor(...filepaths) {
22
+ if (filepaths.length === 0) return "";
23
+ if (filepaths.length === 1) return dirname$1(filepaths[0]);
24
+ filepaths = filepaths.map(normalize);
25
+ const [first, ...rest] = filepaths;
26
+ let ancestor = first.split(sep);
27
+ for (const filepath of rest) {
28
+ const directories = filepath.split(sep, ancestor.length);
29
+ let index = 0;
30
+ for (const directory of directories) if (directory === ancestor[index]) index += 1;
31
+ else {
32
+ ancestor = ancestor.slice(0, index);
33
+ break;
34
+ }
35
+ ancestor = ancestor.slice(0, index);
36
+ }
37
+ return ancestor.length <= 1 && ancestor[0] === "" ? sep + ancestor[0] : ancestor.join(sep);
19
38
  }
20
39
 
21
40
  //#endregion
22
41
  //#region src/features/clean.ts
23
42
  async function cleanOutDir(cwd, patterns) {
24
43
  const files = [];
25
- if (await fsExists(cwd)) files.push(...(await readdir(cwd)).map((file) => path$2.resolve(cwd, file)));
44
+ if (await fsExists(cwd)) files.push(...(await readdir(cwd)).map((file) => path$5.resolve(cwd, file)));
26
45
  if (patterns.length) files.push(...await glob$1(patterns, {
27
46
  cwd,
28
47
  absolute: true
@@ -40,7 +59,7 @@ async function cleanOutDir(cwd, patterns) {
40
59
  //#endregion
41
60
  //#region src/utils/package.ts
42
61
  async function readPackageJson(dir) {
43
- const packageJsonPath = path$1.join(dir, "package.json");
62
+ const packageJsonPath = path$4.join(dir, "package.json");
44
63
  const exists = await fsExists(packageJsonPath);
45
64
  if (!exists) return;
46
65
  debug("Reading package.json:", packageJsonPath);
@@ -59,15 +78,24 @@ function getPackageType(pkg) {
59
78
  function resolveOutputExtension(pkg, format) {
60
79
  const moduleType = getPackageType(pkg);
61
80
  switch (format) {
62
- case "es":
63
- case "esm":
64
- case "module": return moduleType === "module" ? "js" : "mjs";
65
- case "cjs":
66
- case "commonjs": return moduleType === "module" ? "cjs" : "js";
81
+ case "es": return moduleType === "module" ? "js" : "mjs";
82
+ case "cjs": return moduleType === "module" ? "cjs" : "js";
67
83
  default: return "js";
68
84
  }
69
85
  }
70
86
 
87
+ //#endregion
88
+ //#region src/features/shims.ts
89
+ function getShimsInject(format, platform) {
90
+ if (format === "es" && platform === "node") {
91
+ const shimFile = path$3.resolve(pkgRoot, "esm-shims.js");
92
+ return {
93
+ __dirname: [shimFile, "__dirname"],
94
+ __filename: [shimFile, "__filename"]
95
+ };
96
+ }
97
+ }
98
+
71
99
  //#endregion
72
100
  //#region src/features/shortcuts.ts
73
101
  function shortcuts(restart) {
@@ -168,9 +196,13 @@ async function resolveEntry(entry) {
168
196
  if (typeof entry === "string") entry = [entry];
169
197
  if (Array.isArray(entry)) {
170
198
  const resolvedEntry = await glob(entry);
199
+ const base = lowestCommonAncestor(...resolvedEntry);
171
200
  if (resolvedEntry.length > 0) {
172
- entry = resolvedEntry;
173
- logger.info(`entry: ${pc$1.blue(entry.join(", "))}`);
201
+ logger.info(`entry: ${pc$1.blue(resolvedEntry.join(", "))}`);
202
+ entry = Object.fromEntries(resolvedEntry.map((file) => {
203
+ const relative = path$2.relative(base, file);
204
+ return [relative.slice(0, relative.length - path$2.extname(relative).length), file];
205
+ }));
174
206
  } else throw new Error(`Cannot find entry: ${entry}`);
175
207
  } else {
176
208
  const files = Object.values(entry);
@@ -192,16 +224,15 @@ async function resolveOptions(options) {
192
224
  ...subConfig,
193
225
  ...options
194
226
  };
195
- let { entry, format = ["es"], plugins = [], clean = false, silent = false, treeshake = true, platform = "node", outDir = "dist", sourcemap = false, dts = false, unused = false, watch = false, skipNodeModulesBundle = false } = subOptions;
227
+ let { entry, format = ["es"], plugins = [], clean = false, silent = false, treeshake = true, platform = "node", outDir = "dist", sourcemap = false, dts = false, unused = false, watch = false, shims = false, skipNodeModulesBundle = false } = subOptions;
196
228
  entry = await resolveEntry(entry);
197
- format = toArray(format, "es");
198
229
  if (clean === true) clean = [];
199
230
  return {
200
231
  ...subOptions,
201
232
  entry,
202
233
  plugins,
203
- format,
204
- outDir: path.resolve(outDir),
234
+ format: normalizeFormat(format),
235
+ outDir: path$1.resolve(outDir),
205
236
  clean,
206
237
  silent,
207
238
  treeshake,
@@ -210,10 +241,23 @@ async function resolveOptions(options) {
210
241
  dts,
211
242
  unused,
212
243
  watch,
244
+ shims,
213
245
  skipNodeModulesBundle
214
246
  };
215
247
  })), configFile];
216
248
  }
249
+ function normalizeFormat(format) {
250
+ return toArray(format, "es").map((format$1) => {
251
+ switch (format$1) {
252
+ case "es":
253
+ case "esm":
254
+ case "module": return "es";
255
+ case "cjs":
256
+ case "commonjs": return "cjs";
257
+ default: return format$1;
258
+ }
259
+ });
260
+ }
217
261
  async function loadConfigFile(options) {
218
262
  let { config: filePath } = options;
219
263
  if (filePath === false) return [[]];
@@ -221,11 +265,11 @@ async function loadConfigFile(options) {
221
265
  let overrideConfig = false;
222
266
  let stats;
223
267
  if (typeof filePath === "string" && (stats = await stat(filePath).catch(() => null))) {
224
- const resolved = path.resolve(filePath);
268
+ const resolved = path$1.resolve(filePath);
225
269
  if (stats.isFile()) {
226
270
  overrideConfig = true;
227
271
  filePath = resolved;
228
- cwd = path.dirname(filePath);
272
+ cwd = path$1.dirname(filePath);
229
273
  } else cwd = resolved;
230
274
  }
231
275
  const { config, sources } = await loadConfig({
@@ -264,69 +308,71 @@ async function build(userOptions = {}) {
264
308
  const [resolveds, configFile] = await resolveOptions(userOptions);
265
309
  if (configFile) debug("Loaded config:", configFile);
266
310
  else debug("No config file found");
267
- const contexts = await Promise.all(resolveds.map(buildSingle));
311
+ const rebuilds = await Promise.all(resolveds.map(buildSingle));
268
312
  const cleanCbs = [];
269
313
  for (const [i, resolved] of resolveds.entries()) {
270
- const context = contexts[i];
271
- if (!context) continue;
272
- const watcher = await watchBuild(resolved, context.rebuild);
273
- cleanCbs.push(async () => {
274
- await watcher.close();
275
- await contexts[i].close();
276
- });
314
+ const rebuild = rebuilds[i];
315
+ if (!rebuild) continue;
316
+ const watcher = await watchBuild(resolved, rebuild);
317
+ cleanCbs.push(() => watcher.close());
277
318
  }
278
319
  if (cleanCbs.length) shortcuts(async () => {
279
320
  for (const clean of cleanCbs) await clean();
280
321
  build(userOptions);
281
322
  });
282
323
  }
324
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
325
+ const pkgRoot = path.resolve(dirname, "..");
283
326
  async function buildSingle(resolved) {
284
- const { entry, external, plugins, outDir, format, clean, platform, alias, treeshake, sourcemap, dts, minify, watch, unused, target, onSuccess } = resolved;
327
+ const { entry, external, plugins: userPlugins, outDir, format, clean, platform, alias, treeshake, sourcemap, dts, minify, watch, unused, target, define, shims, onSuccess } = resolved;
285
328
  if (clean) await cleanOutDir(outDir, clean);
286
329
  const pkg = await readPackageJson(process.cwd());
287
- let startTime = performance.now();
288
- const inputOptions = {
289
- input: entry,
290
- external,
291
- resolve: { alias },
292
- treeshake,
293
- platform,
294
- plugins: [
330
+ await rebuild(true);
331
+ if (watch) return () => rebuild();
332
+ async function rebuild(first) {
333
+ const startTime = performance.now();
334
+ const plugins = [
295
335
  pkg && ExternalPlugin(pkg, resolved.skipNodeModulesBundle),
296
336
  unused && Unused.rolldown(unused === true ? {} : unused),
297
337
  dts && IsolatedDecl.rolldown(dts === true ? {} : dts),
298
- target && transformPlugin({ target: typeof target === "string" ? target : target.join(",") }),
299
- plugins
300
- ].filter((plugin) => !!plugin),
301
- ...resolved.inputOptions
302
- };
303
- const build$1 = await rolldown(inputOptions);
304
- const outputOptions = await Promise.all(format.map(async (format$1) => {
305
- const extension = resolveOutputExtension(pkg, format$1);
306
- const outputOptions$1 = {
307
- format: format$1,
308
- name: resolved.globalName,
309
- sourcemap,
310
- dir: outDir,
311
- minify,
312
- entryFileNames: `[name].${extension}`,
313
- chunkFileNames: `[name]-[hash].${extension}`
314
- };
315
- const userOutputOptions = typeof resolved.outputOptions === "function" ? await resolved.outputOptions(outputOptions$1, format$1) : resolved.outputOptions;
316
- return {
317
- ...outputOptions$1,
318
- ...userOutputOptions
319
- };
320
- }));
321
- await writeBundle(true);
322
- if (watch) return {
323
- rebuild: writeBundle,
324
- close: () => build$1.close()
325
- };
326
- else await build$1.close();
327
- async function writeBundle(first) {
328
- if (!first) startTime = performance.now();
329
- await Promise.all(outputOptions.map((outputOptions$1) => build$1.write(outputOptions$1)));
338
+ target && transformPlugin({ target: target && (typeof target === "string" ? target : target.join(",")) }),
339
+ userPlugins
340
+ ].filter((plugin) => !!plugin);
341
+ await Promise.all(format.map(async (format$1) => {
342
+ const inputOptions = {
343
+ input: entry,
344
+ external,
345
+ resolve: { alias },
346
+ treeshake,
347
+ platform,
348
+ define,
349
+ plugins,
350
+ ...resolved.inputOptions,
351
+ inject: {
352
+ ...shims && getShimsInject(format$1, platform),
353
+ ...resolved.inputOptions?.inject
354
+ }
355
+ };
356
+ const extension = resolveOutputExtension(pkg, format$1);
357
+ let outputOptions = {
358
+ format: format$1,
359
+ name: resolved.globalName,
360
+ sourcemap,
361
+ dir: outDir,
362
+ minify,
363
+ entryFileNames: `[name].${extension}`,
364
+ chunkFileNames: `[name]-[hash].${extension}`
365
+ };
366
+ const userOutputOptions = typeof resolved.outputOptions === "function" ? await resolved.outputOptions(outputOptions, format$1) : resolved.outputOptions;
367
+ outputOptions = {
368
+ ...outputOptions,
369
+ ...userOutputOptions
370
+ };
371
+ await rolldownBuild({
372
+ ...inputOptions,
373
+ output: outputOptions
374
+ });
375
+ }));
330
376
  logger.success(`${first ? "Build" : "Rebuild"} complete in ${Math.round(performance.now() - startTime)}ms`);
331
377
  await onSuccess?.();
332
378
  }
@@ -336,4 +382,4 @@ function defineConfig(options) {
336
382
  }
337
383
 
338
384
  //#endregion
339
- export { build, buildSingle, defineConfig, logger };
385
+ export { build, buildSingle, defineConfig, logger, pkgRoot };
package/dist/options.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { External } from "./features/external.js";
2
2
  import type { Arrayable, MarkPartial, MaybePromise, Overwrite } from "./utils/types.js";
3
- import type { InputOptions, ModuleFormat, OutputOptions } from "rolldown";
3
+ import type { InputOptions, InternalModuleFormat, ModuleFormat, OutputOptions } from "rolldown";
4
4
  import type { Options as IsolatedDeclOptions } from "unplugin-isolated-decl";
5
5
  import type { Options as UnusedOptions } from "unplugin-unused";
6
6
  export type Sourcemap = boolean | "inline" | "hidden";
@@ -24,8 +24,10 @@ export interface Options {
24
24
  /** @default false */
25
25
  minify?: boolean;
26
26
  target?: string | string[];
27
+ define?: Record<string, string>;
27
28
  /** @default 'node' */
28
29
  platform?: "node" | "neutral" | "browser";
30
+ shims?: boolean;
29
31
  /**
30
32
  * Enable dts generation with `isolatedDeclarations` (experimental)
31
33
  */
@@ -48,8 +50,10 @@ export interface Options {
48
50
  */
49
51
  export type Config = Arrayable<Omit<Options, "config">>;
50
52
  export type ResolvedConfig = Extract<Config, any[]>;
51
- export type ResolvedOptions = Omit<Overwrite<MarkPartial<Options, "globalName" | "inputOptions" | "outputOptions" | "minify" | "target" | "alias" | "external" | "onSuccess">, {
52
- format: ModuleFormat[];
53
+ export type NormalizedFormat = Exclude<InternalModuleFormat, "app"> | "experimental-app";
54
+ export type ResolvedOptions = Omit<Overwrite<MarkPartial<Options, "globalName" | "inputOptions" | "outputOptions" | "minify" | "target" | "define" | "alias" | "external" | "onSuccess">, {
55
+ format: NormalizedFormat[];
53
56
  clean: string[] | false;
54
57
  }>, "config">;
55
58
  export declare function resolveOptions(options: Options): Promise<[configs: ResolvedOptions[], configFile?: string]>;
59
+ export declare function normalizeFormat(format: ModuleFormat | ModuleFormat[]): NormalizedFormat[];
package/dist/run.js CHANGED
@@ -5,13 +5,13 @@ import pc from "picocolors";
5
5
  import { cac } from "cac";
6
6
 
7
7
  //#region package.json
8
- var version = "0.4.2";
8
+ var version = "0.4.4";
9
9
 
10
10
  //#endregion
11
11
  //#region src/cli.ts
12
12
  async function runCLI() {
13
13
  const cli = cac("tsdown");
14
- cli.command("[...files]", "Bundle files", { ignoreOptionDefaultValue: true }).option("-c, --config <filename>", "Use a custom config file").option("--no-config", "Disable config file").option("--format <format>", "Bundle format: esm, cjs, iife", { default: "esm" }).option("--clean", "Clean output directory").option("--minify", "Minify output").option("--target <target>", "Bundle target, e.g \"es2015\", \"esnext\"").option("--silent", "Suppress non-error logs").option("-d, --out-dir <dir>", "Output directory", { default: "dist" }).option("--treeshake", "Tree-shake bundle", { default: true }).option("--sourcemap", "Generate source map", { default: false }).option("--platform <platform>", "Target platform", { default: "node" }).option("-w, --watch [path]", "Watch mode").action(async (input, flags) => {
14
+ cli.command("[...files]", "Bundle files", { ignoreOptionDefaultValue: true }).option("-c, --config <filename>", "Use a custom config file").option("--no-config", "Disable config file").option("--format <format>", "Bundle format: esm, cjs, iife", { default: "esm" }).option("--clean", "Clean output directory").option("--minify", "Minify output").option("--target <target>", "Bundle target, e.g \"es2015\", \"esnext\"").option("--silent", "Suppress non-error logs").option("-d, --out-dir <dir>", "Output directory", { default: "dist" }).option("--treeshake", "Tree-shake bundle", { default: true }).option("--sourcemap", "Generate source map", { default: false }).option("--shims", "Enable cjs and esm shims ", { default: false }).option("--platform <platform>", "Target platform", { default: "node" }).option("-w, --watch [path]", "Watch mode").action(async (input, flags) => {
15
15
  if (!("CONSOLA_LEVEL" in process.env)) logger.level = flags.silent ? 0 : 3;
16
16
  logger.info(`tsdown ${pc.dim(`v${version}`)} powered by rolldown ${pc.dim(`v${rolldownVersion}`)}`);
17
17
  const { build } = await import("./index.js");
@@ -1 +1,2 @@
1
1
  export declare function fsExists(path: string): Promise<boolean>;
2
+ export declare function lowestCommonAncestor(...filepaths: string[]): string;
package/esm-shims.js ADDED
@@ -0,0 +1,9 @@
1
+ // Shim globals in esm bundle
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ const getFilename = () => fileURLToPath(import.meta.url)
6
+ const getDirname = () => path.dirname(getFilename())
7
+
8
+ export const __dirname = /* @__PURE__ */ getDirname()
9
+ export const __filename = /* @__PURE__ */ getFilename()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsdown",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "An even faster bundler powered by Rolldown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,7 +14,8 @@
14
14
  },
15
15
  "author": "三咲智子 Kevin Deng <sxzz@sxzz.moe>",
16
16
  "files": [
17
- "dist"
17
+ "dist",
18
+ "esm-shims.js"
18
19
  ],
19
20
  "main": "./dist/index.js",
20
21
  "module": "./dist/index.js",
@@ -45,7 +46,7 @@
45
46
  "debug": "^4.3.7",
46
47
  "picocolors": "^1.1.1",
47
48
  "pkg-types": "^1.2.1",
48
- "rolldown": "0.15.0-snapshot-993c4a1-20241205003858",
49
+ "rolldown": "0.15.0-snapshot-64a831c-20241207003504",
49
50
  "tinyglobby": "^0.2.10",
50
51
  "unconfig": "^0.6.0",
51
52
  "unplugin-isolated-decl": "^0.9.3",
@@ -58,10 +59,10 @@
58
59
  "@types/node": "^22.10.1",
59
60
  "bumpp": "^9.8.1",
60
61
  "eslint": "^9.16.0",
61
- "execa": "^9.5.1",
62
62
  "fdir": "^6.4.2",
63
63
  "oxc-transform": "^0.39.0",
64
64
  "prettier": "^3.4.2",
65
+ "tinyexec": "^0.3.1",
65
66
  "tsup": "^8.3.5",
66
67
  "tsx": "^4.19.2",
67
68
  "typescript": "~5.7.2",