@edynamix/mf-isolation 0.0.31 → 0.0.33

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.
@@ -5,12 +5,15 @@
5
5
  // followModuleRootFontSize() rewrites every rem length in the module's shadow root to read it.
6
6
  export const MF_ROOT_FONT_SIZE_PROPERTY = '--edyn-mf-rem';
7
7
  const ROOT_FONT_SIZE_FALLBACK = `var(${MF_ROOT_FONT_SIZE_PROPERTY}, 1rem)`;
8
- const ROOT_FONT_SIZE_DECLARATION_PATTERN = new RegExp(`${MF_ROOT_FONT_SIZE_PROPERTY}\\s*:`);
9
- const REM_LENGTH_HINT_PATTERN = /\drem/i;
8
+ export const ROOT_FONT_SIZE_DECLARATION_PATTERN = new RegExp(`${MF_ROOT_FONT_SIZE_PROPERTY}\\s*:`);
9
+ export const REM_LENGTH_HINT_PATTERN = /\drem/i;
10
10
  // A number with the rem unit that is not part of an identifier (`foo-2rem`) and not the fallback this rewrite writes.
11
11
  const REM_LENGTH_PATTERN = new RegExp(`(?<![\\w.-]|var\\(${MF_ROOT_FONT_SIZE_PROPERTY},\\s*)(-?(?:\\d+\\.?\\d*|\\.\\d+))rem(?![\\w-])`, 'gi');
12
12
  // Strings and url() keep their text: `content: "2rem"` or `url(2rem.png)` are not lengths.
