linaria icon indicating copy to clipboard operation
linaria copied to clipboard

Update with-linaria example in next.js repo

Open jayu opened this issue 5 years ago • 27 comments

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

jayu avatar Apr 28 '20 15:04 jayu

@jayu any ideas when this might be updated?

lifeiscontent avatar May 27 '20 02:05 lifeiscontent

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

anulman avatar May 27 '20 03:05 anulman

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

martinbavio avatar May 30 '20 01:05 martinbavio

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

chrisands avatar Jun 20 '20 11:06 chrisands

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

jayu avatar Jun 22 '20 09:06 jayu

@jayu I'll try to do this week

chrisands avatar Jun 22 '20 09:06 chrisands

Also seeing this issue -- set up [email protected] 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',
        },
    ],
};

switz avatar Jun 29 '20 00:06 switz

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?

Mistereo avatar Aug 17 '20 16:08 Mistereo

@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 [email protected] with [email protected] 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.

nickrttn avatar Nov 19 '20 08:11 nickrttn

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

joyfulelement avatar Dec 29 '20 15:12 joyfulelement

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.

steveadams avatar Jan 06 '21 01:01 steveadams

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.

nickrttn avatar Jan 06 '21 09:01 nickrttn

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?

turadg avatar Mar 26 '21 18:03 turadg

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

M1r1k avatar Jun 10 '21 13:06 M1r1k

@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

mikestopcontinues avatar Jun 22 '21 06:06 mikestopcontinues

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

huydq-1218 avatar Jul 12 '22 07:07 huydq-1218

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

jonataslaw avatar Aug 17 '22 19:08 jonataslaw

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

anulman avatar Aug 20 '22 17:08 anulman

@anulman can you push up an example for us?

xndyz avatar Aug 23 '22 14:08 xndyz

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.

anulman avatar Aug 23 '22 16:08 anulman

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

LukasBombach avatar Oct 06 '22 22:10 LukasBombach

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

anulman avatar Oct 06 '22 22:10 anulman

@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; }

Nandan1996 avatar Oct 09 '22 14:10 Nandan1996

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

karlhorky avatar Oct 20 '22 13:10 karlhorky

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"
    ),
  },
});

...

frenicohansen avatar Dec 14 '22 21:12 frenicohansen

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

aaazureee avatar Jun 24 '23 22:06 aaazureee

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

karlhorky avatar Jun 25 '23 05:06 karlhorky