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 import isolated-vm in SSR of web applications #423

Closed
samijaber opened this issue Nov 14, 2023 · 8 comments
Closed

Unable to import isolated-vm in SSR of web applications #423

samijaber opened this issue Nov 14, 2023 · 8 comments

Comments

@samijaber
Copy link

samijaber commented Nov 14, 2023

Here is a repro using latest NextJS: https://github.com/samijaber/isolated-vm-import-bug

I have seen this issue in a variety of build tooling and web frameworks, including Webpack/Vite and Qwik/Vue/Nuxt. So it is not restricted to NextJS/React.

running npm run build (or dev) gives me this issue:

./node_modules/isolated-vm/isolated-vm.js
Module not found: Can't resolve './out/isolated_vm'
CleanShot 2023-11-14 at 16 52 37@2x

For some reason, using this require hack works just fine:

// This hack works
// const ivm = eval("require")("isolated-vm");

// this approach won't work
import ivm from "isolated-vm";

https://github.com/samijaber/isolated-vm-import-bug/blob/0b23a981f64c814e837412ce05c1353294438ff7/src/app/page.tsx#L1-L5

I am out of my depth here, and not sure why the import doesn't work, but the other approach does. I tried finding other issues reporting this but couldn't. Would appreciate any clarity!

@laverdet
Copy link
Owner

This is just your bundler configuration. You'd run into the same problem with other native modules as well, for example fs-extra. I might recommend this if you want to be less hacky.

import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const ivm = require('isolated-vm');

There are other ways around it but unless you feel strongly about your module graph integrity and static analyzability then it's probably not worth bothering.

@samijaber
Copy link
Author

Thank you for the prompt response! I will look into the solution you offered, it does feel a little less hacky 😄.

For full context, I am building several SDKs that export components: for React, React Native, Qwik, Vue, SolidJS, Svelte, NextJS-specific React. Those SDKs are importing isolated-vm, and I publish them to my end-users.

Therefore I have zero control over the web applications that end up importing my SDKs, and I want whatever solution I come up with to work out-of-the-box without needing any change in my users' web app configuration.

@samijaber
Copy link
Author

I am going to close this seeing as it is normal behavior. Thanks for the improved workaround.

@djibomar
Copy link

I facing this issue and cannot get any of the workarounds to work once I deploy on vercel . Is there a solution that worked for anyone ?

@samijaber
Copy link
Author

@djibomar one solution that partially works (for Nextjs specifically) is to:

  • use eval("require")("isolated-vm") in your code wherever you need to.
  • add an import ivm from 'isolated-vm' statement somewhere in code that is servery-side only. like pages/_document.tsx for example. This guarantees that isolated-vm is included in the server-side bundle.

NOTE: for Nextjs, you cannot use @laverdet 's suggestion:

import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const ivm = require('isolated-vm');

because Nextjs does not allow node:* imports. See vercel/next.js#60491

@laverdet
Copy link
Owner

@samijaber what would you expect from isolated-vm in a client-rendered React component?

@samijaber
Copy link
Author

@laverdet I want it to work in the SSR only. Obviously it would break the client render, but I have guarantees in place to make sure the code only executes during SSR.

A very minimal repro of what I'm trying to do is:

import React from "react";

const getBrowserEval = () => new Function("return 1+3")();

const getServerEval = () => {
  const ivm = eval("require")("isolated-vm");
  const isolate = new ivm.Isolate({ memoryLimit: 128 });
  const context = isolate.createContextSync();
  const result = context.evalSync("1+3");
  return result;
};

const isBrowser = typeof window !== "undefined";
const getEvalResult = isBrowser ? getBrowserEval : getServerEval;

export const MyComponent = () => {
  return <div>1 + 3 = {getEvalResult()}</div>;
};

use isolated-vm on the server, and new Function() on the client to generate the same part of the React app.

@laverdet
Copy link
Owner

I see. You can do this in Webpack. Not sure how you would do it in Vite but I am sure there is a way. In Webpack you just add resolve: { alias: { "isolated-vm": false } } to the configuration which would cause that import to be null when bundling. For extra points use DefinePlugin to overwrite typeof window with "undefined" and then Terser will automatically remove the ternary.

I wouldn't recommend using eval and new Function('return '+'...') interchangeably since it will be difficult or impossible to make them behave the same in all cases. In general if you find yourself concating user code then something has gone wrong.

If you want to avoid the direct eval pessimizations in the browser you can make it indirect with (0,eval)('1+2') (assuming strict mode or esm)

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

3 participants