13
13
  const VERBATIM_PATTERN = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|url\((?:[^()"'\\]|\\.)*\)/gi;
14
+ // The same match without the global flag, as a quick test: it skips the fallback this rewrite writes, so a followed
15
+ // value does not look like work again.
16
+ export const UNFOLLOWED_REM_PATTERN = new RegExp(REM_LENGTH_PATTERN.source, 'i');
14
17
  const rewriteOutsideVerbatim = (text) => {
15
18
  return text.replace(REM_LENGTH_PATTERN, (_match, amount) => `calc(${amount} * ${ROOT_FONT_SIZE_FALLBACK})`);
16
19
  };
@@ -24,123 +27,4 @@ export const rewriteRemLengths = (declarations) => {
24
27
  }
25
28
  return rewritten + rewriteOutsideVerbatim(declarations.slice(start));
26
29
  };
27
- const isFontFaceRule = (rule) => {
28
- return typeof CSSFontFaceRule !== 'undefined' && rule instanceof CSSFontFaceRule;
29
- };
30
- // Rule kinds are told apart by what they carry, not by class: @import has a sheet, style and keyframe rules have
31
- // declarations, and grouping rules (@media, @supports, @container, @layer, @keyframes, nesting) have child rules.
32
- const importedSheetOf = (rule) => {
33
- return 'styleSheet' in rule && rule.styleSheet ? rule.styleSheet : undefined;
34
- };
35
- const declarationsOf = (rule) => {
36
- return 'style' in rule && rule.style && !isFontFaceRule(rule) ? rule.style : undefined;
37
- };
38
- const childRulesOf = (rule) => {
39
- return 'cssRules' in rule && rule.cssRules ? rule.cssRules : undefined;
40
- };
41
- // Only declaration blocks change, through the CSSOM: @media, @container and @supports conditions keep their rem (var()
42
- // is invalid there, and a media query rem is the browser's initial size anyway), and !important stays with its value.
43
- const rewriteRules = (rules) => {
44
- for (const rule of Array.from(rules)) {
45
- const importedSheet = importedSheetOf(rule);
46
- if (importedSheet) {
47
- rewriteSheet(importedSheet);
48
- }
49
- const declarations = declarationsOf(rule);
50
- if (declarations && REM_LENGTH_HINT_PATTERN.test(declarations.cssText)) {
51
- const rewritten = rewriteRemLengths(declarations.cssText);
52
- if (rewritten !== declarations.cssText) {
53
- declarations.cssText = rewritten;
54
- }
55
- }
56
- const childRules = childRulesOf(rule);
57
- if (childRules) {
58
- rewriteRules(childRules);
59
- }
60
- }
61
- };
62
- const readRules = (sheet) => {
63
- try {
64
- return sheet.cssRules;
65
- }
66
- catch {
67
- // A cross-origin sheet (a font service's @font-face list) cannot be read, and none of its rules are module rules.
68
- return undefined;
69
- }
70
- };
71
- const rewriteSheet = (sheet) => {
72
- const rules = readRules(sheet);
73
- if (rules) {
74
- rewriteRules(rules);
75
- }
76
- };
77
- const declaresRootFontSize = (rules) => {
78
- return Array.from(rules).some((rule) => {
79
- const childRules = childRulesOf(rule);
80
- return !!declarationsOf(rule)?.getPropertyValue(MF_ROOT_FONT_SIZE_PROPERTY) || (!!childRules && declaresRootFontSize(childRules));
81
- });
82
- };
83
- const isStyleOwner = (node) => {
84
- return node.nodeName === 'STYLE' || (node.nodeName === 'LINK' && node.rel.split(/\s+/).includes('stylesheet'));
85
- };
86
- /**
87
- * Makes rem lengths in a hosted module's shadow root follow the root font size its own page had, once the module's
88
- * CSS declares one (see MF_ROOT_FONT_SIZE_PROPERTY). Covers stylesheets already in the root and every one added or
89
- * changed later, such as the component styles Angular adds as components first render. A module whose CSS never sizes
90
- * its root is left untouched. Returns a function that stops following; rewritten rules stay rewritten.
91
- */
92
- export const followModuleRootFontSize = (root) => {
93
- let following = false;
94
- const rewrittenSheets = new WeakSet();
95
- const watchedOwners = new WeakSet();
96
- const owners = () => Array.from(root.childNodes).filter(isStyleOwner);
97
- const sheetsOf = (styleOwners) => {
98
- return [...(root.adoptedStyleSheets ?? []), ...styleOwners.map((owner) => owner.sheet).filter((sheet) => !!sheet)];
99
- };
100
- // A <style>'s own text is the cheap check; linked and adopted sheets have only their rules.
101
- const findsRootFontSize = (styleOwners) => {
102
- const styles = styleOwners.filter((owner) => owner.nodeName === 'STYLE');
103
- if (styles.some((style) => ROOT_FONT_SIZE_DECLARATION_PATTERN.test(style.textContent ?? ''))) {
104
- return true;
105
- }
106
- return sheetsOf(styleOwners.filter((owner) => owner.nodeName === 'LINK')).some((sheet) => {
107
- const rules = readRules(sheet);
108
- return !!rules && declaresRootFontSize(rules);
109
- });
110
- };
111
- const observer = new MutationObserver(() => update());
112
- const update = () => {
113
- const styleOwners = owners();
114
- for (const owner of styleOwners) {
115
- if (owner.nodeName === 'STYLE' && !watchedOwners.has(owner)) {
116
- // Replacing a <style>'s text gives it a new sheet, which must be rewritten again.
117
- observer.observe(owner, { characterData: true, childList: true, subtree: true });
118
- watchedOwners.add(owner);
119
- }
120
- }
121
- following ||= findsRootFontSize(styleOwners);
122
- if (!following) {
123
- return;
124
- }
125
- for (const sheet of sheetsOf(styleOwners)) {
126
- if (!rewrittenSheets.has(sheet)) {
127
- rewriteSheet(sheet);
128
- rewrittenSheets.add(sheet);
129
- }
130
- }
131
- };
132
- // A <link> sheet has no rules until it loads; load does not bubble, so it is caught on the way down.
133
- const onLoad = (event) => {
134
- if (event.target instanceof Node && isStyleOwner(event.target)) {
135
- update();
136
- }
137
- };
138
- observer.observe(root, { childList: true });
139
- root.addEventListener('load', onLoad, true);
140
- update();
141
- return () => {
142
- observer.disconnect();
143
- root.removeEventListener('load', onLoad, true);
144
- };
145
- };
146
30
  //# sourceMappingURL=root-font-size.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"root-font-size.js","sourceRoot":"","sources":["../src/root-font-size.ts"],"names":[],"mappings":"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","sourcesContent":["// `rem` always resolves against the page's <html>, even inside a shadow root. A module page that sized its own root\n// (`html, body { font-size: 12px }`) drew every rem length from that size, but hosted, the same lengths use the host\n// page's root (16px): Material's `.mat-mdc-button > .mat-icon { font-size: 1.125rem }` drew 13.5px on the old page and\n// 18px hosted. The build-time PostCSS plugin records the module's root size as this property on the module boundary;\n// followModuleRootFontSize() rewrites every rem length in the module's shadow root to read it.\nexport const MF_ROOT_FONT_SIZE_PROPERTY = '--edyn-mf-rem';\n\nconst ROOT_FONT_SIZE_FALLBACK = `var(${MF_ROOT_FONT_SIZE_PROPERTY}, 1rem)`;\nconst ROOT_FONT_SIZE_DECLARATION_PATTERN = new RegExp(`${MF_ROOT_FONT_SIZE_PROPERTY}\\\\s*:`);\nconst REM_LENGTH_HINT_PATTERN = /\\drem/i;\n// A number with the rem unit that is not part of an identifier (`foo-2rem`) and not the fallback this rewrite writes.\nconst REM_LENGTH_PATTERN = new RegExp(`(?<![\\\\w.-]|var\\\\(${MF_ROOT_FONT_SIZE_PROPERTY},\\\\s*)(-?(?:\\\\d+\\\\.?\\\\d*|\\\\.\\\\d+))rem(?![\\\\w-])`, 'gi');\n// Strings and url() keep their text: `content: \"2rem\"` or `url(2rem.png)` are not lengths.\nconst VERBATIM_PATTERN = /\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|url\\((?:[^()\"'\\\\]|\\\\.)*\\)/gi;\n\nconst rewriteOutsideVerbatim = (text: string): string => {\n return text.replace(REM_LENGTH_PATTERN, (_match, amount: string) => `calc(${amount} * ${ROOT_FONT_SIZE_FALLBACK})`);\n};\n\n/** Rewrites every rem length in CSS declaration text to follow the module's root font size; running it again changes nothing. */\nexport const rewriteRemLengths = (declarations: string): string => {\n let rewritten = '';\n let start = 0;\n for (const verbatim of declarations.matchAll(VERBATIM_PATTERN)) {\n rewritten += rewriteOutsideVerbatim(declarations.slice(start, verbatim.index)) + verbatim[0];\n start = verbatim.index + verbatim[0].length;\n }\n return rewritten + rewriteOutsideVerbatim(declarations.slice(start));\n};\n\nconst isFontFaceRule = (rule: CSSRule): boolean => {\n return typeof CSSFontFaceRule !== 'undefined' && rule instanceof CSSFontFaceRule;\n};\n\n// Rule kinds are told apart by what they carry, not by class: @import has a sheet, style and keyframe rules have\n// declarations, and grouping rules (@media, @supports, @container, @layer, @keyframes, nesting) have child rules.\nconst importedSheetOf = (rule: CSSRule): CSSStyleSheet | undefined => {\n return 'styleSheet' in rule && rule.styleSheet ? (rule.styleSheet as CSSStyleSheet) : undefined;\n};\n\nconst declarationsOf = (rule: CSSRule): CSSStyleDeclaration | undefined => {\n return 'style' in rule && rule.style && !isFontFaceRule(rule) ? (rule.style as CSSStyleDeclaration) : undefined;\n};\n\nconst childRulesOf = (rule: CSSRule): CSSRuleList | undefined => {\n return 'cssRules' in rule && rule.cssRules ? (rule.cssRules as CSSRuleList) : undefined;\n};\n\n// Only declaration blocks change, through the CSSOM: @media, @container and @supports conditions keep their rem (var()\n// is invalid there, and a media query rem is the browser's initial size anyway), and !important stays with its value.\nconst rewriteRules = (rules: CSSRuleList): void => {\n for (const rule of Array.from(rules)) {\n const importedSheet = importedSheetOf(rule);\n if (importedSheet) {\n rewriteSheet(importedSheet);\n }\n const declarations = declarationsOf(rule);\n if (declarations && REM_LENGTH_HINT_PATTERN.test(declarations.cssText)) {\n const rewritten = rewriteRemLengths(declarations.cssText);\n if (rewritten !== declarations.cssText) {\n declarations.cssText = rewritten;\n }\n }\n const childRules = childRulesOf(rule);\n if (childRules) {\n rewriteRules(childRules);\n }\n }\n};\n\nconst readRules = (sheet: CSSStyleSheet): CSSRuleList | undefined => {\n try {\n return sheet.cssRules;\n } catch {\n // A cross-origin sheet (a font service's @font-face list) cannot be read, and none of its rules are module rules.\n return undefined;\n }\n};\n\nconst rewriteSheet = (sheet: CSSStyleSheet): void => {\n const rules = readRules(sheet);\n if (rules) {\n rewriteRules(rules);\n }\n};\n\nconst declaresRootFontSize = (rules: CSSRuleList): boolean => {\n return Array.from(rules).some((rule) => {\n const childRules = childRulesOf(rule);\n return !!declarationsOf(rule)?.getPropertyValue(MF_ROOT_FONT_SIZE_PROPERTY) || (!!childRules && declaresRootFontSize(childRules));\n });\n};\n\ntype TStyleOwner = HTMLLinkElement | HTMLStyleElement;\n\nconst isStyleOwner = (node: Node): node is TStyleOwner => {\n return node.nodeName === 'STYLE' || (node.nodeName === 'LINK' && (node as HTMLLinkElement).rel.split(/\\s+/).includes('stylesheet'));\n};\n\n/**\n * Makes rem lengths in a hosted module's shadow root follow the root font size its own page had, once the module's\n * CSS declares one (see MF_ROOT_FONT_SIZE_PROPERTY). Covers stylesheets already in the root and every one added or\n * changed later, such as the component styles Angular adds as components first render. A module whose CSS never sizes\n * its root is left untouched. Returns a function that stops following; rewritten rules stay rewritten.\n */\nexport const followModuleRootFontSize = (root: ShadowRoot): (() => void) => {\n let following = false;\n const rewrittenSheets = new WeakSet<CSSStyleSheet>();\n const watchedOwners = new WeakSet<TStyleOwner>();\n\n const owners = (): TStyleOwner[] => Array.from(root.childNodes).filter(isStyleOwner);\n\n const sheetsOf = (styleOwners: TStyleOwner[]): CSSStyleSheet[] => {\n return [...(root.adoptedStyleSheets ?? []), ...styleOwners.map((owner) => owner.sheet).filter((sheet): sheet is CSSStyleSheet => !!sheet)];\n };\n\n // A <style>'s own text is the cheap check; linked and adopted sheets have only their rules.\n const findsRootFontSize = (styleOwners: TStyleOwner[]): boolean => {\n const styles = styleOwners.filter((owner) => owner.nodeName === 'STYLE');\n if (styles.some((style) => ROOT_FONT_SIZE_DECLARATION_PATTERN.test(style.textContent ?? ''))) {\n return true;\n }\n return sheetsOf(styleOwners.filter((owner) => owner.nodeName === 'LINK')).some((sheet) => {\n const rules = readRules(sheet);\n return !!rules && declaresRootFontSize(rules);\n });\n };\n\n const observer = new MutationObserver(() => update());\n\n const update = (): void => {\n const styleOwners = owners();\n for (const owner of styleOwners) {\n if (owner.nodeName === 'STYLE' && !watchedOwners.has(owner)) {\n // Replacing a <style>'s text gives it a new sheet, which must be rewritten again.\n observer.observe(owner, { characterData: true, childList: true, subtree: true });\n watchedOwners.add(owner);\n }\n }\n\n following ||= findsRootFontSize(styleOwners);\n if (!following) {\n return;\n }\n\n for (const sheet of sheetsOf(styleOwners)) {\n if (!rewrittenSheets.has(sheet)) {\n rewriteSheet(sheet);\n rewrittenSheets.add(sheet);\n }\n }\n };\n\n // A <link> sheet has no rules until it loads; load does not bubble, so it is caught on the way down.\n const onLoad = (event: Event): void => {\n if (event.target instanceof Node && isStyleOwner(event.target)) {\n update();\n }\n };\n\n observer.observe(root, { childList: true });\n root.addEventListener('load', onLoad, true);\n update();\n\n return () => {\n observer.disconnect();\n root.removeEventListener('load', onLoad, true);\n };\n};\n"]}
1
+ {"version":3,"file":"root-font-size.js","sourceRoot":"","sources":["../src/root-font-size.ts"],"names":[],"mappings":"AAAA,oHAAoH;AACpH,qHAAqH;AACrH,uHAAuH;AACvH,qHAAqH;AACrH,+FAA+F;AAC/F,MAAM,CAAC,MAAM,0BAA0B,GAAG,eAAe,CAAC;AAE1D,MAAM,uBAAuB,GAAG,OAAO,0BAA0B,SAAS,CAAC;AAC3E,MAAM,CAAC,MAAM,kCAAkC,GAAG,IAAI,MAAM,CAAC,GAAG,0BAA0B,OAAO,CAAC,CAAC;AACnG,MAAM,CAAC,MAAM,uBAAuB,GAAG,QAAQ,CAAC;AAChD,sHAAsH;AACtH,MAAM,kBAAkB,GAAG,IAAI,MAAM,CAAC,qBAAqB,0BAA0B,iDAAiD,EAAE,IAAI,CAAC,CAAC;AAC9I,2FAA2F;AAC3F,MAAM,gBAAgB,GAAG,iEAAiE,CAAC;AAE3F,oHAAoH;AACpH,uCAAuC;AACvC,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAEjF,MAAM,sBAAsB,GAAG,CAAC,IAAY,EAAU,EAAE;IACtD,OAAO,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAc,EAAE,EAAE,CAAC,QAAQ,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC;AACtH,CAAC,CAAC;AAEF,iIAAiI;AACjI,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,YAAoB,EAAU,EAAE;IAChE,IAAI,SAAS,GAAG,EAAE,CAAC;IACnB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,QAAQ,IAAI,YAAY,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC/D,SAAS,IAAI,sBAAsB,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC7F,KAAK,GAAG,QAAQ,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9C,CAAC;IACD,OAAO,SAAS,GAAG,sBAAsB,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AACvE,CAAC,CAAC","sourcesContent":["// `rem` always resolves against the page's <html>, even inside a shadow root. A module page that sized its own root\n// (`html, body { font-size: 12px }`) drew every rem length from that size, but hosted, the same lengths use the host\n// page's root (16px): Material's `.mat-mdc-button > .mat-icon { font-size: 1.125rem }` drew 13.5px on the old page and\n// 18px hosted. The build-time PostCSS plugin records the module's root size as this property on the module boundary;\n// followModuleRootFontSize() rewrites every rem length in the module's shadow root to read it.\nexport const MF_ROOT_FONT_SIZE_PROPERTY = '--edyn-mf-rem';\n\nconst ROOT_FONT_SIZE_FALLBACK = `var(${MF_ROOT_FONT_SIZE_PROPERTY}, 1rem)`;\nexport const ROOT_FONT_SIZE_DECLARATION_PATTERN = new RegExp(`${MF_ROOT_FONT_SIZE_PROPERTY}\\\\s*:`);\nexport const REM_LENGTH_HINT_PATTERN = /\\drem/i;\n// A number with the rem unit that is not part of an identifier (`foo-2rem`) and not the fallback this rewrite writes.\nconst REM_LENGTH_PATTERN = new RegExp(`(?<![\\\\w.-]|var\\\\(${MF_ROOT_FONT_SIZE_PROPERTY},\\\\s*)(-?(?:\\\\d+\\\\.?\\\\d*|\\\\.\\\\d+))rem(?![\\\\w-])`, 'gi');\n// Strings and url() keep their text: `content: \"2rem\"` or `url(2rem.png)` are not lengths.\nconst VERBATIM_PATTERN = /\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|url\\((?:[^()\"'\\\\]|\\\\.)*\\)/gi;\n\n// The same match without the global flag, as a quick test: it skips the fallback this rewrite writes, so a followed\n// value does not look like work again.\nexport const UNFOLLOWED_REM_PATTERN = new RegExp(REM_LENGTH_PATTERN.source, 'i');\n\nconst rewriteOutsideVerbatim = (text: string): string => {\n return text.replace(REM_LENGTH_PATTERN, (_match, amount: string) => `calc(${amount} * ${ROOT_FONT_SIZE_FALLBACK})`);\n};\n\n/** Rewrites every rem length in CSS declaration text to follow the module's root font size; running it again changes nothing. */\nexport const rewriteRemLengths = (declarations: string): string => {\n let rewritten = '';\n let start = 0;\n for (const verbatim of declarations.matchAll(VERBATIM_PATTERN)) {\n rewritten += rewriteOutsideVerbatim(declarations.slice(start, verbatim.index)) + verbatim[0];\n start = verbatim.index + verbatim[0].length;\n }\n return rewritten + rewriteOutsideVerbatim(declarations.slice(start));\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edynamix/mf-isolation",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "description": "CSS and overlay isolation helpers for eDynamix microfrontends.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -94,6 +94,7 @@
94
94
  "@playwright/test": "1.60.0",
95
95
  "@types/bun": "1.3.14",
96
96
  "@types/node": "22.20.2",
97
+ "happy-dom": "20.11.2",
97
98
  "knip": "6.32.2",
98
99
  "postcss": "8.5.28",
99
100
  "typescript": "5.8.3",