tw-in-js / use-twind-with

Twind integration packages for frameworks & libraries with examples
MIT License
67 stars 17 forks source link

LATE_SETUP_CALL error with Next.js #12

Open rstacruz opened 3 years ago

rstacruz commented 3 years ago

Hi there! Great work with Twind... it's an amazing concept and I am really loving the API.

I tried integrating it with Next.js + Webpack 5 and got this error:

info  - ready on http://localhost:3000
Error: [LATE_SETUP_CALL] {}
    at withTwindDocument (/home/rsc/@dev/@rstacruz/til-2021/node_modules/@twind/next/document/document.cjs:38:27)
    at eval (webpack-internal:///./pages/_document.js:42:123)
    at Object../pages/_document.js (/home/rsc/@dev/@rstacruz/til-2021/.next/server/pages/_document.js:22:1)
    ...

A lot of times it's not really causing any issues (pages continue to work) but sometimes they cause Internal Server Error to show up for some pages.

package.json ```json "dependencies": { "@twind/next": "^1.0.7", "next": "10.1.3", "next-mdx-remote": "^2.1.4", "twind": "^0.16.13", } ```
twind.config.js ```js import * as colors from 'twind/colors' /** @type {import('twind').Configuration} */ export default { // mode: 'strict', theme: { extend: { borderColor: { line: colors.gray['200'], }, textColor: { mute: colors.gray['600'], accent: colors.red['600'], }, }, fontFamily: { sans: ['Roboto', 'ui-sans', 'sans-serif'], serif: ['Eczar', 'ui-serif', 'Georgia', 'Cambria', 'sans-serif'], }, maxWidth: { article: '580px', }, }, } ```
pages/_document.js ```js import withTwindDocument from '@twind/next/document' import twindConfig from '../twind.config' export default withTwindDocument(twindConfig) ```
pages/_app.js ```js import twindConfig from '../twind.config' import withTwindApp from '@twind/next/app' import '../styles/globals.css' import '@fontsource/eczar/400.css' import '@fontsource/roboto/400.css' import '@fontsource/roboto/700.css' import { css, tw } from 'twind/css' function MyApp({ Component, pageProps }) { // tw( // css({ // ':global': { body: { '@apply': 'font-sans', }, // }, // }) // ) return } export default withTwindApp(twindConfig, MyApp) ```
next.config.js ```js module.exports = { future: { webpack5: true }, } ```
Andrewnt219 commented 3 years ago

For me, when using Webpack 5, the style is not pre-rendered on the server (which is what the setup for). A demo for this: issue https://codesandbox.io/s/magical-river-45guq?file=/pages/index.js

sastan commented 3 years ago

Sorry for not responding earlier. I try to look into this at the end of the week.

rodrigojmr commented 3 years ago

Unsure if this is the right issue to share this on. I also have this happen to values of a custom theme I've made, but only on a build and thus in production, not in development. It results in breaking classes that have mentions to these custom theme values, and thus breaking other styles as well. This happens at random during refresh, and most often during the first load of the site for any given client, in desktop, mobile, pagespeedinsights, etc.

brave_rFKXzmUBOO brave_y951KwrSCh

package.json ```json "dependencies": { "@twind/next": "^1.0.7", "@twind/react": "^0.0.4", "next": "^10.2.0", "twind": "^0.16.16", }, ```
Theme ```ts const config: Configuration = { preflight: (preflight, { theme }) => ({ ...preflight, html: { "scroll-behavior": "smooth", "scroll-padding": "4.5rem", }, "h1, h2, h3, h4, h5, h6": { fontFamily: "Montserrat", }, body: { fontFamily: "Gotham", }, // Modal background ".snipcart-modal__container, .snipcart-modal, .snipcart-cart-header": { backgroundColor: `${theme("colors.gray.100")} !important`, }, [etc...] prefix: true, theme: { extend: { fontFamily: { sans: ["Montserrat"], secondary: ["Gotham"] }, colors: { ...colors, transparent: "transparent", current: "currentColor", gray: { "50": "#f7f9f8", "70": "#eff5f7", "100": "#ebf1f4", "200": "#d2dfe8", "300": "#a7becb", "400": "#7397a6", "500": "#587684", "600": "#475b66", "700": "#37444d", "800": "#262e36", "900": "#171c23", }, red: colors.red, blue: colors.lightBlue, yellow: colors.amber, primary: { logo: "#446C52", saturated: "#67A147", dark: "#35542F", light: "#EDF2B0", }, yellowLight: "#F4E9BD", blueLight: "#D3E6E9", lightBlueGrey: "#e9f1f2", bg: "#EDF7EA", newGreen: { "50": "#F5F7F2", "100": "#E8F0D9", "200": "#CAE5B0", "300": "#97C877", "400": "#72B162", "500": "#378B29", "600": "#2D731B", "700": "#275817", "800": "#1C3C13", "900": "#132411", }, [more custom colors...] }, container: { padding: { DEFAULT: "1rem", sm: "2rem", lg: "4rem", xl: "5rem", "2xl": "6rem", }, }, boxShadow: (theme) => ({ highlighted: `0px 0px 2px 3px ${theme("colors.chocolate.200")}`, }), screens: { standalone: { raw: "(display-mode:standalone)" } }, }; }, ````
_document ```ts import { FB_PIXEL_ID } from "@lib/fpixel"; import withTwindDocument from "@twind/next/shim/document"; import Document, { Head, Html, Main, NextScript } from "next/document"; import twindConfig from "../twind.config"; class CustomDocument extends Document { render() { return (
); } } export default withTwindDocument(twindConfig, CustomDocument); ```
_app ```ts import FBPixelProvider from "@components/FacebookPixel"; import Layout from "@components/layout"; import { useNif, useSnipcart } from "@lib/snipcart"; import { wrapper } from "@redux/store"; import withTwindApp from "@twind/next/shim/app"; import { DefaultSeo } from "next-seo"; import { AppProps } from "next/app"; import Head from "next/head"; import * as React from "react"; import { useEffect } from "react"; import SEO from "../next-seo.config"; import twindConfig from "../twind.config"; function MyApp({ Component, pageProps }: AppProps) { return ( <> ); } export default wrapper.withRedux(withTwindApp(twindConfig, MyApp)); ```
next.config.js ```ts const withPlugins = require("next-compose-plugins"); const withImages = require("next-images"); const withBundleAnalyzer = require("@next/bundle-analyzer")({ enabled: process.env.ANALYZE === "true", }); const { withSentryConfig } = require("@sentry/nextjs"); const config = { future: { webpack5: true, }, webpack(config) { config.module.rules.push({ test: /\.svg$/, use: ["@svgr/webpack"], }); return config; }, }; module.exports = withPlugins([[withBundleAnalyzer, withImages]], config); ```
rschristian commented 3 years ago

I've been running into this too with WMR, so doesn't look to be tied to Next. (might be premature assumption, but does look to be tied to the lack of styles being prerendered)

Repo + instructions to reproduce: https://github.com/rschristian/twind-late-setup-call-bug

moviezhou commented 2 years ago

Has anyone solved this problem? Please let me know, thx a lot.

sastan commented 2 years ago

As we run into some trouble with this and re-configuring twind during runtime I'm thinking about to allow setup calls anytime you want. That would require some internal changes and may only come v1.

JLarky commented 2 years ago

Is there a way to check if setup function has already run from the user point of view? :) for me it happens because of HMR and I store the flag on window :) kind of like:

if (!window.Twind) {
  setup({});
  window.Twind = true;
}
JLarky commented 2 years ago

Here's a more involved workaround:

const createTwind = () => {
  setup({
    theme: {
      extend: {
        colors: {
          "brand-red-500": "#FA3541",
        },
      },
    },
  });
  // in my code here I create some custom sheets
  return 123;
};

let singletonHolder: ReturnType<typeof createTwind> | undefined;

// call this function instead of `setup` itself
export function getTwind() {
  return singletonHolder || (singletonHolder = createTwind());
}

// my typescript is a bit misconfigured, you might not need this
declare global {
  interface NodeModule {
    hot?: {
      data: { singletonHolder?: typeof singletonHolder };
      dispose: (callback: (data: { singletonHolder?: typeof singletonHolder }) => void) => void;
    };
  }
}

if (module.hot) {
  // here I'm restoring my sheets, you can just check if twind is already initialized
  singletonHolder = module.hot?.data?.singletonHolder || singletonHolder;
  module.hot.dispose(data => {
    // I'm storing my stuff, you will just mark that initialization happened
    data.singletonHolder = singletonHolder;
  });
}