I tried to add scoped CSS plug-in to preact. But I have a problem. Can i get the component instance in "vnode hook" function?
import { options } from 'preact'
options.vnode = (vnode, component) =>{
//generate scoped attr by component
//add attr to vnode
}
Thanks!
-dntzhang
The options.vnode hook is executed as part of h/createElement and that runs way before any component instances are created.
There is options.diffed(vnode) which runs after the component is diffed and rendered. If that's too late for you we have a private hook that isn't made public yet as __r (=_render) which is called right before render.
Let me know if that's the one you were looking for. We're a bit conservative with making more public until there's a valid use case, because it increases maintenance area. That's why we love to know about yours :)
We haven't made that hook public yet, but you could use th
h runs in the render function, it's after any component instances are created in preact 8. I'm going to write the scoped CSS plug-in for preact 8 first. It would be best if both versions were supported.
I need the hook. My plan:
preact-scoped-css.js
options.__r = (vnode, component) => {
//generate scoped attr
//set scoped atrr to vnode
//set scoped attr to component
}
options.afterMount = (component) => {
//get css string from component.css
//made the css scoped by scoped atrr and css string of component
//add scoped css to the head
}
Although there is a slight runtime overhead, but most projects are worth it. And your CSS can be written simply enough such as h1 { color: red }.
usage:
import 'preact-scoped-css'
//to string loader
import cssStr from './_index.css'
Component.css = cssStr
@dntzhang what if you used a Component instead of a vnode plugin? Then you could install the component and use its lifecycle, which would be much easier:
import { options } from 'preact';
// For Preact 8
const old = options.vnode;
options.vnode = vnode => {
// <style scoped>css</style> âž¡ <Style>css</Style>
if (vnode.nodeName==='style' && vnode.attributes.scoped) {
vnode.nodeName = Style;
}
if (old) old(vnode);
};
class Style extends Component {
componentDidMount() {
this.style = document.createElement('style');
this.style.textContent = [].concat(props.children).join('\n');
document.head.appendChild(this.style);
}
render() {
return null;
}
}
But how to add scoped attr to the dom in the runtime.
Usage:
<>
<h1>I am red color</h1>
<style scoped>
{`h1 {
color: red;
}`}
</style>
</>
Rendered:
<head>
h1[_ss1]{
color: red;
}
</head>
<h1 _ss1>I am red color</h1>
Pure css string is very useful. For example, extend rpx unit of css:
function rpx(css) {
return css.replace(/([1-9]\d*|0)(\.\d*)*rpx/g, (a, b) => {
return (window.innerWidth * Number(b)) / 750 + 'px'
})
}
<>
<h1>I am red color</h1>
<style scoped>
{rpx(`h1 {
color: 20rpx;
}`)}
</style>
</>
I want to use rpx + scoped style in preact, but no way was found.
https://github.com/Tencent/omi/blob/master/packages/react-snake/src/components/index/index.js
This is omi snake source code and demo.
@dntzhang technically there is a reference to the component instance associated with a given VNode, as a ._component property (compressed in production as .__c). However, VNodes are created long before they can be associated with a component, so this is not a reliable method of associating vnodes and components during the options.vnode hook.
The workaround is to store the component's associated VNode, then reference it dynamically in your options.vnode hook:
import { options } from 'preact';
let componentNode;
// store a reference to the "current component" vnode
let oldDiff = options._diff || options.__b;
options._diff = options.__b = vnode => {
componentNode = vnode;
if (oldDiff) oldDiff(vnode);
};
// reset component reference at end of diffing:
let oldDiffed = options.diffed;
options.diffed = vnode => {
if (componentNode === vnode) componentNode = null;
if (oldDiffed) oldDiffed(vnode);
};
// our vnode hook looks up the associated component
let old = options.vnode;
options.vnode = vnode => {
const component = componentNode && (componentNode._component || componentNode.__c);
if (component) {
// component is the component instance
component.css;
// example: assign component's unique CSS ID:
(vnode.props || (vnode.props = {}))[component._styleId] = '';
}
if (old) old(vnode);
};
@developit I wonder if we should make the options.diff hook public
https://github.com/Tencent/omi/tree/master/packages/preact-css
Done. Many thanks to @developit and @marvinhagemeister .
Most helpful comment
@dntzhang technically there is a reference to the component instance associated with a given VNode, as a
._componentproperty (compressed in production as.__c). However, VNodes are created long before they can be associated with a component, so this is not a reliable method of associating vnodes and components during theoptions.vnodehook.The workaround is to store the component's associated VNode, then reference it dynamically in your
options.vnodehook: