As featured in Open JS World 2020 by Ryan Dahl.
Deno v1 has shipped and is causing a real buzz in the JavaScript community.
For those that haven't come across it yet, Deno is a new runtime for JavaScript and TypeScript outside of the web browser. It based on the V8 JavaScript Engine, written in Rust and was created by Ryan Dahl, the original founder of Node.js.
If you want to find out more about Deno and it's mission, check out the Deno 1.0 launch blogpost written by the creators.
Background over, let's begin with writing our React SSR application in Deno!
Installation
Deno can be installed using all the main package installers as well using the official installer scripts. Here are some of the main ways to install:
Shell (Mac, Linux):
curl -fsSL https://deno.land/x/install/install.sh | sh
PowerShell (Windows):
iwr https://deno.land/x/install/install.ps1 -useb | iex
Homebrew (Mac):
brew install deno
Chocolatey (Windows):
choco install deno
Head over to the Deno installation page for other installation methods and further details!
Getting started
Please note that Deno is rapidly developing, which can quickly make articles like this one become outdated. Always check to see if there are newer versions of the modules used to ensure the code works for the latest version of Deno.
Having installed Deno you can now run make use of the deno
command! Use deno help
to explore the commands on offer. We'll be using this command to run our React SSR app later on.
But first let's create a project!
In a new project directory let's create three files:
.
├── app.tsx
├── client.tsx
└── server.tsx
app.tsx
will contain our React component code, server.tsx
will hold all of our server code and client.tsx
will act as our entrypoint to the client-side bundle. Be careful to get the correct file extensions!
Writing our client-side bundle
In the client.tsx
file, add the following code to set up our client-side entrypoint:
import React from "https://dev.jspm.io/react@16.13.1";
import ReactDOM from "https://dev.jspm.io/react-dom@16.13.1";
import App from "./app.tsx";
(ReactDOM as any).hydrate(
<App />,
//@ts-ignore
document.getElementById("root"),
);
First we import React and React DOM like we're used to in any React app, but instead of importing from "react"
, we're importing it from a url...!?
That's right, in Deno you can import modules from any URL and relative or absolute file path that exports a module. This means you can easily pull in any code from the web, e.g. gists, GitHub code and are no longer tied to versions that have been released - if there's something on a main
branch that you can't wait to try, you can just import it!
Here we are importing React and React DOM from JSPM, but you could equally use any CDN that provides React as an ES Module. Check out the Deno website for CDN alternatives.
Following our imports of React libraries we import our App component (yet to be written!) and finally set up the code to render our application, using the React DOM hydrate method.
Now let's write our first React component in Deno!
Writing the React component
Our app.tsx
:
// @deno-types="https://raw.githubusercontent.com/Soremwar/deno_types/4a50660/react/v16.13.1/react.d.ts"
import React from "https://dev.jspm.io/react@16.13.1";
const App = () => {
const [count, setCount] = React.useState(0);
return (
<div>
<h1>Hello Deno Land!</h1>
<button onClick={() => setCount(count + 1)}>Click the 🦕</button>
<p>You clicked the 🦕 {count} times</p>
</div>
);
};
export default App;
There's a lot going on here, so let's break it down -
First we import React
like we're used to in any React app, but notice we also are using a @deno-types
hint comment. This allows us to inform deno of where to find the TypeScript types for the imported module - neat huh?
You can choose to omit this type hint, but TypeScript will require you to provide the types yourself (imported or custom written). Alternatively, you have avoid using TypeScript altogether simply by changing the file extension to .jsx
. Deno supports both TypeScript and JavaScript out-of-the-box!
Lastly we create a small React component called App
which uses hooks to create a button click counter - simple! Overall, there isn't much difference to writing a React component in NodeJS.
Writing the server
For the server we will be using the Deno web framework Opine, which is a port of the ExpressJS web framework that is commonly used in NodeJS.
Here's the code we'll be using for server.tsx
:
// @deno-types="https://raw.githubusercontent.com/Soremwar/deno_types/4a50660/react/v16.13.1/react.d.ts"
import React from "https://dev.jspm.io/react@16.13.1";
import ReactDOMServer from "https://dev.jspm.io/react-dom@16.13.1/server";
import { opine } from "https://deno.land/x/opine@0.25.0/mod.ts";
import App from "./app.tsx";
/**
* Create our client bundle - you could split this out into
* a preprocessing step.
*/
const [diagnostics, js] = await Deno.bundle(
"./client.tsx",
undefined,
{ lib: ["dom", "dom.iterable", "esnext"] },
);
if (diagnostics) {
console.log(diagnostics);
}
/**
* Create our Opine server.
*/
const app = opine();
const browserBundlePath = "/browser.js";
const html =
`<html><head><script type="module" src="${browserBundlePath}"></script><style>* { font-family: Helvetica; }</style></head><body><div id="root">${
(ReactDOMServer as any).renderToString(<App />)
}</div></body></html>`;
app.use(browserBundlePath, (req, res, next) => {
res.type("application/javascript").send(js);
});
app.use("/", (req, res, next) => {
res.type("text/html").send(html);
});
app.listen({ port: 3000 });
console.log("React SSR App listening on port 3000");
Here's what is going on:
- First we import our main dependencies of
React
,ReactDOMServer
and the Opine web framework. - We then import the React app we just created, being careful to include the
.tsx
extension - file extensions are required by Deno unlike in NodeJS. - First we use the
Deno.bundle()
method to create our client-side JavaScript bundle from our application. - Next we create an Opine app, much like you would do with ExpressJs, and define some routes: one to serve a simple HTML page containing our rendered app, and another
/browser.js
route to server our app's client-side bundle so we can hydrate the React application on the client. - Finally we start the server using the
listen()
method on port3000
.
And that's it! We're now ready to run our React application 🎉.
Running our React SSR application
We can now run our React SSR application using the following deno
command:
deno run --allow-net --allow-read --unstable ./server.tsx
Note the use of the various flags! A major difference between Deno and NodeJS is that Deno was build with security in mind. Any action that needs to access the web, read or write to files, or even consume environment variables needs to have the permission granted before Deno will allow it.
To find out more, check out the Deno permissions section of the Deno Manual.
For our example application, we require --allow-net
so that our server is allowed to access the network, --allow-read
is required by the Opine framework (so it can read templates if make use of it's "views" feature), and we also need the --unstable
flag to make use of the Deno.bundle()
API, which is still in preview.
Head over to http://localhost:3000/ and voila! You should now see your React SSR application running in your browser. 😄
Next steps
This is just a basic server and app setup, but by now you should hopefully see that there isn't too much to do to convert your existing applications over to Deno.
That's all gang! Would love to hear your thoughts and how you're getting on with Deno - drop your comments below!
Update 29-06-2020: Deno is progressing quickly and all the aforementioned bugs with JSX are resolved, so I have removed references to them in this article!
Update 20-07-2020: If you followed this tutorial prior to the release of Deno 1.2.0 you will find that after upgrading there are several url related errors. Deno 1.2.0 brought about a breaking change for the std library so any module using std version before 0.61.0 may well error! Generally try seeing if modules you are using can be upgraded to a later version, and if that doesn’t fix it then try opening an issue on the repo you are having issues with!
Top comments (29)
[notecomm@localhost react]$ deno run --allow-net ./server.tsx
error: TS2345 [ERROR]: Argument of type 'string | URL' is not assignable to parameter of type 'string'.
Type 'URL' is not assignable to type 'string'.
return new URL(url).pathname
~~~
at deno.land/std@0.59.0/path/win32.ts...
TS2345 [ERROR]: Argument of type 'string | URL' is not assignable to parameter of type 'string'.
Type 'URL' is not assignable to type 'string'.
return new URL(url).pathname;
~~~
at deno.land/std@0.59.0/path/posix.ts...
TS2345 [ERROR]: Argument of type 'string | URL' is not assignable to parameter of type 'string'.
Type 'URL' is not assignable to type 'string'.
return new URL(url).pathname
~~~
at deno.land/std@0.58.0/path/win32.ts...
TS2345 [ERROR]: Argument of type 'string | URL' is not assignable to parameter of type 'string'.
Type 'URL' is not assignable to type 'string'.
return new URL(url).pathname;
~~~
at deno.land/std@0.58.0/path/posix.ts...
TS2345 [ERROR]: Argument of type 'ParsedURL' is not assignable to parameter of type 'string'.
const loc = encodeUrl(new URL(originalUrl).toString());
~~~~~~~~~~~
at deno.land/x/opine@0.14.0/src/middl...
Found 5 errors.
github.com/apiel/adka/issues/1
on deps.ts change
export { opine } from "deno.land/x/opine@0.19.1/mod.ts"; to
export { opine } from "deno.land/x/opine@0.19.1/mod.ts"; or
export { opine } from "deno.land/x/opine@main/mod.ts";
Ah yes! Thanks for pointing this out.
Unfortunately the release of Deno 1.2.0 brought about some breaking changes meaning it no longer works with several std libraries pre 0.61.0
I will try to update this blog post to list the latest versions to use - unfortunately with the pace Deno is moving blog post become outdated very quickly 😂
I get 27
TS1205
errors using latest Deno:Errors:
Found 27 errors.
Hey Christos 👋
This article was written back in May when Deno was on a much earlier version than
1.5.4
! May I recommend you have a look at all of the Deno modules you import in thedeps.ts
to ensure you are using the latest version which (hopefully!) works for the version of Deno you are running.For example, Opine's current latest version is
0.25.0
, so updating to that may solve your issues 😄 (REF: github.com/asos-craigmorten/opine)Let us know if upgrading modules works out for you!
I've had wrong
react-dom
16.3.1
instead od16.13.1
and now that I've updated it and alsoopine
to latest0.25.0
I get hooks error:even after doing Deno reload using:
I understand that this article and example source code was written for an older Dyno version, but I believe it’s kind of useless if it’s not updated for the latest version and that’s not even a major version, it’s a minor version with some obvious breaking changes.
Having new to Deno developers installing an old version is not convenient.
Anyway, thanks for your time.
Hi Christos, I'm sorry you felt my article was useless 😕
Over the past year, with every minor version of Deno there has been almost always a breaking change (even with just minor version upgrades), which has made it difficult to keep every demo and article up to date, and working with the latest versions, as this can take a lot of time.
I haven't and wouldn't ever recommend people use an older version 🙂 but I hope people come to expect that an old article might not work with a newer version after so many breaking changes 😅
If you find issues with any articles, what can be really useful for me (and all writers!) is if you are able to overcome the issues and leave comments on how to fix them - then the writer can easily update the article when they have time, or if not, at least others can read the comment and see the solution!
For now anyway, I have updated the article to work for Deno 1.5.4 (the latest). Please let me know if you have any further issues! I will always try to respond (eventually)! 😄
Best of luck with your Deno journey!
Thank you Craig for making some time to update this article.
I didn't mean to offend you by saying the article is "useless", it just does not provide much value for begginers and new to Deno developers.
I have my own blog (lytrax.io) that I have many outdated articles and also many outdated answers in Stack Overflow and every single article has value; it's value just declines in time because it gets outdated and sometimes not even working with new versions.
Now for the updated version, I still get React hooks error in
server.tsx
line 29 where(ReactDOMServer as any).renderToString
tries to render the<App/>
component:I tried to run this using Windows 10 and also macOS Catalina and getting the exact same error on both systems. I have created a Github repo here github.com/clytras/deno-react-ssr to make it easy test the code on different environments.
What am I doing wrong?
Again thanks for replying and updating the article! 👍
Curious...! I'll check out your repo and see what I've missed 😄 Had it working locally when did the rewrite so maybe something lost in translation somewhere! 😅
Update:
So I ran the following:
Opened up
http://localhost:3000
on both Firefox and Chrome (for MacOS) and could see the app running as intended without any console errors!Firefox:
Chrome:
When you have run your updated application, have you checked to see if the old version of the application has been stopped! I commonly use port 3000 for apps and find all the time that I'm wondering why one app isn't right when I have a forked version running in a different window! (E.g.
lsof -ti tcp:3000
)Otherwise, rather unhelpfully / frustratingly(!) your setup works on my machine! (MacOS Mojave 10.14.6)
If you don't make any progress debugging, can you try out the React example on the Opine repo (REF: github.com/asos-craigmorten/opine/...) - if that doesn't work we can get an issue raised and start debugging properly!
Very strange!
It's not the port, I just tried a different free port (
17123
) and getting the same error and I don't get anything when I runlsof -ti tcp:3000
on macOS Catalina (10.15.7).Here is the screenshot of the console and the error:
And Windows terminal screenshot:
How can I debug this?
I also cloned opine and tried to run the example using
deno run --allow-net --allow-read --unstable ./examples/react/server.tsx
and I got an other error about invalid imports that are using thev
infront of versions:And this again for both Windows and macOS!
Does opine server running on your machine with Deno 1.5.4 without any modifications?
Hi Craig, thanks for the nice tutorial!
Launching the example with a today updated deno installation (deno 1.0.2, v8 8.4.300, typescript 3.9.2) fails with following errors:
I'm unfortunatelly not an expert in deno. Do you have already an idea to solve these problems?
Really sorry about that! I may have missed something when copying snippets from IDE to blog post! Alternatively it may be the Deno version - I tested this with Deno 1.0.0, and I know the newer versions broke some dependencies with type errors (namely the EVT Deno library had issues with v1.0.1 at one point!) - I’ll take a look when I can next, make fixes, and get back to you as soon as possible :)
In the meantime, you can try and see if it’s a Deno version by downgrading Deno to v1.0.0 - there instructions on how to install specific versions here: github.com/denoland/deno_install#i... (I’ll test myself as well!)
I can confirm this code, including:
Appears to be broken for versions >= 1.0.1, so there must have been an accidental breaking change in Deno?
For now, the code should work fine on v1.0.0 (just tested) and I'll look to get it updated to be compatible with these latest versions of Deno :)
Further to my last comment, the easiest way to change to a specific version of Deno is to use the
upgrade
command:Edit:
I have found the relevant issues, and they should hopefully be resolved by the Deno team in the next Deno release. In the meantime I have updated the article to provide guidance on how to set Deno version to 1.0.0, added links to the issues and will look to update the code samples to support v1.0.1 and v1.0.2.
Edit 2:
Code and article updated to support Deno v1.0.2 🦕 🥳. Unfortunately Deno v1.0.1 support won't be possible due to a bug in the Deno core.
Upgrade to Deno v1.0.2 using the
upgrade
command:Thanks a lot!
With deno 1.0.2 I do still get
Cannot find module 'https://dev.jspm.io/react@16.13.1' or its corresponding type declarations
Oh no! 😢I’ll take another look - the underlying problem (as far as I know) is a bug in Deno which will be released in 1.0.3, check out github.com/denoland/deno/issues/5772
For now I believe v1.0.0 should be fine, so can downgrade in the meantime to get started.
Another open issue is that the unstable Import Map feature of Deno doesn’t play nicely with React atm - see github.com/denoland/deno/issues/5815
Off the top of my head, one thing to try is to use the Deno Types hint:
Will test again myself and get back to you!
Ok, I am able to reproduce! Here are the steps that I took:
a. Ran the Deno
upgrade
command to change to v1.0.2:b. Checked that I was definitely testing on Deno v1.0.2
c. Cleared my Deno cache in case I had managed to get the module previously and cache it:
And then deleting everything inside the directories listed for
Remote modules cache
andTypeScript compiler cache
.d. Created an
app.tsx
andserver.tsx
as per this article.e. Ran the following command:
And voila, errors:
In this instance, adding the
@deno-types
directive (though a good thing to do to get the React types) does nothing to solve the issue because Deno has the current bug that it's typescript compiler ignores all.tsx
and.jsx
files (see github.com/denoland/deno/pull/5785) and they are exactly the extensions we've been using!Unfortunately neither Deno nor Typescript have a particularly great solution to this problem 😞 but there are 2 workarounds that you can use for now until v1.0.3 is released (if can find more then please comment!):
a. Use
// @ts-ignore
comments above each top-level React import statement. For exampleThough I prefer option 2 (which I will use to update this blog post):
b. Create a
dep.ts
(note the.ts
extension will mean that Deno v1.0.2 will compile it despite the bug). We will then import all of our dependencies into this file, and re-export them for use in ourapp.tsx
andserver.tsx
:You can then run
deno run --allow-net --reload ./server.tsx
and it should start the application 🥳 🎉Let me know how you get on!
Edit: Blog post all updated! Thanks so much for providing the feedback - articles become so much more useful to everyone through comments and pointing out bugs! I've certainly learned things 😄
Great tutorial, able to get it running within few minutes!
I'm new to JS world, have not used nodejs, and directly learning deno. I wanted to convert this little react js, and in the process learn deno & react :
github.com/anborg/react-pwa
Can someone help me to convert the above node to deno - to make it well organized source/subcomponent staticpage/images etc.
I am looking for an example deno-react project that is structurally similar e.g src/index.js, /public_html/index.html, writing into div "root" instead of rewriting "body", react subcomponents in separate subfolders etc.
Hey @anborg ! 👋
Wow, I'd say a PWA React app is a reasonably complex thing! 😲
I have an example of how you can write an MVC like setup in another post including static CSS and EJS templates.
I've not seen any particularly complex examples of React + Deno - if you google there are a few examples / videos that use a mixture of Node and Deno for the desired effect. If you find any awesome examples then please do share!
I would be happy to create a more complex example React application which has some sub-components, uses static CSS etc. and tutorial to go with it - would that be useful?
I’ve put together a quick example of using SSR React with suspense, static CSS and sub-components here —> github.com/asos-craigmorten/opine/...
I plan to write up a new post about it over the next couple of days.
@craig Thanks for the SSR. Would be great to have a CSR (client side rendering) React & Deno example, with a structured project so it can be used as a template to write maintainable production quality code.
Hey sure, thanks for the suggestion. CSR would be very similar to the example, but instead of performing a server-side
renderToString
(here github.com/asos-craigmorten/opine/...) you would just not bother! Instead effectively setting the content of the React root (here github.com/asos-craigmorten/opine/...) as empty.Finally instead of hydrating the content in your client-side script (here github.com/asos-craigmorten/opine/...), you would just use the
reactDOM.render()
method to create your app completely on the client. 🎉For this particular example you could then likely refactor a couple of things such as the
isServer
prop that is used in this example to manage the fact that React Suspense is yet supported server-side.Yes a simple react + deno + calling some weservice (e.g elasticsearch), subcompoenents, css etc. would serve as a production quality template!
Great stuff, very promising!
Is there any way to import css/less module?
Unfortunately CSS Modules etc. aren't there yet with Deno as they generally require a bundler to make them work.
You are able to use static CSS easily, see here for an example of how you can server static CSS.
I've also got a post on how to write a more complex app using static CSS here.
Fingers crossed tooling for things like CSS modules will be developed by the community shortly!
you can try Aleph.js, a react framework in deno, with built-in css/less support, or import sass with plugin.
Hi Craig, nice tutorial!
I would suggest using denon, like nodemon but for deno, to ease the development process of this kind of apps.