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