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

[BUG] Vite fails on 3p package that contains scss imports during SSR pass #869

Closed
wizardlyhel opened this issue Mar 8, 2022 · 2 comments
Labels
bug Something isn't working

Comments

@wizardlyhel
Copy link
Collaborator

wizardlyhel commented Mar 8, 2022

Describe the bug

Vite fails to process scss files imported by 3p package during SSR pass

Screen Shot 2022-03-08 at 1 55 36 PM

To Reproduce

  1. Unpack @weareallbirds.zip (Get it internally) and put this into project's node_module folder
  2. Add "@weareallbirds/birdponents.ui.button": "0.0.5", into package.json as a dependency
  3. Create a client component that wraps this component
// components/BirdButton.client.js

import {Button} from '@weareallbirds/birdponents-button'

export default function BirdButton({children}) {
  return (
    <Button >
      {children}
    </Button>
  );
}
  1. In src/routes/index.server.js, import the client component
import BirdButton from '../components/BirdButton.client';

By this point, yarn dev on the project would fail on request with just the file import.

Expected behaviour
This should not fail

Additional Information

Vite does not fail if we just import scss file directly

@wizardlyhel wizardlyhel added the bug Something isn't working label Mar 8, 2022
@frandiox
Copy link
Contributor

frandiox commented Mar 9, 2022

This seems to be a mix of different issues, not entirely related to SCSS, I think. Also, there's not much we can do in Hydrogen itself.

Vite, by default, won't transform files in node_modules when running in SSR because it assumes things are nicely provided in a way that Node.js understands out of the box. This is not the case for the package provided here, @weareallbirds/birdponents.ui.button.

This package provides CommonJS dist files that require the SCSS file, which is not Node compatible. Also, these files use exports instead of module.exports (probably because they were generated by Rollup), which does not work properly in Vite when imported in SSR from node_modules (known bug).

It also provides the source files in ESM. Therefore, there are two alternatives:

  1. Import from source files to bypass the exports issue in distribution files. For that, we need to:
  • Change the import path of the components: import {Button} from '@weareallbirds/birdponents.ui.button/button'; (JSX file) or import {Button} from '@weareallbirds/birdponents.ui.button/index'; (TS file).
  • Let Vite know that it needs to transform JSX/TS/SCSS from this dependency. This can be done by adding ssr.noExternal: [/@weareallbirds\/birdponents.ui.button/] in vite.config.js. The "noExternal" property is telling Vite this package must be transformed and bundled in SSR.
  1. Import from distribution files:
  • Keep the normal import path that is resolved from package.json#main. See edit below.
  • Transform the files from CJS to ESM to fix the exports keyword issue. For this, we can rely on @originjs/vite-plugin-commonjs.
  • Since files are now ESM, we need to teach Vite again to transform the files with the same ssr.noExternal option as before.

Full example of the second option:

// vite.config.js

import {defineConfig} from 'vite';
import hydrogen from '@shopify/hydrogen/plugin';
import shopifyConfig from './shopify.config';
import {esbuildCommonjs, viteCommonjs} from '@originjs/vite-plugin-commonjs';

export default defineConfig({
  plugins: [viteCommonjs(), hydrogen(shopifyConfig)],
  ssr: {
    noExternal: [/@weareallbirds\/birdponents.ui.button/],
  },
  optimizeDeps: {
    include: ['@headlessui/react', '@weareallbirds/birdponents.ui.button'],
    esbuildOptions: {
      plugins: [esbuildCommonjs(['@weareallbirds/birdponents.ui.button'])],
    },
  },
});

For package authors, a way to simplify this would be providing an optional dist in ESM format (using package.json#exports field if needed). You might still need the ssr.noExternal, though.

Hope that helps!


Edit: I just noticed that option 2 also requires changing the import code because it creates issues in Server vs Browser imports:

// BirdButton.client.jsx, importing from dist files
import * as Component from '@weareallbirds/birdponents.ui.button';
const Button = Component.default?.Button || Component.Button || Component;

export default function BirdButton({children}) {
  return <Button>{children}</Button>;
}

Perhaps this can be solved with more options around @originjs/vite-plugin-commonjs, not sure.

In any case, I'd recommend option 1 which is simpler when importing:

// BirdButton.client.jsx, importing from source files
import {Button} from '@weareallbirds/birdponents.ui.button/button';

export default function BirdButton({children}) {
  return <Button>{children}</Button>;
}

@colindunn109
Copy link

Hey @frandiox , thanks a ton for the explanation - definitely makes sense.

We'll play around with this stuff on our end, but I believe you can close this out for now as it unblocks our team. Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants