vadimdemedes / ink

🌈 React for interactive command-line apps
https://term.ink
MIT License
26.58k stars 600 forks source link

Deno support #250

Open maxdumas opened 4 years ago

maxdumas commented 4 years ago

deno seems like a great environment for running ink, as it aims to be a simple, secure, and fast way to ship JavaScript executables to users while being extremely light in setup for developers. Looking at ink and putzing around a bit (I'm admittedly pretty new to deno and ink), it seems that there are a few issues with its current setup that make it difficult to use with deno:

  1. ink does not publish a version of itself as a bundled ESM. deno requires that packages be consumed as ESM, and that ESM would likely have to have bundled with it all of inks dependencies. To fix that would require introducing an ESM bundling step as part of the build for the package, using something like rollup. I took a quick stab at doing that in a fork, which can be found here. The fork successfully bundles but still does not work in deno.
  2. Once the ESM bundle is being published as part of the build, the simplest way to vend that bundle would be to just point to the generated bundle using the "module" field in package.json. Then package managers and CDNs that vend ES6 modules, like pika.dev, could pick up ink and serve the ESM bundle. Note how currently pika.dev has ink listed but cannot serve it because there is no "module" field in the package.json.
  3. Any dependencies that ink or its npm dependencies have on the Node standard library or Node-specific runtime behavior may need to be refactored to accommodate a deno environment. See here for the deno standard library. It does deviate somewhat from the Node standard library. I have not taken the time to actually identify which libraries or parts of ink might fall under this category, but this could be the largest chunk of work to provide deno support and will potentially involve updating multiple libraries.

Have any of the library maintainers considered deno support? Points (1) and (2) seem valuable in general, even outside of a goal of deno support, as moving the JS ecosystem towards an ESM-first world will make a big impact in the usability of libraries in a more progressive, lightweight manner. However, with deno, getting up and started with ink could be as simple as (sloppily adapting the Counter demo):

import React from 'https://cdn.pika.dev/react/latest';
import {render, Color} from 'https://cdn.pika.dev/ink/latest';

const Counter = () => {
    const [counter, setCounter] = React.useState(0);

    React.useEffect(() => {
        const timer = setInterval(() => {
            setCounter(prevCounter => prevCounter + 1);
        }, 100);

        return () => {
            clearInterval(timer);
        };
    });

    return (
        <Color green>
            {counter} tests passed
        </Color>
    );
};

render(<Counter/>);

Note that the code hasn't changed much, but the real difference is that this one file would be the entire project for getting started with an ink app. No package.json, no Babel, no Typescript compiler, no need for associated code generators like create-ink-app. It would allow new developers and folks just experimenting to use ink much more quickly and easily.

Comments? Thoughts?

sindresorhus commented 4 years ago
  1. We'll just wait until we can target ESM in Node.js, which should be possible soon.
  2. We would rather use the "type": "module" field: https://nodejs.org/api/esm.html#esm_code_package_json_code_code_type_code_field
  3. This would be very large undertaking.

Have any of the library maintainers considered deno support?

I've been passively following the Deno development, and while it has great potential and has done many things right (Rust, TS, security), it's still very immature project. I don't think it makes sense at this point to even consider adding Deno support to large complex Node.js modules like Ink.

If you really want to see this happen, I would recommend forking and trying to implement support for it yourself. Could be a fun exercise. But don't expect anything to be merged upstream anytime soon.

No package.json, no Babel, no Typescript compiler, no need for associated code generators like create-ink-app.

The only thing you need is a package.json, and it can be small. Babel/TypeScript/etc are not required.

maxdumas commented 4 years ago

Thanks for the response @sindresorhus! Those are definitely fair points, especially regarding (3). I may continue exploring the idea in my own fork. If I am able to get anywhere then I'll definitely keep you posted.

songkeys commented 3 years ago

Hi! It's been a year. Deno has published its official 1.x version as well. Since v1.7, we can even compile a deno project into a single binary file. I'd be much happy to see ink supporting it! Any plan so far?

aadamsx commented 3 years ago

Any word on if this is ever going to happen? We are at Deno 1.8 now and Deno is heavly used on the command line.

vadimdemedes commented 3 years ago

Hey, I'm currently spending all my free time on Lotus, so unfortunately I can't promise anything. As Sindre suggested, feel free to experiment with it by forking Ink and let's see what we can do to support it out of the box if possible.

vadimdemedes commented 3 years ago

Going to re-open this issue to increase visibility.

brad-jones commented 2 years ago

I'd love Deno support too, this would be great to pair with https://cliffy.io/

I gave it a go with the following code.

import { render, Text } from "https://esm.sh/ink@3.2.0";
import React from "https://esm.sh/react@18.2.0";

export const Example = () => (
  <>
    <Text color="green">I am green</Text>
    <Text color="black" backgroundColor="white">
      I am black on white
    </Text>
    <Text color="#ffffff">I am white</Text>
    <Text bold>I am bold</Text>
    <Text italic>I am italic</Text>
    <Text underline>I am underline</Text>
    <Text strikethrough>I am strikethrough</Text>
    <Text inverse>I am inversed</Text>
  </>
);

render(<Example />);

But ultimately it failed with:

This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills
This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills
error: Uncaught TypeError: _ is not a function
    at https://esm.sh/v90/restore-cursor@3.1.0/deno/restore-cursor.js:2:1092
    at https://esm.sh/v90/restore-cursor@3.1.0/deno/restore-cursor.js:2:700
    at https://esm.sh/v90/restore-cursor@3.1.0/deno/restore-cursor.js:2:1173

I haven't bothered looking at it any further. I imagine it's likely to be very long & dark rabbit hole for someone to go down.

Also just had a look at https://bun.sh & boy is it fast. It seems to be striving to have better node compat than deno too so it might one day be easier to make this work on bun than deno. Although granted today bun is really just an experiment not a useable bit of software IMO. Defo one to keep an eye on though.

clo4 commented 2 years ago

A couple months ago I put in about an hour into making Ink work with Deno, did get it working albeit with the same warning. Screenshot below has two of the builtin demos and the code @brad-jones used above:

Demo of Ink working in Deno

Ultimately didn't end up pursuing it further, but it's definitely possible to do!

Morantron commented 2 years ago

apparently deno is improving node/npm compatiblity, so it might just work out of the box in the following months https://deno.com/blog/changes#compatibility-with-node-and-npm

clo4 commented 2 years ago

Deno 1.25.0 is out, it's now possible to use NPM module specifiers (experimental).

There's still React's obnoxious rAF warning though.

$ deno run --unstable --allow-all ink-test.jsx
// required because ink does some trickery with node's Console object
import console from "https://deno.land/std@0.153.0/node/console.ts";
window.console = console;

import React from "npm:react";
import { render, Box, Text, useFocus } from "npm:ink";

const Focus = () => (
  <Box flexDirection="column" padding={1}>
    <Box marginBottom={1}>
      <Text>
        Press Tab to focus next element, Shift+Tab to focus previous element,
        Esc to reset focus.
      </Text>
    </Box>
    <Item label="First" />
    <Item label="Second" />
    <Item label="Third" />
  </Box>
);

const Item = ({label}) => {
  const {isFocused} = useFocus();
  return (
    <Text>
      {label} {isFocused && <Text color="green">(focused)</Text>}
    </Text>
  );
};

render(<Focus />)

Edit: raf polyfill - make sure this is the first import!

raf.js

// based on https://gist.github.com/paulirish/1579671

let lastTime = 0;

window.requestAnimationFrame = function (callback, _element) {
  let currTime = new Date().getTime();
  let timeToCall = Math.max(0, 16 - (currTime - lastTime));
  let id = setTimeout(() => callback(currTime + timeToCall), timeToCall);
  lastTime = currTime + timeToCall;
  return id;
};

window.cancelAnimationFrame = function (id) {
  clearTimeout(id);
};

ink-test.jsx

import "./raf.js";

import console from "https://deno.land/std@0.153.0/node/console.ts";
window.console = console;

import React from "npm:react";
import { render, Box, Text, useFocus } from "npm:ink";

// ...
airtonix commented 1 year ago

Protip: don't use react 18 with deno and ink.

import_map.json

{
  "imports": {
    "react": "npm:react@17",
    "ink": "npm:ink",
    "cli-spinners": "npm:cli-spinners"
  }
}

You'll just end up with lots of errors around either:

pethin commented 1 year ago

With the latest Deno. And the above suggestion. This seems to work without the --unstable flag.

$ deno run --allow-all ink-test.jsx
// required because ink does some trickery with node's Console object
import console from "node:console";
window.console = console;

import React from "https://esm.sh/react@18";
import { render, Box, Text, useFocus } from "https://esm.sh/ink@4";

const Focus = () => (
  <Box flexDirection="column" padding={1}>
    <Box marginBottom={1}>
      <Text>
        Press Tab to focus next element, Shift+Tab to focus previous element,
        Esc to reset focus.
      </Text>
    </Box>
    <Item label="First" />
    <Item label="Second" />
    <Item label="Third" />
  </Box>
);

const Item = ({label}) => {
  const {isFocused} = useFocus();
  return (
    <Text>
      {label} {isFocused && <Text color="green">(focused)</Text>}
    </Text>
  );
};

render(<Focus />)
lanceturbes commented 1 year ago

Copying above instructions hasn't worked for me when making use of useStdin() or useFocus() inside an ink app with deno 1.37.0, tested with Windows/Mac/Linux.

Getting a stdin.ref is not a function error.

lanceturbes commented 1 year ago

Downgrading to ink 4.4.0 (vs 4.4.1) seems to remove the stdin.ref is not a function error for me.

App does not exit gracefully with CTRL+C, though.

vadimdemedes commented 11 months ago

https://nodejs.org/dist/latest-v20.x/docs/api/net.html#socketref must be missing in Deno then.

ulken commented 11 months ago

https://nodejs.org/dist/latest-v20.x/docs/api/net.html#socketref must be missing in Deno then.

https://deno.land/std@0.158.0/node/net.ts?s=Socket&p=prototype.ref seems to suggest otherwise? I might be mistaken, though.

ulken commented 11 months ago

@vadimdemedes you are right, it's indeed missing. I don't know what the docs above are referring to. Opened an issue.

hugojosefson commented 11 months ago

https://deno.land/std@0.158.0/node/net.ts?s=Socket&p=prototype.ref seems to suggest otherwise? I might be mistaken, though.

That looks like an older version of the standard library. I'm not sure where that code went, since the file does not exist in the latest version of the standard lib, or if it was removed for some reason.

I'm quite sure the standard library is not part of the Deno runtime environment, and I think it's not part of what's included in the import ... from "npm:..." support in Deno, but I could be mistaken :)

Hopefully you will find help through the issue you filed, @ulken!

ulken commented 11 months ago

@hugojosefson you're absolutely right. Silly me.

I think Node API Compatibility List is a better resource.

Under node:tty one can read:

Missing ReadStream and WriteStream implementation.

ReadStream is essential here.

mabasic commented 8 months ago

I have managed to use this library with Deno 1.40.4. I could not get useInput to work because of the difference between Deno and Node with stdin, but I got it working by using the "input" from Cliffy (deno library).

cowboyd commented 7 months ago

@mabasic I was able to run the demo as well with the latest version of Deno, but among other things, hitting CTRL-C didn't work. Do you have a snippet or a gist of what you did to get keyboard input to function?

mabasic commented 7 months ago

@mabasic I was able to run the demo as well with the latest version of Deno, but among other things, hitting CTRL-C didn't work. Do you have a snippet or a gist of what you did to get keyboard input to function?

There is a trick which I have found somewhere in the issues ... I'll try to find it in my code and send you the example

mabasic commented 7 months ago

@cowboyd

  const handleKeyPresses = useCallback(async (
    event: KeyPressEvent,
  ) => {
    if (event.key === "c" && event.ctrlKey) {
      exit(); // exits ink
      Deno.exit(0); // exits deno process
    }
  }, []);

  useEffect(() => {
    keypress().addEventListener("keydown", handleKeyPresses);

    return () => {
      keypress().removeEventListener("keydown", handleKeyPresses);
    };
  }, [handleKeyPresses]);
mbostwick commented 7 months ago

You may want to also consider supporting https://bun.sh/ at the same time, in theory its suppose to be a light replacement, but I confirmed that the display is not getting the inputs when running in bun

https://www.builder.io/blog/bun-vs-node-js has more about the whole bun and other run times ...

I did do a deep dive to see what I could do, but it looks like the biggest issue is the way use input is hooked into the context change detection from process.stdin and the event call backs around it. There are also some defaults of process. but giving up on it now ..

jlandowner commented 5 months ago

I arrived at here for trying to create tui app by Deno. With deno v1.43.1, I found the @pethin code works without node:console and CTRL-C also works without any tricks🙌

import React from "npm:react";
import { Box, render, Text, useFocus } from "npm:ink";

const Focus = () => (
  <Box flexDirection="column" padding={1}>
    <Box marginBottom={1}>
      <Text>
        Press Tab to focus next element, Shift+Tab to focus previous element,
        Esc to reset focus.
      </Text>
    </Box>
    <Item label="First" />
    <Item label="Second" />
    <Item label="Third" />
  </Box>
);

const Item = ({ label }) => {
  const { isFocused } = useFocus();
  return (
    <Text>
      {label} {isFocused && <Text color="green">(focused)</Text>}
    </Text>
  );
};

render(<Focus />);
スクリーンショット 2024-05-07 20 38 17
foster-hangdaan commented 5 months ago

I am on Deno v1.43.3 and my TUI app is using Ink v5.0.0 without any hacks.

My only gripe is my app now requires certain read and env permissions after installing the react and ink packages. The app requires these permissions even after compiling the executable with deno compile. More specifically, it requires read access to the current working directory and .cache/deno/npm/registry.npmjs.org/yoga-wasm-web/0.3.3/dist/yoga.wasm; as well as access to all environment variables. Does anyone know of a way around this? I'd rather not grant all permissions by supplying -A as that defeats the purpose of Deno's sandboxing capability.