laverdet / isolated-vm

Secure & isolated JS environments for nodejs
ISC License
2.18k stars 154 forks source link

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

Closed samijaber closed 1 year ago

samijaber commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

djibomar commented 9 months ago

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 commented 8 months ago

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

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 https://github.com/vercel/next.js/issues/60491

laverdet commented 8 months ago

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

samijaber commented 8 months ago

@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 commented 7 months ago

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)