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

Unable to use alias Preact in next branch #1552

Open
birtles opened this issue Sep 14, 2023 · 9 comments
Open

Unable to use alias Preact in next branch #1552

birtles opened this issue Sep 14, 2023 · 9 comments

Comments

@birtles
Copy link
Contributor

birtles commented Sep 14, 2023

What's wrong?

I'm trying to set up react-cosmos@next on a project using Preact but I get include errors due to some files including react:

$ cosmos
node:internal/errors:490
    ErrorCaptureStackTrace(err);
    ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'react' imported from C:\Users\Brian\10ten-ja-reader\node_modules\react-cosmos-core\dist\utils\react\ClassStateMock.js
    at new NodeError (node:internal/errors:399:5)
    at packageResolve (node:internal/modules/esm/resolve:889:9)
    at moduleResolve (node:internal/modules/esm/resolve:938:20)
    at defaultResolve (node:internal/modules/esm/resolve:1153:11)
    at nextResolve (node:internal/modules/esm/loader:163:28)
    at ESMLoader.resolve (node:internal/modules/esm/loader:838:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:424:18)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:77:40)
    at link (node:internal/modules/esm/module_job:76:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}

Node.js v18.16.0

I've tried adding a separate webpack config for cosmos like the following but, as far as I can tell, the error occurs before that is applied:

return {
  resolve: {
    alias: {
      react: 'preact/compat',
      'react-dom/test-utils': 'preact/test-utils',
      'react-dom': 'preact/compat',
    },
    extensions: ['.ts', '.tsx', '.js'],
  },
};

Steps to reproduce

Sorry, it's going to take me a while to reduce this. But it should be possible to reproduce on any existing project by removing react from the project's dependencies and installing preact instead, then aliasing it in webpack config as shown above.

Useful info

  • Cosmos version: react-cosmos@next
  • Cosmos config
{
  "$schema": "http://json.schemastore.org/cosmos-config",
  "plugins": ["react-cosmos-plugin-webpack"],
  "webpack": {
    "configPath": "./cosmos-webpack.config.js"
  }
}
  • Server terminal output (see above)
  • Browser console output (n/a)

Additional context

This used to work after I fixed it in #1316.

@ovidiuch
Copy link
Member

ovidiuch commented Sep 20, 2023

This doesn't seem to be webpack related since the error originates on the Node server side.

This probably has to do with the fact that react-cosmos-core has a single entry point that is imported either on the server or on the client side, and it also includes modules exports that contain react imports.

export * from './utils/react/ClassStateMock.js';
export * from './utils/react/DelayRender.js';
export * from './utils/react/areNodesEqual.js';
export * from './utils/react/getComponentName.js';
export * from './utils/react/isReactElement.js';

One potential solution is to decouple the main react-cosmos-core entry point from react and have a separate react-cosmos-core/react entry point that is only imported once already in a React context. But I'm not sure what the implications are at the moment.

Also there are some react-is imports outside the utils/react folder in the core package:

image

@birtles
Copy link
Contributor Author

birtles commented Sep 20, 2023

Thanks for looking into this. I guess converting every place that imports react to a dynamic import would complicate things too much?

I had a quick look at how to set up import aliases with node and it looks like it might be possible with imports in package.json but I couldn't find a way to override that from the command-line. Maybe symlinks?

@birtles
Copy link
Contributor Author

birtles commented Sep 21, 2023

I tried getting this to work today by using tsconfig-paths and also using bun's path mapping but didn't have any luck with either yet.

@birtles
Copy link
Contributor Author

birtles commented Sep 21, 2023

I also tried using module-alias and a cosmos-bootstrap.js file like the following but it didn't work:

#!/usr/bin/env node

import moduleAlias from 'module-alias';

moduleAlias.addAliases({
  react: 'preact/compat',
  'react-dom': 'preact/compat',
});

await import('react-cosmos/bin/cosmos.js');

I just get:

