rolldown-plugin-dts 0.0.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/LICENSE +21 -0
- package/README.md +92 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +516 -0
- package/package.json +62 -7
- package/.vscode/settings.json +0 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright © 2025 三咲智子 Kevin Deng (https://github.com/sxzz)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# rolldown-plugin-dts [](https://npmjs.com/package/rolldown-plugin-dts)
|
|
2
|
+
|
|
3
|
+
[](https://github.com/sxzz/rolldown-plugin-dts/actions/workflows/unit-test.yml)
|
|
4
|
+
|
|
5
|
+
A Rolldown plugin to generate and bundle dts files.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i rolldown-plugin-dts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Add the plugin to your `rolldown.config.js`:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
// rolldown.config.js
|
|
19
|
+
import { dts } from 'rolldown-plugin-dts'
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
input: './src/index.ts',
|
|
23
|
+
plugins: [dts()],
|
|
24
|
+
output: [{ dir: 'dist', format: 'es' }],
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
You can find a real demo in [here](./rolldown.config.ts).
|
|
29
|
+
|
|
30
|
+
## Options
|
|
31
|
+
|
|
32
|
+
````ts
|
|
33
|
+
interface Options {
|
|
34
|
+
/**
|
|
35
|
+
* When entries are `.d.ts` files (instead of `.ts` files), this option should be set to `true`.
|
|
36
|
+
*
|
|
37
|
+
* If enabled, the plugin will skip generating a `.d.ts` file for the entry point.
|
|
38
|
+
*/
|
|
39
|
+
dtsInput?: boolean
|
|
40
|
+
|
|
41
|
+
isolatedDeclaration?: Omit<IsolatedDeclarationsOptions, 'sourcemap'>
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* dts file name alias `{ [filename]: path }`
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* inputAlias: {
|
|
49
|
+
* 'foo.d.ts': 'foo/index.d.ts',
|
|
50
|
+
* }
|
|
51
|
+
*/
|
|
52
|
+
inputAlias?: Record<string, string>
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Determines whether the module imported by `.d.ts` files should be treated as external or not.
|
|
56
|
+
*/
|
|
57
|
+
external?: (
|
|
58
|
+
id: string,
|
|
59
|
+
importer: string,
|
|
60
|
+
extraOptions: ResolveIdExtraOptions,
|
|
61
|
+
) => boolean | void
|
|
62
|
+
}
|
|
63
|
+
````
|
|
64
|
+
|
|
65
|
+
## Caveats
|
|
66
|
+
|
|
67
|
+
- The plugin uses Oxc's `isolatedDeclarations` to generate `.d.ts` files,
|
|
68
|
+
which means you need to set `isolatedDeclarations: true` in your `tsconfig.json` and ensure there are no errors.
|
|
69
|
+
|
|
70
|
+
- Namespaces are not supported yet.
|
|
71
|
+
- `export * as ns from './ns'`
|
|
72
|
+
- `import * as ns from './ns'` and then `export { ns }`
|
|
73
|
+
- `type ns = import('./ns')`
|
|
74
|
+
|
|
75
|
+
## Credits
|
|
76
|
+
|
|
77
|
+
The project is inspired by [rollup-plugin-dts](https://github.com/Swatinem/rollup-plugin-dts)
|
|
78
|
+
but has been independently implemented.
|
|
79
|
+
We extend our gratitude to the original creators for their contributions.
|
|
80
|
+
Furthermore, the test suite is authorized by them and distributed under the MIT license.
|
|
81
|
+
|
|
82
|
+
## Sponsors
|
|
83
|
+
|
|
84
|
+
<p align="center">
|
|
85
|
+
<a href="https://cdn.jsdelivr.net/gh/sxzz/sponsors/sponsors.svg">
|
|
86
|
+
<img src='https://cdn.jsdelivr.net/gh/sxzz/sponsors/sponsors.svg'/>
|
|
87
|
+
</a>
|
|
88
|
+
</p>
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
[MIT](./LICENSE) License © 2025 [三咲智子 Kevin Deng](https://github.com/sxzz)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { IsolatedDeclarationsOptions } from "oxc-transform";
|
|
2
|
+
import { FunctionPluginHooks, Plugin } from "rolldown";
|
|
3
|
+
|
|
4
|
+
//#region src/fake-js.d.ts
|
|
5
|
+
declare function createFakeJsPlugin({ dtsInput }: Pick<Options, "dtsInput">): Plugin;
|
|
6
|
+
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/generate.d.ts
|
|
9
|
+
declare function createGeneratePlugin({ isolatedDeclaration, inputAlias, external }: Pick<Options, "external" | "isolatedDeclaration" | "inputAlias">): Plugin;
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/index.d.ts
|
|
13
|
+
type ResolveIdExtraOptions = Parameters<FunctionPluginHooks["resolveId"]>[2];
|
|
14
|
+
interface Options {
|
|
15
|
+
/**
|
|
16
|
+
* When entries are `.d.ts` files (instead of `.ts` files), this option should be set to `true`.
|
|
17
|
+
*
|
|
18
|
+
* If enabled, the plugin will skip generating a `.d.ts` file for the entry point.
|
|
19
|
+
*/
|
|
20
|
+
dtsInput?: boolean;
|
|
21
|
+
isolatedDeclaration?: Omit<IsolatedDeclarationsOptions, "sourcemap">;
|
|
22
|
+
/**
|
|
23
|
+
* dts file name alias `{ [filename]: path }`
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* inputAlias: {
|
|
28
|
+
* 'foo.d.ts': 'foo/index.d.ts',
|
|
29
|
+
* }
|
|
30
|
+
*/
|
|
31
|
+
inputAlias?: Record<string, string>;
|
|
32
|
+
/**
|
|
33
|
+
* Determines whether the module imported by `.d.ts` files should be treated as external or not.
|
|
34
|
+
*/
|
|
35
|
+
external?: (id: string, importer: string, extraOptions: ResolveIdExtraOptions) => boolean | void;
|
|
36
|
+
}
|
|
37
|
+
declare function dts(options?: Options): Plugin[];
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
export { Options, createFakeJsPlugin, createGeneratePlugin, dts };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import { MagicStringAST } from "magic-string-ast";
|
|
2
|
+
import { parseAsync } from "oxc-parser";
|
|
3
|
+
import path, { basename } from "node:path";
|
|
4
|
+
import { isolatedDeclaration } from "oxc-transform";
|
|
5
|
+
|
|
6
|
+
//#region node_modules/.pnpm/estree-walker@3.0.3/node_modules/estree-walker/src/walker.js
|
|
7
|
+
var WalkerBase = class {
|
|
8
|
+
constructor() {
|
|
9
|
+
/** @type {boolean} */
|
|
10
|
+
this.should_skip = false;
|
|
11
|
+
/** @type {boolean} */
|
|
12
|
+
this.should_remove = false;
|
|
13
|
+
/** @type {Node | null} */
|
|
14
|
+
this.replacement = null;
|
|
15
|
+
/** @type {WalkerContext} */
|
|
16
|
+
this.context = {
|
|
17
|
+
skip: () => this.should_skip = true,
|
|
18
|
+
remove: () => this.should_remove = true,
|
|
19
|
+
replace: (node) => this.replacement = node
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* @template {Node} Parent
|
|
24
|
+
* @param {Parent | null | undefined} parent
|
|
25
|
+
* @param {keyof Parent | null | undefined} prop
|
|
26
|
+
* @param {number | null | undefined} index
|
|
27
|
+
* @param {Node} node
|
|
28
|
+
*/
|
|
29
|
+
replace(parent, prop, index, node) {
|
|
30
|
+
if (parent && prop) if (index != null)
|
|
31
|
+
/** @type {Array<Node>} */ parent[prop][index] = node;
|
|
32
|
+
else
|
|
33
|
+
/** @type {Node} */ parent[prop] = node;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @template {Node} Parent
|
|
37
|
+
* @param {Parent | null | undefined} parent
|
|
38
|
+
* @param {keyof Parent | null | undefined} prop
|
|
39
|
+
* @param {number | null | undefined} index
|
|
40
|
+
*/
|
|
41
|
+
remove(parent, prop, index) {
|
|
42
|
+
if (parent && prop) if (index !== null && index !== void 0)
|
|
43
|
+
/** @type {Array<Node>} */ parent[prop].splice(index, 1);
|
|
44
|
+
else delete parent[prop];
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region node_modules/.pnpm/estree-walker@3.0.3/node_modules/estree-walker/src/sync.js
|
|
50
|
+
var SyncWalker = class extends WalkerBase {
|
|
51
|
+
/**
|
|
52
|
+
*
|
|
53
|
+
* @param {SyncHandler} [enter]
|
|
54
|
+
* @param {SyncHandler} [leave]
|
|
55
|
+
*/
|
|
56
|
+
constructor(enter, leave) {
|
|
57
|
+
super();
|
|
58
|
+
/** @type {boolean} */
|
|
59
|
+
this.should_skip = false;
|
|
60
|
+
/** @type {boolean} */
|
|
61
|
+
this.should_remove = false;
|
|
62
|
+
/** @type {Node | null} */
|
|
63
|
+
this.replacement = null;
|
|
64
|
+
/** @type {WalkerContext} */
|
|
65
|
+
this.context = {
|
|
66
|
+
skip: () => this.should_skip = true,
|
|
67
|
+
remove: () => this.should_remove = true,
|
|
68
|
+
replace: (node) => this.replacement = node
|
|
69
|
+
};
|
|
70
|
+
/** @type {SyncHandler | undefined} */
|
|
71
|
+
this.enter = enter;
|
|
72
|
+
/** @type {SyncHandler | undefined} */
|
|
73
|
+
this.leave = leave;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* @template {Node} Parent
|
|
77
|
+
* @param {Node} node
|
|
78
|
+
* @param {Parent | null} parent
|
|
79
|
+
* @param {keyof Parent} [prop]
|
|
80
|
+
* @param {number | null} [index]
|
|
81
|
+
* @returns {Node | null}
|
|
82
|
+
*/
|
|
83
|
+
visit(node, parent, prop, index) {
|
|
84
|
+
if (node) {
|
|
85
|
+
if (this.enter) {
|
|
86
|
+
const _should_skip = this.should_skip;
|
|
87
|
+
const _should_remove = this.should_remove;
|
|
88
|
+
const _replacement = this.replacement;
|
|
89
|
+
this.should_skip = false;
|
|
90
|
+
this.should_remove = false;
|
|
91
|
+
this.replacement = null;
|
|
92
|
+
this.enter.call(this.context, node, parent, prop, index);
|
|
93
|
+
if (this.replacement) {
|
|
94
|
+
node = this.replacement;
|
|
95
|
+
this.replace(parent, prop, index, node);
|
|
96
|
+
}
|
|
97
|
+
if (this.should_remove) this.remove(parent, prop, index);
|
|
98
|
+
const skipped = this.should_skip;
|
|
99
|
+
const removed = this.should_remove;
|
|
100
|
+
this.should_skip = _should_skip;
|
|
101
|
+
this.should_remove = _should_remove;
|
|
102
|
+
this.replacement = _replacement;
|
|
103
|
+
if (skipped) return node;
|
|
104
|
+
if (removed) return null;
|
|
105
|
+
}
|
|
106
|
+
/** @type {keyof Node} */
|
|
107
|
+
let key;
|
|
108
|
+
for (key in node) {
|
|
109
|
+
/** @type {unknown} */
|
|
110
|
+
const value = node[key];
|
|
111
|
+
if (value && typeof value === "object") {
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
const nodes = value;
|
|
114
|
+
for (let i = 0; i < nodes.length; i += 1) {
|
|
115
|
+
const item = nodes[i];
|
|
116
|
+
if (isNode(item)) {
|
|
117
|
+
if (!this.visit(item, node, key, i)) i--;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} else if (isNode(value)) this.visit(value, node, key, null);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (this.leave) {
|
|
124
|
+
const _replacement = this.replacement;
|
|
125
|
+
const _should_remove = this.should_remove;
|
|
126
|
+
this.replacement = null;
|
|
127
|
+
this.should_remove = false;
|
|
128
|
+
this.leave.call(this.context, node, parent, prop, index);
|
|
129
|
+
if (this.replacement) {
|
|
130
|
+
node = this.replacement;
|
|
131
|
+
this.replace(parent, prop, index, node);
|
|
132
|
+
}
|
|
133
|
+
if (this.should_remove) this.remove(parent, prop, index);
|
|
134
|
+
const removed = this.should_remove;
|
|
135
|
+
this.replacement = _replacement;
|
|
136
|
+
this.should_remove = _should_remove;
|
|
137
|
+
if (removed) return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return node;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Ducktype a node.
|
|
145
|
+
*
|
|
146
|
+
* @param {unknown} value
|
|
147
|
+
* @returns {value is Node}
|
|
148
|
+
*/
|
|
149
|
+
function isNode(value) {
|
|
150
|
+
return value !== null && typeof value === "object" && "type" in value && typeof value.type === "string";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region node_modules/.pnpm/estree-walker@3.0.3/node_modules/estree-walker/src/index.js
|
|
155
|
+
function walk(ast, { enter, leave }) {
|
|
156
|
+
const instance = new SyncWalker(enter, leave);
|
|
157
|
+
return instance.visit(ast, null);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/utils/ast.ts
|
|
162
|
+
function getIdentifierRange(node, offset = 0) {
|
|
163
|
+
if ("typeAnnotation" in node && node.typeAnnotation) return [node.start + offset, node.typeAnnotation.start + offset];
|
|
164
|
+
return [node.start + offset, node.end + offset];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/utils/filename.ts
|
|
169
|
+
const RE_JS = /\.([cm]?)js$/;
|
|
170
|
+
const RE_TS = /\.([cm]?)ts$/;
|
|
171
|
+
const RE_DTS = /\.d\.([cm]?)ts$/;
|
|
172
|
+
const RE_NODE_MODULES = /node_modules/;
|
|
173
|
+
function filename_js_to_dts(id) {
|
|
174
|
+
return id.replace(RE_JS, ".d.$1ts");
|
|
175
|
+
}
|
|
176
|
+
function filename_ts_to_dts(id) {
|
|
177
|
+
return id.replace(RE_TS, ".d.$1ts");
|
|
178
|
+
}
|
|
179
|
+
function filename_dts_to(id, ext) {
|
|
180
|
+
return id.replace(RE_DTS, `.$1${ext}`);
|
|
181
|
+
}
|
|
182
|
+
function isRelative(id) {
|
|
183
|
+
return path.isAbsolute(id) || id[0] === ".";
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/utils/magic-string.ts
|
|
188
|
+
function overwriteOrAppend(s, range, replacement, suffix) {
|
|
189
|
+
if (range[0] === range[1]) {
|
|
190
|
+
s.appendLeft(range[0], ` ${replacement}`);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const original = s.slice(range[0], range[1]);
|
|
194
|
+
if (original !== replacement) s.overwrite(range[0], range[1], replacement + (suffix || ""));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
//#endregion
|
|
198
|
+
//#region src/fake-js.ts
|
|
199
|
+
const RE_TYPE = /\btype\b/;
|
|
200
|
+
function createFakeJsPlugin({ dtsInput }) {
|
|
201
|
+
let symbolIdx = 0;
|
|
202
|
+
let identifierIdx = 0;
|
|
203
|
+
const symbolMap = new Map();
|
|
204
|
+
const preserveMap = new Map();
|
|
205
|
+
function getIdentifierIndex() {
|
|
206
|
+
return identifierIdx++;
|
|
207
|
+
}
|
|
208
|
+
function register(info) {
|
|
209
|
+
const symbolId = symbolIdx++;
|
|
210
|
+
symbolMap.set(symbolId, info);
|
|
211
|
+
return symbolId;
|
|
212
|
+
}
|
|
213
|
+
function retrieve(symbolId) {
|
|
214
|
+
return symbolMap.get(symbolId);
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
name: "rolldown-plugin-dts:fake-js",
|
|
218
|
+
options: dtsInput ? (options) => {
|
|
219
|
+
return {
|
|
220
|
+
...options,
|
|
221
|
+
resolve: {
|
|
222
|
+
extensions: [
|
|
223
|
+
".d.ts",
|
|
224
|
+
".d.mts",
|
|
225
|
+
".d.cts"
|
|
226
|
+
],
|
|
227
|
+
extensionAlias: {
|
|
228
|
+
".js": [".d.ts"],
|
|
229
|
+
".mjs": [".d.mts"],
|
|
230
|
+
".cjs": [".d.cts"]
|
|
231
|
+
},
|
|
232
|
+
...options.resolve
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
} : void 0,
|
|
236
|
+
outputOptions(options) {
|
|
237
|
+
return {
|
|
238
|
+
...options,
|
|
239
|
+
entryFileNames: options.entryFileNames ?? (dtsInput ? "[name].ts" : void 0),
|
|
240
|
+
chunkFileNames(chunk) {
|
|
241
|
+
const original = (typeof options.chunkFileNames === "function" ? options.chunkFileNames(chunk) : options.chunkFileNames) || "[name]-[hash].js";
|
|
242
|
+
if (chunk.name.endsWith(".d")) return filename_js_to_dts(original);
|
|
243
|
+
return original;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
transform: {
|
|
248
|
+
filter: { id: {
|
|
249
|
+
include: [RE_DTS],
|
|
250
|
+
exclude: [RE_NODE_MODULES]
|
|
251
|
+
} },
|
|
252
|
+
async handler(code, id) {
|
|
253
|
+
const { program, comments } = await parseAsync(id, code);
|
|
254
|
+
const preserved = collectReferenceDirectives(comments);
|
|
255
|
+
preserveMap.set(id, preserved);
|
|
256
|
+
const s = new MagicStringAST(code);
|
|
257
|
+
for (let node of program.body) {
|
|
258
|
+
if (node.type === "ExportAllDeclaration" && node.exported && isRelative(node.source.value)) throw new Error("`export * as foo from './...'` is not supported");
|
|
259
|
+
if (rewriteImportExport(s, node)) continue;
|
|
260
|
+
const sideEffect = node.type === "TSModuleDeclaration" && node.kind !== "namespace";
|
|
261
|
+
const stmt = node;
|
|
262
|
+
const isDefaultExport = node.type === "ExportDefaultDeclaration";
|
|
263
|
+
if ((node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") && node.declaration) node = node.declaration;
|
|
264
|
+
if (node.type === "VariableDeclaration" && node.declarations.length !== 1) throw new Error("Only one declaration is supported");
|
|
265
|
+
if (node.type === "TSDeclareFunction" || node.type.endsWith("Declaration")) {
|
|
266
|
+
const binding = node.type === "VariableDeclaration" ? node.declarations[0].id : node.id;
|
|
267
|
+
const code$1 = s.sliceNode(node);
|
|
268
|
+
const jsdoc = comments.find((c) => c.type === "Block" && c.value[0] === "*" && c.start < node.start && stmt.start - c.end <= 1);
|
|
269
|
+
const offset = node.start;
|
|
270
|
+
let bindingRange;
|
|
271
|
+
if (sideEffect) bindingRange = [0, 0];
|
|
272
|
+
else if (binding) bindingRange = getIdentifierRange(binding, -offset);
|
|
273
|
+
else if (isDefaultExport) {
|
|
274
|
+
const idx = s.sliceNode(node).indexOf("function") + 8;
|
|
275
|
+
bindingRange = [idx, idx];
|
|
276
|
+
} else continue;
|
|
277
|
+
const depsNodes = collectDependencies(s, node, getIdentifierIndex);
|
|
278
|
+
const depsString = stringifyDependencies(s, depsNodes);
|
|
279
|
+
const depsSymbols = depsNodes.map((dep) => [
|
|
280
|
+
dep.start - offset,
|
|
281
|
+
dep.end - offset,
|
|
282
|
+
dep._suffix
|
|
283
|
+
]);
|
|
284
|
+
const needDeclare = (node.type === "TSEnumDeclaration" || node.type === "ClassDeclaration" || node.type === "FunctionDeclaration" || node.type === "TSDeclareFunction" || node.type === "TSModuleDeclaration" || node.type === "VariableDeclaration") && !node.declare;
|
|
285
|
+
const symbolId = register({
|
|
286
|
+
code: code$1,
|
|
287
|
+
binding: bindingRange,
|
|
288
|
+
deps: depsSymbols,
|
|
289
|
+
needDeclare,
|
|
290
|
+
jsdoc: jsdoc ? s.sliceNode(jsdoc) : void 0,
|
|
291
|
+
preserveName: sideEffect
|
|
292
|
+
});
|
|
293
|
+
const runtime = `[${symbolId}, ${depsString}${depsString && sideEffect ? ", " : ""}${sideEffect ? "sideEffect()" : ""}]`;
|
|
294
|
+
const bindingName = sideEffect ? `_${identifierIdx++}` : binding ? s.sliceNode(binding) : "export_default";
|
|
295
|
+
if (isDefaultExport) s.overwriteNode(stmt, `var ${bindingName} = ${runtime};export { ${bindingName} as default }`);
|
|
296
|
+
else s.overwriteNode(node, `var ${bindingName} = ${runtime};`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (!s.hasChanged()) return;
|
|
300
|
+
const str = s.toString();
|
|
301
|
+
return str;
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
async renderChunk(code, chunk) {
|
|
305
|
+
if (!RE_DTS.test(chunk.fileName)) return;
|
|
306
|
+
const { program } = await parseAsync(chunk.fileName, code);
|
|
307
|
+
const s = new MagicStringAST(code);
|
|
308
|
+
const comments = new Set();
|
|
309
|
+
for (const id of chunk.moduleIds) {
|
|
310
|
+
const preserveComments = preserveMap.get(id);
|
|
311
|
+
if (preserveComments) {
|
|
312
|
+
preserveComments.forEach((c) => comments.add(c));
|
|
313
|
+
preserveMap.delete(id);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (comments.size) s.prepend(`${[...comments].join("\n")}\n`);
|
|
317
|
+
for (const node of program.body) {
|
|
318
|
+
if (patchImportSource(s, node)) continue;
|
|
319
|
+
if (node.type !== "VariableDeclaration" || node.declarations.length !== 1) continue;
|
|
320
|
+
const [decl] = node.declarations;
|
|
321
|
+
if (decl.init?.type !== "ArrayExpression" || !decl.init.elements[0]) {
|
|
322
|
+
patchVariableDeclarator(s, node, decl);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const [symbolIdNode, ...depsNodes] = decl.init.elements;
|
|
326
|
+
if (symbolIdNode?.type !== "Literal" || typeof symbolIdNode.value !== "number") {
|
|
327
|
+
patchVariableDeclarator(s, node, decl);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const symbolId = symbolIdNode.value;
|
|
331
|
+
const { code: code$1, binding, deps, needDeclare, jsdoc, preserveName } = retrieve(symbolId);
|
|
332
|
+
const depsRaw = depsNodes.filter((node$1) => node$1?.type === "ArrowFunctionExpression").map((dep) => s.sliceNode(dep.body));
|
|
333
|
+
const ss = new MagicStringAST(code$1);
|
|
334
|
+
if (!preserveName) overwriteOrAppend(ss, binding, s.sliceNode(decl.id));
|
|
335
|
+
for (const dep of deps) {
|
|
336
|
+
const [start, end, suffix] = dep;
|
|
337
|
+
overwriteOrAppend(ss, [start, end], depsRaw.shift(), suffix);
|
|
338
|
+
}
|
|
339
|
+
if (needDeclare) ss.prepend("declare ");
|
|
340
|
+
if (jsdoc) ss.prepend(`${jsdoc}\n`);
|
|
341
|
+
s.overwriteNode(node, ss.toString());
|
|
342
|
+
}
|
|
343
|
+
const str = s.toString();
|
|
344
|
+
if (str.trim().length === 0) return "export {}";
|
|
345
|
+
return str;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
const REFERENCE_RE = /\/\s*<reference\s+(?:path|types)=/;
|
|
350
|
+
function collectReferenceDirectives(comment) {
|
|
351
|
+
return comment.filter((c) => REFERENCE_RE.test(c.value)).map((c) => `//${c.value}`);
|
|
352
|
+
}
|
|
353
|
+
function collectDependencies(s, node, getIdentifierIndex) {
|
|
354
|
+
const deps = new Set();
|
|
355
|
+
walk(node, { leave(node$1) {
|
|
356
|
+
if (node$1.type === "ExportNamedDeclaration") {
|
|
357
|
+
for (const specifier of node$1.specifiers) if (specifier.type === "ExportSpecifier") {
|
|
358
|
+
let _suffix;
|
|
359
|
+
if (specifier.local.start === specifier.exported.start && specifier.local.end === specifier.exported.end) _suffix = ` as ${s.sliceNode(specifier.local)}`;
|
|
360
|
+
addDependency({
|
|
361
|
+
...specifier.local,
|
|
362
|
+
_suffix
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
} else if (node$1.type === "TSInterfaceDeclaration" && node$1.extends) for (const heritage of node$1.extends || []) addDependency(heritage.expression);
|
|
366
|
+
else if (node$1.type === "ClassDeclaration") {
|
|
367
|
+
if (node$1.superClass) addDependency(node$1.superClass);
|
|
368
|
+
if (node$1.implements) for (const implement of node$1.implements) addDependency(implement.expression);
|
|
369
|
+
} else if (node$1.type === "MethodDefinition" || node$1.type === "PropertyDefinition" || node$1.type === "TSPropertySignature") {
|
|
370
|
+
if (node$1.computed && isReferenceId(node$1.key)) addDependency(node$1.key);
|
|
371
|
+
if ("value" in node$1 && isReferenceId(node$1.value)) addDependency(node$1.value);
|
|
372
|
+
} else if (node$1.type === "TSTypeReference") addDependency(node$1.typeName);
|
|
373
|
+
else if (node$1.type === "TSTypeQuery") addDependency(node$1.exprName);
|
|
374
|
+
else if (node$1.type === "TSImportType") {
|
|
375
|
+
if (node$1.argument.type !== "TSLiteralType" || node$1.argument.literal.type !== "Literal" || typeof node$1.argument.literal.value !== "string") return;
|
|
376
|
+
if (!node$1.qualifier) throw new Error("Import namespace is not supported");
|
|
377
|
+
const source = node$1.argument.literal.value;
|
|
378
|
+
const imported = s.sliceNode(node$1.qualifier);
|
|
379
|
+
const local = importNamespace(s, source, imported, getIdentifierIndex);
|
|
380
|
+
addDependency({
|
|
381
|
+
type: "Identifier",
|
|
382
|
+
name: local,
|
|
383
|
+
start: node$1.start + (node$1.isTypeOf ? 7 : 0),
|
|
384
|
+
end: node$1.qualifier.end
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
} });
|
|
388
|
+
return Array.from(deps);
|
|
389
|
+
function addDependency(node$1) {
|
|
390
|
+
if (node$1.type === "Identifier" && node$1.name === "this") return;
|
|
391
|
+
deps.add(node$1);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function isReferenceId(node) {
|
|
395
|
+
return !!node && (node.type === "Identifier" || node.type === "MemberExpression");
|
|
396
|
+
}
|
|
397
|
+
function stringifyDependencies(s, deps) {
|
|
398
|
+
return deps.map((node) => `() => ${node.type === "Identifier" ? node.name : s.sliceNode(node)}`).join(", ");
|
|
399
|
+
}
|
|
400
|
+
function patchVariableDeclarator(s, node, decl) {
|
|
401
|
+
if (decl.init && !decl.id.typeAnnotation) s.overwriteNode(node, `type ${s.sliceNode(decl.id)} = ${s.sliceNode(decl.init)}`);
|
|
402
|
+
else if (!node.declare) s.prependLeft(node.start, "declare ");
|
|
403
|
+
}
|
|
404
|
+
function patchImportSource(s, node) {
|
|
405
|
+
if ((node.type === "ImportDeclaration" || node.type === "ExportAllDeclaration" || node.type === "ExportNamedDeclaration") && node.source?.value && RE_DTS.test(node.source.value)) {
|
|
406
|
+
s.overwriteNode(node.source, JSON.stringify(filename_dts_to(node.source.value, "js")));
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function rewriteImportExport(s, node) {
|
|
411
|
+
if (node.type === "ImportDeclaration" || node.type === "ExportNamedDeclaration" && !node.declaration) {
|
|
412
|
+
for (const specifier of node.specifiers) if (specifier.type === "ImportSpecifier" && specifier.importKind === "type" || specifier.type === "ExportSpecifier" && specifier.exportKind === "type") s.overwriteNode(specifier, s.sliceNode(specifier).replace(RE_TYPE, ""));
|
|
413
|
+
const firstSpecifier = node.specifiers[0];
|
|
414
|
+
const kind = node.type === "ImportDeclaration" ? node.importKind : node.exportKind;
|
|
415
|
+
if (kind === "type" && firstSpecifier) s.overwrite(node.start, firstSpecifier.start, s.slice(node.start, firstSpecifier.start).replace(RE_TYPE, ""));
|
|
416
|
+
return true;
|
|
417
|
+
} else if (node.type === "ExportAllDeclaration") {
|
|
418
|
+
if (node.exportKind === "type") s.overwrite(node.start, node.source.start, s.slice(node.start, node.source.start).replace(RE_TYPE, ""));
|
|
419
|
+
return true;
|
|
420
|
+
} else if (node.type === "TSImportEqualsDeclaration") {
|
|
421
|
+
if (node.moduleReference.type === "TSExternalModuleReference") s.overwriteNode(node, `import ${s.sliceNode(node.id)} from ${s.sliceNode(node.moduleReference.expression)}`);
|
|
422
|
+
return true;
|
|
423
|
+
} else if (node.type === "TSExportAssignment") {
|
|
424
|
+
s.overwriteNode(node, `export default ${s.sliceNode(node.expression)}`);
|
|
425
|
+
return true;
|
|
426
|
+
} else if (node.type === "ExportDefaultDeclaration" && node.declaration.type === "Identifier") {
|
|
427
|
+
s.overwriteNode(node, `export { ${s.sliceNode(node.declaration)} as default }`);
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function importNamespace(s, source, imported, getIdentifierIndex) {
|
|
432
|
+
const local = `_${getIdentifierIndex()}`;
|
|
433
|
+
s.prepend(`import { ${imported} as ${local} } from ${JSON.stringify(source)};\n`);
|
|
434
|
+
return local;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
//#endregion
|
|
438
|
+
//#region src/generate.ts
|
|
439
|
+
function createGeneratePlugin({ isolatedDeclaration: isolatedDeclaration$1, inputAlias, external }) {
|
|
440
|
+
const dtsMap = new Map();
|
|
441
|
+
return {
|
|
442
|
+
name: "rolldown-plugin-dts:generate",
|
|
443
|
+
transform: {
|
|
444
|
+
order: "pre",
|
|
445
|
+
filter: { id: {
|
|
446
|
+
include: [RE_TS],
|
|
447
|
+
exclude: [RE_DTS, RE_NODE_MODULES]
|
|
448
|
+
} },
|
|
449
|
+
handler(code, id) {
|
|
450
|
+
const { code: dtsCode, errors } = isolatedDeclaration(id, code, isolatedDeclaration$1);
|
|
451
|
+
if (errors.length) return this.error(errors[0]);
|
|
452
|
+
const dtsId = filename_ts_to_dts(id);
|
|
453
|
+
dtsMap.set(dtsId, dtsCode);
|
|
454
|
+
const mod = this.getModuleInfo(id);
|
|
455
|
+
if (mod?.isEntry) {
|
|
456
|
+
let fileName = basename(dtsId);
|
|
457
|
+
if (inputAlias?.[fileName]) fileName = inputAlias[fileName];
|
|
458
|
+
this.emitFile({
|
|
459
|
+
type: "chunk",
|
|
460
|
+
id: dtsId,
|
|
461
|
+
fileName
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
async resolveId(id, importer, extraOptions) {
|
|
467
|
+
if (dtsMap.has(id)) return {
|
|
468
|
+
id,
|
|
469
|
+
meta: { dtsFile: true }
|
|
470
|
+
};
|
|
471
|
+
const importerMod = importer ? this.getModuleInfo(importer) : null;
|
|
472
|
+
if (importerMod?.meta.dtsFile) {
|
|
473
|
+
if (!isRelative(id) || external?.(id, importer, extraOptions) === true) return {
|
|
474
|
+
id,
|
|
475
|
+
external: true
|
|
476
|
+
};
|
|
477
|
+
const resolution = await this.resolve(id, filename_dts_to(importer, "ts"));
|
|
478
|
+
if (!resolution || resolution.external) return;
|
|
479
|
+
const dtsId = filename_ts_to_dts(resolution.id);
|
|
480
|
+
if (dtsMap.has(dtsId)) return {
|
|
481
|
+
id: dtsId,
|
|
482
|
+
meta: { dtsFile: true }
|
|
483
|
+
};
|
|
484
|
+
await this.load(resolution);
|
|
485
|
+
if (dtsMap.has(dtsId)) return {
|
|
486
|
+
id: dtsId,
|
|
487
|
+
meta: { dtsFile: true }
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
load: {
|
|
492
|
+
filter: { id: {
|
|
493
|
+
include: [RE_DTS],
|
|
494
|
+
exclude: [RE_NODE_MODULES]
|
|
495
|
+
} },
|
|
496
|
+
handler(id) {
|
|
497
|
+
if (dtsMap.has(id)) return {
|
|
498
|
+
code: dtsMap.get(id),
|
|
499
|
+
moduleSideEffects: false
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
//#endregion
|
|
507
|
+
//#region src/index.ts
|
|
508
|
+
function dts(options = {}) {
|
|
509
|
+
const plugins = [];
|
|
510
|
+
if (!options.dtsInput) plugins.push(createGeneratePlugin(options));
|
|
511
|
+
plugins.push(createFakeJsPlugin(options));
|
|
512
|
+
return plugins;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
//#endregion
|
|
516
|
+
export { createFakeJsPlugin, createGeneratePlugin, dts };
|
package/package.json
CHANGED
|
@@ -1,12 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rolldown-plugin-dts",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A Rolldown plugin to bundle dts files",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/sxzz/rolldown-plugin-dts#readme",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/sxzz/rolldown-plugin-dts/issues"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/sxzz/rolldown-plugin-dts.git"
|
|
14
|
+
},
|
|
15
|
+
"author": "三咲智子 Kevin Deng <sxzz@sxzz.moe>",
|
|
16
|
+
"funding": "https://github.com/sponsors/sxzz",
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": "./dist/index.js",
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"magic-string-ast": "^0.9.1",
|
|
32
|
+
"oxc-parser": "^0.62.0",
|
|
33
|
+
"oxc-transform": "^0.62.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@sxzz/eslint-config": "^6.1.1",
|
|
37
|
+
"@sxzz/prettier-config": "^2.2.1",
|
|
38
|
+
"@sxzz/test-utils": "^0.5.4",
|
|
39
|
+
"@types/diff": "^7.0.2",
|
|
40
|
+
"@types/node": "^22.14.0",
|
|
41
|
+
"bumpp": "^10.1.0",
|
|
42
|
+
"diff": "^7.0.0",
|
|
43
|
+
"eslint": "^9.24.0",
|
|
44
|
+
"estree-walker": "^3.0.3",
|
|
45
|
+
"prettier": "^3.5.3",
|
|
46
|
+
"rolldown": "1.0.0-beta.7",
|
|
47
|
+
"rollup-plugin-dts": "^6.2.1",
|
|
48
|
+
"tsdown": "^0.8.0-beta.1",
|
|
49
|
+
"tsx": "^4.19.3",
|
|
50
|
+
"typescript": "^5.8.3",
|
|
51
|
+
"vitest": "^3.1.1"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=20.18.0"
|
|
55
|
+
},
|
|
56
|
+
"prettier": "@sxzz/prettier-config",
|
|
9
57
|
"scripts": {
|
|
10
|
-
"
|
|
58
|
+
"lint": "eslint --cache .",
|
|
59
|
+
"lint:fix": "pnpm run lint --fix",
|
|
60
|
+
"build": "tsdown",
|
|
61
|
+
"dev": "tsdown --watch",
|
|
62
|
+
"test": "vitest",
|
|
63
|
+
"typecheck": "tsc --noEmit",
|
|
64
|
+
"format": "prettier --cache --write .",
|
|
65
|
+
"release": "bumpp && pnpm publish"
|
|
11
66
|
}
|
|
12
67
|
}
|