How to create a React + Typescript Web Project with Rollup from scratch
This is how I started a React application with Typescript and Rollup from scratch. I try to do this kind of things from time to time because in my daily job I don’t usually start new projects, and if I do, it’s through some toolchain that does all the tooling for me. So often the outcome is that I don’t really know how to start a frontend project from the ground up.
Init project and install Rollup#
Create a folder and a default package.json
with npm init
. Install Rollup
,
create a basic config file, and some Hello World typescript code.
npm install rollup --save-dev
// rollup.config.js
export default {
input: 'src/main.ts',
output: {
file: 'public/index.js',
format: 'es'
}
};
// src/main.ts
const message: string = 'Hello World';
document.querySelectorAll('body')[0].innerText = message;
This config basically tells Rollup to bundle everything in src/main.ts
(imports included) into a single file in
public/index.js
as a ES6 Module
. If you’re
not familiar with JavaScript modules, I recommend A 10 minute primer to JavaScript modules, module formats, module
loaders and module
bundlers
by Jurgen Van de Moere.
At this point, we can already run Rollup doing
./node_modules/rollup/dist/bin/rollup --config
Local dependecies cannot be directly executed in CLI unless we explicitly run them from their relative path to
node_modules
folder, or much easier, setting up a script in our package.json
{
"scripts": {
...
"build": "rollup --config"
},
}
npm run build
When we do, we’ll get an error:
src/main.ts → public/index.js...
[!] Error: Unexpected token (Note that you need plugins to import files that are not JavaScript)
Add TypeScript support#
That’s because Rollup cannot transpile TypeScript. So, let’s install a plugin to enable that feature. Even though I’ve seen some criticism about its speed, I’ve found the not official ezolenko/rollup-plugin-typescript2 to be easier to setup.
As easy as:
// rollup.config.js
import typescript from 'rollup-plugin-typescript2';
export default {
input: 'src/main.ts',
output: {
file: 'public/index.js',
format: 'es'
},
plugins: [typescript()]
};
Building the project now should succed and produce a readable bundle on public/index.js
'use strict';
var message = 'Hello World';
document.querySelectorAll('body')[0].innerText = message;
Add React#
Install React and create a HelloWorld React component to be used in our main file.
npm install react react-dom --save
// src/components/hello-world.tsx
import React from 'react';
interface HelloWorldProps {
name: string;
}
export const HelloWorld: React.FC<HelloWorldProps> = ({ name }) => (
<h1>Hello {name ? name : 'World'}</h1>
);
// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { HelloWorld } from './components/hello-world';
document.body.appendChild(
Object.assign(document.createElement(`div`), { id: 'root' })
);
ReactDOM.render(<HelloWorld name="Jordi" />, document.getElementById('root'));
Remember to rename main.ts
extension to .tsx
since we’re using JSX
syntax in a TypeScript file, as well as its reference in rollup.config.js
.
Running the build command npm run build
will now produce a different error message:
src/main.tsx → public/index.js...
[!] (plugin rpt2) Error: /src/main.tsx(2,28): semantic error TS6142: Module './components/hello-world' was resolved to '/src/components/hello-world.tsx', but '--jsx' is not set.
Fix this by adding a simple tsconfig.json
file in project’s root, so that we can pass some configuration to the
TypeScript compiler, such as enabling support for JSX
{
"include": ["src/**/*"],
"exclude": ["node_modules"],
"compilerOptions": {
"jsx": "react"
}
}
Build for a new error message:
src/main.tsx → public/index.js...
[!] (plugin rpt2) Error: /src/main.tsx(4,18): semantic error TS2304: Cannot find name 'React'.
What’s going on here? Rollup doesn’t include a built-in module resolution method and it delegates this
responsability
to a plugin. That means it’s
not able to find React in our node_modules
folder. Let’s resolve this
npm install @rollup/plugin-node-resolve --save-dev
// rollup.config.js
import typescript from 'rollup-plugin-typescript2';
import resolve from '@rollup/plugin-node-resolve';
export default {
input: 'src/main.tsx',
output: {
file: 'public/index.js',
format: 'es'
},
plugins: [
resolve(),
typescript(),
]
};
Does this work? Not yet.
src/main.tsx → public/index.js...
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
src/components/hello-world.tsx: (1:7)
[!] Error: 'default' is not exported by node_modules/react/index.js, imported by src/components/hello-world.tsx
https://rollupjs.org/guide/en/#error-name-is-not-exported-by-module
From Rollup’s FAQ :
Some libraries expose ES modules that you can import as-is […] But at the moment, the majority of packages on NPM are exposed as CommonJS modules instead. Until that changes, we need to convert CommonJS to ES2015 before Rollup can process them.
The @rollup/plugin-commonjs plugin does exactly that.
npm install @rollup/plugin-commonjs --save-dev
// rollup.config.js
import typescript from 'rollup-plugin-typescript2';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
export default {
input: 'src/main.tsx',
output: {
file: 'public/index.js',
format: 'es'
},
plugins: [
resolve(),
commonjs(),
typescript(),
]
};
npm run build
src/main.tsx → public/index.js...
created public/index.js in 2.4s
Hooray! It works but, are we done? Well, not yet. Since the goal is to build a web application, we might want to have a local server for developing. Let’s do this. We need basically two things: an HTML file that loads the React app and a server that makes it accessible in our browser.
Setting up the local development environment#
As you might guess, there’s also a Rollup plugin to generate automatically an HTML file to serve our generated bundle.
It’s called @rollup/plugin-html
. And its default configuration is, as usual, install, import, include in the plugins
array. As a server, we’re going to use rollup-plugin-serve
:
npm install @rollup/plugin-html rollup-plugin-serve --save-dev
// rollup.config.js
import typescript from 'rollup-plugin-typescript2';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import html from '@rollup/plugin-html';
import serve from 'rollup-plugin-serve';
export default {
input: 'src/main.tsx',
output: {
file: 'public/index.js',
format: 'es'
},
plugins: [
resolve(),
commonjs(),
typescript(),
html(),
serve('public'),
]
};
Build should succed…
src/main.tsx → public/index.js...
http://localhost:10001 -> /public
created public/index.js in 2.5s
But we’ll find an error in our browser’s devtools console when navigating to http://localhost:10001
Uncaught ReferenceError: process is not defined
react_development http://localhost:10001/index.js:124
createCommonjsModule http://localhost:10001/index.js:8
<anonymous> http://localhost:10001/index.js:122
This happens because React checks process.env.NODE_ENV
for a production
or development
value, and Rollup isn’t
providing any. Apparently, providing this information is the most common usage for @rollup/plugin-replace
npm install @rollup/plugin-replace --save-dev
// rollup.config.js
import typescript from 'rollup-plugin-typescript2';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import html from '@rollup/plugin-html';
import serve from 'rollup-plugin-serve';
import replace from '@rollup/plugin-replace';
export default {
input: 'src/main.tsx',
output: {
file: 'public/index.js',
format: 'es'
},
plugins: [
replace({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}),
resolve(),
commonjs(),
typescript(),
html(),
serve('public')
]
};
This way we can pass the NODE_ENV
value from rollup.config.js
to the bundle. To set NODE_ENV
we can use the
argument --environment
in Rollup’s execution. We’re at a good point to have separate scripts for development and
production environments
// package.json
{
...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rollup --config --environment NODE_ENV:production",
"start": "rollup --config --environment NODE_ENV:development"
},
...
}
To avoid serving our bundle locally on production
builds we can modify rollup.config.js
’s serve()
to be run only
in development mode:
// rollup.config.js
...
export default {
...
plugins: [
replace({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}),
resolve(),
commonjs(),
typescript(),
html(),
process.env.NODE_ENV === 'development' && serve('public')
]
};
And that’s it! A basic TS + React Web application setup with Rollup, step by step. We should probably add support for CSS, as well as some automated testing, linting, live reload in the development server… But I’ll leave this for a future post.
Recommended links:#
- Rollup Awesome : A curated collection of Rollup Plugins, Packages, and Resources.