$ node cosmos-bootstrap.js
node:internal/process/esm_loader:97
    internalBinding('errors').triggerUncaughtException(
                              ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'react' imported from C:\Users\Brian\10ten-ja-reader\node_modules\react-cosmos-core\dist\utils\react\ClassStateMock.js
Did you mean to import react/compat/dist/compat.js?
    at new NodeError (node:internal/errors:399:5)
    at packageResolve (node:internal/modules/esm/resolve:889:9)
    at moduleResolve (node:internal/modules/esm/resolve:938:20)
    at defaultResolve (node:internal/modules/esm/resolve:1153:11)
    at nextResolve (node:internal/modules/esm/loader:163:28)
    at ESMLoader.resolve (node:internal/modules/esm/loader:838:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:424:18)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:77:40)
    at link (node:internal/modules/esm/module_job:76:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}

@birtles
Copy link
Contributor Author

birtles commented Sep 22, 2023

I got a bit closer today using Node's module-based permissions to emulate import maps.

policy.json

{
  "dependencies": true,
  "scopes": {
    "": {
      "cascade": true,
      "integrity": true,
      "dependencies": {
        "react": "./node_modules/preact/compat/dist/compat.mjs"
      }
    }
  }
}

(It might be necessary to use compat.js instead of compat.mjs on non-ESM projects.)

Then run:

node --experimental-policy=policy.json node_modules/react-cosmos/bin/cosmos.js

I now hit a different error though:

$ node --experimental-policy=policy.json node_modules/react-cosmos/bin/cosmos.js
(node:35104) ExperimentalWarning: Policies are experimental.
(Use `node --trace-warnings ...` to show where the warning was created)
[Cosmos] Using cosmos config found at cosmos.config.json
[Cosmos] Found 1 plugin: Webpack
[Cosmos] See you at http://localhost:5000
[Cosmos] Using webpack config found at webpack.config.js
[Cosmos] Learn how to override webpack config for cosmos: https://github.com/react-cosmos/react-cosmos/tree/main/docs#webpac
k-config-override
[Cosmos][plugin:webpack] Dev server init failed
[Cosmos] Server crashed...

  (╯°□°)╯︵ ┻━┻

