twind
twind copied to clipboard
[Feature]: nextjs without shim mode
Describe the problem
Trying to use nextjs and twind without the shim mode isn't very easy.
Things that would be great to have:
- call
setup(config)
once on the server side and once on the client side - have one global
tw
instance (with a config provided by setup call) - no shim mode, no
consume(..)
calls, no DOM observer, justtw(..)
calls - have resume data present in the generated styles sent by the server
Describe the proposed solution
I've had success getting all of the above to work, but having this be built-in would be better
An API like this could work:
// exports: { install, installWithoutShim }, default: install
import { installWithoutShim } from '@twind/with-next/app'
import config from '../twind.config'
export default installWithoutShim(config)
// export default installWithoutShim(config, MyApp)
and
// exports: { install, installWithoutShim }, default: install
import { installWithoutShim } from '@twind/with-next/document'
import config from '../twind.config'
export default installWithoutShim(config)
// export default installWithoutShim(config, MyDocument)
Alternatives considered
No response
Importance
would make my life easier
Additional Information
the setup I came up with is the following:
export default function withNextApp<Base>(config: TwindConfig, BaseApp: Base): Base {
if (typeof window !== 'undefined') {
/*
using a fake element as the target argument here is necessary.
setup will internally call observe which has a default target of document.documentElement
which gets used when the argument is not supplied, meaning that passing
`undefined` will just result in the default target being used.
we don't want anything to be observed but still want to use the global twind instance.
*/
const fakeObserverTarget = document.createElement('body')
/*
this forces the usage of a CSSOM sheet and enables "resuming".
the resume function is the reason getSheet is used instead of just cssom() as getSheet
sets the resume function which is not possible otherwise as `@twind/core` doesn't export it
*/
const sheet = getSheet(false, false)
setup(config, sheet, fakeObserverTarget)
}
return BaseApp
}
export default function withNextDocument<Component extends typeof Document = typeof Document>(
config: TwindConfig,
BaseDocument: Component = Document as Component
): Component {
let instance: ReturnType<typeof setup>
try {
instance = setup(config, virtual(true))
} catch (e) {
console.warn(e)
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return class extends BaseDocument {
static getInitialProps(ctx: DocumentContext & {
defaultGetInitialProps: (ctx: DocumentContext, options?: { nonce?: string }) => Promise<DocumentInitialProps>
}) {
const defaultGetInitialProps = ctx.defaultGetInitialProps.bind(ctx)
ctx.defaultGetInitialProps = async (ctx, options: { nonce?: string } = {}) => {
const props = await defaultGetInitialProps(ctx, options)
const css = stringify(instance.target)
const styleElement = createElement('style', {
'data-twind': '',
id: '__twind',
key: 'twind',
nonce: options.nonce,
dangerouslySetInnerHTML: {
__html: css
}
})
return { ...props, styles: [...props.styles, styleElement] }
}
return super.getInitialProps(ctx)
}
}
}
Makes sense and I like your solution.I will see what I can implement.
I just want to place a note here regarding Next 13 with app directory — seeing support for this too would be incredible, though I'm sure at this point it's pretty much entirely dependent on the Next team to implement ways for libraries to easily work with it. Just wanted to express my interest in a solution here.
@tylerforesthauser
It will not be easy because nextjs does not expose any way to intercept the response html or stream (then we could just use @twind/with-react). I'm playing around with a twind jsx runtime — a thin wrapper to track used classNames directly as the virtual dom nodes are created. Not sure yet. It has some downside (perf, context tracking, prevent use of other jsx runtimes, ...)
Fair enough. I've been trying to follow along with their development as I feel they really need to expose this to support major CSS-in-JS players such as Emotion and whatnot. So hopefully that happens eventually. 🤷♂️
It would be good to be able to deactivate the shim functionality not only on nextjs, with an option in the config object
export default defineConfig({
shim: false
})
Hey folks. This issue hasn't received any traction for 90 days, so we're going to close this for housekeeping. If this is still an ongoing issue, please do consider contributing a Pull Request to resolve it. Further discussion is always welcome even with the issue closed. If anything actionable is posted in the comments, we'll consider reopening it. ⓘ