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/index.cjs CHANGED
@@ -640,6 +640,7 @@ function analyzeProject(project, overrideType, overrideEntry) {
640
640
  hasBareImports: hasBareImports(files),
641
641
  isVite
642
642
  });
643
+ const usesTailwind = usesTailwindCSS(files);
643
644
  return {
644
645
  type,
645
646
  entryPoint: htmlEntry,
@@ -648,7 +649,8 @@ function analyzeProject(project, overrideType, overrideEntry) {
648
649
  hasJSX,
649
650
  hasTypeScript,
650
651
  jsEntryPoint: jsEntry,
651
- isVite
652
+ isVite,
653
+ usesTailwind
652
654
  };
653
655
  }
654
656
  function detectProjectType(input) {
@@ -670,6 +672,17 @@ function hasBareImports(files) {
670
672
  }
671
673
  return false;
672
674
  }
675
+ function usesTailwindCSS(files) {
676
+ for (const [path, content] of files) {
677
+ if (/(?:^|\/)tailwind\.config\.(?:js|cjs|mjs|ts)$/.test(path)) {
678
+ return true;
679
+ }
680
+ if (path.endsWith(".css") && /@tailwind\b|@apply\b/.test(content)) {
681
+ return true;
682
+ }
683
+ }
684
+ return false;
685
+ }
673
686
 
674
687
  // src/core/sandbox.ts
675
688
  function buildErrorHTML(message) {
@@ -1524,6 +1537,18 @@ function getCDNProvider(name) {
1524
1537
  return esmShProvider;
1525
1538
  }
1526
1539
  }