Error: [Cosmos] Local dependency not found: react
    at resolveLocalReactDeps (file:///C:/Users/Brian/10ten-ja-reader/node_modules/react-cosmos-plugin-webpack/dist/server/webpackConfig/getWebpackConfigResolve.js:31:19)
    at getWebpackConfigResolve (file:///C:/Users/Brian/10ten-ja-reader/node_modules/react-cosmos-plugin-webpack/dist/server/webpackConfig/getWebpackConfigResolve.js:4:36)
    at getDevWebpackConfig (file:///C:/Users/Brian/10ten-ja-reader/node_modules/react-cosmos-plugin-webpack/dist/server/webpackConfig/getDevWebpackConfig.js:18:18)
    at Object.webpackDevServerPlugin [as devServer] (file:///C:/Users/Brian/10ten-ja-reader/node_modules/react-cosmos-plugin-webpack/dist/server/webpackDevServerPlugin.js:13:28)
    at startDevServer (file:///C:/Users/Brian/10ten-ja-reader/node_modules/react-cosmos/dist/devServer/startDevServer.js:48:34)

That might just be because my webpack config is very complicated and the aliases for react there are not being picked up correctly.

@ovidiuch
Copy link
Member

@birtles Thanks for posting these updates!

FYI the Local dependency not found exception is thrown here when React aliases aren't defined.

throw new Error(`[Cosmos] Local dependency not found: react`);

And this is how aliases are checked:

function hasAlias(alias: webpack.ResolveOptions['alias'], name: string) {
if (!alias) return false;
const exactName = `${name}$`;
if (Array.isArray(alias)) {
return alias.some(a => a.name === name || a.name === exactName);
} else {
const keys = Object.keys(alias);
return keys.includes(name) || keys.includes(exactName);
}
}

Also as a general idea. Not sure if this works but couldn't you point react to preact straight from package.json? I don't know if the APIs are identical but maybe you could create a dummy React package that exports Preact. Maybe use local paths in the react version string.

@birtles
Copy link
Contributor Author

birtles commented Sep 28, 2023

Thank you!

I tried the local paths approach and that works too. That might be better than using Node's module-based permissions since they're still experimental.

I haven't debugged why React Cosmos can't find the aliases in my current webpack config but I've confirmed that if I make a separate webpack config for cosmos everything seems to work. I'll probably try to debug that later.

For reference, for the local paths approach I used the following package.json

{
  "name": "react-polyfill",
  "version": "4.0.0",
  "private": true,
  "description": "A wrapper around preact-compat so react-cosmos will load Preact instead of React",
  "main": "../node_modules/preact/compat/dist/compat.js",
  "module": "../node_modules/preact/compat/dist/compat.module.js",
  "umd:main": "../node_modules/preact/compat/dist/compat.umd.js",
  "source": "../node_modules/preact/compat/src/index.js",
  "types": "../node_modules/preact/compat/src/index.d.ts",
  "peerDependencies": {
    "preact": "^10.0.0"
  }
}

And referenced it from the root package.json like so:

    "react": "file:./react-polyfill/",
    "react-cosmos": "6.0.0-beta.6",
    "react-cosmos-plugin-webpack": "6.0.0-beta.6",
    "react-dom": "file:./react-polyfill/",

Regarding this issue, I guess the resolution is to add something to the docs about how to use Preact.

@birtles
Copy link
Contributor Author

birtles commented Sep 30, 2023

I went back to this today but after rebasing my work somehow the local paths approach stopped working.

As best I can tell, node initially wants to require react using CJS when running react-cosmos on startup (despite my project and react-cosmos being ESM) so I can't just make the wrapper expose only the ESM version.

When it gets to react-cosmos-renderer, however, it seems to prefer loading as ESM when available but I can't make it available without adding "exports" to package.json and "exports" requires a relative path to a subdirectory so I can't do it from a wrapper (which refers to a parent directory).

i.e. node will reject something like the following:

{
  "name": "react-polyfill",
  "version": "4.0.0",
  "private": true,
  "description": "A wrapper around preact-compat so react-cosmos will load Preact instead of React",
  "main": "../preact/compat/dist/compat.js",
  "module": "../preact/compat/dist/compat.module.js",
  "umd:main": "../preact/compat/dist/compat.umd.js",
  "source": "../preact/compat/src/index.js",
  "types": "../preact/compat/src/index.d.ts",
  "exports": {
    ".": {
      "types": "../preact/compat/src/index.d.ts",
      "browser": "../preact/compat/dist/preact.module.js",
      "umd": "../preact/compat/dist/preact.umd.js",
      "import": "../preact/compat/dist/preact.mjs",
      "require": "../preact/compat/dist/preact.js"
    }
  },
  "peerDependencies": {
    "preact": "^10.0.0"
  }
}

I might have to go back to the policy approach after all.

@birtles
Copy link
Contributor Author

birtles commented Sep 30, 2023

The module-based approach seems to work fine so I'll stick with that. It basically boils down to:

  1. Add a file, e.g. cosmos-policy.json
{
  "dependencies": true,
  "scopes": {
    "": {
      "cascade": true,
      "integrity": true,
      "dependencies": {
        "react": "./node_modules/preact/compat/dist/compat.mjs"
      }
    }
  }
}
  1. Update package.json to include:
{
  "scripts": {
    "cosmos": "node --experimental-policy=cosmos-policy.json node_modules/react-cosmos/bin/cosmos.js",
    "cosmos-export": "node --experimental-policy=cosmos-policy.json node_modules/react-cosmos/bin/cosmos-export.js"
  }
}

Also, I debugged why react-cosmos-plugin-webpack wasn't handling my webpack.config.js and it appears to be because it doesn't handle multiple configurations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants