ink icon indicating copy to clipboard operation
ink copied to clipboard

Deno support

Open maxdumas opened this issue 4 years ago • 25 comments

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?

maxdumas avatar Dec 13 '19 16:12 maxdumas

  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.

sindresorhus avatar Dec 16 '19 13:12 sindresorhus

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.

maxdumas avatar Dec 16 '19 15:12 maxdumas

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?

Songkeys avatar Feb 20 '21 05:02 Songkeys

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.

aadamsx avatar Mar 26 '21 03:03 aadamsx

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 avatar Apr 15 '21 18:04 vadimdemedes

Going to re-open this issue to increase visibility.

vadimdemedes avatar Apr 15 '21 18:04 vadimdemedes

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/[email protected]";
import React from "https://esm.sh/[email protected]";

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/[email protected]/deno/restore-cursor.js:2:1092
    at https://esm.sh/v90/[email protected]/deno/restore-cursor.js:2:700
    at https://esm.sh/v90/[email protected]/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.

brad-jones avatar Aug 13 '22 03:08 brad-jones

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!

clo4 avatar Aug 14 '22 07:08 clo4

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

Morantron avatar Aug 16 '22 11:08 Morantron

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/[email protected]/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/[email protected]/node/console.ts";
window.console = console;

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

// ...

clo4 avatar Aug 25 '22 03:08 clo4

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:

  • can't find types
  • something something something useContext
  • something something something null of useState
  • etc etc etc, blah-blah-blah: boring-non-working-ink-in-deno-things

airtonix avatar Nov 12 '22 00:11 airtonix

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 />)

pethin avatar Aug 16 '23 05:08 pethin

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 avatar Sep 30 '23 07:09 lanceturbes

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.

lanceturbes avatar Sep 30 '23 15:09 lanceturbes

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

vadimdemedes avatar Nov 11 '23 18:11 vadimdemedes

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

https://deno.land/[email protected]/node/net.ts?s=Socket&p=prototype.ref seems to suggest otherwise? I might be mistaken, though.

ulken avatar Nov 11 '23 19:11 ulken

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

ulken avatar Nov 15 '23 11:11 ulken

https://deno.land/[email protected]/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!

hugojosefson avatar Nov 16 '23 08:11 hugojosefson

@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.

ulken avatar Nov 16 '23 14:11 ulken

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).

mabasic avatar Feb 10 '24 13:02 mabasic

@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?

cowboyd avatar Mar 23 '24 13:03 cowboyd

@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 avatar Mar 23 '24 14:03 mabasic

@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]);

mabasic avatar Mar 24 '24 09:03 mabasic

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 ..

mbostwick avatar Apr 03 '24 07:04 mbostwick