How can Webpack 5 polyfills resolve with Yarn 2 PnP? (Cypress issue)

2.4k views Asked by At

I ran into this issue in the context of adding the cypress-dark (theme) plugin to the "test" package of a Yarn 2 workspace, but I think the issue is general. To put a finer point on the original question, I think what I need to know is: how can I get Webpack's resolve.fallback option to make use of PnPWebpackPlugin?

In order to use Webpack 5 with Cypress, I've been using cypress-webpack-preprocessor-v5. After importing cypress-dark in support/index.ts, running a spec yielded the following error in the browser:

Error: Webpack CompLooked for and couldn't find the file at the following paths:resolve 'path' in '/home/john/Projects/current/djinndex-remastered/.yarn/cache/postcss-npm-7.0.16-2507f8c3e2-b867411523.zip/node_modules/postcss/lib'

BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.

If you want to include a polyfill, you need to:
    - add a fallback 'resolve.fallback: { "path": require.resolve("path-browserify") }'
    - install 'path-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
    resolve.fallback: { "path": false }

Accordingly, I added

resolveLoader: {
        plugins: [PnpWebpackPlugin.moduleLoader(module)],
},

to the my plugins/index.ts as follows:

/**
 * @type {Cypress.PluginConfig}
 */
//@ts-ignore
module.exports = (on, config) => {
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config
  const options = {
    webpackOptions: {
      ...require('../../../client/webpack/webpack.cypress.js'),
      resolve: {
        plugins: [PnpWebpackPlugin],
        extensions: ['.ts', '.js'],
        fallback: { path: require.resolve('path-browserify') },
      },
      resolveLoader: {
        plugins: [PnpWebpackPlugin.moduleLoader(module)],
      },
      module: {
        rules: [
          {
            test: /\.ts$/,
            exclude: [/node_modules/],
            use: [
              {
                loader: 'ts-loader',
              },
            ],
          },
        ],
      },
    },
    watchOptions: {},
  };
  on('file:preprocessor', webpackPreprocessor(options));
};

Running a spec then yields the following error:

cypress_runner.js:199777 AssertionError: Timed out retrying: `cy.readFile("dark.css")` failed because the file does not exist at the following path:

`/home/[.../<workspace>/]packages/test/dark.css`

Because this error occurred during a `before all` hook we are skipping all of the remaining tests.
    at Context.eval (http://localhost:3000/__cypress/tests?p=cypress/support/index.ts:3280:8)

Webpack should be looking for dark.css in a Yarn 2 virtual pacakge from /.yarn/cache/cypress-dark-npm-1.7.14-295ea64c64-9b608eccac.zip, rather than in the root of the "test" package (of the workspace) from which I am running Cypress. So it seems like

{resolve: fallback: { path: require.resolve('path-browserify')} 

in webpackOptions falls back to something which doesn't know about PnPWebpackPlugin for resolutions. That's where I'm stuck. Any guidance would be much appreciated :)

1

There are 1 answers

0
John Camden On BEST ANSWER

Turns out that the path polyfill for Webpack 5 is working fine with Yarn 2. The issue is that the package itself, cypress-dark, specifically seeks a node_modules directory:

? join('node_modules/cypress-dark/src')

So, that's good news. For other Yarn 2 users, in order to avoid the node_modules thing, I ended up just recreating a simplified version of the cypress-dark plugin locally in cypress/support/theme/index.ts:

import postcss from 'postcss';
//@ts-ignore
import cssVariables from 'postcss-css-variables';

const getHead = () => Cypress.$(parent.window.document.head);
const $head = getHead();
const isStyleLoaded = ($head: JQuery<HTMLHeadElement>) =>
  $head.find('#cypress-dark').length > 0;

const convertCssVariables = (mycss: any) =>
  postcss([cssVariables()]).process(mycss).css;

export const loadTheme = (theme: string) => {
  return () => {
    const $head = getHead();
    if (isStyleLoaded($head)) {
      return;
    }

    const themeFilename = `${theme}.css`;

    cy.readFile(themeFilename, { log: false })
      .then(convertCssVariables)
      .then((css) => {
        $head.append(
          `<style type="text/css" id="cypress-dark" theme="${theme}">\n${css}</style>`
        );
      });
  };
};

I copied dark.css to cypress/support/theme (and made some personal preference tweaks). And then I called loadTheme("dark") in cypress/support/index.ts:

import { loadTheme } from './theme';
before(loadTheme('dark'));