@onlook/storybook-plugin 0.3.5 → 0.3.6

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 +49 -223
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import fs5, { existsSync } from 'fs';
2
- import path5, { dirname, join, relative } from 'path';
1
+ import fs4, { existsSync } from 'fs';
2
+ import path2, { 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,7 +7,6 @@ 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';
11
10
  import { readCsf } from 'storybook/internal/csf-tools';
12
11
 
13
12
  // src/storybook-onlook-plugin.ts
@@ -33,7 +32,7 @@ function componentLocPlugin(options = {}) {
33
32
  sourceFilename: filepath
34
33
  });
35
34
  let mutated = false;
36
- const relativePath = path5.relative(root, filepath);
35
+ const relativePath = path2.relative(root, filepath);
37
36
  traverse(ast, {
38
37
  JSXElement(nodePath) {
39
38
  const opening = nodePath.node.openingElement;
@@ -70,9 +69,9 @@ function componentLocPlugin(options = {}) {
70
69
  }
71
70
  };
72
71
  }
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");
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");
76
75
  var VIEWPORT_WIDTH = 1920;
77
76
  var VIEWPORT_HEIGHT = 1080;
78
77
  var MIN_COMPONENT_WIDTH = 420;
@@ -80,30 +79,30 @@ var MIN_COMPONENT_HEIGHT = 280;
80
79
 
81
80
  // src/utils/fileSystem/fileSystem.ts
82
81
  function ensureCacheDirectories() {
83
- if (!fs5.existsSync(CACHE_DIR)) {
84
- fs5.mkdirSync(CACHE_DIR, { recursive: true });
82
+ if (!fs4.existsSync(CACHE_DIR)) {
83
+ fs4.mkdirSync(CACHE_DIR, { recursive: true });
85
84
  }
86
- if (!fs5.existsSync(SCREENSHOTS_DIR)) {
87
- fs5.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
85
+ if (!fs4.existsSync(SCREENSHOTS_DIR)) {
86
+ fs4.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
88
87
  }
89
88
  }
90
89
  function computeFileHash(filePath) {
91
- if (!fs5.existsSync(filePath)) {
90
+ if (!fs4.existsSync(filePath)) {
92
91
  return "";
93
92
  }
94
- const content = fs5.readFileSync(filePath, "utf-8");
93
+ const content = fs4.readFileSync(filePath, "utf-8");
95
94
  return crypto.createHash("sha256").update(content).digest("hex");
96
95
  }
97
96
  function loadManifest() {
98
- if (fs5.existsSync(MANIFEST_PATH)) {
99
- const content = fs5.readFileSync(MANIFEST_PATH, "utf-8");
97
+ if (fs4.existsSync(MANIFEST_PATH)) {
98
+ const content = fs4.readFileSync(MANIFEST_PATH, "utf-8");
100
99
  return JSON.parse(content);
101
100
  }
102
101
  return { stories: {} };
103
102
  }
104
103
  function saveManifest(manifest) {
105
104
  ensureCacheDirectories();
106
- fs5.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
105
+ fs4.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
107
106
  }
108
107
  function updateManifest(storyId, sourcePath, fileHash, boundingBox) {
109
108
  const manifest = loadManifest();
@@ -129,8 +128,8 @@ async function getBrowser() {
129
128
  return browser;
130
129
  }
131
130
  function getScreenshotPath(storyId, theme) {
132
- const storyDir = path5.join(SCREENSHOTS_DIR, storyId);
133
- return path5.join(storyDir, `${theme}.png`);
131
+ const storyDir = path2.join(SCREENSHOTS_DIR, storyId);
132
+ return path2.join(storyDir, `${theme}.png`);
134
133
  }
135
134
  async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
136
135
  const browser2 = await getBrowser();
@@ -217,9 +216,9 @@ async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, h
217
216
  async function generateScreenshot(storyId, theme, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
218
217
  try {
219
218
  ensureCacheDirectories();
220
- const storyDir = path5.join(SCREENSHOTS_DIR, storyId);
221
- if (!fs5.existsSync(storyDir)) {
222
- fs5.mkdirSync(storyDir, { recursive: true });
219
+ const storyDir = path2.join(SCREENSHOTS_DIR, storyId);
220
+ if (!fs4.existsSync(storyDir)) {
221
+ fs4.mkdirSync(storyDir, { recursive: true });
223
222
  }
224
223
  const screenshotPath = getScreenshotPath(storyId, theme);
225
224
  const { buffer, boundingBox } = await captureScreenshotBuffer(
@@ -230,7 +229,7 @@ async function generateScreenshot(storyId, theme, storybookUrl = "http://localho
230
229
  storybookUrl,
231
230
  timeoutMs
232
231
  );
233
- fs5.writeFileSync(screenshotPath, buffer);
232
+ fs4.writeFileSync(screenshotPath, buffer);
234
233
  return { path: screenshotPath, boundingBox };
235
234
  } catch (error) {
236
235
  console.error(`Error generating screenshot for ${storyId} (${theme}):`, error);
@@ -257,7 +256,7 @@ async function fetchStorybookIndex() {
257
256
  }
258
257
  function getStoriesForFile(filePath) {
259
258
  if (!cachedIndex) return [];
260
- const fileName = path5.basename(filePath);
259
+ const fileName = path2.basename(filePath);
261
260
  return Object.values(cachedIndex.entries).filter((entry) => entry.type === "story" && entry.importPath.endsWith(fileName)).map((entry) => entry.id);
262
261
  }
263
262
  async function regenerateScreenshotsForFiles(files) {
@@ -322,196 +321,6 @@ function handleStoryFileChange({ file, modules }) {
322
321
  return modules;
323
322
  }
324
323
  }
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
- }
515
324
  function findGitRoot(startPath) {
516
325
  let currentPath = startPath;
517
326
  while (currentPath !== dirname(currentPath)) {
@@ -537,7 +346,7 @@ var DEFAULT_ALLOWED_ORIGINS = [
537
346
  var serveMetadataAndScreenshots = (req, res, next) => {
538
347
  if (req.url === "/onbook-index.json") {
539
348
  console.log("[STORYBOOK_PLUGIN] Serving /onbook-index.json endpoint");
540
- const manifestPath = path5.join(process.cwd(), ".storybook-cache", "manifest.json");
349
+ const manifestPath = path2.join(process.cwd(), ".storybook-cache", "manifest.json");
541
350
  const cacheBuster = Date.now();
542
351
  console.log("[STORYBOOK_PLUGIN] Fetching http://localhost:6006/index.json");
543
352
  fetch(`http://localhost:6006/index.json?_t=${cacheBuster}`, {
@@ -554,7 +363,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
554
363
  });
555
364
  return response.json();
556
365
  }).then((indexData) => {
557
- const manifest = fs5.existsSync(manifestPath) ? JSON.parse(fs5.readFileSync(manifestPath, "utf-8")) : { stories: {} };
366
+ const manifest = fs4.existsSync(manifestPath) ? JSON.parse(fs4.readFileSync(manifestPath, "utf-8")) : { stories: {} };
558
367
  const defaultBoundingBox = { width: 1920, height: 1080 };
559
368
  for (const [storyId, entry] of Object.entries(indexData.entries || {})) {
560
369
  const manifestEntry = manifest.stories?.[storyId];
@@ -622,7 +431,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
622
431
  return;
623
432
  }
624
433
  if (req.url?.startsWith("/screenshots/")) {
625
- const screenshotPath = path5.join(
434
+ const screenshotPath = path2.join(
626
435
  process.cwd(),
627
436
  ".storybook-cache",
628
437
  req.url.replace("/screenshots/", "screenshots/")
@@ -631,11 +440,11 @@ var serveMetadataAndScreenshots = (req, res, next) => {
631
440
  const storyId = urlParts[0];
632
441
  const themeFile = urlParts[1];
633
442
  const theme = themeFile?.replace(".png", "");
634
- if (fs5.existsSync(screenshotPath)) {
443
+ if (fs4.existsSync(screenshotPath)) {
635
444
  res.setHeader("Content-Type", "image/png");
636
445
  res.setHeader("Access-Control-Allow-Origin", "*");
637
446
  res.setHeader("Cache-Control", "public, max-age=3600");
638
- fs5.createReadStream(screenshotPath).pipe(res);
447
+ fs4.createReadStream(screenshotPath).pipe(res);
639
448
  return;
640
449
  }
641
450
  if (storyId && theme && (theme === "light" || theme === "dark")) {
@@ -643,16 +452,16 @@ var serveMetadataAndScreenshots = (req, res, next) => {
643
452
  `[STORYBOOK_PLUGIN] Generating screenshot on-demand: ${storyId}/${theme}`
644
453
  );
645
454
  captureScreenshotBuffer(storyId, theme).then(({ buffer }) => {
646
- const storyDir = path5.join(
455
+ const storyDir = path2.join(
647
456
  process.cwd(),
648
457
  ".storybook-cache",
649
458
  "screenshots",
650
459
  storyId
651
460
  );
652
- if (!fs5.existsSync(storyDir)) {
653
- fs5.mkdirSync(storyDir, { recursive: true });
461
+ if (!fs4.existsSync(storyDir)) {
462
+ fs4.mkdirSync(storyDir, { recursive: true });
654
463
  }
655
- fs5.writeFileSync(screenshotPath, buffer);
464
+ fs4.writeFileSync(screenshotPath, buffer);
656
465
  res.setHeader("Content-Type", "image/png");
657
466
  res.setHeader("Access-Control-Allow-Origin", "*");
658
467
  res.setHeader("Cache-Control", "public, max-age=3600");
@@ -709,6 +518,23 @@ function storybookOnlookPlugin(options = {}) {
709
518
  cors: {
710
519
  origin: allowedOrigins
711
520
  }
521
+ },
522
+ optimizeDeps: {
523
+ // Collapse lucide-react's icon graph into a single prebundled chunk.
524
+ // Without these two directives, repos that import both `lucide-react`
525
+ // AND `lucide-react/dynamic` become two co-equal esbuild scan entries
526
+ // sharing ~5,800 icon modules — esbuild's code-splitting then shatters
527
+ // the shared graph per-icon, producing ~1,770 HTTP requests per iframe
528
+ // and tripping Chrome's URLLoader scheduler on canvases with multiple
529
+ // frames (net::ERR_INSUFFICIENT_RESOURCES / "Failed to fetch
530
+ // dynamically imported module"). Parent: ONL-832. Fix: ONL-875.
531
+ //
532
+ // `include: ['lucide-react']` pins it as a single scan entry that
533
+ // esbuild bundles into one chunk. `exclude: ['lucide-react/dynamic']`
534
+ // keeps the DynamicIcon pathway out of prebundling so it can't become
535
+ // a second shared-graph entry.
536
+ include: ["lucide-react"],
537
+ exclude: ["lucide-react/dynamic"]
712
538
  }
713
539
  };
714
540
  },
@@ -728,7 +554,7 @@ function storybookOnlookPlugin(options = {}) {
728
554
  },
729
555
  handleHotUpdate: handleStoryFileChange
730
556
  };
731
- return [lucideBarrelPlugin(), componentLocPlugin(), mainPlugin];
557
+ return [componentLocPlugin(), mainPlugin];
732
558
  }
733
559
  var tolerantCsfIndexer = {
734
560
  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.5",
3
+ "version": "0.3.6",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "onlook-storybook": "dist/cli/index.js"