callstack / linaria

Zero-runtime CSS in JS library
https://linaria.dev
MIT License
11.68k stars 417 forks source link

Update with-linaria example in next.js repo #589

Open jayu opened 4 years ago

jayu commented 4 years ago

Describe the enhancement

After release of 2.0 with shaker as a default strategy, we should update with-linaria example in next.js repo. https://github.com/zeit/next.js/tree/master/examples/with-linaria

Motivation

There is a known bug with next.js babel config and extractor evaluator, which can be fixed by the shaker evaluator. See #447

Possible implementations

open a PR to next.js with an update of linaria version

lifeiscontent commented 4 years ago

@jayu any ideas when this might be updated?

anulman commented 4 years ago

@lifeiscontent Not a maintainer, but here are the instructions for modern Next.js: https://github.com/callstack/linaria/issues/447

marbiano commented 4 years ago

Done: https://github.com/vercel/next.js/pull/13568

chrisands commented 4 years ago

How to use linaria css api (not styled) with nextjs? It tries to inject files but got nextjs error

jayu commented 4 years ago

@chrisands can you spin up a repo with reproduction and open a new issue for that?

chrisands commented 4 years ago

@jayu I'll try to do this week

switz commented 4 years ago

Also seeing this issue -- set up linaria@2.0.0-alpha.1 and added:

const shaker = require('linaria/lib/babel/evaluators/shaker').default;

module.exports = {
    displayName: process.env.NODE_ENV !== 'production',
    rules: [
        {
            action: shaker,
        },
        {
            test: /\/node_modules\//,
            action: 'ignore',
        },
    ],
};
Mistereo commented 4 years ago

I recently discovered Linaria and trying to make it work with Next.js. One of the problems of current official example is that it uses @zeit/next-css, which doesn't support CSS splitting, this is a huge disadvantage for me, so I tried to make built-in CSS support (available since 9.2) to play nice with linaria/loader and ended up with this simple plugin: https://github.com/Mistereo/next-linaria. Plugin changes extension to .linaria.module.css so that Next.js loader picks Linaria files without complains and also overrides getLocalIdent to return unmodified localName for Linaria classes.

Still testing it, but so far seems promising. Any possible issues with this approach?

nickrttn commented 3 years ago

@Mistereo I tried your plugin. It unfortunately seems that the Linaria's :global() selector doesn't work with it, receiving errors like:

Error: Syntax error: Selector "html" is not pure (pure selectors must contain at least one local class or id)

Without your plugin, I need to use @zeit/next-css, which means that I'm getting errors from mini-css-extract-plugin eg.

chunk styles [mini-css-extract-plugin]
Conflicting order between:

Is there any new insight on how to configure linaria@3.0.0-beta.0 with next@10.0.1 in a way that doesn't have us jump through hoops? I don't mind some complexity in my configuration if that means we can have a somewhat simpler developer experience.

joyfulelement commented 3 years ago

@Mistereo I tried your plugin. It unfortunately seems that the Linaria's :global() selector doesn't work with it, receiving errors like:

Error: Syntax error: Selector "html" is not pure (pure selectors must contain at least one local class or id)

Thanks for sharing @nickrttn, I am too baffled by the same issue, was curious how your setup Next web app to use Linaria global CSS?

My current fallback workaround was simply using the build-in css support by Next.js with a standalone globals.css file and import the it to _app.js with import '../styles/globals.css';. Does anyone know the implication of such setup? i.e. does that mean a CSS runtime also gets bundled at the end of the day (defeating the purpose of using Linaria in the first place) since this is using the built in CSS support by Next.js?

Tried adding the following to _app.js didn't work either:

export const globals = css`
  :global() {
    html {
      box-sizing: border-box;
    }

    *,
    *:before,
    *:after {
      box-sizing: inherit;
    }
  }
`;

and got an error like:

error - .linaria-cache/pages/_app.linaria.module.css:4:13
Syntax error: Selector "html" is not pure (pure selectors must contain at least one local class or id)

  2 | import { css } from '@linaria/core';
  3 | 
