How to create an NPM package out of a React app

Matt Mazzola
Level Up Coding
Published in
5 min readJan 4, 2020

--

In this post we’ll look at how to create an NPM package out of the build of a React application. Specifically, we’ll use the build output of a Create-React-App and create a package which exports the paths to the index.html and build folder. The enables the static files to be served from a backend such as express. Even though this example uses React, I believe the technique should apply to all front-end frameworks as they all eventually generate HTML, JS, and CSS to be rendered by browsers.

You might be asking:

Why would you want to create a package?

Good question! It’s true in most cases your app is deployed online and viewed as a website so the user simply visits a known URL rather than downloading a package. In my case the app acted as an interface to communicate with a server running on the local machine over HTTP. The API between them needed to be known and changes coupled.

In normal websites, both the UI and Server are under your control so when you redeploy the interface to use a new feature which requires a new endpoint. You can also guarantee the server is updated to expose the new endpoint the feature needs. However, when the server is running on localhost (which is required for other reasons outside scope of post) you can’t guarantee this would be updated.

This requirement made the traditional practice of hosting the interface as a website undesirable. This would have required the user to know which version of the server they were running and then make sure they put the same version in the URL such as www.react-ui.com/admin/v2.20.1/home and there could be many user errors due to version mismatch that would be hard to diagnose due only partial API support.

Instead eliminate that class of errors by serving the UI from this local server. This server knows the version of itself and it can request the correct UI using standard NPM package versions so they are guaranteed to be the same and remove the burden from customer. You could think of this being similar to an Electron app where it can have a UI and browser like features but also use lower level node features, but this is much lighter weigh solution and still uses a normal browser experience. This post will focus on the UI as that’s the unconventional part and the interesting part.

Now that we’re clear on why the package is needed. Let’s look at how to create the package.

What’s the goal?

First let’s look at what is needed in order to host an app on Express and compare with the output of CRA.

On the CRA website you can see how to server the output from express.

How to host React app with Express

Note here the only information we need is the path to the /build folder to serve static files and path to index.html to route all requests.

Build Folder: path.join(__dirname, ‘build’)
Index File: path.join(__dirname, ‘build’, ‘index.html’)

Next, let’s look at how to get this information from within the package.

Create a new React app.

Start by creating a new react app

npx create-react-app cra-package --template typescript

If we go into the project cd cra-package and then build the app npm run build Note the /build folder andindex.html file referenced above.

Now we need to create a package of this build folder contents, but it also must export theses special paths and work regardless of where it’s installed on the local machine or in node_modules.

First let’s write a script to create the package folder.

In the root of the cra-package create a new folder scripts which will contain a script to generate the package folder.

Run the following commands

mkdir scripts
cd scripts
npx tsc --init

Adjust the tsconfig.json to only compile createPackage.ts which we’ll create now.

Create the createPackage.ts with the following contents:

The Gist seems to be clipped by medium. Here is direct link
https://gist.github.com/mattmazzola/97c1b4fa2036f5b9ada78131e79e4bc1

Next let’s create that index.ts script which this script depends on.

Similarly in the root of the cra-package create a new folder packageScripts to represent scripts that we’ll put inside the package. This is a separate folder because it has a separate tsconfig.json settings.

Well use typescript again and leverage the compilation process to output into the package folder.

mkdir packageScripts
cd ./packageScripts
npx tsx --init

Create an index.ts file will represent the main script of our package.

Add these contents:

Notice we use __dirname this is what makes the package work regardless of where it’s installed and not depend on hard-coded paths.

See: https://nodejs.org/api/modules.html#modules_dirname

Create and NPM script to run two typescript projects

Add an NPM script in the package.json to actually utilize these two scripts you just created and create the package folder.

"builduipackage": "tsc -p ./packageScripts/tsconfig.json --outDir ./package && tsc -p ./scripts/tsconfig.json && node ./scripts/createPackage.js",

Reminder that the first compilation of packageScripts which effectively copies the output to the package folder, then the execution of the createPackage.ts script effectively copies the build folder into the package folder, and creates a new package.json which re-uses the name of the root, but drops the dependencies since there are none.

Now the package folder has all the requirements of being published as an NPM package. You can manually run npm publish from that folder, or better yet setup a CD pipeline to publish and automate the versioning but that’s a topic for another post as there are many out there.

For this article we’ll just test it locally by running npm pack to create a local package file.

Next lets create a simple server to test our package functionality.

Create the express server to serve the React app

git init express-server
cd express-server
npm init -y
npm i typescript express
npm i -D @types/express
npx tsc --init

2. Modify the tsconfig.json to compile to the build folder.

"outDir": "./build"

3. Install the local package file you created above. Such as: (File path may be different)

npm i E://Repos/cra-package/package/cra-package-0.1.0.tgz

Ensure this adds and entry in the package.json such as:

"cra-package": "file:../cra-package/package/cra-package-0.1.0.tgz",

Add src/index.ts to represent our server using slightly modified version of the example given on the React docs but using our package paths.

index.ts

We use the directoryPath and defaultFilePath from the package.

Now let’s add the npm scripts to run the server!

"start": "node build/index.js"

Run the build (tsc) then start the server. You should see the default React app showing on your browser. You can go edit the React app and republish to see changes if you like.

You now have a process to create versioned react applications which leverage distributed by NPM.

Hope you learned something from this article.

If you’re interest in the code or I made a mistake in the article, here are the two repos to test final setup yourself.

Video

Some of the scripts for file may not have been clear so here is a video of it in action.

--

--