Skip to content

React Workspaces

Description / Purpose

Instead of using the production-style React rendering pipeline (i.e. what you see with create-react-app or Next.js), we have a custom setup for running React workspaces. At the time of writing, this includes react-16 and react-18 workspace types.

Workspace Structure

To build a React workspace, you'll need index.html, index.js, and .babel-adapter.json. The HTML file should import index.compiled.js and our hosted React bundle. Here's an example:

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="bundle.css" />
  </head>
  <body>
    <main id="app"></main>
    <script src="https://content.codecademy.com/courses/React/react-18-course-bundle.min.js"></script>
    <script src="/index.compiled.js"></script>
  </body>
</html>

index.js

import React from "react";
import ReactDOM from "react-dom/client";

function App() {
  return <h1>Hello from the App component</h1>;
}

ReactDOM.createRoot(document.getElementById("app")).render(<App />);

.babel-adapter.json

{
  "excludedModules": ["react", "react-dom"]
}

1. Import the react bundle

The React bundle used in the above index.html is for react-18.

<script src="https://content.codecademy.com/courses/React/react-18-course-bundle.min.js"></script>

If you are building in React 16, you'll use a different bundle: replace react-18 with react-16 in the src attribute.

2. Use an existing DOM node

In index.js, we select an existing DOM node in index.html. In this case it's the element with id app.

ReactDOM.createRoot(document.getElementById("app")).render(<App />);

3. You can import js files

You don't need to write everything in index.js: you can define additional components in separate files and import them. For example, we could have defined the App component in an App.js file and imported it:

import { App } from "./App.js";

4. Use any pre-installed npm package

You can use any npm package available in the Docker image, such as prop-types, react-redux, and msw. The full list of available packages for react-18 is in the ein repository here.

5. Do not include a package.json

For the sake of long-term maintainability, don't do it. If you need additional packages installed, talk to the Learning Platform team.

6. .babel-adapter.json should always have the excludedModules entry

This speeds up compilation and load time by skipping compilation of the react and react-dom packages. Those are pre-compiled and imported via the bundle (see #1).

Note: if you don't want to use index.js or app.js as the entrypoint, you can define one in this file. For example, if your JavaScript entrypoint is main.js, you would have JSON like this (you would have to update index.html to import main.compiled.js as well):

{
  "entryFiles": ["main.js"],
  "excludedModules": ["react", "react-dom"]
}

7. Import bundle.css (optional)

The react-18 workspace type supports .css imports in JavaScript files. For example, one of your components could have the lines:

import styles from "./style.module.css";
// or
import "./App.css";

If you use this approach, you'll need to import bundle.css in your index.html file. bundle.css will be automatically generated during compilation of your React components:

<link rel="stylesheet" href="bundle.css" />

Behind the scenes

When you have a react-16 or react-18 workspace type and you click Run with any .js file open in the code editor, this script is executed.

  • It reads the contents of .babel-adapter.json if it exists
  • It determines the file to compile: index.js, app.js, or whatever is defined as the entry file(s) in .babel-adapter.json.
  • It determines which modules, if any, to exclude from compilation. These are defined in the excludedModules array in .babel-adapter.json. Usually we skip react and react-dom because those are pre-compiled and imported in index.html.
  • It compiles the file(s) with browserify and babelify. The output file is something like index.compiled.js or app.compiled.js, depending on your entry file name.

In most cases, this is the effective command that is run in the Docker container:

browserify index.js -o index.compiled.js -t babelify -p [ css-modulesify -o bundle.css ] -t imgurify --exclude react --exclude react-dom

Debugging

Since the compilation process happens in the Docker container, you won't see compilation errors in your browser or console. This is something we would like to fix in the future. For now, the best way to identify compilation errors is by inspecting the websocket connection.

  1. Use your browser's developer tools and open the network tab.
  2. Filter for websocket, or WS, request types (if you don't see any websocket requests, reload the page with the network tab open).
  3. Look for messages with the method EvalService.Run.
  4. Check for stderr in the response.
  5. If there is a string there, you have a compilation error. Copy and paste that base64-encoded string into a decoder, e.g. base64decode.org.

Network developer tools with EvalService.Run message and its stderr parameter highlighted

Examples