> 4 | export const globals = css`
    |             ^
  5 |   :global() {
  6 |     html {

Not 100% sure if it's just the way I had linaria configured, currently using next-linaria by @Mistereo with .babelrc as:

{
  "presets": ["next/babel", "linaria/babel"]
}

Any guidance would be highly appreciated! Thanks for everyone's experience sharing thus far.

steveadams commented 3 years ago

I'm having the same issue as @nickrttn and @joyfulelement . I've attempted to modify the babel configuration used within the next-linaria plugin, but haven't made any progress yet... Our application is fairly complex, and we're migrating to Next.js from Gatsby, meaning there are a lot of moving pieces in the way of this working. I might be best to sandbox it and fix it in isolation. I'll check in again when I find something - this is critical for my team so we've got 2 people trying to work it out.

nickrttn commented 3 years ago

Thanks for sharing @nickrttn, I am too baffled by the same issue, was curious how your setup Next web app to use Linaria global CSS?

I've (sadly!) since moved to use Emotion in our application. We were trying out Linaria in a smaller project with a short timeline and there wasn't a lot of time to set up tooling unfortunately. I'll make sure to revisit Linaria as version 3 comes out of beta and sees some more adoption because the ideas make a lot of sense to me.

My current fallback workaround was simply using the build-in css support by Next.js with a standalone globals.css file and import the it to _app.js with import '../styles/globals.css';. Does anyone know the implication of such setup? i.e. does that mean a CSS runtime also gets bundled at the end of the day (defeating the purpose of using Linaria in the first place) since this is using the built in CSS support by Next.js?

I think that's a good solution for now. Importing a .css file should also output a CSS file on the webpack end and afaik wouldn't use any runtime like the styled-jsx one that Next.js includes.

turadg commented 3 years ago

FWIW, with-linaria uses next-linaria since https://github.com/vercel/next.js/pull/23332

Of course :global() still doesn't work. Anyone have ideas for solving that?

M1r1k commented 3 years ago

Here is the fix, that I believe will be in next release if next-linaria - https://github.com/callstack/linaria/issues/724#issuecomment-853063166

mikestopcontinues commented 3 years ago

@M1r1k I'm not convinced there's going to be a next release of next-linaria. The with-linaria example should probably just use the raw code, or someone should fork it. Here's the updates needed for global support

huydq-1218 commented 2 years ago

Anyone have a working example with NextJS 12 with Typescript and linaria 3 beta?

jonataslaw commented 2 years ago

Linaria 4 no longer works with Nextjs 12. At least not the dynamic properties. Version 2 works normally.

anulman commented 2 years ago

I got this working with NextJS 12 & Linaria 4. Just walking back through my changes; will publish something shortly

andyfangaf commented 2 years ago

@anulman can you push up an example for us?

anulman commented 2 years ago

Oop, sorry — only got it stable end-of-day Friday and cycled off the project this week.

// babel.config.js
module.exports = {
  presets: ['next/babel', '@linaria'],
};

// next.config.js
const _ = require('lodash');

const BABEL_LOADER_STRING = 'babel/loader';
const makeLinariaLoaderConfig = (babelOptions) => ({
  loader: require.resolve('@linaria/webpack-loader'),
  options: {
    sourceMap: true,
    extension: '.linaria.module.css',
    babelOptions: _.omit(
      babelOptions,
      'isServer',
      'distDir',
      'pagesDir',
      'development',
      'hasReactRefresh',
      'hasJsxRuntime',
    ),
  },
});

let injectedBabelLoader = false;
function crossRules(rules) {
  for (const rule of rules) {
    if (typeof rule.loader === 'string' && rule.loader.includes('css-loader')) {
      if (
        rule.options &&
        rule.options.modules &&
        typeof rule.options.modules.getLocalIdent === 'function'
      ) {
        const nextGetLocalIdent = rule.options.modules.getLocalIdent;
        rule.options.modules.mode = 'local';
        rule.options.modules.auto = true;
        rule.options.modules.exportGlobals = true;
        rule.options.modules.exportOnlyLocals = true;
        rule.options.modules.getLocalIdent = (context, _, exportName, options) => {
          if (context.resourcePath.includes('.linaria.module.css')) {
            return exportName;
          }
          return nextGetLocalIdent(context, _, exportName, options);
        };
      }
    }

    if (typeof rule.use === 'object') {
      if (Array.isArray(rule.use)) {
        const babelLoaderIndex = rule.use.findIndex(
          ({ loader }) => typeof loader === 'string' && loader.includes(BABEL_LOADER_STRING),
        );

        const babelLoaderItem = rule.use[babelLoaderIndex];

        if (babelLoaderIndex !== -1) {
          rule.use = [
            ...rule.use.slice(0, babelLoaderIndex),
            babelLoaderItem,
            makeLinariaLoaderConfig(babelLoaderItem.options),
            ...rule.use.slice(babelLoaderIndex + 2),
          ];
          injectedBabelLoader = true;
        }
      } else if (
        typeof rule.use.loader === 'string' &&
        rule.use.loader.includes(BABEL_LOADER_STRING)
      ) {
        rule.use = [rule.use, makeLinariaLoaderConfig(rule.use.options)];
        injectedBabelLoader = true;
      }

      crossRules(Array.isArray(rule.use) ? rule.use : [rule.use]);
    }

    if (Array.isArray(rule.oneOf)) {
      crossRules(rule.oneOf);
    }
  }
}

function withLinaria(nextConfig = {}) {
  return {
    ...nextConfig,
    webpack(config, options) {
      crossRules(config.module.rules);

      if (!injectedBabelLoader) {
        config.module.rules.push({
          test: /\.(tsx?|jsx?)$/,
          exclude: /node_modules/,
          use: [
            {
              loader: 'babel-loader',
              options: linariaWebpackLoaderConfig.options.babelOptions,
            },
            linariaWebpackLoaderConfig,
          ],
        });
      }

      if (typeof nextConfig.webpack === 'function') {
        return nextConfig.webpack(config, options);
      }

      return config;
    },
  };
}

module.exports = withLinaria({ /* ... */ });

That whole crossRules thing is a little messy but the point of it is to find Next's Babel rule, read the Babel options directly from it, and add the @linaria/webpack-loader sequentially after the Babel one.

Also I'm not sure if the getLocalIdent stuff is strictly necessary; I cargo-culted that in from next-linaria.

LukasBombach commented 2 years ago

I made a working example from @anulman 's comment. Thank you, really good work!

Of course I credited you in that repo!

https://github.com/LukasBombach/nextjs-linaria/tree/working-poc

anulman commented 2 years ago

Awww thanks @LukasBombach! In a recent project I had to rm @linaria from the babel config; looks like some updates in the core libs auto-injects the Babel plugin or something

Nandan1996 commented 2 years ago

@LukasBombach Thanks for publishing a working example. But in case I use css module and linaria as well then some hydration issue is coming.

it seems that the culprit is rule.options.modules.exportOnlyLocals = true;. exportOnlyLocals should be set to true only for server environment otherwise css file doesn't gets generated.

also, only having rule.options.modules.auto = true; while updating css module config just works perfectly fine. I cleaned up most of the stuff and it's still working.

Here is my snippet if (rule.options && rule.options.modules && typeof rule.options.modules.getLocalIdent === "function") { rule.options.modules.auto = true; }

karlhorky commented 2 years ago

Does the PR vercel/next.js#41085 by @ThePatriczek apply the same changes that are being proposed here?

Changed files: https://github.com/vercel/next.js/pull/41085/files

@jayu If the example has been updated, maybe this issue can be now closed?

cc @mikestopcontinues

frenicohansen commented 1 year ago

Thanks @LukasBombach for the example. I got some issues while using configurations from your repo on Next 13.

For reproduction: I cloned your repo and upgraded the Next.js to version 13

$ yarn build
yarn run v1.22.19
$ next build
info  - Linting and checking validity of types  
info  - Disabled SWC as replacement for Babel because of custom Babel configuration "babel.config.js" https://nextjs.org/docs/messages/swc-disabled
info  - Using external babel configuration from /workspaces/nextjs-linaria/babel.config.js
info  - Creating an optimized production build ..<w> [webpack.cache.PackFileCacheStrategy] Skipped not serializable cache item 'Compilation/modules|/workspaces/nextjs-linaria/node_modules/next/dist/build/babel/loader/index.js??ruleSet[1].rules[3].oneOf[2].use[0]!/workspaces/nextjs-linaria/node_modules/@linaria/webpack-loader/lib/index.js??ruleSet[1].rules[3].oneOf[2].use[1]!/workspaces/nextjs-linaria/pages/_document.tsx': No serializer registered for ConfigError
<w> while serializing webpack/lib/cache/PackFileCacheStrategy.PackContentItems -> webpack/lib/NormalModule -> webpack/lib/ModuleBuildError -> ConfigError
<w> [webpack.cache.PackFileCacheStrategy] Skipped not serializable cache item 'Compilation/modules|/workspaces/nextjs-linaria/node_modules/next/dist/build/babel/loader/index.js??ruleSet[1].rules[3].oneOf[2].use[0]!/workspaces/nextjs-linaria/node_modules/@linaria/webpack-loader/lib/index.js??ruleSet[1].rules[3].oneOf[2].use[1]!/workspaces/nextjs-linaria/pages/index.tsx': No serializer registered for ConfigError
<w> while serializing webpack/lib/cache/PackFileCacheStrategy.PackContentItems -> webpack/lib/NormalModule -> webpack/lib/ModuleBuildError -> ConfigError
info  - Creating an optimized production build  
Failed to compile.

./pages/_document.tsx
Error: Unknown option: .hasServerComponents. Check out https://babeljs.io/docs/en/babel-core/#options for more information about options.
    at validate (/workspaces/nextjs-linaria/node_modules/@babel/core/lib/config/validation/options.js:92:25)
    at loadPrivatePartialConfig (/workspaces/nextjs-linaria/node_modules/@babel/core/lib/config/partial.js:80:50)
    at loadPrivatePartialConfig.next (<anonymous>)
    at loadFullConfig (/workspaces/nextjs-linaria/node_modules/@babel/core/lib/config/full.js:57:46)
    at loadFullConfig.next (<anonymous>)
    at Function.<anonymous> (/workspaces/nextjs-linaria/node_modules/@babel/core/lib/config/index.js:35:43)
    at Generator.next (<anonymous>)
    at evaluateSync (/workspaces/nextjs-linaria/node_modules/gensync/index.js:251:28)
    at Function.sync (/workspaces/nextjs-linaria/node_modules/gensync/index.js:89:14)
    at Object.<anonymous> (/workspaces/nextjs-linaria/node_modules/@babel/core/lib/config/index.js:53:60)

./pages/index.tsx
Error: Unknown option: .hasServerComponents. Check out https://babeljs.io/docs/en/babel-core/#options for more information about options.
    at validate (/workspaces/nextjs-linaria/node_modules/@babel/core/lib/config/validation/options.js:92:25)
    at loadPrivatePartialConfig (/workspaces/nextjs-linaria/node_modules/@babel/core/lib/config/partial.js:80:50)
    at loadPrivatePartialConfig.next (<anonymous>)
    at loadFullConfig (/workspaces/nextjs-linaria/node_modules/@babel/core/lib/config/full.js:57:46)
    at loadFullConfig.next (<anonymous>)
    at Function.<anonymous> (/workspaces/nextjs-linaria/node_modules/@babel/core/lib/config/index.js:35:43)
    at Generator.next (<anonymous>)
    at evaluateSync (/workspaces/nextjs-linaria/node_modules/gensync/index.js:251:28)
    at Function.sync (/workspaces/nextjs-linaria/node_modules/gensync/index.js:89:14)
    at Object.<anonymous> (/workspaces/nextjs-linaria/node_modules/@babel/core/lib/config/index.js:53:60)

> Build failed because of webpack errors
error Command failed with exit code 1.

EDIT

Adding a new option inside makeLinariaLoaderConfig solved the problem. The code would then look something like this.

...

const makeLinariaLoaderConfig = babelOptions => ({
  loader: require.resolve("@linaria/webpack-loader"),
  options: {
    sourceMap: true,
    extension: ".linaria.module.css",
    babelOptions: _.omit(
      babelOptions,
      "isServer",
      "distDir",
      "pagesDir",
      "development",
      "hasReactRefresh",
      "hasJsxRuntime",
      "hasServerComponents"
    ),
  },
});

...
aaazureee commented 1 year ago

Putting this here if anyone finds this useful: https://github.com/aaazureee/nextjs-linaria-purgecss

karlhorky commented 1 year ago

Hi @aaazureee, thanks for the example!

However, it looks like the example is using the old Pages Router (pages/ directory)

Would it be possible to switch this example to the App Router and show styling with React Server Components?

I also asked in https://github.com/aaazureee/nextjs-linaria-purgecss/issues/1