@ethisyscore/vite-plugin 1.59.0 → 1.62.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.js CHANGED
@@ -605,6 +605,327 @@ function titleCase(s) {
605
605
  function escapeRegExp(s) {
606
606
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
607
607
  }
608
+
609
+ // src/platform-react/scope-plugin-css.ts
610
+ var EHX_PLUGIN_CSS_SCOPE = ":where(.ehx-plugin-root, [data-ehx-plugin-portal])";
611
+ var EHX_PLUGIN_ROOT_SCOPE = ":is(.ehx-plugin-root, [data-ehx-plugin-portal])";
612
+ var EHX_SCOPED_CSS_LAYERS = ["utilities", "properties"];
613
+ var VERBATIM_AT_RULES = /* @__PURE__ */ new Set([
614
+ "keyframes",
615
+ "-webkit-keyframes",
616
+ "property",
617
+ "font-face",
618
+ "font-feature-values",
619
+ "font-palette-values",
620
+ "counter-style",
621
+ "page",
622
+ "viewport"
623
+ ]);
624
+ var CONDITIONAL_AT_RULES = /* @__PURE__ */ new Set([
625
+ "media",
626
+ "supports",
627
+ "container",
628
+ "layer",
629
+ "scope",
630
+ "starting-style",
631
+ "document",
632
+ "-moz-document"
633
+ ]);
634
+ var EHX_PLUGIN_SCOPE_ROOTS = [
635
+ /\.ehx-plugin-root\b/,
636
+ /\.ethisys-[a-z0-9-]+-root\b/,
637
+ /\[data-ehx-plugin-portal\]/
638
+ ];
639
+ var DOCUMENT_ROOT_PREFIX = /^:(?:root|host)\b/;
640
+ function isSpace(ch) {
641
+ return ch === " " || ch === "\n" || ch === " " || ch === "\r" || ch === "\f";
642
+ }
643
+ function skipString(css, i) {
644
+ const quote = css[i];
645
+ let j = i + 1;
646
+ while (j < css.length) {
647
+ const ch = css[j];
648
+ if (ch === "\\") {
649
+ j += 2;
650
+ continue;
651
+ }
652
+ if (ch === quote) return j + 1;
653
+ j++;
654
+ }
655
+ return j;
656
+ }
657
+ function skipComment(css, i) {
658
+ const end = css.indexOf("*/", i + 2);
659
+ return end === -1 ? css.length : end + 2;
660
+ }
661
+ function readPrelude(css, i) {
662
+ let j = i;
663
+ let depth = 0;
664
+ while (j < css.length) {
665
+ const ch = css[j];
666
+ if (ch === "\\") {
667
+ j += 2;
668
+ continue;
669
+ }
670
+ if (ch === '"' || ch === "'") {
671
+ j = skipString(css, j);
672
+ continue;
673
+ }
674
+ if (ch === "/" && css[j + 1] === "*") {
675
+ j = skipComment(css, j);
676
+ continue;
677
+ }
678
+ if (ch === "(" || ch === "[") {
679
+ depth++;
680
+ } else if (ch === ")" || ch === "]") {
681
+ if (depth > 0) depth--;
682
+ } else if (depth === 0) {
683
+ if (ch === "{") return { end: j, terminator: "{" };
684
+ if (ch === ";") return { end: j + 1, terminator: ";" };
685
+ if (ch === "}") return { end: j, terminator: "" };
686
+ }
687
+ j++;
688
+ }
689
+ return { end: css.length, terminator: "" };
690
+ }
691
+ function matchBrace(css, open) {
692
+ let j = open + 1;
693
+ let depth = 1;
694
+ while (j < css.length) {
695
+ const ch = css[j];
696
+ if (ch === "\\") {
697
+ j += 2;
698
+ continue;
699
+ }
700
+ if (ch === '"' || ch === "'") {
701
+ j = skipString(css, j);
702
+ continue;
703
+ }
704
+ if (ch === "/" && css[j + 1] === "*") {
705
+ j = skipComment(css, j);
706
+ continue;
707
+ }
708
+ if (ch === "{") depth++;
709
+ else if (ch === "}") {
710
+ depth--;
711
+ if (depth === 0) return j + 1;
712
+ }
713
+ j++;
714
+ }
715
+ return css.length;
716
+ }
717
+ function splitTopLevel(text) {
718
+ const parts = [];
719
+ let depth = 0;
720
+ let start = 0;
721
+ let j = 0;
722
+ while (j < text.length) {
723
+ const ch = text[j];
724
+ if (ch === "\\") {
725
+ j += 2;
726
+ continue;
727
+ }
728
+ if (ch === '"' || ch === "'") {
729
+ j = skipString(text, j);
730
+ continue;
731
+ }
732
+ if (ch === "/" && text[j + 1] === "*") {
733
+ j = skipComment(text, j);
734
+ continue;
735
+ }
736
+ if (ch === "(" || ch === "[") depth++;
737
+ else if (ch === ")" || ch === "]") {
738
+ if (depth > 0) depth--;
739
+ } else if (ch === "," && depth === 0) {
740
+ parts.push(text.slice(start, j));
741
+ start = j + 1;
742
+ }
743
+ j++;
744
+ }
745
+ parts.push(text.slice(start));
746
+ return parts;
747
+ }
748
+ function atRuleName(prelude) {
749
+ const m = /^@([\w-]*)/.exec(prelude.trim());
750
+ return (m?.[1] ?? "").toLowerCase();
751
+ }
752
+ function layerNames(prelude) {
753
+ const body = prelude.trim().replace(/^@layer\s*/i, "");
754
+ return splitTopLevel(body).map((n) => n.trim()).filter(Boolean);
755
+ }
756
+ function rewriteSelector(selector, ctx, prefixing) {
757
+ if (DOCUMENT_ROOT_PREFIX.test(selector)) {
758
+ return selector.replace(DOCUMENT_ROOT_PREFIX, ctx.rootScope);
759
+ }
760
+ if (!prefixing) return selector;
761
+ if (selector.startsWith(ctx.scope) || selector.startsWith(ctx.rootScope)) return selector;
762
+ return `${ctx.scope} ${selector}`;
763
+ }
764
+ function rewriteSelectorList(selectors, ctx, prefixing) {
765
+ const seen = /* @__PURE__ */ new Set();
766
+ let changed = false;
767
+ for (const part of splitTopLevel(selectors)) {
768
+ const trimmed = part.trim();
769
+ if (trimmed.length === 0) continue;
770
+ const rewritten = rewriteSelector(trimmed, ctx, prefixing);
771
+ if (rewritten !== trimmed) changed = true;
772
+ seen.add(rewritten);
773
+ }
774
+ return changed ? [...seen].join(",\n") : selectors;
775
+ }
776
+ function walk(css, scoping, ctx) {
777
+ let out = "";
778
+ let i = 0;
779
+ while (i < css.length) {
780
+ const ch = css[i];
781
+ if (isSpace(ch)) {
782
+ out += ch;
783
+ i++;
784
+ continue;
785
+ }
786
+ if (ch === "/" && css[i + 1] === "*") {
787
+ const end2 = skipComment(css, i);
788
+ out += css.slice(i, end2);
789
+ i = end2;
790
+ continue;
791
+ }
792
+ if (ch === "}") {
793
+ out += ch;
794
+ i++;
795
+ continue;
796
+ }
797
+ const { end, terminator } = readPrelude(css, i);
798
+ if (terminator !== "{") {
799
+ out += css.slice(i, end);
800
+ i = end;
801
+ continue;
802
+ }
803
+ const raw = css.slice(i, end);
804
+ const blockEnd = matchBrace(css, end);
805
+ const inner = css.slice(end + 1, blockEnd - 1);
806
+ if (raw.trimStart().startsWith("@")) {
807
+ const name = atRuleName(raw);
808
+ if (VERBATIM_AT_RULES.has(name) || !CONDITIONAL_AT_RULES.has(name)) {
809
+ out += css.slice(i, blockEnd);
810
+ } else {
811
+ const nextScoping = name === "layer" ? scoping || layerNames(raw).some((n) => ctx.layers.has(n)) : scoping;
812
+ out += `${raw}{${walk(inner, nextScoping, ctx)}}`;
813
+ }
814
+ } else {
815
+ const trailing = /\s*$/.exec(raw)?.[0] ?? "";
816
+ const selectors = raw.slice(0, raw.length - trailing.length);
817
+ const rewritten = rewriteSelectorList(selectors, ctx, scoping);
818
+ out += rewritten === selectors ? css.slice(i, blockEnd) : `${rewritten}${trailing}{${inner}}`;
819
+ }
820
+ i = blockEnd;
821
+ }
822
+ return out;
823
+ }
824
+ function scopePluginCss(css, options = {}) {
825
+ const ctx = {
826
+ scope: options.scope ?? EHX_PLUGIN_CSS_SCOPE,
827
+ rootScope: options.rootScope ?? EHX_PLUGIN_ROOT_SCOPE,
828
+ layers: new Set(options.layers ?? EHX_SCOPED_CSS_LAYERS)
829
+ };
830
+ return walk(css, false, ctx);
831
+ }
832
+ function findUnscopedSelectors(css, options = {}) {
833
+ const roots = options.scopeRoots ?? EHX_PLUGIN_SCOPE_ROOTS;
834
+ const found = [];
835
+ collectSelectors(css, (selector) => {
836
+ if (!roots.some((re) => re.test(selector))) found.push(selector);
837
+ });
838
+ return found;
839
+ }
840
+ function collectSelectors(css, visit) {
841
+ let i = 0;
842
+ while (i < css.length) {
843
+ const ch = css[i];
844
+ if (isSpace(ch) || ch === "}") {
845
+ i++;
846
+ continue;
847
+ }
848
+ if (ch === "/" && css[i + 1] === "*") {
849
+ i = skipComment(css, i);
850
+ continue;
851
+ }
852
+ const { end, terminator } = readPrelude(css, i);
853
+ if (terminator !== "{") {
854
+ i = end;
855
+ continue;
856
+ }
857
+ const raw = css.slice(i, end).trim();
858
+ const blockEnd = matchBrace(css, end);
859
+ if (raw.startsWith("@")) {
860
+ const name = atRuleName(raw);
861
+ if (CONDITIONAL_AT_RULES.has(name) && !VERBATIM_AT_RULES.has(name)) {
862
+ collectSelectors(css.slice(end + 1, blockEnd - 1), visit);
863
+ }
864
+ } else {
865
+ for (const part of splitTopLevel(raw)) {
866
+ const selector = part.trim();
867
+ if (selector.length > 0 && !selector.startsWith("&")) visit(selector);
868
+ }
869
+ }
870
+ i = blockEnd;
871
+ }
872
+ }
873
+ var MAX_REPORTED_LEAKS = 12;
874
+ function formatUnscopedSelectorError(id, leaks) {
875
+ const shown = leaks.slice(0, MAX_REPORTED_LEAKS);
876
+ const more = leaks.length - shown.length;
877
+ return [
878
+ `${leaks.length} selector(s) in ${id} would leak into the host document.`,
879
+ "",
880
+ "A plugin's compiled CSS is injected into the HOST <head>, so every selector must",
881
+ "be scoped to a plugin root (`.ehx-plugin-root`, `.ethisys-<name>-root`) or the",
882
+ "portal marker `[data-ehx-plugin-portal]`. Unscoped selectors restyle host chrome.",
883
+ "",
884
+ ...shown.map((s) => ` ${s}`),
885
+ ...more > 0 ? [` \u2026 and ${more} more`] : [],
886
+ "",
887
+ 'Most common cause: `@import "tailwindcss"` re-emits Tailwind\'s global preflight.',
888
+ "Use the split import so the host's own preflight governs shared resets:",
889
+ ' @import "@ethisyscore/plugin-ui/plugin-base.css";',
890
+ ' @import "tailwindcss/theme.css" layer(theme) source(none);',
891
+ ' @import "tailwindcss/utilities.css" layer(utilities) source(none);'
892
+ ].join("\n");
893
+ }
894
+ function isScopableCssId(id) {
895
+ const qIndex = id.indexOf("?");
896
+ const path = qIndex === -1 ? id : id.slice(0, qIndex);
897
+ const query = qIndex === -1 ? "" : id.slice(qIndex + 1);
898
+ if (!path.endsWith(".css")) return false;
899
+ if (path.endsWith(".module.css")) return false;
900
+ if (/(?:^|&)(?:raw|url)(?:=|&|$)/.test(query)) return false;
901
+ return true;
902
+ }
903
+ function scopePluginCssPlugin(options = {}) {
904
+ return {
905
+ name: "ethisys-scope-plugin-css",
906
+ transform(code, id) {
907
+ if (!isScopableCssId(id)) return null;
908
+ const scoped = scopePluginCss(code, options);
909
+ const guard = options.guard ?? "error";
910
+ if (guard !== "off") {
911
+ const leaks = findUnscopedSelectors(scoped, options);
912
+ if (leaks.length > 0) {
913
+ const message = formatUnscopedSelectorError(id, leaks);
914
+ const ctx = this;
915
+ if (guard === "error") {
916
+ if (ctx?.error) ctx.error(message);
917
+ throw new Error(message);
918
+ }
919
+ if (ctx?.warn) ctx.warn(message);
920
+ else console.warn(message);
921
+ }
922
+ }
923
+ return scoped === code ? null : { code: scoped, map: null };
924
+ }
925
+ };
926
+ }
927
+
928
+ // src/platform-react/build-pages.ts
608
929
  var PLATFORM_REACT_EXTERNALS = [
609
930
  "react",
610
931
  "react/jsx-runtime",
@@ -703,7 +1024,16 @@ function makeDefaultBuild(viteBuild) {
703
1024
  await build({
704
1025
  root: ctx.root,
705
1026
  configFile: false,
706
- plugins: [ctx.surfacePlugin, ...ctx.plugins, rewriteAliasedExternalImports()],
1027
+ // `scopePluginCssPlugin` must sit AFTER the plugin's own `tailwindcss()` in
1028
+ // effect, which it does by enforce ordering, not array position: Tailwind is
1029
+ // `enforce: "pre"`, this one declares none. See its doc for why "none" is the
1030
+ // only position that sees compiled CSS.
1031
+ plugins: [
1032
+ ctx.surfacePlugin,
1033
+ ...ctx.plugins,
1034
+ scopePluginCssPlugin(),
1035
+ rewriteAliasedExternalImports()
1036
+ ],
707
1037
  resolve: {
708
1038
  dedupe: [...ctx.dedupe],
709
1039
  alias: ctx.alias
@@ -847,6 +1177,9 @@ function assertHostOriginRelativePath2(value, label) {
847
1177
  }
848
1178
  }
849
1179
  function ethisysPlatformReactPlugin(options = {}) {
1180
+ return [ethisysPlatformReactPagesPlugin(options), scopePluginCssPlugin()];
1181
+ }
1182
+ function ethisysPlatformReactPagesPlugin(options = {}) {
850
1183
  const explicitRoot = options.root;
851
1184
  const manifestRel = options.manifestPath ?? "feature.manifest.json";
852
1185
  const outputPrefix = (options.outputPrefix ?? "platform-react/").replace(/\/+$/, "/").replace(/^\/+/, "");
@@ -969,7 +1302,7 @@ function ethisysPlatformReactPlugin(options = {}) {
969
1302
  }
970
1303
  }
971
1304
  let isBuild = false;
972
- return {
1305
+ const plugin = {
973
1306
  name: "ethisys-platform-react",
974
1307
  // `pre` ordering: validation should fire before user-supplied plugins.
975
1308
  enforce: "pre",
@@ -1125,6 +1458,7 @@ function ethisysPlatformReactPlugin(options = {}) {
1125
1458
  });
1126
1459
  }
1127
1460
  };
1461
+ return plugin;
1128
1462
  }
1129
1463
  var ID_REGEX2 = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/i;
1130
1464
  function assertHostOriginRelativePath3(value, label) {
@@ -1254,12 +1588,12 @@ function computePlatformReactPages(input) {
1254
1588
  }
1255
1589
  return { entries, missingViews };
1256
1590
  }
1257
- function walk(dir) {
1591
+ function walk2(dir) {
1258
1592
  const out = [];
1259
1593
  for (const n of readdirSync(dir)) {
1260
1594
  const p = resolve(dir, n);
1261
1595
  if (statSync(p).isDirectory()) {
1262
- out.push(...walk(p));
1596
+ out.push(...walk2(p));
1263
1597
  } else {
1264
1598
  out.push(p);
1265
1599
  }
@@ -1280,7 +1614,7 @@ function generatePlatformReactManifest(config) {
1280
1614
  const exclude = new Set(config.paths?.pageExclude ?? []);
1281
1615
  const viewFiles = {};
1282
1616
  if (existsSync(adapterDir)) {
1283
- for (const f of walk(adapterDir)) {
1617
+ for (const f of walk2(adapterDir)) {
1284
1618
  if (f.endsWith(".tsx")) {
1285
1619
  viewFiles[basename(f, ".tsx")] = slash2(relative(root, f));
1286
1620
  }
@@ -1701,6 +2035,6 @@ function ethisysManifestPlugin(options = {}) {
1701
2035
  };
1702
2036
  }
1703
2037
 
1704
- export { CONTRACT_B_IMPORT_MAP_ALLOWLIST, CONTRACT_B_RUNTIME_IMPORTS, CONTRACT_B_SEMANTIC_PRIMITIVES, PLATFORM_REACT_DEDUPE, PLATFORM_REACT_EXTERNALS, buildIframeSandboxPages, buildPlatformReactPages, computePlatformReactPages, ethisysContractAPlugin, ethisysContractBPlugin, ethisysIframeSandboxPlugin, ethisysManifestPlugin, ethisysPlatformReactPlugin, generatePlatformReactManifest, parsePlatformReactPages, rewriteAliasedExternalImports, validateDeclarativeResource, validateReactiveRule, virtualPageEntry };
2038
+ export { CONTRACT_B_IMPORT_MAP_ALLOWLIST, CONTRACT_B_RUNTIME_IMPORTS, CONTRACT_B_SEMANTIC_PRIMITIVES, EHX_PLUGIN_CSS_SCOPE, EHX_PLUGIN_ROOT_SCOPE, EHX_PLUGIN_SCOPE_ROOTS, EHX_SCOPED_CSS_LAYERS, PLATFORM_REACT_DEDUPE, PLATFORM_REACT_EXTERNALS, buildIframeSandboxPages, buildPlatformReactPages, computePlatformReactPages, ethisysContractAPlugin, ethisysContractBPlugin, ethisysIframeSandboxPlugin, ethisysManifestPlugin, ethisysPlatformReactPagesPlugin, ethisysPlatformReactPlugin, findUnscopedSelectors, formatUnscopedSelectorError, generatePlatformReactManifest, isScopableCssId, parsePlatformReactPages, rewriteAliasedExternalImports, scopePluginCss, scopePluginCssPlugin, validateDeclarativeResource, validateReactiveRule, virtualPageEntry };
1705
2039
  //# sourceMappingURL=index.js.map
1706
2040
  //# sourceMappingURL=index.js.map