Create a RingCentral Single Page Application without webpack

Tyler Liu
4 min readDec 8, 2022

--

In this article, I am going to do an interesting experiment: create a RingCentral Single Page Application (SPA) without webpack. Why “without” webpack? Is there anything wrong with using webpack? No, webpack is fine. I use webpack in almost all of my web applications. However, as a developer relations engineer, from time to time, I need to do support tickets to troubleshoot customer’s web applications which don’t use webpack at all. I realized that people are creating web applications using different tool chains. And today I am going to try one of them: create a web application without webpack to see how different it is.

Alternative to “webpack serve"

As a webpack user for years, I really like the webpack serve command (it used to be webpack-dev-serverbefore webpack 5.x) which will start a web server to host my application locally. So that you can simply test your web application by visiting http://localhost:8080 (the port number may be different).

Without webpack, I suggest to install the http-server npm package. It will start a local http server. So that you can access your web application without a web browser to test it. Please note that, by default, http-server will cache the html/javascript/css contents for 1 hour. We do not want such a cache in our development environment. Cache is good for production because it can improve performance. But for local development, we need to immediately see the results once we make any changes to our code base. So, it’s important to disable cache by starting http-server like this: http-server -c-1 , the -c-1 option is to disable cache.

The HTML file

Since it is very simple, let me paste the full content first:

<!DOCTYPE html>
<html>
<head>
<title>
RingCentral SPA Demo
</title>
</head>
<body>
<h1>Hello world!</h1>
<script type="text/javascript" src="https://unpkg.com/@ringcentral/sdk@latest/dist/ringcentral.js"></script>
<script type="text/javascript" src="https://unpkg.com/pubnub@4.37.0/dist/web/pubnub.js"></script>
<script type="text/javascript" src="https://unpkg.com/@ringcentral/subscriptions@latest/dist/ringcentral-subscriptions.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/localforage/1.10.0/localforage.min.js"></script>
<script type="text/javascript" src="./credentials.js"></script>
<script type="text/javascript" src="./index.js"></script>
</body>
</html>

Nothing is too special but I want to talk about one thing that is quite different from a webpack based application: how you reference NPM packages.

If you use webpack, then you first install the package (take @ringcentral/sdk for example), and there will be an entry in your package.json file. And in your source code, you just reference the package like this:

import RingCentral from '@ringcentral/sdk';

Without webpack, you reference the packages directly in your HTML file. Still take @ringcentral/sdk as example:

<script type="text/javascript" src="https://unpkg.com/@ringcentral/sdk@latest/dist/ringcentral.js"></script>

The HTML file

It’s also very simple and straightforward. I just paste all the code below:

const rc = new RingCentral.SDK({
server,
clientId,
});
const platform = rc.platform();
const redirectUri = window.location.origin + window.location.pathname;
const urlSearchParams = new URLSearchParams(
new URL(window.location.href).search
);
const code = urlSearchParams.get('code');
if (code === null) {
const loginUrl = platform.loginUrl({
redirectUri,
usePKCE: true,
});
localforage.setItem('codeVerifier', platform.codeVerifier).then(() => {
const link = document.createElement('a');
link.href = loginUrl;
link.innerText = 'Login';
document.body.appendChild(link);
});
} else {
// exchange code for token
localforage.getItem('codeVerifier', (err, codeVerifier) => {
platform
.login({
code,
redirect_uri: redirectUri,
code_verifier: codeVerifier
})
.then(r => r.json())
.then(token => document.write('<pre>' + JSON.stringify(token, null, 2) + '</pre>'));

// make API call
platform.get('/restapi/v1.0/account/~/extension/~')
.then(r => r.json())
.then(ext => {
document.write(`<p>Your extension ID is ${ext.id}</p>`);
// subscription
const subscriptions = new RingCentral.Subscriptions({
sdk: rc
});
const subscription = subscriptions.createSubscription();
subscription.on(subscription.events.notification, (evt) => {
console.log(JSON.stringify(evt, null, 2));
});
subscription
.setEventFilters(['/restapi/v1.0/account/~/extension/~/message-store'])
.register()
.then(() => {
// trigger a message
platform.post('/restapi/v1.0/account/~/extension/~/company-pager', {
from: {extensionId: ext.id},
to: [{extensionId: ext.id}],
text: 'Hello world!',
});
});
});
});
}

The initialization part:

const rc = new RingCentral.SDK({
server,
clientId,
});
const platform = rc.platform();
const redirectUri = window.location.origin + window.location.pathname;
const urlSearchParams = new URLSearchParams(
new URL(window.location.href).search
);

Because we have reference the @ringcentral/sdk package in HTML <script> tag, the RingCentral class will be available to us out of box.

The authentication part:

const code = urlSearchParams.get('code');
if (code === null) {
const loginUrl = platform.loginUrl({
redirectUri,
usePKCE: true,
});
localforage.setItem('codeVerifier', platform.codeVerifier).then(() => {
const link = document.createElement('a');
link.href = loginUrl;
link.innerText = 'Login';
document.body.appendChild(link);
});
} else {
// exchange code for token
localforage.getItem('codeVerifier', (err, codeVerifier) => {
platform
.login({
code,
redirect_uri: redirectUri,
code_verifier: codeVerifier
})
.then(r => r.json())
.then(token => document.write('<pre>' + JSON.stringify(token, null, 2) + '</pre>'));
...

We are using Auth Code flow + PKCE here. It is the recommended way for a web application to do authentication. Pay attention to how I save the codeVerifier so that it could survive page redirection. There is another way to do authentication that takes advantage of a popup window, it doesn’t redirect so you don’t have to save the codeVerifier variable.

How to make an API call:

// make API call
platform.get('/restapi/v1.0/account/~/extension/~')
.then(r => r.json())
.then(ext => {
document.write(`<p>Your extension ID is ${ext.id}</p>`);
...

How to create subscriptions:

// subscription
const subscriptions = new RingCentral.Subscriptions({
sdk: rc
});
const subscription = subscriptions.createSubscription();
subscription.on(subscription.events.notification, (evt) => {
console.log(JSON.stringify(evt, null, 2));
});
subscription
.setEventFilters(['/restapi/v1.0/account/~/extension/~/message-store'])
.register();

Summary

Creating a web application without webpack is totally viable. However, there are something that I miss a lot during the process: first of all, I would like to write TypeScript instead of JavaScript; secondly, I would like to write async/await instead of .then().catch() . Both of them require an additional “transcompile” step. But if I do transcompile, why not use webpack because webpack could handle it neatly?Maybe it is just my personal optinion. Please let me know your opinions about web development without webpack in the comments section.

Full Source Code

Full source code for this experiment is available here.

--

--