tsdown 0.1.0 → 0.2.0

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.
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # tsdown [![npm](https://img.shields.io/npm/v/tsdown.svg)](https://npmjs.com/package/tsdown) [![Unit Test](https://github.com/sxzz/tsdown/actions/workflows/unit-test.yml/badge.svg)](https://github.com/sxzz/tsdown/actions/workflows/unit-test.yml)
1
+ # tsdown [![npm](https://img.shields.io/npm/v/tsdown.svg)](https://npmjs.com/package/tsdown) [![Unit Test](https://github.com/sxzz/tsdown/actions/workflows/unit-test.yml/badge.svg)](https://github.com/sxzz/tsdown/actions/workflows/unit-test.yml) [![JSR](https://jsr.io/badges/@sxzz/tsdown)](https://jsr.io/@sxzz/tsdown)
2
2
 
3
3
  An even faster bundler powered by Rolldown.
4
4
 
5
5
  > [!NOTE]
6
6
  > 🚧 **Work in Progress**
7
7
  >
8
- > tsdown is currently in active development and not usable for production yet.
8
+ > Both Rolldown and tsdown are currently under active development and not yet ready for production use.
9
9
 
10
10
  ## Install
11
11
 
@@ -17,6 +17,9 @@ npm i tsdown
17
17
 
