twind icon indicating copy to clipboard operation
twind copied to clipboard

[Feature]: nextjs without shim mode

Open JaninaWibker opened this issue 2 years ago • 5 comments

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, just tw(..) 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)
    }
  }
}

JaninaWibker avatar Jan 05 '23 17:01 JaninaWibker

Makes sense and I like your solution.I will see what I can implement.

sastan avatar Jan 06 '23 22:01 sastan

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 avatar Jan 12 '23 03:01 tylerforesthauser

@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, ...)

sastan avatar Jan 13 '23 21:01 sastan

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. 🤷‍♂️

tylerforesthauser avatar Jan 13 '23 23:01 tylerforesthauser

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
})

Dav3rs avatar Jun 22 '23 20:06 Dav3rs

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.

stale[bot] avatar Apr 18 '24 07:04 stale[bot]