-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathimport.js
More file actions
120 lines (95 loc) · 3.08 KB
/
Copy pathimport.js
File metadata and controls
120 lines (95 loc) · 3.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import fetch from "node-fetch";
import { pathToFileURL } from "node:url";
/* ============================
Utils
============================ */
function hash(str) {
return crypto.createHash("sha256").update(str).digest("hex");
}
function ensureDir(dir) {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
}
/* ============================
Import parser (ESM)
============================ */
const IMPORT_RE =
/(?:import|export)\s+(?:[^'"]*?\s+from\s*)?["']([^"']+)["']|import\(\s*["']([^"']+)["']\s*\)/g;
/* ============================
Recursive importer
============================ */
export async function importFromUrlRecursive(
entryUrl,
cacheDir = "./.tmp_modules",
seen = new Map()
) {
ensureDir(cacheDir);
if (seen.has(entryUrl)) {
return seen.get(entryUrl);
}
const fileHash = hash(entryUrl);
const localFile = path.join(cacheDir, `${fileHash}.mjs`);
seen.set(entryUrl, localFile);
if (!fs.existsSync(localFile)) {
const res = await fetch(entryUrl);
if (!res.ok) {
throw new Error(`Failed to fetch ${entryUrl}`);
}
let code = await res.text();
let deps = new Set();
let match;
while ((match = IMPORT_RE.exec(code))) {
const raw = match[1] || match[2];
if (!raw) continue;
// 🔥 РЕЗОЛВИНГ ПО СПЕКЕ ESM
try {
const resolved = resolveImport(raw, entryUrl);
if (!resolved) continue;
deps.add({ raw, resolved });
} catch {
// например import "node:fs" — пропускаем
}
}
for (const { raw, resolved } of deps) {
if (
resolved.startsWith("http://") ||
resolved.startsWith("https://")
) {
const depLocal = await importFromUrlRecursive(
resolved,
cacheDir,
seen
);
const depFileUrl = pathToFileURL(
path.resolve(depLocal)
).href;
code = code.split(raw).join(depFileUrl);
}
}
fs.writeFileSync(localFile, code, "utf8");
}
return localFile;
}
/* ============================
Helper: import entry module
============================ */
export default async function importUrl(entryUrl, cacheDir) {
const localFile = await importFromUrlRecursive(entryUrl, cacheDir);
return import(pathToFileURL(path.resolve(localFile)).href);
}
function resolveImport(raw, parentUrl) {
try {
return new URL(raw, parentUrl).href;
} catch {}
// bare import → CDN
const parent = new URL(parentUrl);
if (parent.hostname.includes("jsdelivr.net")) {
return `https://cdn.jsdelivr.net/npm/${raw}`;
}
if (parent.hostname.includes("unpkg.com")) {
return `https://unpkg.com/${raw}`;
}
return null;
}