spark-html-bun 0.1.3 → 0.1.4

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.
Files changed (2) hide show
  1. package/package.json +3 -3
  2. package/src/index.js +37 -12
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "spark-html-bun",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Dev server, build, and preview for spark-html apps — built entirely on Bun. Scoped component HMR over a plain WebSocket, import-map dev serving (no bundling in dev), Bun.build for the app shell, and an explicit post-build pipeline (prerender, image, font, manifest, offline, sri). Zero dependencies.",
5
- "homepage": "https://wilkinnovo.github.io/spark",
5
+ "homepage": "https://wilkinnovo.github.io/spark-html",
6
6
  "type": "module",
7
7
  "main": "./src/index.js",
8
8
  "types": "./src/index.d.ts",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "repository": {
26
26
  "type": "git",
27
- "url": "git+https://github.com/wilkinnovo/spark.git"
27
+ "url": "git+https://github.com/wilkinnovo/spark-html.git"
28
28
  },
29
29
  "keywords": [
30
30
  "bun",
package/src/index.js CHANGED
@@ -46,7 +46,7 @@
46
46
  * devRoutes?({ config }) → { '/path': { type, body() } }, // dev serving
47
47
  * transformHtml?(html, { dev }) } // dev page injection
48
48
  */
49
- import { join, resolve, extname, basename, sep } from 'node:path';
49
+ import { join, resolve, dirname, extname, basename, sep } from 'node:path';
50
50
  import { existsSync, watch, readdirSync, statSync, readFileSync } from 'node:fs';
51
51
  import { rm, mkdir, cp, readFile } from 'node:fs/promises';
52
52
 
@@ -200,16 +200,34 @@ function connect() {
200
200
  connect();
201
201
  `;
202
202
 
203
+ // Resolve a bare package to the { dir, entry } of its module entry file, so we
204
+ // can serve the entry AND its sibling files under one /@modules/<pkg>/ prefix.
205
+ // Cached — the resolution doesn't change while the server is up.
206
+ const moduleInfoCache = new Map();
207
+ function moduleEntry(pkg, projectRoot) {
208
+ const key = projectRoot + '\0' + pkg;
209
+ if (moduleInfoCache.has(key)) return moduleInfoCache.get(key);
210
+ let info = null;
211
+ try {
212
+ const file = Bun.resolveSync(pkg, projectRoot);
213
+ info = { dir: dirname(file), entry: file.slice(file.lastIndexOf('/') + 1) };
214
+ } catch { /* unresolvable — leave null */ }
215
+ moduleInfoCache.set(key, info);
216
+ return info;
217
+ }
218
+
203
219
  // Import map for the app's bare specifiers: every dependency in package.json
204
- // maps to /@modules/<name>, which the dev server resolves with Bun's resolver.
205
- // Spark packages are single-file modules whose only bare import is
206
- // 'spark-html' (also in the map), so no rewriting is needed anywhere.
220
+ // maps to /@modules/<name>/<entry-file>. The trailing entry filename matters
221
+ // a package's own relative imports (e.g. spark-html-theme's `./init.js`) resolve
222
+ // against that URL, so they land at /@modules/<name>/init.js and stay inside the
223
+ // package instead of collapsing to /@modules/init.js (a 404 that blanks the app).
207
224
  function buildImportMap(projectRoot) {
208
225
  const imports = {};
209
226
  try {
210
227
  const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8'));
211
228
  for (const dep of Object.keys({ ...pkg.dependencies, ...pkg.devDependencies })) {
212
- imports[dep] = `/@modules/${dep}`;
229
+ const info = moduleEntry(dep, projectRoot);
230
+ if (info) imports[dep] = `/@modules/${dep}/${info.entry}`;
213
231
  }
214
232
  } catch { /* no package.json — no bare imports to map */ }
215
233
  return imports;
@@ -285,15 +303,22 @@ export async function dev(overrides = {}) {
285
303
  return new Response(await route.body(), { headers: { 'Content-Type': route.type } });
286
304
  }
287
305
 
288
- // Bare-specifier modules: /@modules/<name> → Bun-resolved entry file.
306
+ // Bare-specifier modules: /@modules/<name>/<file> → the package's entry
307
+ // (or a sibling file it imports relatively), served from the entry's dir.
289
308
  if (path.startsWith('/@modules/')) {
290
- const spec = path.slice('/@modules/'.length);
291
- try {
292
- const file = Bun.resolveSync(spec, projectRoot);
293
- return new Response(Bun.file(file), { headers: { 'Content-Type': 'text/javascript' } });
294
- } catch {
295
- return new Response(`/* cannot resolve "${spec}" */`, { status: 404, headers: { 'Content-Type': 'text/javascript' } });
309
+ const rest = path.slice('/@modules/'.length);
310
+ const slash = rest.indexOf('/');
311
+ const pkg = slash === -1 ? rest : rest.slice(0, slash);
312
+ const subpath = slash === -1 ? '' : rest.slice(slash + 1);
313
+ const info = moduleEntry(pkg, projectRoot);
314
+ if (info) {
315
+ const file = resolve(info.dir, subpath || info.entry);
316
+ // Guard against escaping the package dir via a crafted subpath.
317
+ if (file.startsWith(info.dir + sep) && isFile(file)) {
318
+ return new Response(Bun.file(file), { headers: { 'Content-Type': 'text/javascript' } });
319
+ }
296
320
  }
321
+ return new Response(`/* cannot resolve "${rest}" */`, { status: 404, headers: { 'Content-Type': 'text/javascript' } });
297
322
  }
298
323
 
299
324
  // Static lookup: project root first (index.html, src/…), then publicDir.