diorama-js 0.1.0 → 0.2.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/dist/react.cjs CHANGED
@@ -626,6 +626,7 @@ function analyzeProject(project, overrideType, overrideEntry) {
626
626
  hasBareImports: hasBareImports(files),
627
627
  isVite
628
628
  });
629
+ const usesTailwind = usesTailwindCSS(files);
629
630
  return {
630
631
  type,
631
632
  entryPoint: htmlEntry,
@@ -634,7 +635,8 @@ function analyzeProject(project, overrideType, overrideEntry) {
634
635
  hasJSX,
635
636
  hasTypeScript,
636
637
  jsEntryPoint: jsEntry,
637
- isVite
638
+ isVite,
639
+ usesTailwind
638
640
  };
639
641
  }
640
642
  function detectProjectType(input) {
@@ -656,6 +658,17 @@ function hasBareImports(files) {
656
658
  }
657
659
  return false;
658
660
  }
661
+ function usesTailwindCSS(files) {
662
+ for (const [path, content] of files) {
663
+ if (/(?:^|\/)tailwind\.config\.(?:js|cjs|mjs|ts)$/.test(path)) {
664
+ return true;
665
+ }
666
+ if (path.endsWith(".css") && /@tailwind\b|@apply\b/.test(content)) {
667
+ return true;
668
+ }
669
+ }
670
+ return false;
671
+ }
659
672
 
660
673
  // src/core/sandbox.ts
661
674
  function buildErrorHTML(message) {
@@ -1510,6 +1523,18 @@ function getCDNProvider(name) {
1510
1523
  return esmShProvider;
1511
1524
  }
1512
1525
  }
