Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support NextJS 13 font resolvers #2021

Open
jordanarldt opened this issue Sep 22, 2023 · 6 comments
Open

Support NextJS 13 font resolvers #2021

jordanarldt opened this issue Sep 22, 2023 · 6 comments
Labels
enhancement New feature or request

Comments

@jordanarldt
Copy link

Is your feature request related to a problem? Please describe.
Hey again @fwouts :-) it's been a while.

I'm using PreviewJS with my NextJS app and I want my __previewjs__/Wrapper.tsx file to wrap components in the NextJS layout component, but it seems to be failing because I'm attempting to use a Google Font with the new NextJS fonts API.

Here's my layout file:

import "./globals.scss";
import { Raleway } from "next/font/google";

const raleway = Raleway({ subsets: ["latin"] });

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}): JSX.Element {
  return (
    <html lang="en">
      <body className={raleway.className}>{children}</body>
    </html>
  );
}

and here's my Wrapper.tsx file:

import "@/app/globals.scss";

import Layout from "@/app/layout";

export default function Wrapper({ children }: React.PropsWithChildren): JSX.Element {
  return (
    <div>
      <Layout>{children}</Layout>
    </div>
  );
}

Describe the solution you'd like
It would be cool if the PreviewJS preview was able to automatically resolve these fonts so that they display correctly in the preview. Perhaps you could leverage the Google API and the name of the requested font from the NextJS import and use that to inject the font family tags into the preview body.

Describe alternatives you've considered
Manually setting the font in the wrapper file, but this won't be feasible as my project grows.

Additional context
Any more support for NextJS 13 will be awesome. Thanks again for the great work!

@jordanarldt jordanarldt added the enhancement New feature or request label Sep 22, 2023
@fwouts
Copy link
Owner

fwouts commented Sep 24, 2023

Thanks for reporting this @jordanarldt, I didn't even know NextJS fonts API was a thing. Will look into it.

@jordanarldt
Copy link
Author

Thanks for reporting this @jordanarldt, I didn't even know NextJS fonts API was a thing. Will look into it.

I believe it is a new thing since version 13 👍 still very cool though, and will be super useful in PreviewJS!

@fwouts
Copy link
Owner

fwouts commented Sep 26, 2023

So I found the logic that makes this work in Next.js: https://github.com/vercel/next.js/blob/c56f9f4ff906fb2ad4f1acb126071cb876d4363c/packages/next/src/build/webpack/loaders/next-font-loader/index.ts#L16

This is close to magic, and it has various weird edge cases. For example the following will fail:

import * as googleFonts from "next/font/google";

const inter = googleFonts.Inter({ subsets: ["latin"] });

I agree that making this work would be convenient, but I'm not quite sure how to proceed yet. I don't really want to hack Next.js-specific code in Preview.js, as it adds a maintenance burden in the future (whenever this API changes in future versions of Next.js, Preview.js needs to be updated to support all current and future versions, just like React itself).

In the meantime, here's a workaround:

  • add the following config in preview.config.js (next to package.json):
const path = require("path");

module.exports = {
  vite: {
    resolve: {
      alias: {
        "next/font/google": path.resolve(__dirname, "./google-font-mock.js"),
      },
    },
  },
};
  • add the following code in the corresponding google-font-mock.js file:
export const Inter = () => ({});

This won't load the correct font, but at least it won't crash!

@jordanarldt
Copy link
Author

jordanarldt commented Sep 27, 2023

@fwouts Awesome! Nice find, by the way! The way NextJS does it definitely seems like magic, and of course I wouldn't expect the PreviewJS implementation to be the same way they do it. I have a suggestion on an implementation that may help you.

So typically in a NextJS project you'll have a main file to contain all the font imports (e.g. fonts.ts), and this file will be used when you need to import a font as a style, classname, or font family.

Here's an example:

// fonts.ts
import { Plus_Jakarta_Sans } from "next/font/google";

export const jakartaSans = Plus_Jakarta_Sans({ subsets: ["latin"] });

And if i console.log(jakartaSans) here is the output:

{
  style: {
    fontFamily: "'__Plus_Jakarta_Sans_d21556', '__Plus_Jakarta_Sans_Fallback_d21556'",
    fontStyle: 'normal'
  },
  className: '__className_d21556'
}

So all we would really need PreviewJS to do is to be aware of the fonts we want to import, and then you could populate the Preview HTML with the necessary tags / class names. For example, here's the Google Font API URL for Plus Jakarta Sans:
https://fonts.googleapis.com/css?family=Plus%20Jakarta%20Sans

That will give you all of the font face information (replaced with the font family name from the font data above), and then you would just need to make the styles, for example:

/* latin - Fetched from Google*/
@font-face {
  font-family: '__Plus_Jakarta_Sans_d21556'; // use the family name from the font output above
  font-style: normal;
  font-weight: 400;
  src: url(https://fonts.gstatic.com/s/plusjakartasans/v8/LDIbaomQNQcsA88c7O9yZ4KMCoOg4IA6-91aHEjcWuA_qU79TR_VMq2oRsWk.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

.__className_d21556 {
  font-family: '__Plus_Jakarta_Sans_d21556';
}

Theoretically, this would work, but maybe PreviewJS would be able to control what the returned classnames are? I might have to try this out in my wrapper file to see if it works.

What do you think?

@jordanarldt
Copy link
Author

jordanarldt commented Sep 27, 2023

@fwouts I actually made a working proof of concept, this may help you come up with a broader solution :-)

I mock my fonts.ts module:

// preview.config.js
module.exports = {
  alias: {
    "@/fonts": "__previewjs__/mocks/fonts.js",
  },
};

Here is the mock file:

// mock/fonts.js


export const jakartaSans = {
  style: {
    fontFamily: "'Plus Jakarta Sans'",
    fontStyle: 'normal'
  },
  className: '__previewjs_jakarta_sans',
};

And here is my wrapper:

// Wrapper.tsx

import { useEffect, useState } from "react";

import "@/app/globals.scss";

function useMockFonts() {
  const [fontStyles, setFontStyles] = useState("");

  useEffect(() => {
    (async () => {
      const styles = await fetch(
        "https://fonts.googleapis.com/css?family=Plus%20Jakarta%20Sans",
      ).then(res => res.text());

      setFontStyles(styles);
    })();
  });

  return fontStyles;
}

export function Wrapper({ children }: React.PropsWithChildren): JSX.Element {
  const style = useMockFonts();
  const css = `
  .__previewjs_jakarta_sans {
    font-family: "Plus Jakarta Sans";
  }`;

  return (
    <div>
      <style>{style}</style>
      <style>{css}</style>
      {children}
    </div>
  );
}

@fwouts
Copy link
Owner

fwouts commented Sep 27, 2023

That's neat, thanks.

For anyone reading this issue, @jordanarldt's solution is the recommended approach until we come to a more permanent solution ☝️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants