diff --git a/eslint.config.mjs b/eslint.config.mjs index 6396de82bc3..e65d8918321 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -505,4 +505,12 @@ export default [{ rules: { "react/react-in-jsx-scope": OFF, }, -}]; +}, { + files: ["packages/dev/style-macro-chrome-plugin/**"], + languageOptions: { + globals: { + ...globals.webextensions, + ...globals.browser + } + } +}]; \ No newline at end of file diff --git a/packages/@react-spectrum/s2/src/TableView.tsx b/packages/@react-spectrum/s2/src/TableView.tsx index 58a1ebc837b..352d589e365 100644 --- a/packages/@react-spectrum/s2/src/TableView.tsx +++ b/packages/@react-spectrum/s2/src/TableView.tsx @@ -1465,7 +1465,7 @@ export const Row = /*#__PURE__*/ (forwardRef as forwardRefType)(function Row row({ ...renderProps, ...tableVisualOptions - }) + (renderProps.isFocusVisible && ' ' + raw('&:before { content: ""; display: inline-block; position: sticky; inset-inline-start: 0; width: 3px; height: 100%; margin-inline-end: -3px; margin-block-end: 1px; z-index: 3; background-color: var(--rowFocusIndicatorColor)'))} + }) + (renderProps.isFocusVisible ? ' ' + raw('&:before { content: ""; display: inline-block; position: sticky; inset-inline-start: 0; width: 3px; height: 100%; margin-inline-end: -3px; margin-block-end: 1px; z-index: 3; background-color: var(--rowFocusIndicatorColor)') : '')} {...otherProps}> {selectionMode !== 'none' && selectionBehavior === 'toggle' && ( // Not sure what we want to do with this className, in Cell it currently overrides the className that would have been applied. diff --git a/packages/@react-spectrum/s2/style/__tests__/mergeStyles.test.js b/packages/@react-spectrum/s2/style/__tests__/mergeStyles.test.js index 00376090225..ae8349a616f 100644 --- a/packages/@react-spectrum/s2/style/__tests__/mergeStyles.test.js +++ b/packages/@react-spectrum/s2/style/__tests__/mergeStyles.test.js @@ -13,13 +13,17 @@ import {mergeStyles} from '../runtime'; import {style} from '../spectrum-theme'; +function stripMacro(css) { + return css.replaceAll(/ -macro-static-[0-9a-zA-Z]+/gi, '').replaceAll(/ -macro-dynamic-[0-9a-zA-Z]+/gi, ''); +} + describe('mergeStyles', () => { it('should merge styles', () => { let a = style({backgroundColor: 'red-1000', color: 'pink-100'}); let b = style({fontSize: 'body-xs', backgroundColor: 'gray-50'}); let expected = style({backgroundColor: 'gray-50', color: 'pink-100', fontSize: 'body-xs'}); let merged = mergeStyles(a, b); - expect(merged).toBe(expected); + expect(stripMacro(merged)).toBe(stripMacro(expected.toString())); }); it('should merge with arbitrary values', () => { @@ -27,6 +31,6 @@ describe('mergeStyles', () => { let b = style({fontSize: '[15px]', backgroundColor: 'gray-50'}); let expected = style({backgroundColor: 'gray-50', color: '[hotpink]', fontSize: '[15px]'}); let merged = mergeStyles(a, b); - expect(merged).toBe(expected); + expect(stripMacro(merged)).toBe(stripMacro(expected.toString())); }); }); diff --git a/packages/@react-spectrum/s2/style/__tests__/style-macro.test.js b/packages/@react-spectrum/s2/style/__tests__/style-macro.test.js index 10e1be65cb2..e4cb15540f7 100644 --- a/packages/@react-spectrum/s2/style/__tests__/style-macro.test.js +++ b/packages/@react-spectrum/s2/style/__tests__/style-macro.test.js @@ -53,9 +53,13 @@ describe('style-macro', () => { } } +.-macro-static-E8tar { + --macro-data-E8tar: {"style":{"marginTop":{":first-child":{"default":4,"lg":8}}},"loc":"undefined:undefined:undefined"}; + } + " `); - expect(js).toMatchInlineSnapshot('" Jbs1 Jbpv1"'); + expect(js).toMatchInlineSnapshot('" Jbs1 Jbpv1 -macro-static-E8tar"'); }); it('should support self references', () => { @@ -114,10 +118,14 @@ describe('style-macro', () => { } } +.-macro-static-ootVze { + --macro-data-ootVze: {"style":{"borderWidth":2,"paddingX":"edge-to-text","width":"calc(200px - self(borderStartWidth) - self(paddingStart))"},"loc":"undefined:undefined:undefined"}; + } + " `); - expect(js).toMatchInlineSnapshot('" _kc1 hc1 mCPFGYc1 lc1 SMBFGYc1 Rv1 ZjUQgKd1 -m_-mc1 -S_-Sv1"'); + expect(js).toMatchInlineSnapshot('" _kc1 hc1 mCPFGYc1 lc1 SMBFGYc1 Rv1 ZjUQgKd1 -m_-mc1 -S_-Sv1 -macro-static-ootVze"'); }); it('should support allowed overrides', () => { @@ -134,9 +142,9 @@ describe('style-macro', () => { color: 'green-400' }); - expect(js()).toMatchInlineSnapshot('" gw1 pg1"'); - expect(overrides).toMatchInlineSnapshot('" g8tmWqb1 pHJ3AUd1"'); - expect(js({}, overrides)).toMatchInlineSnapshot('" g8tmWqb1 pg1"'); + expect(js()).toMatchInlineSnapshot('" gw1 pg1 -macro-dynamic-1g1t5qe"'); + expect(overrides).toMatchInlineSnapshot('" g8tmWqb1 pHJ3AUd1 -macro-static-YWkqh"'); + expect(js({}, overrides)).toMatchInlineSnapshot('" g8tmWqb1 pg1 -macro-dynamic-w1re4y"'); }); it('should support allowed overrides for properties that expand into multiple', () => { @@ -151,9 +159,9 @@ describe('style-macro', () => { translateX: 40 }); - expect(js()).toMatchInlineSnapshot('" -_7PloMd-B1 __Ya1"'); - expect(overrides).toMatchInlineSnapshot('" -_7PloMd-D1 __Ya1"'); - expect(js({}, overrides)).toMatchInlineSnapshot('" -_7PloMd-D1 __Ya1"'); + expect(js()).toMatchInlineSnapshot('" -_7PloMd-B1 __Ya1 -macro-dynamic-1g21iuv"'); + expect(overrides).toMatchInlineSnapshot('" -_7PloMd-D1 __Ya1 -macro-static-JbY0Pb"'); + expect(js({}, overrides)).toMatchInlineSnapshot('" -_7PloMd-D1 __Ya1 -macro-dynamic-7es7uh"'); }); it('should support allowed overrides for shorthands', () => { @@ -168,9 +176,9 @@ describe('style-macro', () => { padding: 40 }); - expect(js()).toMatchInlineSnapshot('" Tk1 Qk1 Sk1 Rk1"'); - expect(overrides).toMatchInlineSnapshot('" Tm1 Qm1 Sm1 Rm1"'); - expect(js({}, overrides)).toMatchInlineSnapshot('" Tm1 Qm1 Sm1 Rm1"'); + expect(js()).toMatchInlineSnapshot('" Tk1 Qk1 Sk1 Rk1 -macro-dynamic-ov9j7t"'); + expect(overrides).toMatchInlineSnapshot('" Tm1 Qm1 Sm1 Rm1 -macro-static-JNxqEe"'); + expect(js({}, overrides)).toMatchInlineSnapshot('" Tm1 Qm1 Sm1 Rm1 -macro-dynamic-x7v9g1"'); }); it('should support allowed overrides for fontSize', () => { @@ -185,9 +193,9 @@ describe('style-macro', () => { fontSize: 'ui-xs' }); - expect(js()).toMatchInlineSnapshot('" -_6BNtrc-woabcc1 vx1"'); - expect(overrides).toMatchInlineSnapshot('" -_6BNtrc-a1 vx1"'); - expect(js({}, overrides)).toMatchInlineSnapshot('" -_6BNtrc-a1 vx1"'); + expect(js()).toMatchInlineSnapshot('" -_6BNtrc-woabcc1 vx1 -macro-dynamic-4vhxg6"'); + expect(overrides).toMatchInlineSnapshot('" -_6BNtrc-a1 vx1 -macro-static-mgSTCe"'); + expect(js({}, overrides)).toMatchInlineSnapshot('" -_6BNtrc-a1 vx1 -macro-dynamic-wufo3s"'); }); it("should support allowed overrides for values that aren't defined", () => { @@ -202,9 +210,9 @@ describe('style-macro', () => { minWidth: 32 }); - expect(js()).toMatchInlineSnapshot('" gE1"'); - expect(overrides).toMatchInlineSnapshot('" Nk1"'); - expect(js({}, overrides)).toMatchInlineSnapshot('" Nk1 gE1"'); + expect(js()).toMatchInlineSnapshot('" gE1 -macro-dynamic-6mjybw"'); + expect(overrides).toMatchInlineSnapshot('" Nk1 -macro-static-TMcWFd"'); + expect(js({}, overrides)).toMatchInlineSnapshot('" Nk1 gE1 -macro-dynamic-1255f06"'); }); it('should support runtime conditions', () => { @@ -258,9 +266,9 @@ describe('style-macro', () => { " `); - expect(js({})).toMatchInlineSnapshot('" gH1 pt1"'); - expect(js({isHovered: true})).toMatchInlineSnapshot('" gF1 po1"'); - expect(js({isPressed: true})).toMatchInlineSnapshot('" gE1 pm1"'); + expect(js({})).toMatchInlineSnapshot('" gH1 pt1 -macro-dynamic-1e9ks2s"'); + expect(js({isHovered: true})).toMatchInlineSnapshot('" gF1 po1 -macro-dynamic-fs3pp9"'); + expect(js({isPressed: true})).toMatchInlineSnapshot('" gE1 pm1 -macro-dynamic-qy1zq2"'); }); it('should support nested runtime conditions', () => { @@ -301,10 +309,10 @@ describe('style-macro', () => { " `); - expect(js({})).toMatchInlineSnapshot('" gH1"'); - expect(js({isHovered: true})).toMatchInlineSnapshot('" gF1"'); - expect(js({isSelected: true})).toMatchInlineSnapshot('" g_h1"'); - expect(js({isSelected: true, isHovered: true})).toMatchInlineSnapshot('" g31"'); + expect(js({})).toMatchInlineSnapshot('" gH1 -macro-dynamic-z0gpn3"'); + expect(js({isHovered: true})).toMatchInlineSnapshot('" gF1 -macro-dynamic-1rfxkr1"'); + expect(js({isSelected: true})).toMatchInlineSnapshot('" g_h1 -macro-dynamic-tk9x4e"'); + expect(js({isSelected: true, isHovered: true})).toMatchInlineSnapshot('" g31 -macro-dynamic-1defke2"'); }); it('should support variant runtime conditions', () => { @@ -318,9 +326,9 @@ describe('style-macro', () => { } }); - expect(js({variant: 'accent'})).toMatchInlineSnapshot('" gY1"'); - expect(js({variant: 'primary'})).toMatchInlineSnapshot('" gjQquMe1"'); - expect(js({variant: 'secondary'})).toMatchInlineSnapshot('" gw1"'); + expect(js({variant: 'accent'})).toMatchInlineSnapshot('" gY1 -macro-dynamic-6gbj4w"'); + expect(js({variant: 'primary'})).toMatchInlineSnapshot('" gjQquMe1 -macro-dynamic-aqv4wq"'); + expect(js({variant: 'secondary'})).toMatchInlineSnapshot('" gw1 -macro-dynamic-66ywce"'); }); it('supports runtime conditions nested inside css conditions', () => { @@ -354,8 +362,8 @@ describe('style-macro', () => { " `); - expect(js({})).toMatchInlineSnapshot('" plb1"'); - expect(js({isSelected: true})).toMatchInlineSnapshot('" ple1"'); + expect(js({})).toMatchInlineSnapshot('" plb1 -macro-dynamic-qa0mhq"'); + expect(js({isSelected: true})).toMatchInlineSnapshot('" ple1 -macro-dynamic-1inxdsx"'); }); it('should expand shorthand properties to longhands', () => { @@ -363,7 +371,7 @@ describe('style-macro', () => { padding: 24 }); - expect(js).toMatchInlineSnapshot('" Th1 Qh1 Sh1 Rh1"'); + expect(js).toMatchInlineSnapshot('" Th1 Qh1 Sh1 Rh1 -macro-static-tfFFV"'); expect(css).toMatchInlineSnapshot(` "@layer _.a; @@ -388,6 +396,10 @@ describe('style-macro', () => { } } +.-macro-static-tfFFV { + --macro-data-tfFFV: {"style":{"padding":24},"loc":"undefined:undefined:undefined"}; + } + " `); }); @@ -406,6 +418,10 @@ describe('style-macro', () => { } } +.-macro-static-S2MtWd { + --macro-data-S2MtWd: {"style":{"backgroundColor":"blue-1000/50"},"loc":"undefined:undefined:undefined"}; + } + " `); }); @@ -427,6 +443,10 @@ describe('style-macro', () => { } } +.-macro-static-sFmj5 { + --macro-data-sFmj5: {"style":{"--foo":{"type":"backgroundColor","value":"gray-300"}},"loc":"undefined:undefined:undefined"}; + } + " `); }); diff --git a/packages/@react-spectrum/s2/style/runtime.ts b/packages/@react-spectrum/s2/style/runtime.ts index 5b567e2ab56..97b0f686e24 100644 --- a/packages/@react-spectrum/s2/style/runtime.ts +++ b/packages/@react-spectrum/s2/style/runtime.ts @@ -39,20 +39,45 @@ import {StyleString} from './types'; export function mergeStyles(...styles: (StyleString | null | undefined)[]): StyleString { let definedStyles = styles.filter(Boolean) as StyleString[]; if (definedStyles.length === 1) { - return definedStyles[0]; + let first = definedStyles[0]; + if (typeof first !== 'string') { + // static macro has a toString method so that we generate the style macro map for the entry + // it's automatically called in other places, but for our merging, we have to call it ourselves + return (first as StyleString).toString() as StyleString; + } + return first; } - let map = new Map(); + let map = new Map(); + let macroClasses: string[] = []; + for (let style of definedStyles) { - for (let [k, v] of parse(style)) { + // must call toString here for the static macro + let str = style.toString(); + + // Extract and preserve macro debug classes + let macroMatches = str.matchAll(/-macro-(static|dynamic)-[^\s]+/g); + for (let match of macroMatches) { + if (!macroClasses.includes(match[0])) { + macroClasses.push(match[0]); + } + } + + for (let [k, v] of parse(str)) { map.set(k, v); } } - + let res = ''; for (let value of map.values()) { res += value; } + + // Append all macro debug classes + if (macroClasses.length > 0) { + res += ' ' + macroClasses.join(' '); + } + return res as StyleString; } diff --git a/packages/@react-spectrum/s2/style/style-macro.ts b/packages/@react-spectrum/s2/style/style-macro.ts index 0f43859218f..1aef9197662 100644 --- a/packages/@react-spectrum/s2/style/style-macro.ts +++ b/packages/@react-spectrum/s2/style/style-macro.ts @@ -176,7 +176,7 @@ export function parseArbitraryValue(value: Value): string | undefined { return value.slice(1, -1); } else if ( typeof value === 'string' && ( - /^(var|calc|min|max|clamp|round|mod|rem|sin|cos|tan|asin|acos|atan|atan2|pow|sqrt|hypot|log|exp|abs|sign)\(.+\)$/.test(value) || + /^(var|calc|min|max|clamp|round|mod|rem|sin|cos|tan|asin|acos|atan|atan2|pow|sqrt|hypot|log|exp|abs|sign)\(.+\)$/.test(value) || /^(inherit|initial|unset)$/.test(value) ) ) { @@ -205,6 +205,8 @@ interface MacroContext { addAsset(asset: {type: string, content: string}): void } +let isCompilingDependencies: boolean | null | string = false; + export function createTheme(theme: T): StyleFunction, 'default' | Extract> { let properties = new Map>(Object.entries(theme.properties).map(([k, v]) => { if (!Array.isArray(v) && v.cssProperties) { @@ -280,8 +282,11 @@ export function createTheme(theme: T): StyleFunction(theme: T): StyleFunction(); - let js = 'let rules = " ";\n'; + let js = 'let rules = " ", currentRules = {};\n'; if (allowedOverrides?.length) { for (let property of allowedOverrides) { let shorthand = theme.shorthands[property]; @@ -315,7 +320,7 @@ export function createTheme(theme: T): StyleFunction(theme: T): StyleFunction classNamePrefix(p, p)).join('|')})[^\\s]+/g`; + let regex = `/(?:^|\\s)(${[...allowedOverridesSet].map(p => classNamePrefix(p, p)).join('|')}|-macro\\$)[^\\s]+/g`; if (loop) { - js += `let matches = (overrides || '').matchAll(${regex});\n`; + js += `let matches = String(overrides || '').matchAll(${regex});\n`; js += 'for (let p of matches) {\n'; js += loop; js += ' rules += p[0];\n'; js += '}\n'; } else { - js += `rules += ((overrides || '').match(${regex}) || []).join('')\n`; + js += `rules += (String(overrides || '').match(${regex}) || []).join('')\n`; } } @@ -375,6 +380,15 @@ export function createTheme(theme: T): StyleFunction(theme: T): StyleFunction(theme: T): StyleFunction(theme: T): StyleFunction(theme: T): StyleFunction rule.copy()), this.prelude, this.layer); + return new AtRule(this.rules.map(rule => rule.copy()), this.prelude, this.layer, this.themeCondition); } toCSS(rulesByLayer: Map, preludes: string[] = [], layer?: string): void { @@ -774,6 +826,13 @@ class AtRule extends GroupRule { super.toCSS(rulesByLayer, preludes, layer); preludes?.pop(); } + + toJS(allowedOverridesSet: Set, indent?: string): string { + conditionStack.push(this.themeCondition || this.prelude); + let res = super.toJS(allowedOverridesSet, indent); + conditionStack.pop(); + return res; + } } /** A rule that applies conditionally at runtime. */ @@ -794,7 +853,10 @@ class ConditionalRule extends GroupRule { } toJS(allowedOverridesSet: Set, indent = ''): string { - return `${indent}if (props.${this.condition}) {\n${super.toJS(allowedOverridesSet, indent + ' ')}\n${indent}}`; + conditionStack.push(this.condition); + let res = `${indent}if (props.${this.condition}) {\n${super.toJS(allowedOverridesSet, indent + ' ')}\n${indent}}`; + conditionStack.pop(); + return res; } } diff --git a/packages/dev/style-macro-chrome-plugin/.parcelrc b/packages/dev/style-macro-chrome-plugin/.parcelrc new file mode 100644 index 00000000000..f497a196e5f --- /dev/null +++ b/packages/dev/style-macro-chrome-plugin/.parcelrc @@ -0,0 +1,9 @@ +{ + "extends": "@parcel/config-webextension", + "transformers": { + "*.{js,mjs,jsx,cjs,ts,tsx}": [ + "@parcel/transformer-js", + "@parcel/transformer-react-refresh-wrap" + ] + } +} diff --git a/packages/dev/style-macro-chrome-plugin/README.md b/packages/dev/style-macro-chrome-plugin/README.md new file mode 100644 index 00000000000..bdd967ff3ad --- /dev/null +++ b/packages/dev/style-macro-chrome-plugin/README.md @@ -0,0 +1,382 @@ +# style-macro-chrome-plugin + +This is a chrome plugin to assist in debugging the styles applied by the React Spectrum Style Macro. + +## Expected Usage + +Until the plugin is published to the Chrome web extension store, the easiest thing to do is to build using the command +``` +yarn workspace style-macro-chrome-plugin build +``` + +This will create a dist directory in the directory `packages/dev/style-macro-chrome-plugin`, you should copy this directory to somewhere permanent on your machine. + +Next, open Chrome and go to [chrome://extensions/](chrome://extensions/). + +Load an unpacked extension, it's a button in the top left, and navigate to the dist directory. + +The extension is now registered in Chrome and you can go to storybook or docs, wherever you are working. + +Inspect an element on the page to open dev tools and go to the Style Macro panel. + +## Local development + +From the root of our monopackage, run + +``` +yarn +yarn workspace style-macro-chrome-plugin start +// or build to avoid refresh bugs in HMR +yarn workspace style-macro-chrome-plugin build +``` + +This will create a dist directory in the directory `packages/dev/style-macro-chrome-plugin` which will update anytime the code changes and results in a rebuild. + +Now follow the instructions in the above section starting from "Next, open chrome". + +## Troubleshooting + +If the panel isn't updating with styles, try closing the dev tools and reopening it. + +If the extension doesn't appear to have the latest code, try closing the dev tools and reopening it. You may also want to go to the extensions page and either "refresh" or remove and re-add the extension. + +If every tab you have open (or many of them) reload when you make local changes to the extension, then go into the extension settings and limit it to `localhost` or something appropriate. + +## ToDos + +- [ ] Would be pretty cool if we could match a style condition to trigger it, like hover +- [ ] Our own UI ?? +- [ ] Filtering +- [ ] Resolve css variables inline +- [ ] Link to file on the side instead of grouping by filename? +- [ ] Add classname that is applying style? + +## Extension Architecture + +This extension uses Chrome's standard extension architecture with three main components that communicate via message passing. + +### Components + +#### 1. **Page Context** (style-macro runtime + MutationObserver) +- **Location**: Runs in the actual page's JavaScript context +- **Responsibility**: + - Generates macro metadata (hash, location, styles) when style macro is evaluated + - Hosts MutationObserver that watches selected element for className changes +- **Storage**: None - static macros embed data in CSS, dynamic macros send messages +- **Communication**: + - For static macros: Embeds data in CSS custom property `--macro-data-{hash}` (unique per macro) + - For dynamic macros: Sends `window.postMessage({ action: 'stylemacro-update-macros', hash, loc, style })` to content script + - For className changes: Sends `window.postMessage({ action: 'stylemacro-class-changed', elementId })` to content script + +#### 2. **Content Script** (`content-script.js`) +- **Location**: Isolated sandboxed environment injected into the page +- **Scope**: Acts as a message forwarder between page and extension +- **Responsibility**: + - Listens for `window.postMessage({ action: 'stylemacro-update-macros' })` from the page and forwards to background script + - Forwards `window.postMessage({ action: 'stylemacro-class-changed' })` from page to background script +- **Storage**: None - all macro data is stored in DevTools +- **Communication**: + - Receives: + - `window.postMessage({ action: 'stylemacro-update-macros', hash, loc, style })` from page + - `window.postMessage({ action: 'stylemacro-class-changed', elementId })` from page + - Sends: + - `chrome.runtime.sendMessage({ action: 'stylemacro-update-macros', hash, loc, style })` to background + - `chrome.runtime.sendMessage({ action: 'stylemacro-class-changed', elementId })` to background + +#### 3. **Background Script** (`background.js`) +- **Location**: Service worker (isolated context) +- **Responsibility**: Acts as a message broker between DevTools and content scripts +- **State**: Maintains a map of DevTools connections per tab +- **Communication**: + - Receives: + - `chrome.runtime.onConnect({ name: 'devtools-page' })` from DevTools + - `port.onMessage({ type: 'stylemacro-init' })` from DevTools + - `chrome.runtime.onMessage({ action: 'stylemacro-update-macros', hash, loc, style })` from content script + - `chrome.runtime.onMessage({ action: 'stylemacro-class-changed', elementId })` from content script + - Sends: + - `port.postMessage({ action: 'stylemacro-update-macros', hash, loc, style })` to DevTools + - `port.postMessage({ action: 'stylemacro-class-changed', elementId })` to DevTools + +#### 4. **DevTools Panel** (`devtool.js`) +- **Location**: DevTools sidebar panel context +- **Responsibility**: + - Stores all dynamic macro data in a local Map: `macroData[hash] = { loc, style }` + - Extracts macro class names from selected element: + - Static macros: `-macro-static-{hash}` → reads `--macro-data-{hash}` custom property via `getComputedStyle()` + - Dynamic macros: `-macro-dynamic-{hash}` → looks up data from local storage + - Displays style information in sidebar + - **Automatic Updates**: Sets up a MutationObserver on the selected element to detect className changes and automatically refreshes the panel + - **Cleanup**: Every 5 minutes, checks the DOM for each stored hash and removes data for macros that no longer exist +- **Storage**: `Map` - stores all dynamic macro data +- **Mutation Observer**: + - Created when an element is selected via `chrome.devtools.panels.elements.onSelectionChanged` + - Watches the selected element's `class` attribute for changes + - Disconnects when: + - A new element is selected + - The DevTools connection is closed + - Triggers automatic panel refresh when className changes +- **Communication**: + - Receives: + - `port.onMessage({ action: 'stylemacro-update-macros', hash, loc, style })` from background (stores data and refreshes) + - `port.onMessage({ action: 'stylemacro-class-changed', elementId })` from background (triggers refresh) + - Sends: + - `chrome.runtime.connect({ name: 'devtools-page' })` to establish connection + - `port.postMessage({ type: 'stylemacro-init', tabId })` to background + +### Message Flow Diagrams + +#### Flow 1a: Static Macro Lookup (DevTools reads CSS) + +Static macros are generated when style macro conditions don't change at runtime. The macro data is embedded directly into the CSS as a uniquely-named custom property. + +``` +┌─────────────────┐ +│ DevTools Panel │ User selects element with -macro-static-{hash} class +└────────┬────────┘ + │ Extract hash from className + │ Read --macro-data-{hash} via getComputedStyle($0) + ↓ +┌─────────────────┐ +│ Page DOM/CSS │ Returns custom property value for specific hash +└────────┬────────┘ + │ getPropertyValue('--macro-data-{hash}') + │ { loc: "...", style: {...} } + ↓ +┌─────────────────┐ +│ DevTools Panel │ Parses and displays in sidebar +└─────────────────┘ +``` + +**Key Design**: Each static macro has its own uniquely-named custom property (`--macro-data-{hash}`), which avoids CSS cascade issues when reading multiple macro data from the same element. + +#### Flow 1b: Dynamic Macro Updates (Page → DevTools) + +Dynamic macros are generated when style macro conditions can change at runtime. Updates are sent via message passing and stored directly in DevTools. + +``` +┌─────────────────┐ +│ Page Context │ +│ (style-macro) │ +└────────┬────────┘ + │ window.postMessage({ action: 'stylemacro-update-macros', hash, loc, style }) + ↓ +┌─────────────────┐ +│ Content Script │ Forwards message (no storage) +└────────┬────────┘ + │ chrome.runtime.sendMessage({ action: 'stylemacro-update-macros', hash, loc, style }) + ↓ +┌─────────────────┐ +│ Background │ Looks up DevTools connection for tabId +└────────┬────────┘ + │ port.postMessage({ action: 'stylemacro-update-macros', hash, loc, style }) + ↓ +┌─────────────────┐ +│ DevTools Panel │ Stores in macroData Map and triggers sidebar refresh +└─────────────────┘ +``` + +#### Flow 2: Display Macro Data (Synchronous Lookup) + +When the user selects an element or the panel refreshes, DevTools looks up macro data synchronously from its local storage. + +``` +┌─────────────────┐ +│ DevTools Panel │ User selects element with -macro-dynamic-{hash} class +└────────┬────────┘ + │ Extract hash from className + ↓ +┌─────────────────┐ +│ DevTools Panel │ Look up macroData.get(hash) +│ Local Storage │ Returns { loc, style } if available +└────────┬────────┘ + │ { loc: "...", style: {...} } or null + ↓ +┌─────────────────┐ +│ DevTools Panel │ Display in sidebar (or show nothing if null) +└─────────────────┘ +``` + +**Note**: If macro data hasn't been received yet for a hash, it will appear empty until the next `stylemacro-update-macros` message arrives and triggers a refresh. + +#### Flow 3: Macro Data Cleanup (Automated) + +Every 5 minutes, DevTools checks if stored macro hashes are still in use on the page and removes stale data. + +``` +┌─────────────────┐ +│ DevTools Panel │ Every 5 minutes +└────────┬────────┘ + │ For each hash in macroData Map: + │ chrome.devtools.inspectedWindow.eval( + │ `!!document.querySelector('.-macro-dynamic-${hash}')` + │ ) + ↓ +┌─────────────────┐ +│ Page DOM │ Checks if elements with macro classes exist +└────────┬────────┘ + │ Returns true/false for each hash + ↓ +┌─────────────────┐ +│ DevTools Panel │ Removes stale entries from macroData Map +│ │ macroData.delete(hash) for non-existent elements +└─────────────────┘ +``` + +#### Flow 4: Automatic Updates on className Changes (MutationObserver) + +When you select an element, the DevTools panel automatically watches for className changes and refreshes the panel. + +``` +┌─────────────────┐ +│ DevTools Panel │ User selects element in Elements panel +└────────┬────────┘ + │ chrome.devtools.panels.elements.onSelectionChanged + │ + │ chrome.devtools.inspectedWindow.eval(` + │ // Disconnect old observer (if any) + │ if (window.__styleMacroObserver) { + │ window.__styleMacroObserver.disconnect(); + │ } + │ + │ // Create new MutationObserver on $0 + │ window.__styleMacroObserver = new MutationObserver(() => { + │ window.postMessage({ + │ action: 'stylemacro-class-changed', + │ elementId: $0.__devtoolsId + │ }, '*'); + │ }); + │ + │ window.__styleMacroObserver.observe($0, { + │ attributes: true, + │ attributeFilter: ['class'] + │ }); + │ `) + ↓ +┌─────────────────┐ +│ Page DOM │ MutationObserver active on selected element +└────────┬────────┘ + │ + │ ... User interacts with page, element's className changes ... + │ + │ MutationObserver detects class attribute change + │ window.postMessage({ action: 'stylemacro-class-changed', elementId }, '*') + ↓ +┌─────────────────┐ +│ Content Script │ Receives window message, forwards to extension +└────────┬────────┘ + │ chrome.runtime.sendMessage({ action: 'stylemacro-class-changed', elementId }) + ↓ +┌─────────────────┐ +│ Background │ Looks up DevTools connection for tabId +└────────┬────────┘ + │ port.postMessage({ action: 'stylemacro-class-changed', elementId }) + ↓ +┌─────────────────┐ +│ DevTools Panel │ Verifies elementId matches currently selected element +│ │ Triggers full panel refresh (re-reads classes, re-queries macros) +└─────────────────┘ + +When selection changes or panel closes: + ↓ +┌─────────────────┐ +│ DevTools Panel │ Calls disconnectObserver() +└────────┬────────┘ + │ chrome.devtools.inspectedWindow.eval(` + │ if (window.__styleMacroObserver) { + │ window.__styleMacroObserver.disconnect(); + │ window.__styleMacroObserver = null; + │ } + │ `) + ↓ +┌─────────────────┐ +│ Page DOM │ Old observer disconnected, new observer created for new selection +└─────────────────┘ +``` + +**Key Benefits:** +- Panel automatically refreshes when element classes change (e.g., hover states, conditional styles) +- No manual refresh needed +- Observer is cleaned up properly to prevent memory leaks +- Each element has its own unique tracking ID to prevent cross-contamination + +### Key Technical Details + +#### Why Background Script is Needed +Chrome extensions prevent direct communication between DevTools and content scripts for security reasons. The background script acts as a trusted intermediary. + +#### Static vs Dynamic Macros + +The style macro generates different class name patterns based on whether the styles can change at runtime: + +**Static Macros** (`-macro-static-{hash}`): +- Used when all style conditions are static (e.g., `style({ color: 'red' })`) +- Macro data is embedded in CSS as a uniquely-named custom property: `--macro-data-{hash}: '{...JSON...}'` +- DevTools reads the specific custom property via `getComputedStyle($0).getPropertyValue('--macro-data-{hash}')` +- Unique naming avoids CSS cascade issues when multiple macros are applied to the same element + +**Dynamic Macros** (`-macro-dynamic-{hash}`): +- Used when style conditions can change (e.g., `style({color: {default: 'blue', isActive: 'red'}})`) +- Macro data is sent via `window.postMessage({ action: 'stylemacro-update-macros', ... })` whenever conditions change +- Content script forwards data to DevTools, which stores it in a local Map +- Enables real-time updates when props/state change + +#### Data Storage +- **Static Macros**: Data embedded in CSS as uniquely-named custom properties `--macro-data-{hash}`, read via `getComputedStyle($0).getPropertyValue('--macro-data-{hash}')` + - Each macro has its own custom property name to prevent cascade conflicts + - Example: `.-macro-static-abc123 { --macro-data-abc123: '{"style": {...}, "loc": "..."}'; }` +- **Dynamic Macros**: Data stored in DevTools panel's `macroData` Map +- **No Content Script Storage**: Content script only forwards messages, doesn't store macro data +- **Lifetime**: Macro data persists in DevTools for the duration of the DevTools session +- **Cleanup**: Stale macro data (for elements no longer in DOM) is removed every 5 minutes + +#### Connection Management +- **DevTools → Background**: Uses persistent `chrome.runtime.connect()` with port-based messaging +- **Content Script → Background**: Uses one-time `chrome.runtime.sendMessage()` calls +- **Background tracks**: Map of `tabId → DevTools port` for routing messages + +#### Data Structure + +**Static Macros (in CSS):** +```css +.-macro-static-zsZ9Dc { + --macro-data-zsZ9Dc: '{"style":{"paddingX":"4"},"loc":"packages/@react-spectrum/s2/src/Button.tsx:67"}'; +} +``` + +**Dynamic Macros (in DevTools panel's macroData Map):** +```javascript +Map { + "zsZ9Dc" => { + loc: "packages/@react-spectrum/s2/src/Button.tsx:67", + style: { + "paddingX": "4", + // ... more CSS properties + } + } +} +``` + +**Note**: +- Static macro data is stored in CSS with uniquely-named custom properties +- Dynamic macro data is stored directly in the DevTools panel context +- The content script acts purely as a message forwarder and doesn't store any data + +#### Message Types + +| Message Type | Direction | Purpose | +|-------------|-----------|---------| +| `stylemacro-update-macros` | Page → Content → Background → DevTools | Send macro data (hash, loc, style) to be stored in DevTools | +| `stylemacro-init` | DevTools → Background | Establish connection with tabId | +| `stylemacro-class-changed` | Page → Content → Background → DevTools | Notify that selected element's className changed | + +### Debugging + +Enable debug logs by uncommenting the `console.log()` lines in each component: +- **DevTools Panel**: `devtool.js` → `debugLog()` function +- **Content Script**: `content-script.js` → `debugLog()` function +- **Background Script**: Already logging to service worker console + +View logs in: +- **Page Console**: Content Script and DevTools Panel logs (with `[Content Script]` and `[DevTools]` prefixes) +- **Service Worker Console**: Background Script logs (go to `chrome://extensions` → click "service worker") + diff --git a/packages/dev/style-macro-chrome-plugin/package.json b/packages/dev/style-macro-chrome-plugin/package.json new file mode 100644 index 00000000000..93248a0d6db --- /dev/null +++ b/packages/dev/style-macro-chrome-plugin/package.json @@ -0,0 +1,25 @@ +{ + "name": "style-macro-chrome-plugin", + "version": "0.1.0", + "scripts": { + "start": "parcel watch src/manifest.json --host localhost --config .parcelrc", + "build": "parcel build src/manifest.json --config .parcelrc" + }, + "devDependencies": { + "@parcel/config-default": "^2.16.3", + "@parcel/config-webextension": "^2.16.3", + "@parcel/core": "^2.16.3", + "@parcel/transformer-js": "^2.16.3", + "parcel": "^2.16.3" + }, + "rsp": { + "type": "cli" + }, + "repository": { + "type": "git", + "url": "https://github.com/adobe/react-spectrum" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/dev/style-macro-chrome-plugin/src/background.js b/packages/dev/style-macro-chrome-plugin/src/background.js new file mode 100644 index 00000000000..6524078c5a0 --- /dev/null +++ b/packages/dev/style-macro-chrome-plugin/src/background.js @@ -0,0 +1,51 @@ +// Keep track of DevTools connections per tab +const devtoolsConnections = new Map(); + +// Listen for connections from DevTools +chrome.runtime.onConnect.addListener((port) => { + if (port.name === 'devtools-page') { + let tabId; + + // Listen for messages from DevTools + const messageListener = (message) => { + if (message.type === 'stylemacro-init') { + tabId = message.tabId; + devtoolsConnections.set(tabId, port); + console.log(`[Background] DevTools connected for tab ${tabId}`); + } + }; + + port.onMessage.addListener(messageListener); + + // Clean up when DevTools disconnects + port.onDisconnect.addListener(() => { + if (tabId) { + devtoolsConnections.delete(tabId); + console.log(`DevTools disconnected for tab ${tabId}`); + } + }); + } +}); + +// Listen for messages from content scripts +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + const tabId = sender.tab?.id; + + if (!tabId) { + return; + } + + // Forward messages from content script to DevTools + if (message.action === 'stylemacro-update-macros' || message.action === 'stylemacro-class-changed') { + console.log(`[Background] Forwarding ${message.action} from content script to DevTools, tabId: ${tabId}`); + const devtoolsPort = devtoolsConnections.get(tabId); + if (devtoolsPort) { + devtoolsPort.postMessage(message); + } else { + console.warn(`[Background] No DevTools connection found for tab ${tabId}`); + } + } + + return false; // Don't keep channel open +}); + diff --git a/packages/dev/style-macro-chrome-plugin/src/content-script.js b/packages/dev/style-macro-chrome-plugin/src/content-script.js new file mode 100644 index 00000000000..be2418379bf --- /dev/null +++ b/packages/dev/style-macro-chrome-plugin/src/content-script.js @@ -0,0 +1,62 @@ + +if (window.__macrosLoaded) { + return; +} +window.__macrosLoaded = true; + +let debugLog = (...args) => { + // console.log('[Content Script]', ...args); +}; + +window.addEventListener('message', function (event) { + // Only accept messages from the same frame + if (event.source !== window) { + return; + } + + var message = event.data; + + // Only accept messages that we know are ours. Note that this is not foolproof + // and the page can easily spoof messages if it wants to. + if (message && typeof message === 'object') { + if (message.action === 'stylemacro-update-macros') { + debugLog('Forwarding stylemacro-update-macros for hash:', message.hash); + + // if this script is run multiple times on the page, then only handle it once + event.stopImmediatePropagation(); + event.stopPropagation(); + + // Forward message directly to background script (which forwards to DevTools) + try { + chrome.runtime.sendMessage({ + action: 'stylemacro-update-macros', + hash: message.hash, + loc: message.loc, + style: message.style + }); + } catch (err) { + debugLog('Failed to send stylemacro-update-macros message:', err); + } + } else if (message.action === 'stylemacro-class-changed') { + // Forward class-changed messages from page context to background script + debugLog('Forwarding stylemacro-class-changed for element:', message.elementId); + + // if this script is run multiple times on the page, then only handle it once + event.stopImmediatePropagation(); + event.stopPropagation(); + + try { + chrome.runtime.sendMessage({ + action: 'stylemacro-class-changed', + elementId: message.elementId + }); + } catch (err) { + debugLog('Failed to send stylemacro-class-changed message:', err); + } + } + } +}); + +// No longer need to listen for get-macro requests or manage cleanup +// since macro data is stored directly in DevTools + diff --git a/packages/dev/style-macro-chrome-plugin/src/devtool.js b/packages/dev/style-macro-chrome-plugin/src/devtool.js new file mode 100644 index 00000000000..7587534a07f --- /dev/null +++ b/packages/dev/style-macro-chrome-plugin/src/devtool.js @@ -0,0 +1,268 @@ + +chrome.devtools.panels.elements.createSidebarPane('Style Macros', (sidebar) => { + sidebar.setObject({}); + + // Helper function to log to both DevTools-for-DevTools console and inspected page console + const debugLog = (...args) => { + // console.log(...args); // Logs to DevTools-for-DevTools console + // const message = args.map(arg => + // typeof arg === 'object' ? JSON.stringify(arg) : String(arg) + // ).join(' '); + // chrome.devtools.inspectedWindow.eval(`console.log('[DevTools]', ${JSON.stringify(message)})`); + }; + + const backgroundPageConnection = chrome.runtime.connect({name: 'devtools-page'}); + + // Monitor connection status + backgroundPageConnection.onDisconnect.addListener(() => { + debugLog('ERROR: Background connection disconnected!', chrome.runtime.lastError); + // Clean up observer when connection is lost + disconnectObserver(); + }); + + // Initialize connection with the background script + debugLog('Initializing connection with tabId:', chrome.devtools.inspectedWindow.tabId); + backgroundPageConnection.postMessage({ + type: 'stylemacro-init', + tabId: chrome.devtools.inspectedWindow.tabId + }); + debugLog('Init message sent to background'); + + // Store macro data directly in DevTools + const macroData = new Map(); + + // Track mutation observer for selected element + let currentObserver = null; + let currentElementId = null; + + // Listen for messages from content script (via background script) + backgroundPageConnection.onMessage.addListener((message) => { + debugLog('Message from background:', message); + + if (message.action === 'stylemacro-update-macros') { + debugLog('Received stylemacro-update-macros for hash:', message.hash); + // Store the macro data directly in DevTools + macroData.set(message.hash, { + loc: message.loc, + style: message.style + }); + debugLog('Stored macro data, total macros:', macroData.size); + // Refresh the panel to show updated data + update(); + } else if (message.action === 'stylemacro-class-changed') { + debugLog('Received stylemacro-class-changed notification for element:', message.elementId); + // Only update if the changed element is the one we're currently watching + if (message.elementId === currentElementId) { + debugLog('Class changed on watched element, updating panel...'); + update(); + } + } + }); + + // Get macro data from local storage + const getDynamicMacroData = (hash) => { + debugLog('Looking up dynamic macro with hash:', hash); + const data = macroData.get(hash); + debugLog('Found data:', !!data); + return data || null; + }; + + function getMacroData(className) { + let promise = new Promise((resolve) => { + debugLog('Getting macro data for:', className); + chrome.devtools.inspectedWindow.eval(`window.getComputedStyle($0).getPropertyValue("--macro-data-${className}")`, (style) => { + debugLog('Got style:', style); + resolve(style ? JSON.parse(style) : null); + }); + }); + return promise; + } + + // Function to disconnect the current observer + const disconnectObserver = () => { + if (currentObserver) { + chrome.devtools.inspectedWindow.eval(` + if (window.__styleMacroObserver) { + window.__styleMacroObserver.disconnect(); + window.__styleMacroObserver = null; + } + `); + debugLog('Disconnected mutation observer for element:', currentElementId); + currentObserver = null; + currentElementId = null; + } + }; + + // Function to start observing the currently selected element + const startObserving = () => { + // First disconnect any existing observer + disconnectObserver(); + + // Generate a unique ID for the current element + chrome.devtools.inspectedWindow.eval(` + (function() { + const element = $0; + if (!element || !element.classList) { + return null; + } + + // Generate a unique ID if element doesn't have one + if (!element.__devtoolsId) { + element.__devtoolsId = 'dt-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); + } + + const elementId = element.__devtoolsId; + + // Create mutation observer + if (window.__styleMacroObserver) { + window.__styleMacroObserver.disconnect(); + } + + window.__styleMacroObserver = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type === 'attributes' && mutation.attributeName === 'class') { + // Notify DevTools that the class has changed via window.postMessage + // (chrome.runtime is not available in page context) + window.postMessage({ + action: 'stylemacro-class-changed', + elementId: elementId + }, '*'); + break; + } + } + }); + + window.__styleMacroObserver.observe(element, { + attributes: true, + attributeFilter: ['class'] + }); + + return elementId; + })(); + `, (result, isException) => { + if (isException) { + debugLog('Error setting up mutation observer:', result); + } else if (result) { + currentElementId = result; + currentObserver = true; // Just track that we have an observer + debugLog('Started observing element:', currentElementId); + } + }); + }; + + let update = () => { + debugLog('Starting update...'); + chrome.devtools.inspectedWindow.eval('$0.getAttribute("class")', (className) => { + debugLog('Got className:', className); + + // Handle the async operations outside the eval callback + (async () => { + if (typeof className !== 'string') { + sidebar.setObject({}); + return; + } + + let staticMacroHashes = [...className.matchAll(/-macro-static-([^\s]+)/g)].map(m => m[1]); + let dynamicMacroHashes = [...className.matchAll(/-macro-dynamic-([^\s]+)/g)].map(m => m[1]); + debugLog('Static macro hashes:', staticMacroHashes); + debugLog('Dynamic macro hashes:', dynamicMacroHashes); + + // Get static macro data (async from CSS) + let staticMacros = staticMacroHashes.map(macro => getMacroData(macro)); + + debugLog('Waiting for', staticMacros.length, 'static macros...'); + let staticResults = await Promise.all(staticMacros); + + // Get dynamic macro data (sync from local storage) + let dynamicResults = dynamicMacroHashes.map(hash => getDynamicMacroData(hash)); + + // Combine results + let results = [...staticResults, ...dynamicResults]; + debugLog('Results:', results); + + // Filter out null results (missing data) + results = results.filter(r => r != null); + debugLog('Filtered results:', results); + + if (results.length === 0) { + sidebar.setObject({}); + } else if (results.length === 1) { + sidebar.setObject(results[0].style ?? {}, results[0].loc); + } else { + let seenProperties = new Set(); + for (let i = results.length - 1; i >= 0; i--) { + for (let key in results[i].style) { + if (seenProperties.has(key)) { + delete results[i].style[key]; + } else { + seenProperties.add(key); + } + } + } + + let res = {}; + for (let result of results) { + res[result.loc] = result.style; + } + sidebar.setObject(res); + } + })(); + }); + }; + + chrome.devtools.panels.elements.onSelectionChanged.addListener(() => { + debugLog('Element selection changed'); + // Start observing the newly selected element + startObserving(); + // Update the panel with the new element's macros + update(); + }); + + // Initial observation when the panel is first opened + startObserving(); + + // Cleanup stale macro data every 5 minutes + const CLEANUP_INTERVAL = 1000 * 60 * 5; + setInterval(() => { + if (macroData.size === 0) { + return; + } + + debugLog('Running macro data cleanup, checking', macroData.size, 'macros...'); + const hashes = Array.from(macroData.keys()); + + // Check all hashes in a single eval for efficiency + const checkScript = ` + (function() { + const hashes = ${JSON.stringify(hashes)}; + const results = {}; + for (const hash of hashes) { + results[hash] = !!document.querySelector('.-macro-dynamic-' + hash); + } + return results; + })(); + `; + + chrome.devtools.inspectedWindow.eval(checkScript, (results, isException) => { + if (isException) { + debugLog('Error during cleanup:', results); + return; + } + + let removedCount = 0; + for (const hash in results) { + if (!results[hash]) { + debugLog('Removing stale macro:', hash); + macroData.delete(hash); + removedCount++; + } + } + + if (removedCount > 0) { + debugLog(`Cleaned up ${removedCount} stale macro(s). Remaining: ${macroData.size}`); + } else { + debugLog('No stale macros found.'); + } + }); + }, CLEANUP_INTERVAL); +}); diff --git a/packages/dev/style-macro-chrome-plugin/src/devtools.html b/packages/dev/style-macro-chrome-plugin/src/devtools.html new file mode 100644 index 00000000000..6f5f400e09b --- /dev/null +++ b/packages/dev/style-macro-chrome-plugin/src/devtools.html @@ -0,0 +1,7 @@ + + + + Devtools! + + + diff --git a/packages/dev/style-macro-chrome-plugin/src/icons/128.png b/packages/dev/style-macro-chrome-plugin/src/icons/128.png new file mode 100644 index 00000000000..a0a1377e1a9 Binary files /dev/null and b/packages/dev/style-macro-chrome-plugin/src/icons/128.png differ diff --git a/packages/dev/style-macro-chrome-plugin/src/icons/16.png b/packages/dev/style-macro-chrome-plugin/src/icons/16.png new file mode 100644 index 00000000000..41a22d0a311 Binary files /dev/null and b/packages/dev/style-macro-chrome-plugin/src/icons/16.png differ diff --git a/packages/dev/style-macro-chrome-plugin/src/icons/32.png b/packages/dev/style-macro-chrome-plugin/src/icons/32.png new file mode 100644 index 00000000000..493110735d2 Binary files /dev/null and b/packages/dev/style-macro-chrome-plugin/src/icons/32.png differ diff --git a/packages/dev/style-macro-chrome-plugin/src/icons/48.png b/packages/dev/style-macro-chrome-plugin/src/icons/48.png new file mode 100644 index 00000000000..6794ef2da83 Binary files /dev/null and b/packages/dev/style-macro-chrome-plugin/src/icons/48.png differ diff --git a/packages/dev/style-macro-chrome-plugin/src/icons/96.png b/packages/dev/style-macro-chrome-plugin/src/icons/96.png new file mode 100644 index 00000000000..b12d2c4a2f0 Binary files /dev/null and b/packages/dev/style-macro-chrome-plugin/src/icons/96.png differ diff --git a/packages/dev/style-macro-chrome-plugin/src/manifest.json b/packages/dev/style-macro-chrome-plugin/src/manifest.json new file mode 100644 index 00000000000..6d3bfaf858a --- /dev/null +++ b/packages/dev/style-macro-chrome-plugin/src/manifest.json @@ -0,0 +1,23 @@ +{ + "manifest_version": 3, + "name": "Style Macro DevTools", + "version": "0.0.1", + "devtools_page": "devtools.html", + "background": { + "service_worker": "background.js" + }, + "content_scripts": [{ + "matches": ["*://*/*"], + "js": ["content-script.js"], + "all_frames": true, + "run_at": "document_idle" + }], + "permissions": ["tabs"], + "icons": { + "16": "icons/16.png", + "32": "icons/32.png", + "48": "icons/48.png", + "96": "icons/96.png", + "128": "icons/128.png" + } +} diff --git a/yarn.lock b/yarn.lock index 6500c7bc175..f0d37c29a41 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4255,6 +4255,21 @@ __metadata: languageName: unknown linkType: soft +"@parcel/config-webextension@npm:^2.16.3": + version: 2.16.3 + resolution: "@parcel/config-webextension@npm:2.16.3" + dependencies: + "@parcel/config-default": "npm:2.16.3" + "@parcel/packager-webextension": "npm:2.16.3" + "@parcel/runtime-webextension": "npm:2.16.3" + "@parcel/transformer-raw": "npm:2.16.3" + "@parcel/transformer-webextension": "npm:2.16.3" + peerDependencies: + "@parcel/core": ^2.16.3 + checksum: 10c0/4136d4b9be3cd97a19ebaebc2cacc1539b25ac23af726e5619a2f79c262e3e7af835d4b34b1d5dbce5a8154ad56cf5cdf3a8bf33e6e399125cb5a4cbb639c55b + languageName: node + linkType: hard + "@parcel/core@npm:2.16.3, @parcel/core@npm:^2.16.0, @parcel/core@npm:^2.16.3": version: 2.16.3 resolution: "@parcel/core@npm:2.16.3" @@ -4604,6 +4619,17 @@ __metadata: languageName: node linkType: hard +"@parcel/packager-webextension@npm:2.16.3": + version: 2.16.3 + resolution: "@parcel/packager-webextension@npm:2.16.3" + dependencies: + "@parcel/plugin": "npm:2.16.3" + "@parcel/utils": "npm:2.16.3" + nullthrows: "npm:^1.1.1" + checksum: 10c0/8556362f167836970681b1eece6faaef581a2a6cc68c089fa460be895baf702011ced76fd245d062193bed38a785341c469960c3304556df0b8e7787916983b9 + languageName: node + linkType: hard + "@parcel/plugin@npm:2.16.3, @parcel/plugin@npm:^2.0.0, @parcel/plugin@npm:^2.16.0, @parcel/plugin@npm:^2.16.3": version: 2.16.3 resolution: "@parcel/plugin@npm:2.16.3" @@ -4765,6 +4791,17 @@ __metadata: languageName: node linkType: hard +"@parcel/runtime-webextension@npm:2.16.3": + version: 2.16.3 + resolution: "@parcel/runtime-webextension@npm:2.16.3" + dependencies: + "@parcel/plugin": "npm:2.16.3" + "@parcel/utils": "npm:2.16.3" + nullthrows: "npm:^1.1.1" + checksum: 10c0/74429e2de0e1b2630ef98eb243de1e3d6ae3c0731a892de4ff362b053b386295ec612f737ec88a951eb3eec8a8988dbe04dad84e21f9a258884b438224afb61b + languageName: node + linkType: hard + "@parcel/rust-darwin-arm64@npm:2.16.3": version: 2.16.3 resolution: "@parcel/rust-darwin-arm64@npm:2.16.3" @@ -5094,6 +5131,19 @@ __metadata: languageName: node linkType: hard +"@parcel/transformer-webextension@npm:2.16.3": + version: 2.16.3 + resolution: "@parcel/transformer-webextension@npm:2.16.3" + dependencies: + "@mischnic/json-sourcemap": "npm:^0.1.1" + "@parcel/diagnostic": "npm:2.16.3" + "@parcel/plugin": "npm:2.16.3" + "@parcel/utils": "npm:2.16.3" + content-security-policy-parser: "npm:^0.6.0" + checksum: 10c0/c11ab77923d0f0cef7390bfee7b7b5c4c7d0ab1853664ea40467478af8910ee31444dac6b6337d1ee380ff963f920a6473a470d35a19c1ed86526b912117c873 + languageName: node + linkType: hard + "@parcel/ts-utils@npm:2.16.3": version: 2.16.3 resolution: "@parcel/ts-utils@npm:2.16.3" @@ -13711,6 +13761,13 @@ __metadata: languageName: node linkType: hard +"content-security-policy-parser@npm:^0.6.0": + version: 0.6.0 + resolution: "content-security-policy-parser@npm:0.6.0" + checksum: 10c0/f494e69020e2320179eab47ad2cdafb09752ed63ca4fb5445071381e392d19edd110c0c3ec43f135d27c34b49dbab851b7fcf188dd2ba30cacd6e1107b15b674 + languageName: node + linkType: hard + "content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" @@ -27286,6 +27343,18 @@ __metadata: languageName: node linkType: hard +"style-macro-chrome-plugin@workspace:packages/dev/style-macro-chrome-plugin": + version: 0.0.0-use.local + resolution: "style-macro-chrome-plugin@workspace:packages/dev/style-macro-chrome-plugin" + dependencies: + "@parcel/config-default": "npm:^2.16.3" + "@parcel/config-webextension": "npm:^2.16.3" + "@parcel/core": "npm:^2.16.3" + "@parcel/transformer-js": "npm:^2.16.3" + parcel: "npm:^2.16.3" + languageName: unknown + linkType: soft + "style-to-object@npm:^0.3.0": version: 0.3.0 resolution: "style-to-object@npm:0.3.0"