Launching Web Apps Without Own Servers: How ESM and CDNs Are Changing the Game

Publication date: 2024-07-04

With the advent of modern web technologies, developers of small web applications no longer need complex infrastructure to deploy them. ES modules and the distribution of libraries through CDNs allow applications to run directly in end-user browsers without the need for virtual servers or cloud services. Let me show you how it works with a simple example.

The web application I’m demonstrating is an example of using the OpenAI API directly from the user’s browser:

Image 2

A sample conversation.

This web application “lives” on the client side, loads the necessary libraries from the CDN (jsDelivr), and communicates directly with the OpenAI API. The main application file (index.html) is hosted on GitHub Pages.

Image 3

The principal schema

In the modern world, web developers only need to publish their package in the npm registry for it to be accessible to any user through jsDelivr or Unpkg websites.

Application Description

The application interface is created using the following libraries:

The application connects to the OpenAI API using the corresponding library (openai) and allows a conversation with one of the available ChatGPT models. The application navigation is very simple:

Image 4

The navigation

The application configuration involves specifying the access key for the OpenAI API, selecting the model, and the system prompt to start the conversation:

Image 5

The configuration

Each user must generate their own API key on the OpenAI website. The key is stored in the browser’s local storage and is not shared with third parties. However, it is recommended to invalidate the key on the OpenAI website after using it in the demo application.

The main mode of operation for the application is a standard chat:

Image 6

The chat

The “Delete” button resets the current dialogue and returns it to its initial state.

Key Points in Development

index.html

The launchpad for the application is very compact:

<!DOCTYPE html>

OpenAI API

The index.html file solves the following tasks:

@flancer64/demo-wa-esm-openai

The main code of the web application is in the @flancer64/demo-wa-esm-openai package. Each JavaScript source file in the package is an ES module:

Image 7

The sources of the app package

Files in the ./Mod/ directory are models that perform certain actions in the application. Files in the ./Ui/ directory are Vue components.

The most interesting file is ./src/App.mjs, as it is where the libraries are loaded and the application is initialized.

import * as Vue from ‘https://cdn.jsdelivr.net/npm/vue@3.4.31/dist/vue.esm-browser.prod.js’;
import {

createRouter,

createWebHashHistory

} from ‘https://cdn.jsdelivr.net/npm/vue-router@4.4.0/dist/vue-router.esm-browser.js’; The vue and vue-router libraries have their ESM versions, so they are loaded through a static import.

The quasar library has only a UMD version, so we load it through a dynamic import after preparing the global environment:

window.Vue = Vue;
await import ( ‘https://cdn.jsdelivr.net/npm/quasar@2.16.4/dist/quasar.umd.prod.js’);
const Quasar = window.Quasar; Next, we initialize the router, create the Vue application, and export it via export default for mounting in index.html to the corresponding DOM element:
const {createApp} = Vue;
const routes = [
{path: DEF.ROUTE_ABOUT, component: () => import(‘./Ui/Route/About.mjs’)},
{path: DEF.ROUTE_CONFIG, component: () => import(‘./Ui/Route/Config.mjs’)},
{path: DEF.ROUTE_HOME, component: () => import(‘./Ui/Route/Chat.mjs’)},
];
const router = createRouter({

history: createWebHashHistory(),

routes,

});
const app = createApp(Main);
app.use(router);
app.use(Quasar, {

config: {

dark: false,

}
});
export default app;

Interestingly, we use lazy loading of the source code for Vue components when configuring the router:

{path: …, component: () => import(‘./Ui/Route/About.mjs’)}

This loads a route’s ES modules and dependencies when the user navigates there, rather than at application startup.

View from the Browser

As seen in the Sources tab of the ‘Developer Tools’ panel in Chrome, all resources on the page are obtained from public sources:

Image 8

The resource sources

GitHub Pages

There are only 2 resources hosted on this site: styles and index.html:

Image 9

GitHub Pages

CDN for NPM Packages

The sources loaded through jsDelivr mirror the directory/file structure seen in the IDE:

Image 10

Own ESM sources

Note the ./Ui/Route/ directory — I have not navigated to routes other than ./, so only ./Chat.mjs (the Vue component for this route) is loaded in the browser. The remaining Vue components and their ES modules will be loaded as the user navigates to the corresponding routes.

Here’s an example of loading the sources for a third-party package that supports ES modules:

Image 11

The 3rd party ESM resources

And here’s an example of sources from a package that supports UMD modules:

Image 12

The 3rd party UMD resources

The remaining resources (fonts and images) are also loaded from public sites:

Image 13

The fonts and images

User configuration data is stored in the browser’s local storage:

Image 14

The local storage

Conclusion

Using modern technologies, it is not necessary to have your own resources (virtual servers or cloud space) to create and distribute small web applications that run on user devices and use third-party service APIs. Application code can be hosted and distributed through public services.

The resource load speed is quite decent, with public services using modern technologies (protocols h2 and h3):

Image 15

The Network stats

On repeated launches of the web application, resources are used from the browser cache:

Image 16

The cache usage

As noted earlier, only the resources directly related to user actions in the browser are loaded. This allows for staggered loading over time or even skipping the loading of certain ES modules if, for example, the user does not have the rights to perform certain actions. There is no need to assemble custom bundles and perform tree-shaking — only the modules actually used by the user are loaded from the network.

Developers of your application have access to all the code stored in the npm registry. Any public ES module can be obtained through a CDN. The application can be extended on-the-fly directly in the user’s browser based on their needs. Unfortunately, the downside of such flexibility is the increased likelihood of malicious code ending up in your application.

However, if your web application is not too large, if you can afford open code, and if your user data is not valuable enough to be specifically targeted, you can host your web applications on public resources like GitHub Pages and jsDelivr.

Even if your web applications do not meet the above criteria, you can still benefit from using ES modules during development, loading the required functionality with high granularity directly into the browser from your own resources (servers or cloud).

Thank you for reading and have a great day!

Alex Gusev.