How to Serve a Single Page Application(SPA) using Rollup.js and Web Dev Server
Originally published at labs.thisdot.co
Dec 30, 2020 · 8 minutes read · Follow @luixaviles
In a previous post, I explained How to setup a TypeScript project using Rollup.js. In that article, I covered the necessary steps to have a Project Setup ready to generate a build using TypeScript and Rollup configurations.
So let’s take that project as a starting point to serve a Single-page Application using modern web tools.
What is a SPA (Single-page Application)?
According to MDN Documentation:
An SPA (Single-page application) is a web app implementation that loads only a single web document, and then updates the body content of that single document via JavaScript APIs such as XMLHttpRequest and Fetch when different content is to be shown.
Just to clarify the main idea, A Single-page application allows updating only some portions of the current page instead of doing a page refresh. Think about some applications you use every day:
- Gmail
All of these applications have been built with the user experience in mind along with a good loading speed and navigation between pages.
Then, there are very good reasons to keep building applications following the SPAs approach.
What is Web Dev Server?
Web Dev Server, as its name states, it’s a web server for development and it helps development using native browser features like ES modules and it has a plugin architecture for code transformations.
Web Dev Server is the successor of es-dev-server
It worths mentioning that Web Dev Server allows configuring auto-reload on file changes along with efficient browser caching for faster reloads. It’s configurable and supports rollup plugins too!
Project Setup
Prerequisites
You’ll need to have installed the following tools in your local environment:
- Node.js. Preferably the latest LTS version.
- A package manager. You can use either NPM or Yarn. This tutorial will use NPM.
Initialize the Project
Let’s create a clone or download the project seed before adding the new configurations and tools:
git clone https://github.com/luixaviles/typescript-rollup.git
cd typescript-rollup/
git checkout tags/01-setup-ts-rollup -b 02-serve-spa
The previous commands will download the project and create a new branch 02-serve-spa
to get started.
Source Code Files
Once you have the project ready, open it with your favorite code editor, and pay attention to the current project structure:
|- typescript-rollup
|- src/
|- math/
|- math.ts
|- index.ts
|- string/
|- string.ts
|- index.ts
|- app.ts
|- package.json
|- rollup.config.js
|- tsconfig.json
Installing Web Dev Server and Concurrently
Let’s add some development dependencies to the project.
- Install Web Dev Server
npm install --save-dev @web/dev-server
As I mentioned before, Web Sev Server will be the main tool to configure and run our project this time.
Also, if you already know es-dev-server
tool, used actively on the Open Web Components initiative, you’ll understand that this project migrated to Modern Web website. And that does not mean anything other than this new tool continues to be actively developed and can be used in any JavaScript project(Of course, TypeScript included ;-)).
- Install Concurrently tool
npm install --save-dev concurrently
If you are familiar with any command-line tool, you may have wanted to run commands in parallel.
Concurrently has the same goal. However, it’s hard to keep track of every command output and concurrently
allows you to run any command you want and it’s possible to kill all of them if anyone fails.
TypeScript Configuration
In this case, the tsconfig.json
file will remain the same, without any changes:
{
"compilerOptions": {
"target": "es2018",
"module": "esnext",
"moduleResolution": "node",
"noEmitOnError": true,
"lib": ["es2017"],
"strict": true,
"esModuleInterop": false,
"outDir": "out-tsc",
"rootDir": "./"
},
"include": ["./src/**/*.ts"]
}
Remember, this file is required to be present in the root of any TypeScript project.
Rollup Configuration
The project is configured to use Rollup as the module bundler. It’s possible to run it through command-line parameters. However, if you’re looking for advanced functionality, you can consider a rollup.config.js
file:
import merge from 'deepmerge';
import { createBasicConfig } from '@open-wc/building-rollup';
const baseConfig = createBasicConfig();
export default merge(baseConfig, {
input: './out-tsc/src/app.js',
output: {
dir: 'dist',
},
});
This file contains a basic configuration and it’s needed to set the input
files and the output
directory for the build.
Since we intend to compile a Single-page application now, it will be necessary to apply some changes:
import merge from 'deepmerge';
import { createSpaConfig } from '@open-wc/building-rollup';
const baseConfig = createSpaConfig({
developmentMode: process.env.ROLLUP_WATCH === 'true',
injectServiceWorker: false,
});
export default merge(baseConfig, {
// any <script type="module"> inside will be bundled by Rollup
input: './index.html',
});
The baseConfig
content will be generated from createSpaConfig
, which is defined in @open-wc/building-rollup package.
When createSpaConfig
is used, a service worker is generated using Workbox. However, the service worker is injected into the index.html
file when injectServiceWorker
is enabled.
HTML file as an Input
Once the input
parameter is set using an index.html
file, any module defined as part of it will be bundled by Rollup and this content will be injected into the HTML file result.
The index.html File
Let’s create an index.html
file into the root folder with the following content:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Single Page Application</title>
</head>
<body>
<h1>Welcome to your Single Page Application</h1>
<span>This App uses:</span>
<ul>
<li>TypeScript</li>
<li>Rollup.js</li>
<li>es-dev-server</li>
</ul>
<script type="module" src="./out-tsc/src/app.js"></script>
</body>
</html>
We can say the content of this file is common. However, the most important line would be the reference to the app.js
file:
<script type="module" src="./out-tsc/src/app.js"></script>
Let’s explain what is happening there:
- As you will remember, the
tsconfig.json
file sets"outDir": "out-tsc"
and that means the TypeScript compiler will generate the output files in./out-tsc
folder. - Then, the
index.html
file does reference to the entry point of the source code, the compiled version of the./src/app.ts
file. - When the Rollup configuration starts to build the app, it will take the module defined in
index.html
file to generate a bundle ready for it.
Adding Serve and Build Scripts
If you pay attention to the package.json
file, it already defines a script to generate the build:
...
"scripts": {
"build": "rimraf dist && tsc && rollup -c rollup.config.js"
},
...
The change we introduced into the rollup.config.json
file doesn’t affect the build process.
Now let’s move forward with the addition of some scripts to “serve” the application:
...
"scripts": {
"tsc:watch": "tsc --watch",
"start": "concurrently --kill-others --names tsc,web-dev-server \"npm run tsc:watch\" \"web-dev-server --app-index index.html --node-resolve --open --watch\"",
"build": "rimraf dist && tsc && rollup -c rollup.config.js"
},
...
- “tsc:watch” is the script to start with the compilation process through
tsc
, which is the TypeScript compiler. The--watch
param stands for a compiler option to run the compiler in watch mode(it trigger recompilation on file changes). - “start” is the script to add the execution of some commands in parallel:
npm run tsc:watch
andweb-dev-server
that includes some CLI flags.- You already noted that
concurrently
command is called first! - The
--kill-others
parameter will kill all the invoked commands if one dies(eithertsc
orweb-dev-server
).
- You already noted that
Just think for a while how is going to look the “start” command if we’re planning to add more configurations in the future: An script hard to read and maintain of course.
Adding the web-dev-server.config.js File
The alternative option to include the CLI flags as parameters is the creation of the web-dev-server.config.js
file.
The file extension can be .js, .cjs or .mjs. A .js file will be loaded as an es module or common js module based on your version of node, and the package type of your project.
By default, the Web Dev Server looks at the configuration file in the current working directory.
Let’s create the web-dev-server.config.js
file in the root folder:
// web-dev-server.config.js
module.exports = {
port: 8000,
nodeResolve: true,
open: true,
watch: true,
appIndex: 'index.html',
};
This file now contains the same options we used in the start script with the addition of a custom port number: port: 8000
, which can be configured now.
For a sake of simplicity, let’s update the start script with a reference to the new configuration file:
...
"scripts": {
...
"start": "concurrently --kill-others --names tsc,web-dev-server \"npm run tsc:watch\" \"web-dev-server --config web-dev-server.config.js\"",
...
},
...
That’s it! You’re ready to serve your first application using:
npm run start
You should see the following output:
Web Dev Server started...
[web-dev-server]
[web-dev-server] Root dir: /Users/luixaviles/projects/typescript-rollup
[web-dev-server] Local: http://localhost:8000/
[web-dev-server] Network: http://192.168.1.2:8000/
[web-dev-server]
Also, your default browser will be opened to display the index.html
content.
On the right side(when you open the Developer Tools), you’ll see the output of the main script file. And that is proof that the module is running correctly when the SPA is loaded.
Running the Build
The script is ready and it doesn’t need an update. Execute the following command:
npm run build
This will generate a dist
folder with the following content:
|- dist/
|- 7b857f5b.js
|- index.html
|- ... other scripts
A simple way to serve these files locally would be to use the http-server
tool(a command-line http server):
http-server dist/ -o
The output of this command will show you the host and the port you need to use to access the build version of the app:
Starting up http-server, serving dist/
Available on:
http://127.0.0.1:8080
http://192.168.1.2:8080
Hit CTRL-C to stop the server
open: http://127.0.0.1:8080
I suggest you can review the content of dist/
files and make sure you have http-server installed before running the latest command.
Source Code of the Project
Find the complete project in this GitHub repository: typescript-rollup. Do not forget to give it a star ⭐️ and play around with the code.
Feel free to reach out on Twitter if you have any questions. Follow me on GitHub to see more about my work.