DEV Community 👩‍💻👨‍💻

Cover image for Hack to Run React Application inside Service Worker
Jakub T. Jankiewicz
Jakub T. Jankiewicz

Posted on

Hack to Run React Application inside Service Worker

I was doing my thing and then suddenly have an enlightenment. New cool idea came to my mind.

But first a little bit of background. Last year I've create a JavaScript library called Wayne that abstract the idea of creating pure in browser HTTP requests with the help of Service Worker. The API is similar to express.js NodeJS framework, but work in the browser instead of the server.

So the cool idea that I've had was to use ReactJS application inside Service Worker and returns snapshot of that application from different HTTP requests. I've even was thinking about using HTTP requests to interact with application.

I've started creating a PoC. My first try was searching for library that was used for testing react app. Since you can't use real DOM inside Service Woker. I was looking at react testing library documentation in hope that they use some kind of fake DOM library, but after looking at code it was using real ReactDOM and also it was not very intuitive. They lack getElementByID and was created just for making tests. So I've abandoned the idea of using testing library.

But then I realized that I should probably will need to use jsDOM. This is the library that can be used in nodejs to mock the DOM. This is what jest testing framework is using and this is what I was using to test jQuery Terminal library in Jasmine before jest was created.

One problem was to run jsDOM as UMD module. But luckly I was able to use browserify to compile jsDOM into UMD.

Below is the code I've used:

import * as React from 'react';
import { createRoot } from 'react-dom';
import { Wayne } from '@jcubic/wayne';
importScripts('https://cdn.jsdelivr.net/gh/jcubic/static/js/jsdom.min.js');
const { JSDOM } = jsdom;

const pathname = location.pathname.replace(/\/sw.js$/, '');

const Nav = () => {
    return (
        <ul>
          <li><a href={`${pathname}/`}>home</a></li>
          <li><a href={`${pathname}/__about__`}>about</a></li>
          <li><a href={`${pathname}/__contact__`}>contact</a></li>
          <li><a href={`${pathname}/source.jsx`}>Service Worker source</a></li>
        </ul>
    );
}

let globalSetPage;

const App = () => {
    const [page, setPage] = React.useState('root');
    React.useEffect(() => {
        globalSetPage = setPage;
    }, [setPage]);
    return (
        <>
          <Nav/>
          <p>Helo React + Wayne "{page}"</p>
        </>
    );
};

const dom = new JSDOM(`<!DOCTYPE html>
<html>
  <head><title>React App</title></head>
  <body>
    <div id="root"></div>
  </body>
</html>`);

self.window = dom.window;
self.document = self.window.document;

const root_node = document.getElementById('root');

const root = createRoot(root_node);
root.render(<App/>);

function html(res) {
    setTimeout(() => {
        const html = `<!DOCTYPE html>${document.documentElement.outerHTML}`;
        res.html(html);
    }, 0);
}

const app = new Wayne();

app.get('/__*__', (req, res) => {
    globalSetPage(req.params[0]);
    html(res);
});

app.get('/source.jsx', async (req, res) => {
    const text = await fetch('../sw.jsx').then(res => res.text());
    res.text(text);
});
Enter fullscreen mode Exit fullscreen mode

To explain the code:

  1. It first loadad jsDOM from my private CDN using jsDelivr
  2. Then create application with useState hook
  3. It saves the setState from hook as global variable
  4. It add Wayne routes and call setState function with data from user

And that's it. To pass data to the app you just need to open URL __<SOMETHING>__ and the stuff between double underscores will be page variable inside React.

You can give this hack a try here.

The source code can be found on GitHub in gh-pages branch.

And that's it. If you like this post, you can follow me on twitter at @jcubic and visit my home page.

Top comments (0)

Every Week

We have a Welcome Thread where we invite members to tell us a bit about themselves. Join the conversation with us!