1540
+ function buildCSSURL(specifier, options = {}) {
1541
+ const { dependencies = {}, cdnProvider = "esm.sh" } = options;
1542
+ const clean = specifier.replace(/[?#].*$/, "");
1543
+ const { packageName, subpath } = parseSpecifier(clean);
1544
+ const version = dependencies[packageName];
1545
+ const ver = version ? `@${version}` : "";
1546
+ const sub = subpath ? `/${subpath}` : "";
1547
+ if (cdnProvider === "unpkg") {
1548
+ return `https://unpkg.com/${packageName}${ver}${sub}`;
1549
+ }
1550
+ return `https://esm.sh/${packageName}${ver}${sub}`;
1551
+ }
1527
1552
  function rewriteImports(source, options = {}) {
1528
1553
  const {
1529
1554
  dependencies = {},
@@ -1537,6 +1562,9 @@ function rewriteImports(source, options = {}) {
1537
1562
  if (raw.startsWith(".") || raw.startsWith("/") || /^https?:\/\//.test(raw)) {
1538
1563
  return raw;
1539
1564
  }
1565
+ if (/\.css(?:[?#]|$)/.test(raw)) {
1566
+ return raw;
1567
+ }
1540
1568
  const { packageName, subpath } = parseSpecifier(raw);
1541
1569
  if (NODE_BUILTINS.has(packageName) || NODE_BUILTINS.has(packageName.replace("node:", ""))) {
1542
1570
  throw new NodeBuiltinError(packageName);
@@ -1739,20 +1767,25 @@ var ASSET_IMPORT_EXTENSIONS = {
1739
1767
  ".otf": "font/otf"
1740
1768
  };
1741
1769
  function assembleHTML(options) {
1742
- const { project, config, transformedFiles } = options;
1770
+ const { project, config, transformedFiles, cdnProvider, tailwind = "auto" } = options;
1743
1771
  const files = transformedFiles ?? project.files;
1772
+ let result;
1744
1773
  switch (config.type) {
1745
1774
  case "static":
1746
- return assembleStatic(files, project, config);
1775
+ result = assembleStatic(files, project, config);
1776
+ break;
1747
1777
  case "static-esm":
1748
1778
  case "jsx":
1749
1779
  case "typescript":
1750
1780
  case "jsx-typescript":
1751
1781
  case "vite":
1752
- return assembleESM(files, project, config);
1782
+ result = assembleESM(files, project, config, cdnProvider);
1783
+ break;
1753
1784
  default:
1754
1785
  throw new AssemblyError(`Unsupported project type: ${config.type}`);
1755
1786
  }
1787
+ result.html = applyTailwindRuntime(result.html, project, config, tailwind);
1788
+ return result;
1756
1789
  }
1757
1790
  function assembleStatic(files, project, config) {
1758
1791
  let html = files.get(config.entryPoint);
@@ -1766,10 +1799,13 @@ function assembleStatic(files, project, config) {
1766
1799
  html = inlineAssets(html, project, config.entryPoint);
1767
1800
  return { html, usesESM: false };
1768
1801
  }
1769
- function assembleESM(files, project, config) {
1802
+ function assembleESM(files, project, config, cdnProvider) {
1770
1803
  const isGenerated = config.entryPoint === "__generated__/index.html";
1771
1804
  rewriteAssetImports(files, project);
1772
- const cssFromJS = extractCSSImports(files);
1805
+ const { css: cssFromJS, links: cssLinks } = extractCSSImports(files, {
1806
+ dependencies: config.dependencies,
1807
+ cdnProvider
1808
+ });
1773
1809
  if (config.isVite) {
1774
1810
  injectImportMetaEnv(files);
1775
1811
  }
@@ -1788,6 +1824,9 @@ function assembleESM(files, project, config) {
1788
1824
  html = rewriteScriptSrcsToImportMap(html, config.entryPoint);
1789
1825
  html = inlineAssets(html, project, config.entryPoint);
1790
1826
  }
1827
+ for (const href of cssLinks) {
1828
+ html = injectIntoHead(html, `<link rel="stylesheet" href="${href}">`);
1829
+ }
1791
1830
  if (cssFromJS) {
1792
1831
  html = injectIntoHead(html, `<style>
1793
1832
  ${cssFromJS}
@@ -2037,20 +2076,32 @@ function rewriteAssetImports(files, project) {
2037
2076
  }
2038
2077
  }
2039
2078
  }
2040
- function extractCSSImports(files) {
2079
+ function extractCSSImports(files, options = {}) {
2041
2080
  const cssChunks = [];
2042
- const cssImportRe = /import\s+['"]([^'"]+\.css)['"]\s*;?/g;
2081
+ const links = [];
2082
+ const seenLinks = /* @__PURE__ */ new Set();
2083
+ const cssImportRe = /import\s+['"]([^'"]+\.css(?:[?#][^'"]*)?)['"]\s*;?/g;
2043
2084
  for (const [path, content] of files) {
2044
2085
  if (!/\.(js|ts|jsx|tsx|mjs)$/.test(path)) continue;
2045
2086
  let modified = content;
2046
2087
  let match;
2088
+ cssImportRe.lastIndex = 0;
2047
2089
  while ((match = cssImportRe.exec(content)) !== null) {
2048
- const cssPath = match[1];
2049
- const resolved = resolvePath(directoryOf(path), cssPath);
2050
- const css = files.get(resolved);
2051
- if (css) {
2052
- cssChunks.push(`/* ${resolved} */
2090
+ const specifier = match[1];
2091
+ const bare = specifier.replace(/[?#].*$/, "");
2092
+ if (bare.startsWith(".") || bare.startsWith("/")) {
2093
+ const resolved = resolvePath(directoryOf(path), bare);
2094
+ const css = files.get(resolved);
2095
+ if (css) {
2096
+ cssChunks.push(`/* ${resolved} */
2053
2097
  ${css}`);
2098
+ }
2099
+ } else {
2100
+ const href = /^https?:\/\//.test(bare) ? bare : buildCSSURL(bare, options);
2101
+ if (!seenLinks.has(href)) {
2102
+ seenLinks.add(href);
2103
+ links.push(href);
2104
+ }
2054
2105
  }
2055
2106
  modified = modified.replace(match[0], "");
2056
2107
  }
@@ -2058,7 +2109,95 @@ ${css}`);
2058
2109
  files.set(path, modified);
2059
2110
  }
2060
2111
  }
2061
- return cssChunks.join("\n\n");
2112
+ return { css: cssChunks.join("\n\n"), links };
2113
+ }
2114
+ var TAILWIND_DIRECTIVE_RE = /@tailwind\b|@apply\b/;
2115
+ var TAILWIND_CDN_URL = "https://cdn.tailwindcss.com";
2116
+ function hasTailwindDirectives(css) {
2117
+ return TAILWIND_DIRECTIVE_RE.test(css);
2118
+ }
2119
+ function applyTailwindRuntime(html, project, config, tailwind) {
2120
+ const enabled = tailwind === true || tailwind === "auto" && (config.usesTailwind ?? usesTailwindCSS(project.files));
2121
+ if (!enabled) return html;
2122
+ html = html.replace(
2123
+ /<style>([\s\S]*?)<\/style>/g,
2124
+ (match, css) => hasTailwindDirectives(css) ? `<style type="text/tailwindcss">${css}</style>` : match
2125
+ );
2126
+ let injection = `<script src="${TAILWIND_CDN_URL}"></script>`;
2127
+ const configObject = extractTailwindConfig(project.files);
2128
+ if (configObject) {
2129
+ injection += `
2130
+ <script>tailwind.config = ${configObject};</script>`;
2131
+ }
2132
+ return injectIntoHead(html, injection);
2133
+ }
2134
+ function extractTailwindConfig(files) {
2135
+ let source;
2136
+ for (const [path, content] of files) {
2137
+ if (/(?:^|\/)tailwind\.config\.(?:js|cjs|mjs|ts)$/.test(path)) {
2138
+ source = content;
2139
+ break;
2140
+ }
2141
+ }
2142
+ if (!source) return null;
2143
+ const object = extractBalancedObject(source);
2144
+ if (!object || !isSafeConfigObject(object)) return null;
2145
+ return object;
2146
+ }
2147
+ function extractBalancedObject(source) {
2148
+ const opener = source.match(/(?:export\s+default|module\.exports\s*=)\s*\{/);
2149
+ if (!opener || opener.index === void 0) return null;
2150
+ const start = opener.index + opener[0].length - 1;
2151
+ let depth = 0;
2152
+ let str = null;
2153
+ let lineComment = false;
2154
+ let blockComment = false;
2155
+ for (let i = start; i < source.length; i++) {
2156
+ const c = source[i];
2157
+ const n = source[i + 1];
2158
+ if (lineComment) {
2159
+ if (c === "\n") lineComment = false;
2160
+ continue;
2161
+ }
2162
+ if (blockComment) {
2163
+ if (c === "*" && n === "/") {
2164
+ blockComment = false;
2165
+ i++;
2166
+ }
2167
+ continue;
2168
+ }
2169
+ if (str) {
2170
+ if (c === "\\") {
2171
+ i++;
2172
+ continue;
2173
+ }
2174
+ if (c === str) str = null;
2175
+ continue;
2176
+ }
2177
+ if (c === "/" && n === "/") {
2178
+ lineComment = true;
2179
+ i++;
2180
+ continue;
2181
+ }
2182
+ if (c === "/" && n === "*") {
2183
+ blockComment = true;
2184
+ i++;
2185
+ continue;
2186
+ }
2187
+ if (c === '"' || c === "'" || c === "`") {
2188
+ str = c;
2189
+ continue;
2190
+ }
2191
+ if (c === "{") depth++;
2192
+ else if (c === "}") {
2193
+ depth--;
2194
+ if (depth === 0) return source.slice(start, i + 1);
2195
+ }
2196
+ }
2197
+ return null;
2198
+ }
2199
+ function isSafeConfigObject(object) {
2200
+ 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));
2062
2201
  }
2063
2202
  function injectImportMetaEnv(files) {
2064
2203
  const shim = `if(!import.meta.env){Object.defineProperty(import.meta,'env',{value:{MODE:'production',BASE_URL:'/',PROD:true,DEV:false,SSR:false}});}`;
@@ -2169,7 +2308,9 @@ var Diorama = class {
2169
2308
  const { html: html2, usesESM: usesESM2 } = assembleHTML({
2170
2309
  project,
2171
2310
  config,
2172
- transformedFiles: files
2311
+ transformedFiles: files,
2312
+ cdnProvider: this.options.cdnProvider,
2313
+ tailwind: options.tailwind ?? "auto"
2173
2314
  });
2174
2315
  return { html: html2, usesESM: usesESM2, repoName: repoName2 };
2175
2316
  };