[plugin] plugin to support unminifying `goober` CSS-in-JS library patterns + related JSX decompilation
Mostly creating this based on the exploration I did in https://github.com/j4k0xb/webcrack/issues/10#issuecomment-2693645060 in case there is no generic way to solve that in core, and it needs to be a more library specific plugin solution as per https://github.com/j4k0xb/webcrack/issues/143#issuecomment-2692345330 / https://github.com/j4k0xb/webcrack/issues/143#issuecomment-2692517232
This is also aligned to wakaru's proposed module-detection feature:
- https://github.com/pionxzh/wakaru/issues/41
@j4k0xb I also don't expect this to be something you create; but figured since I already did the deeper exploration in this repo, I may as well create a standalone reference point for it, even if this issue ends up getting closed.
From my prior exploration:
Edit 3: Looking at the code from https://github.com/j4k0xb/webcrack/issues/10#issuecomment-2692599211 again, I think there is another case where JSX-like things may not be currently getting decompiled properly, which is syntax like this:
/* ..snip.. */ /* 541 */ var Z = h("div")` /* 542 */ display: flex; /* 543 */ justify-content: center; /* 544 */ margin: 4px 10px; /* 545 */ color: inherit; /* 546 */ flex: 1 1 auto; /* 547 */ white-space: pre-line; /* 548 */ `; /* ..snip.. */ /* 567 */ let c = t.createElement(Z, { /* 568 */ ...e.ariaProps /* 569 */ }, g(e.message, e)); /* ..snip.. */Looking higher up in the file, we see the definition for
h:/* ..snip.. */ /* 106 */ function h(e, t) { /* 107 */ let l = this || {}; /* 108 */ return function () { /* 109 */ let i = arguments; /* 110 */ function n(a, o) { /* 111 */ let c = Object.assign({}, a); /* 112 */ let s = c.className || n.className; /* 113 */ l.p = Object.assign({ /* 114 */ theme: p && p() /* 115 */ }, c); /* 116 */ l.o = / *go\d+/.test(s); /* 117 */ c.className = m.apply(l, i) + (s ? " " + s : ""); /* 118 */ if (t) { /* 119 */ c.ref = o; /* 120 */ } /* 121 */ let r = e; /* 122 */ if (e[0]) { /* 123 */ r = c.as || e; /* 124 */ delete c.as; /* 125 */ } /* 126 */ if (w && r[0]) { /* 127 */ w(c); /* 128 */ } /* 129 */ return y(r, c); /* 131 */ } /* 132 */ if (t) { /* 133 */ return t(n); /* 134 */ } else { /* 135 */ return n; /* 136 */ } /* 137 */ }; /* 138 */ } /* ..snip.. */And searching GitHub code for
/ *go\d+/.testleads us to the
- https://github.com/search?type=code&q=%22%2F+*go%5Cd%2B%2F.test%22
- https://github.com/cristianbote/goober/blob/5f0b43976fac214262c2c8921b1691fc4729ec98/src/styled.js#L20-L71
- https://github.com/cristianbote/goober
goober, a less than 1KB css-in-js solution
- https://goober.rocks/
Which we can then also see additional confirmation for in earlier code as well:
- https://github.com/cristianbote/goober/blob/5f0b43976fac214262c2c8921b1691fc4729ec98/src/core/get-sheet.js#L11-L25
/* ..snip.. */ /* 6 */ let i = e => typeof window == "object" ? ((e ? e.querySelector("#_goober") : window._goober) || Object.assign((e || document.head).appendChild(document.createElement("style")), { /* 7 */ innerHTML: " ", /* 8 */ id: "_goober" /* 9 */ })).firstChild : e || l; /* ..snip.. */Which seems to be used across a number of libs/projects:
- https://github.com/search?type=code&q=%22%23_goober%22+OR+%22window._goober%22
Sometimes inlined directly:
- https://github.com/KevinVandy/tanstack-query/blob/69476f0ce5778afad4520ed42485b4110993afed/packages/query-devtools/src/utils.tsx#L305-L323
This may end up being another case where, similar to the comment made in https://github.com/j4k0xb/webcrack/issues/143#issuecomment-2692345330, the deeper specifics of this may belong in a separate plugin instead of
webcrackcore; but it makes me wonder if there is some kind of generic way we can identify a pattern of these sort of React component generator libraries so that the JSX decompilation can work effectively with them?Similar'ish prior art from
wakaru:
- https://github.com/pionxzh/wakaru/issues/40
- https://github.com/pionxzh/wakaru/issues/40#issuecomment-1809704264
- https://github.com/pionxzh/wakaru/issues/40#issuecomment-1809962543
Looking back at the main format of the
styledfunction (which wasZin the above code):
- https://github.com/cristianbote/goober/blob/5f0b43976fac214262c2c8921b1691fc4729ec98/src/styled.js#L15-L20
`styled(tag, forwardRef)
This returns an inner wrapper function, which seems to use tagged template literal syntax to provide the CSS, and then it reads that from the
argumentsinto_args:
- https://github.com/cristianbote/goober/blob/5f0b43976fac214262c2c8921b1691fc4729ec98/src/styled.js#L23-L24
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates
It then uses the
_argsto create the CSS class name:
- https://github.com/cristianbote/goober/blob/5f0b43976fac214262c2c8921b1691fc4729ec98/src/styled.js#L41-L43
And then processes the
tag(eg."div") passed to the original function:
- https://github.com/cristianbote/goober/blob/5f0b43976fac214262c2c8921b1691fc4729ec98/src/styled.js#L50-L59
Eventually 'rendering' that through the 'pragma'
h:
- https://github.com/cristianbote/goober/blob/5f0b43976fac214262c2c8921b1691fc4729ec98/src/styled.js#L66
Which was assigned during
setupearlier:
- https://github.com/cristianbote/goober/blob/5f0b43976fac214262c2c8921b1691fc4729ec98/src/styled.js#L4-L13
Tracing through the code in our bundle to find that 'pragma' function binding, we find
t.createElementends up being assigned toh(oryas it's called in our minified code):/* ..snip.. */ /* 582 */ (function (e, t, l, i) { /* 583 */ c.p = undefined; /* 584 */ y = e; /* 585 */ p = undefined; /* 586 */ w = undefined; /* 587 */ })(t.createElement); /* ..snip.. */And of course, we know that
trelates to our React global:/* ..snip.. */ /* 2 */ var t = window.React; /* ..snip.. */This obviously ends up going through a few extra steps of more library specific indirection that probably doesn't make sense to be in
webcrackcore.. but I wonder if we're able to trace/follow the React global /createElement'pragma' /hthrough so that JSX decompilation can work correctly?In the case of this library it also inserts the additional wrapping component
Styledin the middle.. but I think if thecreateElement'pragma' flowed through properly.. that might end up being properly figured out as nested JSX anyway; as theStyledjust ends up wrapping our providedtagcomponent:
- https://github.com/cristianbote/goober/blob/5f0b43976fac214262c2c8921b1691fc4729ec98/src/styled.js#L69
Originally posted by @0xdevalias in https://github.com/j4k0xb/webcrack/issues/10#issuecomment-2693645060
See Also
- https://github.com/j4k0xb/webcrack/issues/151