Skip to content

Commit b3f671a

Browse files
ryoppippisxzz
andauthored
feat: add nodeProtocol option (#336)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
1 parent d8edf77 commit b3f671a

19 files changed

+397
-34
lines changed

docs/guide/migrate-from-tsup.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ While `tsdown` aims to be highly compatible with `tsup`, there are some differen
3737

3838
Some features available in `tsup` are not yet implemented in `tsdown`. If you find an option missing that you need, please [open an issue](https://github.com/rolldown/tsdown/issues) to let us know your requirements.
3939

40+
### New Features in tsdown
41+
42+
`tsdown` also introduces new features not available in `tsup`:
43+
44+
- **`nodeProtocol`**: Control how Node.js built-in module imports are handled:
45+
- `true`: Add `node:` prefix to built-in modules (e.g., `fs``node:fs`)
46+
- `'strip'`: Remove `node:` prefix from imports (e.g., `node:fs``fs`)
47+
- `false`: Keep imports as-is (default)
48+
4049
Please review your configuration after migration to ensure it matches your expectations.
4150

4251
## Acknowledgements

docs/reference/config-options.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -447,13 +447,15 @@ false
447447

448448
***
449449

450-
### removeNodeProtocol?
450+
### nodeProtocol?
451451

452-
> `optional` **removeNodeProtocol**: `boolean`
452+
> `optional` **nodeProtocol**: `'strip'` \| `boolean`
453453
454-
Defined in: [types.ts:319](https://github.com/rolldown/tsdown/blob/0978c68bd505c76d7e3097572cd652f81c23f97e/src/options/types.ts#L319)
454+
Defined in: [types.ts:336](https://github.com/rolldown/tsdown/blob/0978c68bd505c76d7e3097572cd652f81c23f97e/src/options/types.ts#L336)
455455

456-
If enabled, strips the `node:` protocol prefix from import source.
456+
- If true, add `node:` prefix to built-in modules.
457+
- If 'strip', strips the `node:` protocol prefix from import source.
458+
- If false, does not modify the import source.
457459

458460
#### Default
459461

@@ -463,9 +465,15 @@ false
463465

464466
#### Example
465467

468+
<!-- eslint-skip -->
469+
466470
```ts
467-
// With removeNodeProtocol enabled:
471+
// With nodeProtocol enabled:
472+
import('fs'); // becomes import('node:fs')
473+
// With nodeProtocol set to 'strip':
468474
import('node:fs'); // becomes import('fs')
475+
// With nodeProtocol set to false:
476+
import('node:fs'); // remains import('node:fs')
469477
```
470478

471479
***

docs/zh-CN/guide/migrate-from-tsup.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ npx tsdown migrate
3737

3838
`tsdown` 尚未实现 `tsup` 中的某些功能。如果您发现缺少某些您需要的选项,请 [提交问题](https://github.com/rolldown/tsdown/issues) 告诉我们您的需求。
3939

40+
### tsdown 的新功能
41+
42+
`tsdown` 还引入了一些 `tsup` 中没有的新功能:
43+
44+
- **`nodeProtocol`**:控制 Node.js 内置模块导入的处理方式:
45+
- `true`:为内置模块添加 `node:` 前缀(例如,`fs``node:fs`
46+
- `'strip'`:从导入中移除 `node:` 前缀(例如,`node:fs``fs`
47+
- `false`:保持导入不变(默认)
48+
4049
迁移后,请仔细检查您的配置以确保其符合您的预期。
4150

4251
## 致谢

docs/zh-CN/reference/config-options.md

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -447,28 +447,6 @@ false
447447

448448
***
449449

450-
### removeNodeProtocol?
451-
452-
> `optional` **removeNodeProtocol**: `boolean`
453-
454-
Defined in: [types.ts:319](https://github.com/rolldown/tsdown/blob/0978c68bd505c76d7e3097572cd652f81c23f97e/src/options/types.ts#L319)
455-
456-
If enabled, strips the `node:` protocol prefix from import source.
457-
458-
#### Default
459-
460-
```ts
461-
false
462-
```
463-
464-
#### Example
465-
466-
```ts
467-
// With removeNodeProtocol enabled:
468-
import('node:fs'); // becomes import('fs')
469-
```
470-
471-
***
472450

473451
### report?
474452

src/features/node-protocol.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,40 @@
1+
import { builtinModules } from 'node:module'
12
import type { Plugin } from 'rolldown'
23

34
/**
45
* The `node:` protocol was added in Node.js v14.18.0.
56
* @see https://nodejs.org/api/esm.html#node-imports
67
*/
7-
export function NodeProtocolPlugin(): Plugin {
8+
export function NodeProtocolPlugin(nodeProtocolOption: 'strip' | true): Plugin {
9+
if (nodeProtocolOption === 'strip') {
10+
return {
11+
name: 'tsdown:node-protocol:strip',
12+
resolveId: {
13+
order: 'pre',
14+
filter: { id: /^node:/ },
15+
handler(id) {
16+
return {
17+
id: id.slice(5), // strip the `node:` prefix
18+
external: true,
19+
moduleSideEffects: false,
20+
}
21+
},
22+
},
23+
}
24+
}
25+
26+
// create regex from builtin modules
27+
// filter without `node:` prefix
28+
const builtinModulesRegex = new RegExp(`^(${builtinModules.join('|')})$`)
29+
830
return {
9-
name: 'tsdown:node-protocol',
31+
name: 'tsdown:node-protocol:add',
1032
resolveId: {
1133
order: 'pre',
12-
filter: { id: /^node:/ },
34+
filter: { id: builtinModulesRegex },
1335
handler(id) {
1436
return {
15-
id: id.slice(5),
37+
id: `node:${id}`,
1638
external: true,
1739
moduleSideEffects: false,
1840
}

src/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,16 +210,16 @@ async function getBuildOptions(
210210
cwd,
211211
report,
212212
env,
213-
removeNodeProtocol,
213+
nodeProtocol,
214214
loader,
215215
name,
216216
unbundle,
217217
} = config
218218

219219
const plugins: RolldownPluginOption = []
220220

221-
if (removeNodeProtocol) {
222-
plugins.push(NodeProtocolPlugin())
221+
if (nodeProtocol) {
222+
plugins.push(NodeProtocolPlugin(nodeProtocol))
223223
}
224224

225225
if (config.pkg || config.skipNodeModulesBundle) {

src/options/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,21 @@ async function resolveConfig(
208208
exports = false,
209209
bundle,
210210
unbundle = typeof bundle === 'boolean' ? !bundle : false,
211+
removeNodeProtocol,
212+
nodeProtocol,
211213
} = userConfig
212214

213215
if (typeof bundle === 'boolean') {
214216
logger.warn('`bundle` option is deprecated. Use `unbundle` instead.')
215217
}
216218

219+
// Resolve nodeProtocol option with backward compatibility for removeNodeProtocol
220+
nodeProtocol =
221+
nodeProtocol ??
222+
// `removeNodeProtocol: true` means stripping the `node:` protocol which equals to `nodeProtocol: 'strip'`
223+
// `removeNodeProtocol: false` means keeping the `node:` protocol which equals to `nodeProtocol: false` (ignore it)
224+
(removeNodeProtocol ? 'strip' : false)
225+
217226
outDir = path.resolve(cwd, outDir)
218227
clean = resolveClean(clean, outDir, cwd)
219228

@@ -310,6 +319,7 @@ async function resolveConfig(
310319
noExternal,
311320
exports,
312321
unbundle,
322+
nodeProtocol,
313323
}
314324

315325
return config

src/options/types.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,13 +311,32 @@ export interface Options {
311311
* If enabled, strips the `node:` protocol prefix from import source.
312312
*
313313
* @default false
314+
* @deprecated Use `nodeProtocol: 'strip'` instead.
314315
*
315316
* @example
316317
* // With removeNodeProtocol enabled:
317318
* import('node:fs'); // becomes import('fs')
318319
*/
319320
removeNodeProtocol?: boolean
320321

322+
/**
323+
* - If true, add `node:` prefix to built-in modules.
324+
* - If 'strip', strips the `node:` protocol prefix from import source.
325+
* - If false, does not modify the import source.
326+
*
327+
* @default false
328+
*
329+
* @example
330+
* // With nodeProtocol enabled:
331+
* import('fs'); // becomes import('node:fs')
332+
* // With nodeProtocol set to 'strip':
333+
* import('node:fs'); // becomes import('fs')
334+
* // With nodeProtocol set to false:
335+
* import('node:fs'); // remains import('node:fs')
336+
*
337+
*/
338+
nodeProtocol?: 'strip' | boolean
339+
321340
/**
322341
* If enabled, appends hash to chunk filenames.
323342
* @default true
@@ -380,6 +399,7 @@ export type ResolvedOptions = Omit<
380399
tsconfig: string | false
381400
pkg?: PackageJson
382401
exports: false | ExportsOptions
402+
nodeProtocol: 'strip' | boolean
383403
}
384404
>,
385405
'config' | 'fromVite'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
## index.js
2+
3+
```js
4+
//#region index.ts
5+
async function loadBuiltins() {
6+
const fs = await import("node:fs");
7+
const path = await import("node:path");
8+
return {
9+
fs,
10+
path
11+
};
12+
}
13+
14+
//#endregion
15+
export { loadBuiltins };
16+
```
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## index.js
2+
3+
```js
4+
import fs from "node:fs";
5+
import { join } from "node:path";
6+
import express from "express";
7+
8+
export { express, fs, join };
9+
```

0 commit comments

Comments
 (0)