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

Issue implementing Stylable with NextJS #1212

Closed
ivanji opened this issue Jun 16, 2020 · 13 comments
Closed

Issue implementing Stylable with NextJS #1212

ivanji opened this issue Jun 16, 2020 · 13 comments
Labels
integration Bundler, test-runner and node question User questions

Comments

@ivanji
Copy link

ivanji commented Jun 16, 2020

I've been trying to set up Stylable and wix-style-react within a NextJS project and have had no luck doing so. Based on the documentation it seems we'd only need the Stylable webpack plugin but still I'm encountering issues and hope you guys could help.

NextJS has its own webpack config and the way to pass additional rules or config is explained here: https://nextjs.org/docs/api-reference/next.config.js/custom-webpack-config - nothing too out of the ordinary.

We just need to append rules or plugins to it. In this case, I append the stylable plugin with some config options, which I understand is the only build tool necessary to parse .st.css files (that is until a proper webpack loader is implemented which I saw as an experimental branch).

Here's my webpack config (or next.config.js as is known in a NextJS project)

const path = require('path')
const { StylableWebpackPlugin } = require('@stylable/webpack-plugin')
module.exports = {
  // https://nextjs.org/docs/basic-features/built-in-css-support#customizing-sass-options
  sassOptions: {
    includePaths: [
      path.join(__dirname, 'node_modules/wix-animations'),
      path.join(__dirname, 'node_modules/wix-style-react'),
      path.join(__dirname, 'src')
    ]
  },
  webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
    // Note: we provide webpack above so you should not `require` it
    // Perform customizations to webpack config
    // Important: return the modified config
    const options = {
       outputCSS: true, // *If this is not set it seems like the .st.css file is not parsed and webpack complains about unexpected token in .root*
    }
    config.plugins.push(new StylableWebpackPlugin(options))
    config.module.rules.push(
      {
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192
            }
          }
        ]
      },
      {
        test: /\.(png|jpg|jpeg|gif|svg)$/,
        loader: 'file-loader'
      }
     
    )
    return config
  }
}

if OutputCSS is not set then it seems like the .st.css file is not parsed and webpack complains about unexpected token in .root - if set to true, then I get the following error:
UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'outputAst' of undefined

It's worth noting the way NextJS handles css. As explained here: https://nextjs.org/docs/basic-features/built-in-css-support and that out of the box NextJS server renders the app.

NextJS supports CSS modules for component level styling, any global styles must go under a file called _app.js. But with Stylable files, once it sees a .st.css files it believes is a global file as it ends in .css files. So even, if I add: { test: /\.css$/, exclude: /\st\.css$/, use: ['style-loader', 'css-loader'] } , I still get:

UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'outputAst' of undefined
    at StylableOptimizer.optimize (/Users/ivanji/work/wix-style-react-next/node_modules/@stylable/optimizer/cjs/stylable-optimizer.js:23:32)
    at StylableGenerator.afterTransform (/Users/ivanji/work/wix-style-react-next/node_modules/@stylable/webpack-plugin/cjs/stylable-generator.js:54:37)
    at StylableGenerator.toCSS (/Users/ivanji/work/wix-style-react-next/node_modules/@stylable/webpack-plugin/cjs/stylable-generator.js:41:14)
    at /Users/ivanji/work/wix-style-react-next/node_modules/@stylable/webpack-plugin/cjs/stylable-module-helpers.js:130:33
    at Array.map (<anonymous>)
    at Object.renderStaticCSS (/Users/ivanji/work/wix-style-react-next/node_modules/@stylable/webpack-plugin/cjs/stylable-module-helpers.js:126:39)
    at StylableWebpackPlugin.createChunkCSSBundle (/Users/ivanji/work/wix-style-react-next/node_modules/@stylable/webpack-plugin/cjs/stylable-webpack-plugin.js:248:62)
    at /Users/ivanji/work/wix-style-react-next/node_modules/@stylable/webpack-plugin/cjs/stylable-webpack-plugin.js:182:30
    at Array.forEach (<anonymous>)
    at /Users/ivanji/work/wix-style-react-next/node_modules/@stylable/webpack-plugin/cjs/stylable-webpack-plugin.js:181:28
    at SyncHook.eval [as call] (eval at create (/Users/ivanji/work/wix-style-react-next/node_modules/tapable/lib/HookCodeFactory.js:19:10), <anonymous>:7:1)
    at SyncHook.lazyCompileHook (/Users/ivanji/work/wix-style-react-next/node_modules/tapable/lib/Hook.js:154:20)
    at /Users/ivanji/work/wix-style-react-next/node_modules/webpack/lib/Compilation.js:1399:37
    at AsyncSeriesHook.eval [as callAsync] (eval at create (/Users/ivanji/work/wix-style-react-next/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:6:1)
    at AsyncSeriesHook.lazyCompileHook (/Users/ivanji/work/wix-style-react-next/node_modules/tapable/lib/Hook.js:154:20)
    at Compilation.seal (/Users/ivanji/work/wix-style-react-next/node_modules/webpack/lib/Compilation.js:1342:27)
(node:99153) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:99153) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Again, any help or direction you guys could provide would be greatly appreciated.

