@onlook/storybook-plugin 0.3.4 → 0.3.5

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/dist/index.js +223 -32
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import fs4, { existsSync } from 'fs';
2
- import path2, { dirname, join, relative } from 'path';
1
+ import fs5, { existsSync } from 'fs';
2
+ import path5, { dirname, join, relative } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import generateModule from '@babel/generator';
5
5
  import { parse } from '@babel/parser';
@@ -7,6 +7,7 @@ import traverseModule from '@babel/traverse';
7
7
  import * as t from '@babel/types';
8
8
  import crypto from 'crypto';
9
9
  import { chromium } from 'playwright';
10
+ import { createRequire } from 'module';
10
11
  import { readCsf } from 'storybook/internal/csf-tools';
11
12
 
12
13
  // src/storybook-onlook-plugin.ts
@@ -32,7 +33,7 @@ function componentLocPlugin(options = {}) {
32
33
  sourceFilename: filepath
33
34
  });
34
35
  let mutated = false;
35
- const relativePath = path2.relative(root, filepath);
36
+ const relativePath = path5.relative(root, filepath);
36
37
  traverse(ast, {
37
38
  JSXElement(nodePath) {
38
39
  const opening = nodePath.node.openingElement;
@@ -69,9 +70,9 @@ function componentLocPlugin(options = {}) {
69
70
  }
70
71
  };
71
72
  }
72
- var CACHE_DIR = path2.join(process.cwd(), ".storybook-cache");
73
- var SCREENSHOTS_DIR = path2.join(CACHE_DIR, "screenshots");
74
- var MANIFEST_PATH = path2.join(CACHE_DIR, "manifest.json");
73
+ var CACHE_DIR = path5.join(process.cwd(), ".storybook-cache");
74
+ var SCREENSHOTS_DIR = path5.join(CACHE_DIR, "screenshots");
75
+ var MANIFEST_PATH = path5.join(CACHE_DIR, "manifest.json");
75
76
  var VIEWPORT_WIDTH = 1920;
76
77
  var VIEWPORT_HEIGHT = 1080;
77
78
  var MIN_COMPONENT_WIDTH = 420;
@@ -79,30 +80,30 @@ var MIN_COMPONENT_HEIGHT = 280;
79
80
 
80
81
  // src/utils/fileSystem/fileSystem.ts
81
82
  function ensureCacheDirectories() {
82
- if (!fs4.existsSync(CACHE_DIR)) {
83
- fs4.mkdirSync(CACHE_DIR, { recursive: true });
83
+ if (!fs5.existsSync(CACHE_DIR)) {
84
+ fs5.mkdirSync(CACHE_DIR, { recursive: true });
84
85
  }
85
- if (!fs4.existsSync(SCREENSHOTS_DIR)) {
86
- fs4.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
86
+ if (!fs5.existsSync(SCREENSHOTS_DIR)) {
87
+ fs5.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
87
88
  }
88
89
  }
89
90
  function computeFileHash(filePath) {
90
- if (!fs4.existsSync(filePath)) {
91
+ if (!fs5.existsSync(filePath)) {
91
92
  return "";
92
93
  }
93
- const content = fs4.readFileSync(filePath, "utf-8");
94
+ const content = fs5.readFileSync(filePath, "utf-8");
94
95
  return crypto.createHash("sha256").update(content).digest("hex");
95
96
  }
96
97
  function loadManifest() {
97
- if (fs4.existsSync(MANIFEST_PATH)) {
98
- const content = fs4.readFileSync(MANIFEST_PATH, "utf-8");
98
+ if (fs5.existsSync(MANIFEST_PATH)) {
99
+ const content = fs5.readFileSync(MANIFEST_PATH, "utf-8");
99
100
  return JSON.parse(content);
100
101
  }
101
102
  return { stories: {} };
102
103
  }
103
104
  function saveManifest(manifest) {
104
105
  ensureCacheDirectories();
105
- fs4.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
106
+ fs5.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
106
107
  }
107
108
  function updateManifest(storyId, sourcePath, fileHash, boundingBox) {
108
109
  const manifest = loadManifest();
@@ -128,8 +129,8 @@ async function getBrowser() {
128
129
  return browser;
129
130
  }
130
131
  function getScreenshotPath(storyId, theme) {
131
- const storyDir = path2.join(SCREENSHOTS_DIR, storyId);
132
- return path2.join(storyDir, `${theme}.png`);
132
+ const storyDir = path5.join(SCREENSHOTS_DIR, storyId);
133
+ return path5.join(storyDir, `${theme}.png`);
133
134
  }
134
135
  async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
135
136
  const browser2 = await getBrowser();
@@ -216,9 +217,9 @@ async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, h
216
217
  async function generateScreenshot(storyId, theme, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
217
218
  try {
218
219
  ensureCacheDirectories();
219
- const storyDir = path2.join(SCREENSHOTS_DIR, storyId);
220
- if (!fs4.existsSync(storyDir)) {
221
- fs4.mkdirSync(storyDir, { recursive: true });
220
+ const storyDir = path5.join(SCREENSHOTS_DIR, storyId);
221
+ if (!fs5.existsSync(storyDir)) {
222
+ fs5.mkdirSync(storyDir, { recursive: true });
222
223
  }
223
224
  const screenshotPath = getScreenshotPath(storyId, theme);
224
225
  const { buffer, boundingBox } = await captureScreenshotBuffer(
@@ -229,7 +230,7 @@ async function generateScreenshot(storyId, theme, storybookUrl = "http://localho
229
230
  storybookUrl,
230
231
  timeoutMs
231
232
  );
232
- fs4.writeFileSync(screenshotPath, buffer);
233
+ fs5.writeFileSync(screenshotPath, buffer);
233
234
  return { path: screenshotPath, boundingBox };
234
235
  } catch (error) {
235
236
  console.error(`Error generating screenshot for ${storyId} (${theme}):`, error);
@@ -256,7 +257,7 @@ async function fetchStorybookIndex() {
256
257
  }
257
258
  function getStoriesForFile(filePath) {
258
259
  if (!cachedIndex) return [];
259
- const fileName = path2.basename(filePath);
260
+ const fileName = path5.basename(filePath);
260
261
  return Object.values(cachedIndex.entries).filter((entry) => entry.type === "story" && entry.importPath.endsWith(fileName)).map((entry) => entry.id);
261
262
  }
262
263
  async function regenerateScreenshotsForFiles(files) {
@@ -321,6 +322,196 @@ function handleStoryFileChange({ file, modules }) {
321
322
  return modules;
322
323
  }
323
324
  }
325
+ function buildLucideExportMap(barrelSource, barrelRelativeDir) {
326
+ const ast = parse(barrelSource, { sourceType: "module" });
327
+ const map = /* @__PURE__ */ new Map();
328
+ for (const node of ast.program.body) {
329
+ if (node.type !== "ExportNamedDeclaration") continue;
330
+ if (!node.source) continue;
331
+ const rawSource = node.source.value;
332
+ const subpath = path5.posix.join(barrelRelativeDir, rawSource.replace(/^\.\//, ""));
333
+ for (const spec of node.specifiers) {
334
+ if (spec.type !== "ExportSpecifier") continue;
335
+ const exportedName = spec.exported.type === "Identifier" ? spec.exported.name : null;
336
+ if (!exportedName) continue;
337
+ const localName = spec.local.name;
338
+ if (localName === "default") {
339
+ map.set(exportedName, { kind: "default", subpath });
340
+ } else {
341
+ map.set(exportedName, { kind: "named", subpath, name: localName });
342
+ }
343
+ }
344
+ }
345
+ return map;
346
+ }
347
+ function transformLucideImports(code, filepath, exportMap) {
348
+ if (!code.includes("lucide-react")) return null;
349
+ if (!/\bimport\b[^;]*['"]lucide-react['"]/.test(code)) return null;
350
+ const traverse = traverseModule.default ?? traverseModule;
351
+ const generate = generateModule.default ?? generateModule;
352
+ const ast = parse(code, {
353
+ sourceType: "module",
354
+ plugins: ["jsx", "typescript"],
355
+ sourceFilename: filepath
356
+ });
357
+ let mutated = false;
358
+ traverse(ast, {
359
+ ImportDeclaration(nodePath) {
360
+ const node = nodePath.node;
361
+ if (node.source.value !== "lucide-react") return;
362
+ if (node.importKind === "type") return;
363
+ const rewritten = [];
364
+ const preserved = [];
365
+ for (const spec of node.specifiers) {
366
+ if (spec.type !== "ImportSpecifier") {
367
+ preserved.push(spec);
368
+ continue;
369
+ }
370
+ if (spec.importKind === "type") {
371
+ preserved.push(spec);
372
+ continue;
373
+ }
374
+ const importedName = spec.imported.type === "Identifier" ? spec.imported.name : null;
375
+ if (!importedName) {
376
+ preserved.push(spec);
377
+ continue;
378
+ }
379
+ const resolution = exportMap.get(importedName);
380
+ if (!resolution) {
381
+ preserved.push(spec);
382
+ continue;
383
+ }
384
+ const subpathSource = t.stringLiteral(`lucide-react/${resolution.subpath}`);
385
+ if (resolution.kind === "default") {
386
+ rewritten.push(
387
+ t.importDeclaration(
388
+ [t.importDefaultSpecifier(t.identifier(spec.local.name))],
389
+ subpathSource
390
+ )
391
+ );
392
+ } else {
393
+ rewritten.push(
394
+ t.importDeclaration(
395
+ [
396
+ t.importSpecifier(
397
+ t.identifier(spec.local.name),
398
+ t.identifier(resolution.name)
399
+ )
400
+ ],
401
+ subpathSource
402
+ )
403
+ );
404
+ }
405
+ }
406
+ if (rewritten.length === 0) return;
407
+ mutated = true;
408
+ if (preserved.length > 0) {
409
+ const residual = t.importDeclaration(preserved, t.stringLiteral("lucide-react"));
410
+ nodePath.replaceWithMultiple([...rewritten, residual]);
411
+ } else {
412
+ nodePath.replaceWithMultiple(rewritten);
413
+ }
414
+ }
415
+ });
416
+ if (!mutated) return null;
417
+ const output = generate(
418
+ ast,
419
+ { retainLines: true, sourceMaps: true, sourceFileName: filepath },
420
+ code
421
+ );
422
+ return { code: output.code, map: output.map };
423
+ }
424
+ async function resolveLucideBarrel(config) {
425
+ try {
426
+ const resolver = config.createResolver({ asSrc: false });
427
+ const resolved = await resolver("lucide-react", path5.join(config.root, "index.js"));
428
+ if (resolved && fs5.existsSync(resolved)) return resolved;
429
+ } catch {
430
+ }
431
+ try {
432
+ const req = createRequire(path5.join(config.root, "package.json"));
433
+ return req.resolve("lucide-react");
434
+ } catch {
435
+ return null;
436
+ }
437
+ }
438
+ function computeBarrelRelativeDir(barrelPath) {
439
+ let dir = path5.dirname(barrelPath);
440
+ while (dir !== path5.dirname(dir)) {
441
+ const pj = path5.join(dir, "package.json");
442
+ if (fs5.existsSync(pj)) {
443
+ try {
444
+ const pkg = JSON.parse(fs5.readFileSync(pj, "utf-8"));
445
+ if (pkg.name === "lucide-react") {
446
+ const rel = path5.relative(dir, barrelPath).split(path5.sep).join("/");
447
+ return `${path5.posix.dirname(rel)}/`;
448
+ }
449
+ } catch {
450
+ }
451
+ }
452
+ dir = path5.dirname(dir);
453
+ }
454
+ return null;
455
+ }
456
+ var PLUGIN_NAME = "onbook-lucide-barrel";
457
+ function lucideBarrelPlugin() {
458
+ let exportMap = /* @__PURE__ */ new Map();
459
+ let enabled = false;
460
+ let warnedTransformOnce = false;
461
+ return {
462
+ name: PLUGIN_NAME,
463
+ enforce: "pre",
464
+ apply: "serve",
465
+ config() {
466
+ return {
467
+ optimizeDeps: {
468
+ exclude: ["lucide-react"]
469
+ }
470
+ };
471
+ },
472
+ async configResolved(config) {
473
+ const barrelPath = await resolveLucideBarrel(config);
474
+ if (!barrelPath) {
475
+ return;
476
+ }
477
+ const relativeDir = computeBarrelRelativeDir(barrelPath);
478
+ if (!relativeDir) {
479
+ config.logger.warn(
480
+ `[${PLUGIN_NAME}] Could not locate lucide-react package root from ${barrelPath}; plugin is a no-op.`
481
+ );
482
+ return;
483
+ }
484
+ try {
485
+ const source = fs5.readFileSync(barrelPath, "utf-8");
486
+ exportMap = buildLucideExportMap(source, relativeDir);
487
+ enabled = true;
488
+ } catch (e) {
489
+ config.logger.warn(
490
+ `[${PLUGIN_NAME}] Failed to parse lucide-react barrel at ${barrelPath}: ${e instanceof Error ? e.message : String(e)}`
491
+ );
492
+ }
493
+ },
494
+ transform(code, id) {
495
+ if (!enabled) return null;
496
+ const filepath = id.split("?", 1)[0];
497
+ if (!filepath || filepath.includes("node_modules")) return null;
498
+ if (!/\.(tsx?|jsx?)$/.test(filepath)) return null;
499
+ try {
500
+ const result = transformLucideImports(code, filepath, exportMap);
501
+ if (!result) return null;
502
+ return { code: result.code, map: result.map };
503
+ } catch (e) {
504
+ if (!warnedTransformOnce) {
505
+ warnedTransformOnce = true;
506
+ console.warn(
507
+ `[${PLUGIN_NAME}] Transform error for ${id}: ${e instanceof Error ? e.message : String(e)} (further errors suppressed)`
508
+ );
509
+ }
510
+ return null;
511
+ }
512
+ }
513
+ };
514
+ }
324
515
  function findGitRoot(startPath) {
325
516
  let currentPath = startPath;
326
517
  while (currentPath !== dirname(currentPath)) {
@@ -346,7 +537,7 @@ var DEFAULT_ALLOWED_ORIGINS = [
346
537
  var serveMetadataAndScreenshots = (req, res, next) => {
347
538
  if (req.url === "/onbook-index.json") {
348
539
  console.log("[STORYBOOK_PLUGIN] Serving /onbook-index.json endpoint");
349
- const manifestPath = path2.join(process.cwd(), ".storybook-cache", "manifest.json");
540
+ const manifestPath = path5.join(process.cwd(), ".storybook-cache", "manifest.json");
350
541
  const cacheBuster = Date.now();
351
542
  console.log("[STORYBOOK_PLUGIN] Fetching http://localhost:6006/index.json");
352
543
  fetch(`http://localhost:6006/index.json?_t=${cacheBuster}`, {
@@ -363,7 +554,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
363
554
  });
364
555
  return response.json();
365
556
  }).then((indexData) => {
366
- const manifest = fs4.existsSync(manifestPath) ? JSON.parse(fs4.readFileSync(manifestPath, "utf-8")) : { stories: {} };
557
+ const manifest = fs5.existsSync(manifestPath) ? JSON.parse(fs5.readFileSync(manifestPath, "utf-8")) : { stories: {} };
367
558
  const defaultBoundingBox = { width: 1920, height: 1080 };
368
559
  for (const [storyId, entry] of Object.entries(indexData.entries || {})) {
369
560
  const manifestEntry = manifest.stories?.[storyId];
@@ -431,7 +622,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
431
622
  return;
432
623
  }
433
624
  if (req.url?.startsWith("/screenshots/")) {
434
- const screenshotPath = path2.join(
625
+ const screenshotPath = path5.join(
435
626
  process.cwd(),
436
627
  ".storybook-cache",
437
628
  req.url.replace("/screenshots/", "screenshots/")
@@ -440,11 +631,11 @@ var serveMetadataAndScreenshots = (req, res, next) => {
440
631
  const storyId = urlParts[0];
441
632
  const themeFile = urlParts[1];
442
633
  const theme = themeFile?.replace(".png", "");
443
- if (fs4.existsSync(screenshotPath)) {
634
+ if (fs5.existsSync(screenshotPath)) {
444
635
  res.setHeader("Content-Type", "image/png");
445
636
  res.setHeader("Access-Control-Allow-Origin", "*");
446
637
  res.setHeader("Cache-Control", "public, max-age=3600");
447
- fs4.createReadStream(screenshotPath).pipe(res);
638
+ fs5.createReadStream(screenshotPath).pipe(res);
448
639
  return;
449
640
  }
450
641
  if (storyId && theme && (theme === "light" || theme === "dark")) {
@@ -452,16 +643,16 @@ var serveMetadataAndScreenshots = (req, res, next) => {
452
643
  `[STORYBOOK_PLUGIN] Generating screenshot on-demand: ${storyId}/${theme}`
453
644
  );
454
645
  captureScreenshotBuffer(storyId, theme).then(({ buffer }) => {
455
- const storyDir = path2.join(
646
+ const storyDir = path5.join(
456
647
  process.cwd(),
457
648
  ".storybook-cache",
458
649
  "screenshots",
459
650
  storyId
460
651
  );
461
- if (!fs4.existsSync(storyDir)) {
462
- fs4.mkdirSync(storyDir, { recursive: true });
652
+ if (!fs5.existsSync(storyDir)) {
653
+ fs5.mkdirSync(storyDir, { recursive: true });
463
654
  }
464
- fs4.writeFileSync(screenshotPath, buffer);
655
+ fs5.writeFileSync(screenshotPath, buffer);
465
656
  res.setHeader("Content-Type", "image/png");
466
657
  res.setHeader("Access-Control-Allow-Origin", "*");
467
658
  res.setHeader("Cache-Control", "public, max-age=3600");
@@ -537,7 +728,7 @@ function storybookOnlookPlugin(options = {}) {
537
728
  },
538
729
  handleHotUpdate: handleStoryFileChange
539
730
  };
540
- return [componentLocPlugin(), mainPlugin];
731
+ return [lucideBarrelPlugin(), componentLocPlugin(), mainPlugin];
541
732
  }
542
733
  var tolerantCsfIndexer = {
543
734
  test: /(stories|story)\.(m?js|ts)x?$/,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlook/storybook-plugin",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "onlook-storybook": "dist/cli/index.js"
@@ -47,6 +47,7 @@
47
47
  "@types/babel__traverse": "^7.20.6",
48
48
  "@types/node": "^22.15.32",
49
49
  "bun-types": "^1.3.5",
50
+ "storybook": "^10.1.11",
50
51
  "tsup": "^8.5.1",
51
52
  "typescript": "5.8.3",
52
53
  "vite": "^6.3.5"