Enact Basics

Hang on to your hats, we’re going to write some code and get this app running! In this section, we’re going to create our first module, see how the application is rendered into the DOM, and bundle and run the app. Let’s get started.

Building the App module

The main entry point of our application is in ./src/index.js. While you could include all of your code there, what kind of example would you be setting for other programmers? Instead, we’ll create a new module that will be home to our application component.

A module in CommonJS can either refer to a single file directly or a directory which will ultimately resolve to a single file. Node.js defines a set of rules for resolving module references and webpack has its own set of configurations to customize module resolution. The more you know!

The Enact team recommends that any module created for an application:

  • be placed in its own directory — e.g. ./src/App
  • contains a source file with the same name that contains the core logic — e.g. ./src/App/App.js
  • contains a package.json with its main property pointing to that file

The Module’s package.json

The package.json for most modules will be simple and only contain the main property. It can include any of the supported properties for a package descriptor file.

Let’s create a file named ./src/App/package.json and give it the following contents:

{
	"main": "App.js"
}

The Module’s Source File

Now to get to some real code! Let’s create a ./src/App/App.js mighty enough to contain the source code. Here’s the complete source:

const App = function () {
	return (
		<div>
			Hello, Enact!
		</div>
	);
};

export default App;
export {App};

Don’t worry about trying to absorb all that at once, we’ll break it down, piece-by-piece.

You’ll notice that we’ve removed much of the boilerplate code that was created in this file by the enact command line tool. We’ll be slowly adding it back in order to introduce the concepts incrementally.

App component

You may not know it, but App is a component. The simplest type of React component is a Stateless Function that accepts a props object and returns a React element. For this first version of Hello Enact!, we do not accept any properties so we can safely omit that argument. Instead, we will render the greeting within a <div> DOM node.

const App = function () {
	return (
		<div>
			Hello, Enact!
		</div>
	);
};

There are several interesting points in this little block of code so let’s look a little deeper.

React supports two types of components — those created with classes and Stateless Functions. Both of these will be covered in more detail later on. Don’t be impatient!

const vs let

const and let are two statements for declaring variables. const creates a read-only reference to a value and let creates a mutable reference to a value. Both are block-scoped statements rather than global- or function-scoped like var.

In Enact, we recommend using const by default and let only when you determine you need to change the reference.

const App = function () {

Here we’re defining App as a const referring to a function that will render our application component.

Composing Components

In React, every component’s render method must either return a single root element (which can contain zero or more children) or null. The root element must be either a DOM node (like <div>) or a custom component (like we’re creating right now). The root element in turn can contain strings or numbers in addition to DOM nodes and custom components.

Our Hello, Enact! app contains a <div> as its root element and a single string child, Hello, Enact!.

return (
	<div>
		Hello, Enact!
	</div>
);

You’ll notice that by introducing <div> we no longer have valid JavaScript! In fact, it looks a lot like valid HTML. That’s because React introduces JSX, which is a JavaScript syntax extension. In order to make JSX runnable by the browser, it has to be converted to JavaScript. With Enact, this is handled during the build process using webpack and babel. Back in the time, we needed React module in order to use JSX. But as of now, we no longer need it since React 17 introduced the new JSX transform. More on this later.

Exporting the App

Now that we’ve defined our component, the last step is to export it from the module so it can be consumed. This is accomplished with the export statement. You can export a value as the default export of the module, a named export, or both!

In most cases, each module will contain a single component which will be the default export. You can also export additional components, functions, or constants that might be useful for consumers of the component.

export default App;
export {App};

A note on named exports

Within the Enact framework, the default export is also included as a named export for compatibility with CommonJS consumers. With only the default export, a consumer using require() would have to use the following syntax which is a bit awkward:

const App = require('./src/App').default

By including a named export, you can use the more intuitive alternative:

const App = require('./src/App').App

Rendering the App

With the App component ready, we can render it into the DOM. After this step, we’ll be clear of the boilerplate code and ready to start exploring the framework. The rendering logic will live within our application’s entry point, ./src/index.js. Here’s the complete code, which we’ll cover incrementally below:

./src/index.js

import {createRoot} from 'react-dom/client';

import App from './App';

const appElement = (<App />);

// In a browser environment, render the app to the document.
if (typeof window !== 'undefined') {
	const container = document.getElementById('root');
	createRoot(container).render(appElement);
}

export default appElement;

The index.js provided by the dev tools allows the App component to be rendered into the DOM or imported into another component.

import and ReactDOM

We’ll use import statement to bring our dependencies. Let’s import react-dom/client module for rendering our App. Client React DOM APIs let you render React components on the client in the browser. You’ll primarily be interested in the createRoot().render() method.

import {createRoot} from 'react-dom/client';

The curly braces — {createRoot} — are used to import a named export from react-dom/client rather than the default export. Alternatively, we could have imported the module as ReactDOMClient and called createRoot() on that object for the same result:

import * as ReactDOMClient from 'react-dom/client';
ReactDOMClient.createRoot( /*...*/ ).render( /*...*/ );

Importing our App

Next, we’ll import our App module. We use relative paths ('./App' instead of 'App') for internal modules to distinguish them from external modules. We are also able to use the directory name rather than the full path to the source file based on App’s package.json.

import App from './App';

createRoot().render()

Finally, we use createRoot() to create a root DOM node and render() to display our App into the React root’s DOM node. We’re using JSX again to create the React element for our App component and getElementById returns our DOM node.

You might have noticed, though, that we haven’t created an HTML document yet and that’s where Enact comes back into the picture. It will generate a default HTML file for our application during the build. It will contain <div id="root"></div> in the body, which is where we can render our application.

const container = document.getElementById('root');
createRoot(container).render(appElement);

You may notice that it looks like we’re writing some HTML with an element. And, in fact, that’s exactly what we’ve done. JSX treats our App component just like it’s an HTML element we can insert into markup.

Running the App

Enact provides several npm scripts that ease working with apps.

  • npm run pack - Bundles your application in the ./dist directory.
  • npm run pack-p - Bundles your application for production (with minified source and without sourcemaps) in the ./dist directory.
  • npm run serve - Bundles your application in memory, starts an HTTP server on port 8080 and serves your application. Whenever a source file changes, the app will be incrementally rebuilt with the changed file and the browser will automatically refresh (thanks to webpack-dev-server!).
  • npm run clean - Removes ./dist and its contents

You should be able to run npm run serve now and, as we work through the rest of the guide, see the application reload with the new changes each time you save a file.

Conclusion

We’ve built the boilerplate Enact app and have it running using npm run serve. Next up, in Part 2 of Hello, Enact!, we’ll add some style using CSS Modules.

Next: Adding CSS