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