nitro5 1.0.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/src/tsc.js ADDED
@@ -0,0 +1,329 @@
1
+ import * as esbuild from "esbuild";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import crypto from "node:crypto";
5
+ import chokidar from "chokidar";
6
+ import { pathToFileURL } from "node:url";
7
+
8
+ import {
9
+ readDiskCache,
10
+ writeDiskCache
11
+ } from "./disk-cache.js";
12
+
13
+ import {
14
+ getCache,
15
+ setCache,
16
+ hasCache
17
+ } from "./deps-cache.js";
18
+
19
+ import msg from "./msg.js";
20
+
21
+ let config = null;
22
+
23
+ async function loadConfig() {
24
+ if (config) return config;
25
+
26
+ try {
27
+ const configPath = path.join(process.cwd(), "nitro5.config.js");
28
+ const configUrl = pathToFileURL(configPath).href;
29
+ config = (await import(configUrl)).default;
30
+ } catch (err) {
31
+ throw new Error(msg.noConfigFound);
32
+ }
33
+
34
+ if (!config || typeof config !== "object") {
35
+ throw new Error(msg.noConfigFound);
36
+ }
37
+
38
+ return config;
39
+ }
40
+
41
+ function fileExists(filePath) {
42
+ return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
43
+ }
44
+
45
+ function readText(filePath) {
46
+ return fs.readFileSync(filePath, "utf8");
47
+ }
48
+
49
+ function hashText(text) {
50
+ return crypto.createHash("sha1").update(text).digest("hex");
51
+ }
52
+
53
+ function isLocalImport(specifier) {
54
+ return (
55
+ specifier.startsWith("./") ||
56
+ specifier.startsWith("../") ||
57
+ specifier.startsWith("/") ||
58
+ specifier.startsWith("file:")
59
+ );
60
+ }
61
+
62
+ function tryResolveFile(basePath) {
63
+ const candidates = [
64
+ basePath,
65
+ `${basePath}.ts`,
66
+ `${basePath}.tsx`,
67
+ `${basePath}.js`,
68
+ `${basePath}.jsx`,
69
+ `${basePath}.mjs`,
70
+ `${basePath}.cjs`,
71
+ `${basePath}.mts`,
72
+ `${basePath}.cts`,
73
+ path.join(basePath, "index.ts"),
74
+ path.join(basePath, "index.tsx"),
75
+ path.join(basePath, "index.js"),
76
+ path.join(basePath, "index.jsx"),
77
+ path.join(basePath, "index.mjs"),
78
+ path.join(basePath, "index.cjs"),
79
+ ];
80
+
81
+ for (const candidate of candidates) {
82
+ if (fileExists(candidate)) return path.resolve(candidate);
83
+ }
84
+
85
+ return null;
86
+ }
87
+
88
+ function resolveImport(specifier, fromFile) {
89
+ if (!isLocalImport(specifier)) return null;
90
+
91
+ let rawPath = specifier;
92
+
93
+ if (rawPath.startsWith("file:")) {
94
+ try {
95
+ rawPath = new URL(rawPath).pathname;
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+
101
+ const fromDir = path.dirname(fromFile);
102
+ const absBase = path.isAbsolute(rawPath)
103
+ ? rawPath
104
+ : path.resolve(fromDir, rawPath);
105
+
106
+ return tryResolveFile(absBase);
107
+ }
108
+
109
+ function extractLocalImports(source) {
110
+ const result = new Set();
111
+
112
+ const re = /(?:import\s+(?:[\s\S]*?\s+from\s+)?|export\s+[\s\S]*?\s+from\s+|import\s*\()\s*['"]([^'"]+)['"]/g;
113
+ let match;
114
+
115
+ while ((match = re.exec(source)) !== null) {
116
+ result.add(match[1]);
117
+ }
118
+
119
+ return [...result];
120
+ }
121
+
122
+ async function collectDependencyTree(entryPath) {
123
+ const root = path.resolve(entryPath);
124
+ const seen = new Set();
125
+ const stack = [root];
126
+ const files = new Set();
127
+
128
+ while (stack.length) {
129
+ const file = stack.pop();
130
+ const abs = path.resolve(file);
131
+
132
+ if (seen.has(abs)) continue;
133
+ seen.add(abs);
134
+
135
+ if (!fileExists(abs)) continue;
136
+
137
+ files.add(abs);
138
+
139
+ const source = readText(abs);
140
+ const imports = extractLocalImports(source);
141
+
142
+ for (const spec of imports) {
143
+ const resolved = resolveImport(spec, abs);
144
+ if (resolved && !seen.has(resolved)) {
145
+ stack.push(resolved);
146
+ }
147
+ }
148
+ }
149
+
150
+ return [...files].sort();
151
+ }
152
+
153
+ function buildTreeHash(files) {
154
+ const h = crypto.createHash("sha1");
155
+
156
+ for (const file of files) {
157
+ const content = fs.readFileSync(file);
158
+ h.update(file);
159
+ h.update("\0");
160
+ h.update(content);
161
+ h.update("\0");
162
+ }
163
+
164
+ return h.digest("hex");
165
+ }
166
+
167
+ async function buildTS(entryPath, useCache) {
168
+ const deps = await collectDependencyTree(entryPath);
169
+ const treeHash = buildTreeHash(deps);
170
+ const cacheKey = `${path.resolve(entryPath)}:${treeHash}`;
171
+
172
+ if (useCache && hasCache(cacheKey)) {
173
+ return {
174
+ code: getCache(cacheKey),
175
+ deps,
176
+ cacheKey,
177
+ treeHash
178
+ };
179
+ }
180
+
181
+ if (useCache) {
182
+ const disk = readDiskCache(cacheKey);
183
+ if (disk?.code) {
184
+ setCache(cacheKey, disk.code);
185
+ return {
186
+ code: disk.code,
187
+ deps,
188
+ cacheKey,
189
+ treeHash
190
+ };
191
+ }
192
+ }
193
+
194
+ const result = await esbuild.build({
195
+ entryPoints: [entryPath],
196
+ bundle: true,
197
+ format: "esm",
198
+ platform: "browser",
199
+ target: "es2022",
200
+ write: false,
201
+ sourcemap: false,
202
+ minify: false,
203
+ metafile: true,
204
+ loader: {
205
+ ".ts": "ts",
206
+ ".tsx": "tsx",
207
+ ".js": "js",
208
+ ".jsx": "jsx"
209
+ },
210
+ jsx: "automatic",
211
+ jsxImportSource: "react"
212
+ });
213
+
214
+ if (!result.outputFiles?.length) {
215
+ throw new Error("TS build failed");
216
+ }
217
+
218
+ const code = result.outputFiles[0].text;
219
+
220
+ if (useCache) {
221
+ setCache(cacheKey, code);
222
+ writeDiskCache(cacheKey, {
223
+ code,
224
+ time: Date.now()
225
+ });
226
+ }
227
+
228
+ return {
229
+ code,
230
+ deps,
231
+ cacheKey,
232
+ treeHash
233
+ };
234
+ }
235
+
236
+ export async function bundleTS(entryPath, options = {}) {
237
+ if (!entryPath) {
238
+ throw new Error("bundleTS: entryPath undefined");
239
+ }
240
+
241
+ await loadConfig();
242
+
243
+ const useCache = options.cacheTs ?? config.cacheTs !== false;
244
+ const result = await buildTS(entryPath, useCache);
245
+
246
+ return result.code;
247
+ }
248
+
249
+ export async function getDependencyTree(entryPath) {
250
+ if (!entryPath) {
251
+ throw new Error("getDependencyTree: entryPath undefined");
252
+ }
253
+
254
+ return await collectDependencyTree(entryPath);
255
+ }
256
+
257
+ export function watchTS(entryPath, onRebuild, options = {}) {
258
+ if (!entryPath) {
259
+ throw new Error("watchTS: entryPath undefined");
260
+ }
261
+
262
+ let watcher = null;
263
+ let rebuilding = false;
264
+ let watchedFiles = new Set();
265
+
266
+ const useCache = options.cacheTs ?? true;
267
+
268
+ async function rebuild(changedFile = entryPath) {
269
+ if (rebuilding) return;
270
+ rebuilding = true;
271
+
272
+ try {
273
+ const result = await buildTS(entryPath, useCache);
274
+
275
+ const nextFiles = new Set(result.deps);
276
+ const toAdd = [...nextFiles].filter((f) => !watchedFiles.has(f));
277
+ const toRemove = [...watchedFiles].filter((f) => !nextFiles.has(f));
278
+
279
+ for (const file of toRemove) {
280
+ watcher?.unwatch(file);
281
+ }
282
+
283
+ for (const file of toAdd) {
284
+ watcher?.add(file);
285
+ }
286
+
287
+ watchedFiles = nextFiles;
288
+
289
+ if (typeof onRebuild === "function") {
290
+ await onRebuild({
291
+ entryPath,
292
+ changedFile,
293
+ code: result.code,
294
+ deps: result.deps,
295
+ cacheKey: result.cacheKey,
296
+ treeHash: result.treeHash
297
+ });
298
+ }
299
+ } finally {
300
+ rebuilding = false;
301
+ }
302
+ }
303
+
304
+ (async () => {
305
+ const deps = await collectDependencyTree(entryPath);
306
+ watchedFiles = new Set(deps);
307
+
308
+ watcher = chokidar.watch([...watchedFiles], {
309
+ ignoreInitial: true
310
+ });
311
+
312
+ const handleChange = async (file) => {
313
+ await rebuild(file);
314
+ };
315
+
316
+ watcher.on("change", handleChange);
317
+ watcher.on("add", handleChange);
318
+ watcher.on("unlink", handleChange);
319
+ })().catch((err) => {
320
+ console.error("watchTS init error:", err);
321
+ });
322
+
323
+ return {
324
+ close() {
325
+ watcher?.close();
326
+ },
327
+ rebuild
328
+ };
329
+ }
package/src/vite.js ADDED
@@ -0,0 +1,15 @@
1
+ export async function createNitroViteBridge(enabled) {
2
+ if (!enabled) return null;
3
+
4
+ try {
5
+ const vite = await import("vite");
6
+ return await vite.createServer({
7
+ server: {
8
+ middlewareMode: true
9
+ },
10
+ appType: "custom"
11
+ });
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
package/src/watcher.js ADDED
@@ -0,0 +1,63 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ function walkFiles(dir, out = []) {
5
+ if (!fs.existsSync(dir)) return out;
6
+
7
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
8
+
9
+ for (const entry of entries) {
10
+ const fullPath = path.join(dir, entry.name);
11
+
12
+ if (entry.isDirectory()) {
13
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "build") {
14
+ continue;
15
+ }
16
+ walkFiles(fullPath, out);
17
+ } else {
18
+ out.push(fullPath);
19
+ }
20
+ }
21
+
22
+ return out;
23
+ }
24
+
25
+ export function watchProjectFiles({
26
+ roots = [],
27
+ onChange
28
+ }) {
29
+ const watched = new Set();
30
+ const timers = new Map();
31
+
32
+ const files = [];
33
+
34
+ for (const root of roots) {
35
+ files.push(...walkFiles(root));
36
+ }
37
+
38
+ for (const file of files) {
39
+ if (watched.has(file)) continue;
40
+ watched.add(file);
41
+
42
+ fs.watchFile(file, { interval: 500 }, () => {
43
+ clearTimeout(timers.get(file));
44
+
45
+ const timer = setTimeout(() => {
46
+ onChange(file);
47
+ }, 150);
48
+
49
+ timers.set(file, timer);
50
+ });
51
+ }
52
+
53
+ return () => {
54
+ for (const file of watched) {
55
+ fs.unwatchFile(file);
56
+ }
57
+ watched.clear();
58
+ for (const timer of timers.values()) {
59
+ clearTimeout(timer);
60
+ }
61
+ timers.clear();
62
+ };
63
+ }