1526
+ function buildCSSURL(specifier, options = {}) {
1527
+ const { dependencies = {}, cdnProvider = "esm.sh" } = options;
1528
+ const clean = specifier.replace(/[?#].*$/, "");
1529
+ const { packageName, subpath } = parseSpecifier(clean);
1530
+ const version = dependencies[packageName];
1531
+ const ver = version ? `@${version}` : "";
1532
+ const sub = subpath ? `/${subpath}` : "";
1533
+ if (cdnProvider === "unpkg") {
1534
+ return `https://unpkg.com/${packageName}${ver}${sub}`;
1535
+ }
1536
+ return `https://esm.sh/${packageName}${ver}${sub}`;
1537
+ }
1513
1538
  function rewriteImports(source, options = {}) {
1514
1539
  const {
1515
1540
  dependencies = {},
@@ -1523,6 +1548,9 @@ function rewriteImports(source, options = {}) {
1523
1548
  if (raw.startsWith(".") || raw.startsWith("/") || /^https?:\/\//.test(raw)) {
1524
1549
  return raw;
1525
1550
  }
1551
+ if (/\.css(?:[?#]|$)/.test(raw)) {
1552
+ return raw;
1553
+ }
1526
1554
  const { packageName, subpath } = parseSpecifier(raw);
1527
1555
  if (NODE_BUILTINS.has(packageName) || NODE_BUILTINS.has(packageName.replace("node:", ""))) {
1528
1556
  throw new NodeBuiltinError(packageName);
@@ -1725,20 +1753,25 @@ var ASSET_IMPORT_EXTENSIONS = {
1725
1753
  ".otf": "font/otf"
1726
1754
  };
1727
1755
  function assembleHTML(options) {
1728
- const { project, config, transformedFiles } = options;
1756
+ const { project, config, transformedFiles, cdnProvider, tailwind = "auto" } = options;
1729
1757
  const files = transformedFiles ?? project.files;
1758
+ let result;
1730
1759
  switch (config.type) {
1731
1760
  case "static":
1732
- return assembleStatic(files, project, config);
1761
+ result = assembleStatic(files, project, config);
1762
+ break;
1733
1763
  case "static-esm":
1734
1764
  case "jsx":
1735
1765
  case "typescript":
1736
1766
  case "jsx-typescript":
1737
1767
  case "vite":
1738
- return assembleESM(files, project, config);
1768
+ result = assembleESM(files, project, config, cdnProvider);
1769
+ break;
1739
1770
  default:
1740
1771
  throw new AssemblyError(`Unsupported project type: ${config.type}`);
1741
1772
  }
1773
+ result.html = applyTailwindRuntime(result.html, project, config, tailwind);
1774
+ return result;
1742
1775
  }
1743
1776
  function assembleStatic(files, project, config) {
1744
1777
  let html = files.get(config.entryPoint);
@@ -1752,10 +1785,13 @@ function assembleStatic(files, project, config) {
1752
1785
  html = inlineAssets(html, project, config.entryPoint);
1753
1786
  return { html, usesESM: false };
1754
1787
  }
1755
- function assembleESM(files, project, config) {
1788
+ function assembleESM(files, project, config, cdnProvider) {
1756
1789
  const isGenerated = config.entryPoint === "__generated__/index.html";
1757
1790
  rewriteAssetImports(files, project);
1758
- const cssFromJS = extractCSSImports(files);
1791
+ const { css: cssFromJS, links: cssLinks } = extractCSSImports(files, {
1792
+ dependencies: config.dependencies,
1793
+ cdnProvider
1794
+ });
1759
1795
  if (config.isVite) {
1760
1796
  injectImportMetaEnv(files);
1761
1797
  }
@@ -1774,6 +1810,9 @@ function assembleESM(files, project, config) {
1774
1810
  html = rewriteScriptSrcsToImportMap(html, config.entryPoint);
1775
1811
  html = inlineAssets(html, project, config.entryPoint);
1776
1812
  }
1813
+ for (const href of cssLinks) {
1814
+ html = injectIntoHead(html, `<link rel="stylesheet" href="${href}">`);
1815
+ }
1777
1816
  if (cssFromJS) {
1778
1817
  html = injectIntoHead(html, `<style>
1779
1818
  ${cssFromJS}
@@ -2023,20 +2062,32 @@ function rewriteAssetImports(files, project) {
2023
2062
  }
2024
2063
  }
2025
2064
  }
2026
- function extractCSSImports(files) {
2065
+ function extractCSSImports(files, options = {}) {
2027
2066
  const cssChunks = [];
2028
- const cssImportRe = /import\s+['"]([^'"]+\.css)['"]\s*;?/g;
2067
+ const links = [];
2068
+ const seenLinks = /* @__PURE__ */ new Set();
2069
+ const cssImportRe = /import\s+['"]([^'"]+\.css(?:[?#][^'"]*)?)['"]\s*;?/g;
2029
2070
  for (const [path, content] of files) {
2030
2071
  if (!/\.(js|ts|jsx|tsx|mjs)$/.test(path)) continue;
2031
2072
  let modified = content;
2032
2073
  let match;
2074
+ cssImportRe.lastIndex = 0;
2033
2075
  while ((match = cssImportRe.exec(content)) !== null) {
2034
- const cssPath = match[1];
2035
- const resolved = resolvePath(directoryOf(path), cssPath);
2036
- const css = files.get(resolved);
2037
- if (css) {
2038
- cssChunks.push(`/* ${resolved} */
2076
+ const specifier = match[1];
2077
+ const bare = specifier.replace(/[?#].*$/, "");
2078
+ if (bare.startsWith(".") || bare.startsWith("/")) {
2079
+ const resolved = resolvePath(directoryOf(path), bare);
2080
+ const css = files.get(resolved);
2081
+ if (css) {
2082
+ cssChunks.push(`/* ${resolved} */
2039
2083
  ${css}`);
2084
+ }
2085
+ } else {
2086
+ const href = /^https?:\/\//.test(bare) ? bare : buildCSSURL(bare, options);
2087
+ if (!seenLinks.has(href)) {
2088
+ seenLinks.add(href);
2089
+ links.push(href);
2090
+ }
2040
2091
  }
2041
2092
  modified = modified.replace(match[0], "");
2042
2093
  }
@@ -2044,7 +2095,95 @@ ${css}`);
2044
2095
  files.set(path, modified);
2045
2096
  }
2046
2097
  }
2047
- return cssChunks.join("\n\n");
2098
+ return { css: cssChunks.join("\n\n"), links };
2099
+ }
2100
+ var TAILWIND_DIRECTIVE_RE = /@tailwind\b|@apply\b/;
2101
+ var TAILWIND_CDN_URL = "https://cdn.tailwindcss.com";
2102
+ function hasTailwindDirectives(css) {
2103
+ return TAILWIND_DIRECTIVE_RE.test(css);
2104
+ }
2105
+ function applyTailwindRuntime(html, project, config, tailwind) {
2106
+ const enabled = tailwind === true || tailwind === "auto" && (config.usesTailwind ?? usesTailwindCSS(project.files));
2107
+ if (!enabled) return html;
2108
+ html = html.replace(
2109
+ /<style>([\s\S]*?)<\/style>/g,
2110
+ (match, css) => hasTailwindDirectives(css) ? `<style type="text/tailwindcss">${css}</style>` : match
2111
+ );
2112
+ let injection = `<script src="${TAILWIND_CDN_URL}"></script>`;
2113
+ const configObject = extractTailwindConfig(project.files);
2114
+ if (configObject) {
2115
+ injection += `
2116
+ <script>tailwind.config = ${configObject};</script>`;
2117
+ }
2118
+ return injectIntoHead(html, injection);
2119
+ }
2120
+ function extractTailwindConfig(files) {
2121
+ let source;
2122
+ for (const [path, content] of files) {
2123
+ if (/(?:^|\/)tailwind\.config\.(?:js|cjs|mjs|ts)$/.test(path)) {
2124
+ source = content;
2125
+ break;
2126
+ }
2127
+ }
2128
+ if (!source) return null;
2129
+ const object = extractBalancedObject(source);
2130
+ if (!object || !isSafeConfigObject(object)) return null;
2131
+ return object;
2132
+ }
2133
+ function extractBalancedObject(source) {
2134
+ const opener = source.match(/(?:export\s+default|module\.exports\s*=)\s*\{/);
2135
+ if (!opener || opener.index === void 0) return null;
2136
+ const start = opener.index + opener[0].length - 1;
2137
+ let depth = 0;
2138
+ let str = null;
2139
+ let lineComment = false;
2140
+ let blockComment = false;
2141
+ for (let i = start; i < source.length; i++) {
2142
+ const c = source[i];
2143
+ const n = source[i + 1];
2144
+ if (lineComment) {
2145
+ if (c === "\n") lineComment = false;
2146
+ continue;
2147
+ }
2148
+ if (blockComment) {
2149
+ if (c === "*" && n === "/") {
2150
+ blockComment = false;
2151
+ i++;
2152
+ }
2153
+ continue;
2154
+ }
2155
+ if (str) {
2156
+ if (c === "\\") {
2157
+ i++;
2158
+ continue;
2159
+ }
2160
+ if (c === str) str = null;
2161
+ continue;
2162
+ }
2163
+ if (c === "/" && n === "/") {
2164
+ lineComment = true;
2165
+ i++;
2166
+ continue;
2167
+ }
2168
+ if (c === "/" && n === "*") {
2169
+ blockComment = true;
2170
+ i++;
2171
+ continue;
2172
+ }
2173
+ if (c === '"' || c === "'" || c === "`") {
2174
+ str = c;
2175
+ continue;
2176
+ }
2177
+ if (c === "{") depth++;
2178
+ else if (c === "}") {
2179
+ depth--;
2180
+ if (depth === 0) return source.slice(start, i + 1);
2181
+ }
2182
+ }
2183
+ return null;
2184
+ }
2185
+ function isSafeConfigObject(object) {
2186
+ return !(/\brequire\s*\(/.test(object) || /\bimport\b/.test(object) || /=>/.test(object) || /\bfunction\b/.test(object) || /`/.test(object) || /\bprocess\b/.test(object) || /\b__dirname\b|\b__filename\b/.test(object));
2048
2187
  }
2049
2188
  function injectImportMetaEnv(files) {
2050
2189
  const shim = `if(!import.meta.env){Object.defineProperty(import.meta,'env',{value:{MODE:'production',BASE_URL:'/',PROD:true,DEV:false,SSR:false}});}`;
@@ -2155,7 +2294,9 @@ var Diorama = class {
2155
2294
  const { html: html2, usesESM: usesESM2 } = assembleHTML({
2156
2295
  project,
2157
2296
  config,
2158
- transformedFiles: files
2297
+ transformedFiles: files,
2298
+ cdnProvider: this.options.cdnProvider,
2299
+ tailwind: options.tailwind ?? "auto"
2159
2300
  });
2160
2301
  return { html: html2, usesESM: usesESM2, repoName: repoName2 };
2161
2302
  };
@@ -2268,6 +2409,7 @@ var DioramaPreview = react.forwardRef(
2268
2409
  height = "500px",
2269
2410
  frame,
2270
2411
  expand,
2412
+ tailwind,
2271
2413
  onLoad,
2272
2414
  onError,
2273
2415
  options,
@@ -2294,6 +2436,7 @@ var DioramaPreview = react.forwardRef(
2294
2436
  height,
2295
2437
  frame,
2296
2438
  expand,
2439
+ tailwind,
2297
2440
  onLoad,
2298
2441
  onError: (err) => {
2299
2442
  onError?.(err);
@@ -2314,7 +2457,7 @@ var DioramaPreview = react.forwardRef(
2314
2457
  instanceRef.current?.destroy();
2315
2458
  instanceRef.current = null;
2316
2459
  };
2317
- }, [repo, branch, subdirectory, loading, placeholder, height, frame, expand]);
2460
+ }, [repo, branch, subdirectory, loading, placeholder, height, frame, expand, tailwind]);
2318
2461
  const containerStyle = {
2319
2462
  width: "100%",
2320
2463
  minHeight: height,