18
18
  This project also partially contains code derived or copied from [tsup](https://github.com/egoist/tsup).
19
19
 
20
+ - [tsup](https://github.com/egoist/tsup)
21
+ - [pkgroll](https://github.com/privatenumber/pkgroll)
22
+
20
23
  ## Sponsors
21
24
 
22
25
  <p align="center">
@@ -0,0 +1,25 @@
1
+ import { isBuiltin } from "node:module";
2
+
3
+ //#region src/features/external.ts
4
+ function ExternalPlugin(pkg, platform) {
5
+ const deps = getProductionDeps(pkg);
6
+ return {
7
+ name: 'tsdown:external',
8
+ resolveId(id) {
9
+ let shouldExternal = deps.has(id);
10
+ shouldExternal ||= platform === 'node' && isBuiltin(id);
11
+ if (shouldExternal) {
12
+ return {
13
+ id,
14
+ external: true
15
+ };
16
+ }
17
+ }
18
+ };
19
+ }
20
+ function getProductionDeps(pkg) {
21
+ return new Set([...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {}),]);
22
+ }
23
+
24
+ //#endregion
25
+ export { ExternalPlugin };
package/dist/index.js CHANGED
@@ -1,150 +1,208 @@
1
- import {
2
- logger,
3
- removeFiles
4
- } from "./chunk-J2CF23Y7.js";
5
-
6
- // src/index.ts
1
+ import { logger } from "./logger-39fy7Hdx.js";
2
+ import { ExternalPlugin } from "./external-7Fy9jDH4.js";
3
+ import { default as process, default as process$1 } from "node:process";
7
4
  import { rolldown } from "rolldown";
8
-
9
- // src/options.ts
10
- import { existsSync } from "node:fs";
11
- import { stat } from "node:fs/promises";
12
- import process from "node:process";
13
- import path from "node:path";
14
- import { globby } from "globby";
5
+ import { access, readFile, readdir, rm, stat } from "node:fs/promises";
6
+ import { default as path, default as path$1, default as path$2 } from "node:path";
15
7
  import { loadConfig } from "unconfig";
8
+ import { globby, globby as globby$1 } from "globby";
9
+
10
+ //#region src/utils/fs.ts
11
+ function fsExists(path$3) {
12
+ return access(path$3).then(() => true, () => false);
13
+ }
16
14
 
17
- // src/error.ts
18
- var PrettyError = class extends Error {
19
- constructor(message) {
20
- super(message);
21
- this.name = this.constructor.name;
22
- if (typeof Error.captureStackTrace === "function") {
23
- Error.captureStackTrace(this, this.constructor);
24
- } else {
25
- this.stack = new Error(message).stack;
26
- }
27
- }
28
- };
15
+ //#endregion
16
+ //#region src/features/entry.ts
17
+ async function resolveEntry(entry) {
18
+ if (!entry || Object.keys(entry).length === 0) {
19
+ throw new Error(`No input files, try "tsdown <your-file>" instead`);
20
+ }
21
+ if (typeof entry === 'string') {
22
+ entry = [entry];
23
+ }
24
+ if (Array.isArray(entry)) {
25
+ const resolvedEntry = await globby$1(entry);
26
+ if (resolvedEntry.length > 0) {
27
+ entry = resolvedEntry;
28
+ logger.info(`entry: ${entry.join(', ')}`);
29
+ } else {
30
+ throw new Error(`Cannot find entry: ${entry}`);
31
+ }
32
+ } else {
33
+ const files = Object.values(entry);
34
+ files.forEach((filename) => {
35
+ if (!fsExists(filename)) {
36
+ throw new Error(`Cannot find entry: ${filename}`);
37
+ }
38
+ });
39
+ logger.info(`entry: ${files.join(', ')}`);
40
+ }
41
+ return entry;
42
+ }
43
+
44
+ //#endregion
45
+ //#region src/utils/general.ts
46
+ function toArray(val, defaultValue) {
47
+ if (Array.isArray(val)) {
48
+ return val;
49
+ } else if (val == null) {
50
+ if (defaultValue) return [defaultValue];
51
+ return [];
52
+ } else {
53
+ return [val];
54
+ }
55
+ }
29
56
 
30
- // src/options.ts
57
+ //#endregion
58
+ //#region src/options.ts
31
59
  async function normalizeOptions(options) {
32
- options = {
33
- ...await loadConfigFile(options),
34
- ...options
35
- };
36
- let {
37
- entry,
38
- format = ["es"],
39
- plugins = [],
40
- external = [],
41
- clean = false
42
- } = options;
43
- if (!entry || Object.keys(entry).length === 0) {
44
- throw new PrettyError(`No input files, try "tsdown <your-file>" instead`);
45
- }
46
- if (typeof entry === "string") {
47
- entry = [entry];
48
- }
49
- if (Array.isArray(entry)) {
50
- const resolvedEntry = await globby(entry);
51
- if (resolvedEntry.length > 0) {
52
- entry = resolvedEntry;
53
- logger.info(`Building entry: ${entry}`);
54
- } else {
55
- throw new PrettyError(`Cannot find ${entry}`);
56
- }
57
- } else {
58
- Object.keys(entry).forEach((alias) => {
59
- const filename = entry[alias];
60
- if (!existsSync(filename)) {
61
- throw new PrettyError(`Cannot find ${alias}: ${filename}`);
62
- }
63
- });
64
- logger.info(`Building entry: ${JSON.stringify(entry)}`);
65
- }
66
- if (!Array.isArray(format))
67
- format = [format];
68
- if (format.length === 0)
69
- format = ["es"];
70
- if (clean && !Array.isArray(clean))
71
- clean = [];
72
- return {
73
- entry,
74
- plugins,
75
- external,
76
- format,
77
- outDir: options.outDir || "dist",
78
- clean: clean ?? false
79
- };
60
+ options = {
61
+ ...await loadConfigFile(options),
62
+ ...options
63
+ };
64
+ let { entry, format = ['es'], plugins = [], external = [], clean = false, treeshake = true, platform = 'node', outDir = 'dist' } = options;
65
+ entry = await resolveEntry(entry);
66
+ format = toArray(format, 'es');
67
+ if (clean === true) clean = [];
68
+ return {
69
+ entry,
70
+ plugins,
71
+ external,
72
+ format,
73
+ outDir,
74
+ clean,
75
+ alias: {},
76
+ treeshake,
77
+ platform
78
+ };
80
79
  }
81
80
  async function loadConfigFile(options) {
82
- let { config: filePath } = options;
83
- if (filePath === false)
84
- return {};
85
- let cwd = process.cwd();
86
- let overrideConfig = false;
87
- let stats;
88
- if (typeof filePath === "string" && (stats = await stat(filePath).catch(() => null))) {
89
- const resolved = path.resolve(filePath);
90
- if (stats.isFile()) {
91
- overrideConfig = true;
92
- filePath = resolved;
93
- cwd = path.dirname(filePath);
94
- } else {
95
- cwd = resolved;
96
- }
97
- }
98
- const { config, sources } = await loadConfig({
99
- sources: overrideConfig ? [{ files: filePath, extensions: [] }] : [
100
- {
101
- files: "tsdown.config",
102
- extensions: ["ts", "mts", "cts", "js", "mjs", "cjs", "json", ""]
103
- },
104
- {
105
- files: "package.json",
106
- extensions: [],
107
- rewrite: (config2) => config2?.tsdown
108
- }
109
- ],
110
- cwd,
111
- defaults: {}
112
- });
113
- if (sources.length > 0) {
114
- logger.info(`Using tsdown config: ${sources.join(", ")}`);
115
- }
116
- return config;
81
+ let { config: filePath } = options;
82
+ if (filePath === false) return {};
83
+ let cwd = process$1.cwd();
84
+ let overrideConfig = false;
85
+ let stats;
86
+ if (typeof filePath === 'string' && (stats = await stat(filePath).catch(() => null))) {
87
+ const resolved = path$2.resolve(filePath);
88
+ if (stats.isFile()) {
89
+ overrideConfig = true;
90
+ filePath = resolved;
91
+ cwd = path$2.dirname(filePath);
92
+ } else {
93
+ cwd = resolved;
94
+ }
95
+ }
96
+ const { config, sources } = await loadConfig({
97
+ sources: overrideConfig ? [{
98
+ files: filePath,
99
+ extensions: []
100
+ }] : [{
101
+ files: 'tsdown.config',
102
+ extensions: ['ts', 'mts', 'cts', 'js', 'mjs', 'cjs', 'json', '']
103
+ }, {
104
+ files: 'package.json',
105
+ extensions: [],
106
+ rewrite: (config$1) => config$1?.tsdown
107
+ },],
108
+ cwd,
109
+ defaults: {}
110
+ });
111
+ if (sources.length > 0) {
112
+ logger.info(`Using tsdown config: ${sources.join(', ')}`);
113
+ }
114
+ return config;
117
115
  }
118
- function resolveFormat(format) {
119
- return format === "esm" ? "es" : format;
116
+
117
+ //#endregion
118
+ //#region src/features/clean.ts
119
+ async function cleanOutDir(cwd, patterns) {
120
+ const files = [];
121
+ if (await fsExists(cwd)) files.push(...(await readdir(cwd)).map((file) => path$1.resolve(cwd, file)));
122
+ if (patterns.length) {
123
+ files.push(...await globby(patterns, {
124
+ cwd,
125
+ absolute: true
126
+ }));
127
+ }
128
+ logger.info('Cleaning output folder');
129
+ for (const file of files) {
130
+ await rm(file, {
131
+ force: true,
132
+ recursive: true
133
+ });
134
+ }
135
+ }
136
+
137
+ //#endregion
138
+ //#region src/utils/package.ts
139
+ async function readPackageJson(dir) {
140
+ const packageJsonPath = path.join(dir, 'package.json');
141
+ const exists = await fsExists(packageJsonPath);
142
+ if (!exists) {
143
+ throw new Error(`package.json not found at: ${packageJsonPath}`);
144
+ }
145
+ const packageJsonString = await readFile(packageJsonPath, 'utf8');
146
+ try {
147
+ return JSON.parse(packageJsonString);
148
+ } catch (error) {
149
+ throw new Error(`Cannot parse package.json: ${error.message}`);
150
+ }
151
+ }
152
+ function getPackageType(pkg) {
153
+ if (pkg.type) {
154
+ if (!['module', 'commonjs'].includes(pkg.type)) {
155
+ throw new Error(`Invalid package.json type: ${pkg.type}`);
156
+ }
157
+ return pkg.type;
158
+ }
159
+ return 'commonjs';
120
160
  }
121
161
 
122
- // src/index.ts
162
+ //#endregion
163
+ //#region src/features/output.ts
164
+ function resolveOutputExtension(pkg, format) {
165
+ const type = getPackageType(pkg);
166
+ const isEsmFormat = ['es', 'esm', 'module'].includes(format);
167
+ if (type === 'module') {
168
+ if (isEsmFormat) return 'js';
169
+ else return 'cjs';
170
+ } else {
171
+ if (isEsmFormat) return 'mjs';
172
+ return 'js';
173
+ }
174
+ }
175
+
176
+ //#endregion
177
+ //#region src/index.ts
123
178
  async function build(userOptions = {}) {
124
- const { entry, external, plugins, outDir, format, clean } = await normalizeOptions(userOptions);
125
- if (clean) {
126
- await removeFiles(["**/*", ...clean], outDir);
127
- logger.info("Cleaning output folder");
128
- }
129
- const build2 = await rolldown({
130
- input: entry,
131
- external,
132
- plugins
133
- });
134
- await Promise.all(
135
- format.map(
136
- (format2) => build2.write({
137
- format: resolveFormat(format2),
138
- dir: outDir
139
- })
140
- )
141
- );
142
- logger.info("Build complete");
179
+ const { entry, external, plugins, outDir, format, clean, platform } = await normalizeOptions(userOptions);
180
+ if (clean) await cleanOutDir(outDir, clean);
181
+ const pkg = await readPackageJson(process.cwd());
182
+ const inputOptions = {
183
+ input: entry,
184
+ external,
185
+ resolve: {alias: userOptions.alias},
186
+ treeshake: userOptions.treeshake,
187
+ plugins: [ExternalPlugin(pkg, platform), ...plugins]
188
+ };
189
+ const build$1 = await rolldown(inputOptions);
190
+ await Promise.all(format.map((format$1) => {
191
+ const extension = resolveOutputExtension(pkg, format$1);
192
+ return build$1.write({
193
+ format: format$1,
194
+ dir: outDir,
195
+ entryFileNames: `[name].${extension}`,
196
+ chunkFileNames: `[name]-[hash].${extension}`
197
+ });
198
+ }));
199
+ await build$1.destroy();
200
+ logger.info('Build complete');
201
+ process.exit(0);
143
202
  }
144
203
  function defineConfig(options) {
145
- return options;
204
+ return options;
146
205
  }
147
- export {
148
- build,
149
- defineConfig
150
- };
206
+
207
+ //#endregion
208
+ export { build, defineConfig };
@@ -0,0 +1,7 @@
1
+ import { consola } from "consola";
2
+
3
+ //#region src/utils/logger.ts
4
+ const logger = consola.withTag('tsdown');
5
+
6
+ //#endregion
7
+ export { logger };
@@ -0,0 +1,3 @@
1
+ import { ExternalPlugin } from "./external-7Fy9jDH4.js";
2
+
3
+ export { ExternalPlugin };
package/dist/run.js ADDED
@@ -0,0 +1,29 @@
1
+ import { logger } from "./logger-39fy7Hdx.js";
2
+ import { default as process } from "node:process";
3
+ import { cac } from "cac";
4
+
5
+ //#region package.json
6
+ const version = '0.2.0';
7
+ const files = ['dist'];
8
+
9
+ //#endregion
10
+ //#region src/cli.ts
11
+ async function runCLI() {
12
+ const cli = cac('tsdown');
13
+ cli.command('[...files]', 'Bundle files', {ignoreOptionDefaultValue: true}).option('-c, --config <filename>', 'Use a custom config file').option('--format <format>', 'Bundle format: esm, cjs, iife', {default: 'esm'}).option('--clean', 'Clean output directory').option('-d, --out-dir <dir>', 'Output directory', {default: 'dist'}).option('--treeshake', 'Tree-shake bundle', {default: true}).option('--platform <platform>', 'Target platform', {default: 'node'}).action(async (input, flags) => {
14
+ logger.info(`tsdown v${version}`);
15
+ const { build } = await import('./index.js');
16
+ if (input.length > 0) flags.entry = input;
17
+ await build(flags);
18
+ });
19
+ cli.help();
20
+ cli.version(version);
21
+ cli.parse(process.argv, {run: false});
22
+ await cli.runMatchedCommand();
23
+ }
24
+
25
+ //#endregion
26
+ //#region src/run.ts
27
+ runCLI();
28
+
29
+ //#endregion
package/package.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "tsdown",
3
- "version": "0.1.0",
4
- "packageManager": "pnpm@8.15.4",
3
+ "version": "0.2.0",
5
4
  "description": "An even faster bundler powered by Rolldown.",
6
5
  "type": "module",
7
6
  "license": "MIT",
@@ -21,14 +20,12 @@
21
20
  "module": "./dist/index.js",
22
21
  "types": "./dist/index.d.ts",
23
22
  "exports": {
24
- ".": {
25
- "require": "./dist/index.cjs",
26
- "import": "./dist/index.js"
27
- },
23
+ ".": "./dist/index.js",
24
+ "./plugins": "./dist/plugins.js",
28
25
  "./package.json": "./package.json"
29
26
  },
30
27
  "bin": {
31
- "tsdown": "./dist/cli-run.js"
28
+ "tsdown": "./dist/run.js"
32
29
  },
33
30
  "publishConfig": {
34
31
  "access": "public"
@@ -38,19 +35,19 @@
38
35
  "consola": "^3.2.3",
39
36
  "globby": "^14.0.1",
40
37
  "rolldown": "nightly",
41
- "unconfig": "^0.3.11"
38
+ "unconfig": "^0.4.4"
42
39
  },
43
40
  "devDependencies": {
44
- "@sxzz/eslint-config": "^3.8.7",
45
- "@sxzz/prettier-config": "^2.0.1",
46
- "@types/node": "^20.11.30",
47
- "bumpp": "^9.4.0",
48
- "eslint": "^8.57.0",
49
- "prettier": "^3.2.5",
50
- "tsup": "^8.0.2",
51
- "tsx": "^4.7.1",
52
- "typescript": "^5.4.3",
53
- "vitest": "^1.4.0"
41
+ "@sxzz/eslint-config": "^3.13.0",
42
+ "@sxzz/prettier-config": "^2.0.2",
43
+ "@types/node": "^20.14.8",
44
+ "bumpp": "^9.4.1",
45
+ "eslint": "^9.5.0",
46
+ "prettier": "^3.3.2",
47
+ "tsup": "^8.1.0",
48
+ "tsx": "^4.15.7",
49
+ "typescript": "~5.5.2",
50
+ "vitest": "^1.6.0"
54
51
  },
55
52
  "engines": {
56
53
  "node": ">=18.0.0"
@@ -59,8 +56,8 @@
59
56
  "scripts": {
60
57
  "lint": "eslint --cache .",
61
58
  "lint:fix": "pnpm run lint --fix",
62
- "build": "tsup",
63
- "dev": "tsx ./src/cli-run.ts",
59
+ "build": "tsx ./src/run.ts",
60
+ "dev": "tsx ./src/run.ts",
64
61
  "test": "vitest",
65
62
  "typecheck": "tsc --noEmit",
66
63
  "format": "prettier --cache --write .",
@@ -1,18 +0,0 @@
1
- // src/utils.ts
2
- import { existsSync } from "node:fs";
3
- import { unlink } from "node:fs/promises";
4
- import { globby } from "globby";
5
- import { consola } from "consola";
6
- async function removeFiles(patterns, dir) {
7
- const files = await globby(patterns, {
8
- cwd: dir,
9
- absolute: true
10
- });
11
- await Promise.all(files.map((file) => existsSync(file) && unlink(file)));
12
- }
13
- var logger = consola.withTag("tsdown");
14
-
15
- export {
16
- removeFiles,
17
- logger
18
- };
package/dist/cli-run.d.ts DELETED
@@ -1,2 +0,0 @@
1
-
2
- export { }
package/dist/cli-run.js DELETED
@@ -1,31 +0,0 @@
1
- import {
2
- logger
3
- } from "./chunk-J2CF23Y7.js";
4
-
5
- // src/cli.ts
6
- import process from "node:process";
7
- import { cac } from "cac";
8
-
9
- // package.json
10
- var version = "0.1.0";
11
-
12
- // src/cli.ts
13
- async function runCLI() {
14
- const cli = cac("tsdown");
15
- cli.command("[...files]", "Bundle files", {
16
- ignoreOptionDefaultValue: true
17
- }).option("--config <filename>", "Use a custom config file").option("--clean", "Clean output directory").option("-d, --out-dir <dir>", "Output directory", { default: "dist" }).action(async (input, flags) => {
18
- logger.info(`tsdown v${version}`);
19
- const { build } = await import("./index.js");
20
- if (input.length > 0)
21
- flags.entry = input;
22
- await build(flags);
23
- });
24
- cli.help();
25
- cli.version(version);
26
- cli.parse(process.argv, { run: false });
27
- await cli.runMatchedCommand();
28
- }
29
-
30
- // src/cli-run.ts
31
- runCLI();
package/dist/index.d.ts DELETED
@@ -1,17 +0,0 @@
1
- import { InputOptions } from 'rolldown';
2
-
3
- type Format = 'es' | 'esm';
4
- interface Options {
5
- entry?: InputOptions['input'];
6
- format?: Format | Format[];
7
- plugins?: InputOptions['plugins'];
8
- external?: InputOptions['external'];
9
- outDir?: string;
10
- clean?: boolean | string[];
11
- config?: boolean | string;
12
- }
13
-
14
- declare function build(userOptions?: Options): Promise<void>;
15
- declare function defineConfig(options: Options): Options;
16
-
17
- export { build, defineConfig };