http-modules 0.0.1
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/CHANGELOG.md +10 -0
- package/README.md +120 -0
- package/dist/bun.d.ts +1 -0
- package/dist/bun.js +116 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +85 -0
- package/dist/node/hooks.d.ts +21 -0
- package/dist/node/hooks.js +109 -0
- package/dist/node/register.d.ts +1 -0
- package/dist/node/register.js +3 -0
- package/dist/shared.d.ts +17 -0
- package/package.json +47 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.0.1 - 2026-07-15
|
|
4
|
+
|
|
5
|
+
Initial release.
|
|
6
|
+
|
|
7
|
+
- Added Bun support for `https://` imports through a preloadable `Bun.plugin`.
|
|
8
|
+
- Added Node.js support through asynchronous module customization hooks and a `node --import` registration entrypoint.
|
|
9
|
+
- Added shared remote module fetching, HTTP error handling, runtime format detection, and relative import rewriting for nested remote modules.
|
|
10
|
+
- Added package entrypoints for the side-effect-free root API, Bun preload, Node hooks, and Node registration.
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# http-modules
|
|
2
|
+
|
|
3
|
+
`http-modules` lets JavaScript runtimes import modules directly from `https://` URLs.
|
|
4
|
+
|
|
5
|
+
The package currently supports:
|
|
6
|
+
|
|
7
|
+
- Bun, through a preloadable `Bun.plugin`.
|
|
8
|
+
- Node.js, through module customization hooks.
|
|
9
|
+
- Remote JavaScript and TypeScript modules.
|
|
10
|
+
- Relative imports inside fetched remote modules.
|
|
11
|
+
|
|
12
|
+
## Why this exists
|
|
13
|
+
|
|
14
|
+
Browsers and Deno can import URL-based modules as first-class module specifiers. Bun and Node.js normally expect package, file, or built-in specifiers. This package fills that gap for runtime code that wants to write normal static imports against HTTPS module URLs:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { nanoid } from "https://cdn.jsdelivr.net/npm/nanoid@5.0.7/index.browser.js";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
bun add http-modules
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Bun
|
|
27
|
+
|
|
28
|
+
Preload the Bun plugin before any static HTTPS imports are resolved:
|
|
29
|
+
|
|
30
|
+
```toml
|
|
31
|
+
# bunfig.toml
|
|
32
|
+
preload = ["http-modules/bun"]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Then import HTTPS modules directly:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { nanoid } from "https://cdn.jsdelivr.net/npm/nanoid@5.0.7/index.browser.js";
|
|
39
|
+
|
|
40
|
+
console.log(nanoid());
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For local development in this repo, `bunfig.toml` preloads `./src/bun.ts` so the source plugin is exercised directly.
|
|
44
|
+
|
|
45
|
+
## Node.js
|
|
46
|
+
|
|
47
|
+
Preload the Node registration entrypoint:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
node --import http-modules/node/register ./app.mjs
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then import HTTPS modules directly:
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
import { basename } from "https://deno.land/std@0.224.0/path/mod.ts";
|
|
57
|
+
|
|
58
|
+
console.log(basename("/tmp/example.ts"));
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Node.js support uses asynchronous module customization hooks. Remote TypeScript modules require Node.js `>=22.6`, because Node must understand the `module-typescript` loader format.
|
|
62
|
+
|
|
63
|
+
If you need to register hooks yourself, use:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
import { register } from "node:module";
|
|
67
|
+
|
|
68
|
+
register("http-modules/node/hooks", import.meta.url);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Package entrypoints
|
|
72
|
+
|
|
73
|
+
| Export | Purpose |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| `http-modules` | Shared helper exports. No runtime side effects. |
|
|
76
|
+
| `http-modules/bun` | Registers the Bun HTTPS import plugin. Use as a Bun preload. |
|
|
77
|
+
| `http-modules/node/register` | Registers Node customization hooks. Use with `node --import`. |
|
|
78
|
+
| `http-modules/node/hooks` | Raw Node hook module for manual registration. |
|
|
79
|
+
|
|
80
|
+
## Behavior
|
|
81
|
+
|
|
82
|
+
- Only `https://` module specifiers are handled.
|
|
83
|
+
- HTTP errors fail module loading with the response status.
|
|
84
|
+
- Remote JavaScript and TypeScript sources have relative import specifiers rewritten to absolute HTTPS URLs before evaluation. This keeps nested remote imports working across both runtimes.
|
|
85
|
+
- JSON modules are loaded as JSON where the runtime supports that loader format.
|
|
86
|
+
- No cache is implemented yet. Every process fetches remote modules through the runtime loader path.
|
|
87
|
+
|
|
88
|
+
## Development
|
|
89
|
+
|
|
90
|
+
This repo uses Bun for package management and build tasks, with mise pinning Bun and Node versions.
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
mise install
|
|
94
|
+
bun install
|
|
95
|
+
bun run build
|
|
96
|
+
bun run test
|
|
97
|
+
bun run typecheck
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Equivalent mise tasks:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
mise run build
|
|
104
|
+
mise run test
|
|
105
|
+
mise run typecheck
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`bun run test` builds `dist/` and then runs the cross-runtime test suite. The tests start a local HTTPS server with fixture modules, then verify both Bun and Node can import the same remote module graph without depending on third-party networks.
|
|
109
|
+
|
|
110
|
+
## Repository layout
|
|
111
|
+
|
|
112
|
+
```txt
|
|
113
|
+
src/shared.ts Shared fetch, format detection, and import-rewrite logic.
|
|
114
|
+
src/bun.ts Bun plugin entrypoint.
|
|
115
|
+
src/node/hooks.ts Node async resolve/load hooks.
|
|
116
|
+
src/node/register.ts Node --import registration entrypoint.
|
|
117
|
+
src/index.ts Side-effect-free public helper exports.
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Build output is written to `dist/` and included in the published package.
|
package/dist/bun.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/bun.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// src/bun.ts
|
|
2
|
+
import { plugin } from "bun";
|
|
3
|
+
|
|
4
|
+
// src/shared.ts
|
|
5
|
+
var RELATIVE_IMPORT_SPECIFIER = /(\b(?:from|import)\s*(?:\(\s*)?)(["'])(\.{1,2}\/[^"']+)\2/g;
|
|
6
|
+
function isHttpsSpecifier(specifier) {
|
|
7
|
+
return specifier.startsWith("https://");
|
|
8
|
+
}
|
|
9
|
+
function isRelativeSpecifier(specifier) {
|
|
10
|
+
return specifier.startsWith("./") || specifier.startsWith("../");
|
|
11
|
+
}
|
|
12
|
+
function toFetchUrl(namespacePath) {
|
|
13
|
+
if (namespacePath.startsWith("//")) {
|
|
14
|
+
return `https:${namespacePath}`;
|
|
15
|
+
}
|
|
16
|
+
if (namespacePath.startsWith("https://")) {
|
|
17
|
+
return namespacePath;
|
|
18
|
+
}
|
|
19
|
+
throw new Error(`Expected an HTTPS URL, received ${namespacePath}`);
|
|
20
|
+
}
|
|
21
|
+
function toNamespacePath(url) {
|
|
22
|
+
if (url.startsWith("https://")) {
|
|
23
|
+
return url.slice("https:".length);
|
|
24
|
+
}
|
|
25
|
+
if (url.startsWith("//")) {
|
|
26
|
+
return url;
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`Expected an HTTPS URL, received ${url}`);
|
|
29
|
+
}
|
|
30
|
+
function resolveHttpsSpecifier(specifier, parentUrl) {
|
|
31
|
+
if (isHttpsSpecifier(specifier)) {
|
|
32
|
+
return specifier;
|
|
33
|
+
}
|
|
34
|
+
if (parentUrl?.startsWith("https://") && isRelativeSpecifier(specifier)) {
|
|
35
|
+
return new URL(specifier, parentUrl).href;
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
function rewriteRelativeImportSpecifiers(source, importerUrl) {
|
|
40
|
+
return source.replace(RELATIVE_IMPORT_SPECIFIER, (_specifier, prefix, quote, path) => `${prefix}${quote}${new URL(path, importerUrl).href}${quote}`);
|
|
41
|
+
}
|
|
42
|
+
function bunLoaderFor(url, contentType) {
|
|
43
|
+
const pathname = new URL(url).pathname;
|
|
44
|
+
if (contentType?.includes("application/json") || pathname.endsWith(".json")) {
|
|
45
|
+
return "json";
|
|
46
|
+
}
|
|
47
|
+
if (pathname.endsWith(".ts") || pathname.endsWith(".mts") || contentType?.includes("typescript")) {
|
|
48
|
+
return "ts";
|
|
49
|
+
}
|
|
50
|
+
if (pathname.endsWith(".tsx")) {
|
|
51
|
+
return "tsx";
|
|
52
|
+
}
|
|
53
|
+
if (pathname.endsWith(".jsx")) {
|
|
54
|
+
return "jsx";
|
|
55
|
+
}
|
|
56
|
+
return "js";
|
|
57
|
+
}
|
|
58
|
+
function nodeFormatFor(url, contentType) {
|
|
59
|
+
const pathname = new URL(url).pathname;
|
|
60
|
+
if (contentType?.includes("application/json") || pathname.endsWith(".json")) {
|
|
61
|
+
return "json";
|
|
62
|
+
}
|
|
63
|
+
if (pathname.endsWith(".ts") || pathname.endsWith(".mts") || contentType?.includes("typescript")) {
|
|
64
|
+
return "module-typescript";
|
|
65
|
+
}
|
|
66
|
+
return "module";
|
|
67
|
+
}
|
|
68
|
+
function rewritesImports(loader) {
|
|
69
|
+
return loader === "js" || loader === "jsx" || loader === "ts" || loader === "tsx";
|
|
70
|
+
}
|
|
71
|
+
async function fetchRemoteModule(url) {
|
|
72
|
+
const response = await fetch(url);
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error(`Failed to load ${url}: ${response.status} ${response.statusText}`);
|
|
75
|
+
}
|
|
76
|
+
const contentType = response.headers.get("content-type");
|
|
77
|
+
const bunLoader = bunLoaderFor(url, contentType);
|
|
78
|
+
const source = await response.text();
|
|
79
|
+
return {
|
|
80
|
+
bunLoader,
|
|
81
|
+
nodeFormat: nodeFormatFor(url, contentType),
|
|
82
|
+
source: rewritesImports(bunLoader) ? rewriteRelativeImportSpecifiers(source, url) : source
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/bun.ts
|
|
87
|
+
var HTTPS_NAMESPACE = "https";
|
|
88
|
+
plugin({
|
|
89
|
+
name: "https-imports",
|
|
90
|
+
setup(build) {
|
|
91
|
+
build.onResolve({ filter: /^https:\/\// }, (args) => ({
|
|
92
|
+
path: toNamespacePath(args.path),
|
|
93
|
+
namespace: HTTPS_NAMESPACE
|
|
94
|
+
}));
|
|
95
|
+
build.onResolve({ filter: /.*/, namespace: HTTPS_NAMESPACE }, (args) => {
|
|
96
|
+
if (args.path.startsWith("https://") || args.path.startsWith("//")) {
|
|
97
|
+
return {
|
|
98
|
+
path: toNamespacePath(args.path),
|
|
99
|
+
namespace: HTTPS_NAMESPACE
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const importer = toFetchUrl(args.importer);
|
|
103
|
+
return {
|
|
104
|
+
path: toNamespacePath(new URL(args.path, importer).href),
|
|
105
|
+
namespace: HTTPS_NAMESPACE
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
build.onLoad({ filter: /.*/, namespace: HTTPS_NAMESPACE }, async (args) => {
|
|
109
|
+
const remoteModule = await fetchRemoteModule(toFetchUrl(args.path));
|
|
110
|
+
return {
|
|
111
|
+
contents: remoteModule.source,
|
|
112
|
+
loader: remoteModule.bunLoader
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// src/shared.ts
|
|
2
|
+
var RELATIVE_IMPORT_SPECIFIER = /(\b(?:from|import)\s*(?:\(\s*)?)(["'])(\.{1,2}\/[^"']+)\2/g;
|
|
3
|
+
function isHttpsSpecifier(specifier) {
|
|
4
|
+
return specifier.startsWith("https://");
|
|
5
|
+
}
|
|
6
|
+
function isRelativeSpecifier(specifier) {
|
|
7
|
+
return specifier.startsWith("./") || specifier.startsWith("../");
|
|
8
|
+
}
|
|
9
|
+
function toFetchUrl(namespacePath) {
|
|
10
|
+
if (namespacePath.startsWith("//")) {
|
|
11
|
+
return `https:${namespacePath}`;
|
|
12
|
+
}
|
|
13
|
+
if (namespacePath.startsWith("https://")) {
|
|
14
|
+
return namespacePath;
|
|
15
|
+
}
|
|
16
|
+
throw new Error(`Expected an HTTPS URL, received ${namespacePath}`);
|
|
17
|
+
}
|
|
18
|
+
function toNamespacePath(url) {
|
|
19
|
+
if (url.startsWith("https://")) {
|
|
20
|
+
return url.slice("https:".length);
|
|
21
|
+
}
|
|
22
|
+
if (url.startsWith("//")) {
|
|
23
|
+
return url;
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`Expected an HTTPS URL, received ${url}`);
|
|
26
|
+
}
|
|
27
|
+
function resolveHttpsSpecifier(specifier, parentUrl) {
|
|
28
|
+
if (isHttpsSpecifier(specifier)) {
|
|
29
|
+
return specifier;
|
|
30
|
+
}
|
|
31
|
+
if (parentUrl?.startsWith("https://") && isRelativeSpecifier(specifier)) {
|
|
32
|
+
return new URL(specifier, parentUrl).href;
|
|
33
|
+
}
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
function rewriteRelativeImportSpecifiers(source, importerUrl) {
|
|
37
|
+
return source.replace(RELATIVE_IMPORT_SPECIFIER, (_specifier, prefix, quote, path) => `${prefix}${quote}${new URL(path, importerUrl).href}${quote}`);
|
|
38
|
+
}
|
|
39
|
+
function bunLoaderFor(url, contentType) {
|
|
40
|
+
const pathname = new URL(url).pathname;
|
|
41
|
+
if (contentType?.includes("application/json") || pathname.endsWith(".json")) {
|
|
42
|
+
return "json";
|
|
43
|
+
}
|
|
44
|
+
if (pathname.endsWith(".ts") || pathname.endsWith(".mts") || contentType?.includes("typescript")) {
|
|
45
|
+
return "ts";
|
|
46
|
+
}
|
|
47
|
+
if (pathname.endsWith(".tsx")) {
|
|
48
|
+
return "tsx";
|
|
49
|
+
}
|
|
50
|
+
if (pathname.endsWith(".jsx")) {
|
|
51
|
+
return "jsx";
|
|
52
|
+
}
|
|
53
|
+
return "js";
|
|
54
|
+
}
|
|
55
|
+
function nodeFormatFor(url, contentType) {
|
|
56
|
+
const pathname = new URL(url).pathname;
|
|
57
|
+
if (contentType?.includes("application/json") || pathname.endsWith(".json")) {
|
|
58
|
+
return "json";
|
|
59
|
+
}
|
|
60
|
+
if (pathname.endsWith(".ts") || pathname.endsWith(".mts") || contentType?.includes("typescript")) {
|
|
61
|
+
return "module-typescript";
|
|
62
|
+
}
|
|
63
|
+
return "module";
|
|
64
|
+
}
|
|
65
|
+
function rewritesImports(loader) {
|
|
66
|
+
return loader === "js" || loader === "jsx" || loader === "ts" || loader === "tsx";
|
|
67
|
+
}
|
|
68
|
+
async function fetchRemoteModule(url) {
|
|
69
|
+
const response = await fetch(url);
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new Error(`Failed to load ${url}: ${response.status} ${response.statusText}`);
|
|
72
|
+
}
|
|
73
|
+
const contentType = response.headers.get("content-type");
|
|
74
|
+
const bunLoader = bunLoaderFor(url, contentType);
|
|
75
|
+
const source = await response.text();
|
|
76
|
+
return {
|
|
77
|
+
bunLoader,
|
|
78
|
+
nodeFormat: nodeFormatFor(url, contentType),
|
|
79
|
+
source: rewritesImports(bunLoader) ? rewriteRelativeImportSpecifiers(source, url) : source
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
resolveHttpsSpecifier,
|
|
84
|
+
fetchRemoteModule
|
|
85
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
interface ResolveContext {
|
|
2
|
+
parentURL?: string;
|
|
3
|
+
}
|
|
4
|
+
interface ResolveResult {
|
|
5
|
+
format?: string;
|
|
6
|
+
shortCircuit?: boolean;
|
|
7
|
+
url: string;
|
|
8
|
+
}
|
|
9
|
+
interface LoadContext {
|
|
10
|
+
format?: string;
|
|
11
|
+
}
|
|
12
|
+
interface LoadResult {
|
|
13
|
+
format: string;
|
|
14
|
+
shortCircuit?: boolean;
|
|
15
|
+
source: string;
|
|
16
|
+
}
|
|
17
|
+
type NextResolve = (specifier: string, context: ResolveContext) => Promise<ResolveResult>;
|
|
18
|
+
type NextLoad = (url: string, context: LoadContext) => Promise<LoadResult>;
|
|
19
|
+
export declare function resolve(specifier: string, context: ResolveContext, nextResolve: NextResolve): Promise<ResolveResult>;
|
|
20
|
+
export declare function load(url: string, context: LoadContext, nextLoad: NextLoad): Promise<LoadResult>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// src/shared.ts
|
|
2
|
+
var RELATIVE_IMPORT_SPECIFIER = /(\b(?:from|import)\s*(?:\(\s*)?)(["'])(\.{1,2}\/[^"']+)\2/g;
|
|
3
|
+
function isHttpsSpecifier(specifier) {
|
|
4
|
+
return specifier.startsWith("https://");
|
|
5
|
+
}
|
|
6
|
+
function isRelativeSpecifier(specifier) {
|
|
7
|
+
return specifier.startsWith("./") || specifier.startsWith("../");
|
|
8
|
+
}
|
|
9
|
+
function toFetchUrl(namespacePath) {
|
|
10
|
+
if (namespacePath.startsWith("//")) {
|
|
11
|
+
return `https:${namespacePath}`;
|
|
12
|
+
}
|
|
13
|
+
if (namespacePath.startsWith("https://")) {
|
|
14
|
+
return namespacePath;
|
|
15
|
+
}
|
|
16
|
+
throw new Error(`Expected an HTTPS URL, received ${namespacePath}`);
|
|
17
|
+
}
|
|
18
|
+
function toNamespacePath(url) {
|
|
19
|
+
if (url.startsWith("https://")) {
|
|
20
|
+
return url.slice("https:".length);
|
|
21
|
+
}
|
|
22
|
+
if (url.startsWith("//")) {
|
|
23
|
+
return url;
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`Expected an HTTPS URL, received ${url}`);
|
|
26
|
+
}
|
|
27
|
+
function resolveHttpsSpecifier(specifier, parentUrl) {
|
|
28
|
+
if (isHttpsSpecifier(specifier)) {
|
|
29
|
+
return specifier;
|
|
30
|
+
}
|
|
31
|
+
if (parentUrl?.startsWith("https://") && isRelativeSpecifier(specifier)) {
|
|
32
|
+
return new URL(specifier, parentUrl).href;
|
|
33
|
+
}
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
function rewriteRelativeImportSpecifiers(source, importerUrl) {
|
|
37
|
+
return source.replace(RELATIVE_IMPORT_SPECIFIER, (_specifier, prefix, quote, path) => `${prefix}${quote}${new URL(path, importerUrl).href}${quote}`);
|
|
38
|
+
}
|
|
39
|
+
function bunLoaderFor(url, contentType) {
|
|
40
|
+
const pathname = new URL(url).pathname;
|
|
41
|
+
if (contentType?.includes("application/json") || pathname.endsWith(".json")) {
|
|
42
|
+
return "json";
|
|
43
|
+
}
|
|
44
|
+
if (pathname.endsWith(".ts") || pathname.endsWith(".mts") || contentType?.includes("typescript")) {
|
|
45
|
+
return "ts";
|
|
46
|
+
}
|
|
47
|
+
if (pathname.endsWith(".tsx")) {
|
|
48
|
+
return "tsx";
|
|
49
|
+
}
|
|
50
|
+
if (pathname.endsWith(".jsx")) {
|
|
51
|
+
return "jsx";
|
|
52
|
+
}
|
|
53
|
+
return "js";
|
|
54
|
+
}
|
|
55
|
+
function nodeFormatFor(url, contentType) {
|
|
56
|
+
const pathname = new URL(url).pathname;
|
|
57
|
+
if (contentType?.includes("application/json") || pathname.endsWith(".json")) {
|
|
58
|
+
return "json";
|
|
59
|
+
}
|
|
60
|
+
if (pathname.endsWith(".ts") || pathname.endsWith(".mts") || contentType?.includes("typescript")) {
|
|
61
|
+
return "module-typescript";
|
|
62
|
+
}
|
|
63
|
+
return "module";
|
|
64
|
+
}
|
|
65
|
+
function rewritesImports(loader) {
|
|
66
|
+
return loader === "js" || loader === "jsx" || loader === "ts" || loader === "tsx";
|
|
67
|
+
}
|
|
68
|
+
async function fetchRemoteModule(url) {
|
|
69
|
+
const response = await fetch(url);
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new Error(`Failed to load ${url}: ${response.status} ${response.statusText}`);
|
|
72
|
+
}
|
|
73
|
+
const contentType = response.headers.get("content-type");
|
|
74
|
+
const bunLoader = bunLoaderFor(url, contentType);
|
|
75
|
+
const source = await response.text();
|
|
76
|
+
return {
|
|
77
|
+
bunLoader,
|
|
78
|
+
nodeFormat: nodeFormatFor(url, contentType),
|
|
79
|
+
source: rewritesImports(bunLoader) ? rewriteRelativeImportSpecifiers(source, url) : source
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/node/hooks.ts
|
|
84
|
+
async function resolve(specifier, context, nextResolve) {
|
|
85
|
+
const url = resolveHttpsSpecifier(specifier, context.parentURL);
|
|
86
|
+
if (url) {
|
|
87
|
+
return {
|
|
88
|
+
format: nodeFormatFor(url, null),
|
|
89
|
+
shortCircuit: true,
|
|
90
|
+
url
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return nextResolve(specifier, context);
|
|
94
|
+
}
|
|
95
|
+
async function load(url, context, nextLoad) {
|
|
96
|
+
if (!url.startsWith("https://")) {
|
|
97
|
+
return nextLoad(url, context);
|
|
98
|
+
}
|
|
99
|
+
const remoteModule = await fetchRemoteModule(url);
|
|
100
|
+
return {
|
|
101
|
+
format: remoteModule.nodeFormat,
|
|
102
|
+
shortCircuit: true,
|
|
103
|
+
source: remoteModule.source
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export {
|
|
107
|
+
resolve,
|
|
108
|
+
load
|
|
109
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type BunRemoteLoader = "js" | "jsx" | "ts" | "tsx" | "json";
|
|
2
|
+
export type NodeRemoteFormat = "module" | "module-typescript" | "json";
|
|
3
|
+
export interface RemoteModule {
|
|
4
|
+
bunLoader: BunRemoteLoader;
|
|
5
|
+
nodeFormat: NodeRemoteFormat;
|
|
6
|
+
source: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function isHttpsSpecifier(specifier: string): boolean;
|
|
9
|
+
export declare function isRelativeSpecifier(specifier: string): boolean;
|
|
10
|
+
export declare function toFetchUrl(namespacePath: string): string;
|
|
11
|
+
export declare function toNamespacePath(url: string): string;
|
|
12
|
+
export declare function resolveHttpsSpecifier(specifier: string, parentUrl?: string): string | undefined;
|
|
13
|
+
export declare function rewriteRelativeImportSpecifiers(source: string, importerUrl: string): string;
|
|
14
|
+
export declare function bunLoaderFor(url: string, contentType: string | null): BunRemoteLoader;
|
|
15
|
+
export declare function nodeFormatFor(url: string, contentType: string | null): NodeRemoteFormat;
|
|
16
|
+
export declare function rewritesImports(loader: BunRemoteLoader): boolean;
|
|
17
|
+
export declare function fetchRemoteModule(url: string): Promise<RemoteModule>;
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "http-modules",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Import HTTPS modules in Bun and Node.js.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": [
|
|
7
|
+
"./dist/bun.js",
|
|
8
|
+
"./dist/node/register.js"
|
|
9
|
+
],
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./bun": {
|
|
16
|
+
"types": "./dist/bun.d.ts",
|
|
17
|
+
"import": "./dist/bun.js"
|
|
18
|
+
},
|
|
19
|
+
"./node/hooks": {
|
|
20
|
+
"types": "./dist/node/hooks.d.ts",
|
|
21
|
+
"import": "./dist/node/hooks.js"
|
|
22
|
+
},
|
|
23
|
+
"./node/register": {
|
|
24
|
+
"types": "./dist/node/register.d.ts",
|
|
25
|
+
"import": "./dist/node/register.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"CHANGELOG.md"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "rm -rf ./dist && bun build ./src/index.ts ./src/bun.ts ./src/node/hooks.ts ./src/node/register.ts --outdir ./dist --target node --format esm --packages external && bun x tsc --project tsconfig.build.json",
|
|
35
|
+
"test": "bun run build && bun test",
|
|
36
|
+
"typecheck": "bun x tsc --noEmit"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"bun": ">=1.3.1",
|
|
40
|
+
"node": ">=22.6"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/bun": "latest",
|
|
44
|
+
"@types/node": "latest",
|
|
45
|
+
"typescript": "^5"
|
|
46
|
+
}
|
|
47
|
+
}
|