@tomrav tomrav added integration Bundler, test-runner and node question User questions labels Jun 16, 2020
@tomrav
Copy link
Collaborator

tomrav commented Jun 21, 2020

Sorry for the delay in responding and thanks for opening this issue. We've played around a bit with NextJS to see what can be done.

In this case, NextJS comes pre-configured with multiple loaders that target CSS files (error-loader, ignore-loader, file-loader, css-loader) under different circumstances. Because loaders are resolved before plugins in webpack this means that unfortunately you cannot simply add one more exclude configuration for Stylable to work.

I've opened a new issue (#1216) to add support for an additional file suffix (say, .stcss) which would allow us to avoid such configuration issues all together. This would require some effort on our part, though I don't estimate this task to be a big one.

A more fragile solution that we have been able to get working in the meanwhile is to iterate over the entire configuration tree and insert exclusions for .st.css files anywhere we see CSS is being targeted.
I say this is more fragile because it will probably break under certain conditions. I suspect loaders that use a function for this test instead of a regex would be a problem (though it doesn't look like any exist at the moment). Furthermore, if NextJS make drastic changes to their configurations I imagine it can also break. I'm not sure what behaviour to expect if one were to try using global CSS or CSS Modules in addition to Stylable with this configuration, but probably nothing good.

This solution seems to work, though we do see NextJS do all sorts of optimizations in the background. You can try running ahead with this, and let us know if you end up breaking on another issue down the line.

#### Example configuration override:
See updated configuration further below.

@ivanji
Copy link
Author

ivanji commented Jun 22, 2020

Thanks @tomrav - great to see progress being made towards integrating with NextJS. That being said, I tried adding your temp solution to next.config.js and got the following error (This was tested on a blank NextJS project as well)

./node_modules/wix-animations/dist/src/components/Animator/components/Debug.scss
Global CSS cannot be imported from within node_modules.
Read more: https://err.sh/next.js/css-npm
Location: node_modules/wix-animations/dist/src/components/Animator/components/Debug.js

It's similar to the errors I was getting before. Is there anything else missing from your implementation?

@tomrav
Copy link
Collaborator

tomrav commented Jun 22, 2020

I was able to reproduce the latest error by adding wix-style-react and bringing in a component through the main index. This apparently causes some .scss files to be included. NextJS currently has several issues regarding loading CSS in various ways through node_modules - open issue.

At this point I'd say this is more of a NextJS issue, and less one where we are a contributing factor.

We found a way of directly importing a stylable component from its dist (e.g. wix-style-react/dist/src/Button) path that works, but requires several additional work-arounds.

In order to get this to work, you'll need to add our node require hook (@stylable/node) to both the config file, and a custom SSR server.
Furthermore, WSR currently uses Stylable v1, so you'll need to make sure that's the dependency version you've also requested in your project. WSR is expected to finish upgrading to v3 soonish.

  • Updated next.config.js:
const StylableWebpackPlugin = require("@stylable/webpack-plugin");
const { attachHook } = require("@stylable/node");

attachHook();

module.exports = {
  webpack: (config) => {
    // Fixes npm packages that depend on `fs` module
    config.node = {
      fs: "empty",
    };

    safelyWalkJSON(config.module, insertStCssExclude(/\.st\.css$/));

    config.plugins.push(
      new StylableWebpackPlugin({
        includeDynamicModulesInCSS: false,
      })
    );

    return config;
  },
};

function insertStCssExclude(stRegex) {
  return (key, value) => {
    if (value && value.test) {
      if (value.test.toString().includes("css")) {
        if (value.exclude && Array.isArray(value.exclude)) {
          value.exclude.push(stRegex);
        } else if (value.exclude instanceof RegExp) {
          value.exclude = [value.exclude, stRegex];
        } else if (typeof value.exclude === "function") {
          value.exclude = [value.exclude, stRegex];
        } else if (value.exclude) {
          throw new Error("unknown exclude pattern: " + value.exclude);
        } else {
          value.exclude = [stRegex];
        }
      }
    }
  };
}

function safelyWalkJSON(obj, visitor, path = [], visited = new Set()) {
  for (var key in obj) {
    const currentPath = [...path, key];
    if (visited.has(obj[key])) {
      continue;
    } else {
      visited.add(obj[key]);
    }
    const res = visitor(key, obj[key], currentPath);
    if (res === false) {
      continue;
    }
    if (typeof obj[key] === "object") {
      safelyWalkJSON(obj[key], visitor, currentPath, visited);
    }
  }
}
  • Change build script in package.json to node server.js
  • Add server.js file with our require hook to project root (based on example from NextJS docs):
// server.js
const { createServer } = require("http");
const { parse } = require("url");
const next = require("next");

const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();

require("@stylable/node").attachHook();

app.prepare().then(() => {
  createServer((req, res) => {
    // Be sure to pass `true` as the second argument to `url.parse`.
    // This tells it to parse the query portion of the URL.
    const parsedUrl = parse(req.url, true);

    handle(req, res, parsedUrl);
  }).listen(3000, (err) => {
    if (err) throw err;
    console.log("> Ready on http://localhost:3000");
  });
});

If you've done all of these, the project should work in both development and production modes.

@ivanji
Copy link
Author

ivanji commented Jun 22, 2020

Yeah, I mentioned this to the wsr guys last week - https://github.com/wix/wix-style-react/issues/5429#issuecomment-644871702

I tried your suggestion and got still the same error.

Furthermore, WSR currently uses Stylable v1, so you'll need to make sure that's the dependency version you've also requested in your project. WSR is expected to finish upgrading to v3 soonish.

Correct me if I'm wrong, but I thought they had already upgraded to Stylable v3 - https://github.com/wix/wix-style-react/pull/5528#event-3428200197

@tomrav
Copy link
Collaborator

tomrav commented Jun 22, 2020

They rolled back the upgrade in version 8.20.0 - can you share your project so that I will take a look?
Or specify which components you were trying to use, maybe it's a component specific issue.

@McZenith
Copy link

McZenith commented Jun 22, 2020

const {StylableWebpackPlugin} = require("@stylable/webpack-plugin");

fixed the issue. seem like @tomrav made a typo.

thanks @tomrav @ivanji

@tomrav
Copy link
Collaborator

tomrav commented Jun 22, 2020

The API for importing the webpack plugin was changed from a default import to a named one in the transition from v1 to v3.

v1 import syntax:

const StylableWebpackPlugin = require("@stylable/webpack-plugin");

v3 import syntax:

const { StylableWebpackPlugin } = require("@stylable/webpack-plugin");

Edit: I've removed my first configuration example above as that one wasn't targeted at v1 and the one below it is more encompassing anyway.

@ivanji
Copy link
Author

ivanji commented Jun 22, 2020

@McZenith Are you saying this worked for you?

I had encountered the issue with StylableWebpackPlugin not being a constructor, but I was aware import syntax change, what I meant earlier was that the error continued being the same.

error - ./node_modules/wix-animations/dist/src/components/Animator/components/Debug.scss
Global CSS cannot be imported from within node_modules.
Read more: https://err.sh/next.js/css-npm
Location: node_modules/wix-animations/dist/src/components/Animator/components/Debug.js
/.../nextjs/blank-nextjs/node_modules/wix-style-react/dist/es/src/FontUpgrade/FontUpgrade.scss:1
.root {
^

SyntaxError: Unexpected token .

This is a blank (brand new NextJS) project - I just added wsr and stylable, and tried importing a single component into the project.

@ivanji
Copy link
Author

ivanji commented Jun 22, 2020

I went back and read your comment more carefully - I had missed this:

We found a way of directly importing a stylable component from its dist (e.g. wix-style-react/dist/src/Button) path that works, but requires several additional work-arounds.

I reimported now and it's all good! Thanks so much. I'll play around with it a bit more and report back if I find anything else.

@ivanji
Copy link
Author

ivanji commented Jun 22, 2020

Thanks again @tomrav - in wsr, some components still use .scss instead of Stylable. Take as an example adding the MarketingLayout component which includes some Layout-related components which still rely on scss - when adding those, we'd get:

./node_modules/wix-style-react/dist/es/src/Layout/Cell/styles.scss
Global CSS cannot be imported from within node_modules.
Read more: https://err.sh/next.js/css-npm
Location: node_modules/wix-style-react/dist/src/Layout/Cell/index.js

.root {
^

SyntaxError: Unexpected token .

I took a look at the style.scss and seems like valid scss, What would be the approach here without breaking anything else? Normally, in NextJS I'd add the paths containing the scss files but that isn't working here. Adding a rule (overwriting the Built-in CSS support), I'm assuming wouldn't be the correct approach (and it didn't work anyway)

config.module.rules.push({
      test: /.scss$/,
      include: [
        path.join(__dirname, "node_modules/wix-animations"),
        path.join(__dirname, "node_modules/wix-style-react"),
        path.join(__dirname, "node_modules/bootstrap-sass"),
        path.join(__dirname, "styles"),
      ],
      use: [
        {
          loader: "style-loader",
        },
        {
          loader: "css-loader",
          options: {
            importLoaders: 1,
            modules: true,
          },
        },
        {
          loader: "resolve-url-loader",
        },
        {
          loader: "sass-loader",
        },
      ],
    });

Your guidance once again would be appreciated.

@tomrav
Copy link
Collaborator

tomrav commented Jun 22, 2020

I tried playing around with it a bit but haven't reached any good results. This isn't related to Stylable anymore and I don't have the same level of experience with sass and its toolset.

The NextJS issue I mentioned above seems to be all about working with global css. It looks like they are aware of the issue and marked it for the next iteration.

@tomrav
Copy link
Collaborator

tomrav commented Jun 25, 2020

I'm closing this issue for now - if you encounter any more troubles getting Stylable to work please reopen this issue or submit a new one.

@tomrav tomrav closed this as completed Jun 25, 2020
@ivanji
Copy link
Author

ivanji commented Jun 25, 2020

Thanks Tom for your help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
integration Bundler, test-runner and node question User questions
Projects
None yet
Development

No branches or pull requests

3 participants