Category Archives: JavaScript

Trying out bun

You can never get too complacent with the JavaScript eco-system, no sooner do you start to feel comfortable than something else becomes the new hotness, this time it’s bun. Bun is a JavaScript runtime, pretty much analogous to Node.js, so basically a potential replacement for Node.js.

Setting things up

The Installation page gives details on setting up for various OS’s, Windows is not well supported at the moment (i.e. experimental release), but as I love devcontainers in VSCode, we’ll just set up a devcontainer to use the docker image.

  • Create a folder for your (yes we’re going to do it) hello world app, mine’s named hello-world
  • Create folder named .devcontainer (for more info. see my post Visual Code with vscontainers)
  • Create a file in .devcontainer named devcontainer.json and put the following code in it
    {
      "image": "oven/bun",
      "forwardPorts": [3000]
    }
    

Now open Visual Code on the folder hello-world (or whatever you named it) and VS Code will hopefully ask if you want to open the folder as a devcontainer, obviously say yes and it’ll set up the docker image for you and then you’ll be working in a devcontainer with bun.

Is it working?

If you carried the steps above (or installed by one of the other means) we now want to check bun is working. From your terminal either on the devcontainer or any terminal if you installed it on your machine or container, run the command

bun

You should see a list of commands etc. If not, check through your installation and in the case of the devcontainer make sure you’re using the terminal in VS Code and it’s showing your root folder of the docker image.

Getting Started

Hopefully everything is running, so we need to create something, so run

bun init

Then fill in the options, mine are using all the defaults, but I’ll then them below anyway

  • package name hello-world
  • entry point index.ts

and that’s all there’s is so it. Bun will create and index.ts file, .gitignore, tsconfig.json, package.json and READEME.md. The index.ts looks like this

console.log("Hello via Bun!");

Let’s run this using

bun run index.ts

As you’d expect we’re seeing Hello via Bun! in our terminal window.

I want a server!

Most, if not all my work with Node.js was writing server based code, so let’s take the example from the Bun Quick Start page and fire up a little server app.

const server = Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response("Hello World from the server");
  },
});

console.log(`Listening on http://localhost:${server.port} ...`);

We’re using port 3000 which we also have listed in our devcontainer.json within forwardPorts. So running index.ts from bun will start the server on port 3000 and VS Code (in my case) will ask if I wish to open the browser for that port. If you’re using a different method then simply open your browser with URL http://localhost:3000/.

We can ofcourse put scripts into our package.json, to save us some typing, for example adding

"scripts": {
    "start": "bun run index.ts"
  },

Now we can use the command

bun run start

Everything else should pretty much work as Node.js, but it’s reported that bun (which essentially also includes package management tooling) is quite a bit faster than npm and yarn, also it includes hot reload out of the box, i.e. we don’t need to use Nodemon. It automatically transpiles TypeScript and JSX. So it’s very much an “all in one” solution to Node.js style development.

So that’s a really quick run through and a reminder to myself how to get bun up and running as quickly as possible.

fetch, no-cors no custom context type

Note: This post was written a while back but sat in draft. I’ve published this now, but I’m not sure it’s relevant to the latest versions etc. so please bear this in mind.

If you’re using the Javascript fetch api with mode set to ‘no-cors’ beware that this limits the set of headers you can use in your request. Specifically if you’re trying to interact with a server using application/json, the ‘no-cors’ will simply not apply that content-type.

See Using Fetch

In no-cors mode only the following are allowed

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type which can only take the following
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

HTML Canvas

HTML Canvas allows us to draw graphics (using JavaScript). We can create a canvas element and use a WebGL API to draw lines, fill rectangles etc.

Let’s demonstrate some of the canvas and drawing functionality by creating a simple Tic-Tac-Toe UI. Start of by creating a HTML file and within that we add a canvas element, for example

<canvas id="tic-tac-toe-board"></canvas>

Now in our JavaScript (or in my case I’m going to use TypeScript) we can get the element using the id and then start interacting with the canvas using WebGL, for for example here’s a “main” method which we’d run in the HTML body’s onload event. The first thing we need to do is get the canvas element using

const board = <HTMLCanvasElement> document.getElementById('tic-tac-toe-board');

Next up we need to get the element’s context using

const ctx = board.getContext('2d');  
if(ctx != null) {
}

Once we have the context we can use methods, such as fillRect, moveTo, lineTo etc. as well as set properties on the context, for example

const ctx = board.getContext('2d');  
if(ctx != null) {
  ctx.fillStyle = 'white';
  ctx.fillRect(0, 0, board.clientWidth, board.clientHeight);
}

Below is an example of an implementation of code to draw a Tic-Tac-Toe board and handle click events.

function main() {
  const board = <HTMLCanvasElement> document.getElementById('tic-tac-toe-board');

  const size = 500;
  const lineColour = "#ddd";
  const lineStart = 4;
  const lineLength = size - 5;
  const sectionSize = size / 3;

  board.width = size;
  board.height = size;

  const elemLeft = board.offsetLeft + board.clientLeft;
  const elemTop = board.offsetTop + board.clientTop;
  const elemWidth = board.clientWidth;
  const elemHeight = board.clientHeight;

  // example of handling click event
  board.addEventListener('click', ev => {
     const x = ev.pageX - elemLeft;
     const y = ev.pageY - elemTop;

     console.log(`X: ${x}, Y: ${y}`);
  });

  const ctx = board.getContext('2d');  
  if(ctx != null) {
    ctx.fillStyle = 'white';
    ctx.fillRect(0, 0, elemWidth, elemHeight);

    ctx.lineWidth = 10;
    ctx.lineCap = 'round';
    ctx.strokeStyle = lineColour;
    ctx.beginPath();

    for (let y = 1; y <= 2; y++) {  
      ctx.moveTo(lineStart, y * sectionSize);
      ctx.lineTo(lineLength, y * sectionSize);
    }
      
    for (let x = 1; x <= 2; x++) {
      ctx.moveTo(x * sectionSize, lineStart);
      ctx.lineTo(x * sectionSize, lineLength);
    }
      
    ctx.stroke();
  }
}

Source code for this post is available on github

Blazor and the JavaScript interop.

In the post Logging in Blazor we saw how we can use the IJSRuntime to call out to the JavaScript runtime.

Ofcourse we can interop with any JavaScript code within our application, so for example let’s make some simple changes to the index.html from that post and add a script block, such as this

<script>
   function displayMessage(msg) {
      alert(msg);
   }
</script>

Nothing very special here, but we just want something visual to more easily see our interop calls.

Next, in our .razor file let’s simple add a button and associated code block

<button @onclick="DisplayMessage">Alert</button>

@code {
   private void DisplayMessage()
   {
      JsRuntime.InvokeVoidAsync("displayMessage", "Hello JavaScript World");
   }
}

The IJSRuntime interface has the following methods

public interface IJSRuntime
{
   ValueTask<TValue> InvokeAsync<TValue>(string identifier, 
      object[] args);
   ValueTask<TValue> InvokeAsync<TValue>(string identifier, 
      CancellationToken cancellationToken, object[] args);
}

As you can see, both return a ValueTask, hence are awaitable, but neither matches our InvokeVoidAsync method. This is because there are a bunch of extension methods within JSRuntimeExtensions. These extension methods check that the supplied IJSRuntime is not null and then calls the interface methods.

In the example above, we are not interested in the return value, so let’s now return some value from our JavaScript…

Change the script to

<script>
   function displayMessage(msg) {
      return prompt(msg);
   }
</script>

and now change the C# code in our code block to

private async void DisplayMessage()
{
   Debug.WriteLine(
      await JsRuntime.InvokeAsync<string>(
         "displayMessage", "Hello JavaScript World"
      )
   );
}

At this point, we’re receiving the return from our JavaScript function and (in the case of a WebAssembly application) outputting to the browser console.

Ofcourse we can now write JavaScript code and call this from Blazor as well.

Modules, modules and yet more modules

Another post that’s sat in draft for a while – native JavaScript module implementations are now supported in all modern browsers, but I think it’s still interesting to look at the variations in module systems.

JavaScript doesn’t (as such) come with a built in module system. Originally it was primarily used for script elements within an HTML document (inline scripting or even scripts per file) not a full blown framework/application as we see nowadays with the likes of React, Angular, Vue etc.

The concept of modules was introduced as a means to enable developers to separate their code into separate files and this ofcourse aids in reuse of code, maintainability etc. This is not all that modules offer. In the early days of JavaScript you could store your scripts in separate files but these would then end up in the global namespace which is not ideal, especially if you start sharing your scripts with others (or using other’s scripts), then there’s the potential of name collisions, i.e. more than one function with the same name in the global namespace.

If you come from a language such as C#, Java or the likes then you may find yourself being slightly surprised that this is a big deal in JavaScript, after all, these languages (and languages older than JavaScript) seemed to have solved this problems already. This is simply the way it was with JavaScript because of it’s original scripting background.

What becomes more confusing is that there isn’t a single solution to modules and how they should work, several groups have created there own versions of modules over the life of JavaScript.

Supported by TypeScript

This post is not a history of JavaScript modules so we’re not going to cover every system every created but instead primarily concentrate on those supported by the TypeScript transpiler, simply because this where my interest in the different modules came from.

TypeScript supports the following module types, “none”, “commonjs”, “amd”, “system”, “umd”, “es2015” and “ESNext”. These are the one’s that you can set in the tsconfig.json’s compilerOptions, module.

To save me looking at writing my own examples of the JavaScript code, we’ll simply let the TypeScript transpiler generate code for us and look at how that looks/works.

Here’s a simple TypeScript (Demo.ts) file with some code (for simplicity we’re not going to worry about lint rules such as one class per file or the likes).

export class MyClass {    
}

export default class MyDefaultClass {    
}

Now my tsconfig.json looks like this

{
  "compilerOptions": {
    "target": "es6",  
    "module": "commonjs",  
    "strict": true
  },
  "include": ["Demo.ts"]
}

And I’ll simply change the “module” to each of the support module kinds and we’ll look at the resultant JavaScript source. Alternatively we can use this index.js

var ts = require('typescript');

let code = 'export class MyClass {' + 
'}' +
'export default class MyDefaultClass {' +
'}';

let result = ts.transpile(code, { module: ts.ModuleKind.CommonJS});
console.log(result);

and change the ModuleKind the review the console output. Either way we should get a chance to look at what style of output we get.

Module CommonJS

Setting tsconfig module to CommonJS results in the following JavaScript being generated. The Object.defineProperty simple assigns the property __esModule to the exports. Other than this the class definitions are very similar to our original code. The main difference is in how the exports are created.

The __esModule is set to true to let “importing” modules know that this code is is a transpiled ES module. It appears this matters specifically with regards to default exports. See module.ts

The exports are made directly to the exports map and available via the name/key. For example exports[‘default’] would supply the MyDefaultClass, like wise exports[‘MyClass’] would return the MyClass type.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class MyClass {
}
exports.MyClass = MyClass;
class MyDefaultClass {
}
exports.default = MyDefaultClass;

We can import the exports using require, for example

var demo = require('./DemoCommon');

var o = new demo.MyClass();

Module AMD

The Asynchronous Module Definition was designed to allow the module to be asynchronously loaded. CommonJS is a synchronous module style and hence when it’s being loaded the application will be blocked/halted. With the AMD module style expects the define function which passes arguments into a callback function.

As you can see the code within the define function is basically our CommonJS file wrapped in the callback.

define(["require", "exports"], function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    class MyClass {
    }
    exports.MyClass = MyClass;
    class MyDefaultClass {
    }
    exports.default = MyDefaultClass;
});

To import/require AMD modules we need to use requirejs, for example assuming we created a file from the above generated code named DemoAmd.js, then we can access the module using

var requirejs = require('requirejs');
requirejs.config({
    baseUrl: __dirname,
    nodeRequire: require
});

var demo = requirejs('./DemoAmd');

var o = new demo.MyClass();

Module System

System.register([], function (exports_1, context_1) {
    "use strict";
    var MyClass, MyDefaultClass;
    var __moduleName = context_1 && context_1.id;
    return {
        setters: [],
        execute: function () {
            MyClass = class MyClass {
            };
            exports_1("MyClass", MyClass);
            MyDefaultClass = class MyDefaultClass {
            };
            exports_1("default", MyDefaultClass);
        }
    };
});

Module UMD

UMD or Universal Module Definition is a module style which can be used in place of CommonJS or AMD in that it uses if/else to generate modules in either CommonJS or AMD style. This means you can write your modules in a way that can be imported into a system expecting either CommonJS or AMD.

(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    class MyClass {
    }
    exports.MyClass = MyClass;
    class MyDefaultClass {
    }
    exports.default = MyDefaultClass;
});

We can import UMD modules using either require (as shown in our CommonJS code) or requirejs (as shown in our AMD code) as UMD modules represent modules in both styles.

Module es2015 and ESNext

As you might expect as the TypeScript we’ve written is pretty standard for ES2015 the resultant code looks identical.

export class MyClass {
}
export default class MyDefaultClass {
}

Module None

I’ve left this one until last as it needs further investigation as module None suggests that code is created which is not part of the module system, however the code below works quite happily with require form of import. This is possibly a lack of understanding of it’s use by me. It’s included for completeness.

"use strict";
exports.__esModule = true;
var MyClass = /** @class */ (function () {
    function MyClass() {
    }
    return MyClass;
}());
exports.MyClass = MyClass;
var MyDefaultClass = /** @class */ (function () {
    function MyDefaultClass() {
    }
    return MyDefaultClass;
}());
exports["default"] = MyDefaultClass;

Inspecting modules

function printModule(m, indent = 0) {
    let indents = ' '.repeat(indent);

    console.log(`${indents}Filename: ${m.filename}`);    
    console.log(`${indents}Id: ${m.id}`);    
    let hasParent = m.parent !== undefined && m.parent !== null;
    console.log(`${indents}HasParent: ${hasParent}`);
    console.log(`${indents}Loaded: ${m.loaded}`);
    if(m.export !== []) {
        console.log(`${indents}Exports`);
        for(const e of Object.keys(m.exports)) {
            console.log(`${indents}  ${e}`);
        }
    }
    if(m.paths !== []) {
        console.log(`${indents}Paths`);
        for(const p of m.paths) {
            console.log(`${indents}  ${p}`);
        }
    }

    if(m.children !== []) {
        console.log(`${indents}Children`);
        for (const child of m.children) {
            printModule(child, indent + 3);
        }
    }
}


console.log(Module._nodeModulePaths(Path.dirname('')));
printModule(module);

Creating scaffolding with yeoman

Yeoman is basically a tool for generating scaffolding, i.e. predefined projects (see generators for a list of some existing generators).

You could generate things other than code/projects equally well using yeoman.

There’s a fair few existing generators but you can also define your own generators and hence… Say for example you have a standard set of tools used to create your Node based servers, i.e. you want Typescript, express, eslint, jest etc. we could use yeoman to set everything up.

Ofcourse you could create a shell script for this or a custom CLI, but yeoman gives you the ability to do all this in Javascipt with the power that comes from that language and it’s eco-system.

Within a yeoman script, we can also interact with the user via the console, i.e. ask for input. Create templates for building code, configuration etc.

Installing the tooling

Before we start using yeoman we need to install it, so run either npm or yarn as shown below

npm install -g yo 

To check everything worked simply run

yo --version

At the time of writing, mine version is 3.1.1.

Before we get onto the real topic of this post, lets just check out some yeoman commands

  • Listing installed generators
    yo --generators
    
  • Diagnose yeoman issues
    yo doctor
    

Creating our own generators

Okay so this is what we’re really interested in. I have a bunch of technologies I often use (my usual stack of tech./packages). For example, if I’m creating a Node based server, I’ll tend to use Typescript, express, jest and so on. Whilst we can, ofcourse, create things like a git repos with everything set-up and just clone it or write shell scripts to run our commands. As mentioned, with yeoman we can also template our code as well as interact with the user via the CLI to conditionally generate parts of our application.

There appears to be a generator for producing generators, but this failed to work for me, but for completeness here it is

npm install -g yo generator-generator

Now, let’s write our first generator…

Run the following, to create our package.json file

yarn init -y

The first thing to note is, the generator name should be prefixed with generator-. Therefore we need to change our “name” within package.json, for example

"name": "generator-server"

The layout of our files is expected to be a either (off of our root)

packages.json
generators/app/index.js
generators/router/index.js

OR

packages.json
app/index.js
router/index.js

Whichever layout we choose should be reflected in package.json like this

"files": [
    "generators"
  ],

OR

"files": [
    "app",
    "router"
  ],

You might, at this point, wonder what the point of the router is, and whilst this is within the yeoman getting started guide, it appears ultimately any folder added alongside the app folder will appear as a “subcommand” (if you like) of your generator. In this example, assuming the app name is generator-server (see below) then will will also see that router can be run using yo server:router syntax. Hence you can create multiple commands under your main yeoman application.

We’ll also need to add the yeoman-generator package before we go too much further, so run

yarn add yeoman-generator

So here’s a minimal example of what your package.json might look like

{
  "name": "generator-server",
  "version": "0.1.0",
  "description": "",
  "files": [
    "generators"
  ],
  "keywords": ["yeoman-generator"],
  "dependencies": {
    "yeoman-generator": "^1.0.0"
  }
}

Writing our generator code

In the previous section we got everything in place to allow our generator to be recognised by yeoman, so let’s now write some code.

Here’s an example of a starting point from the yeoman website.

In generator/app/index.js we have a simple example

var Generator = require("yeoman-generator");
module.exports = class extends Generator {
   method1() {
      this.log('method 1 just ran');
   }
   method2() {
      this.log('method 2 just ran');
   }
};

Sadly this is not using ES6 syntax, maybe I’ll look into that in a future post, but for now it’s not too big of a deal. There is a @types/yeoman-generator package if you want to work with Typescript, but I’ll again leave that for another possible post.

When we get to run this generator, you’ll find that both methods are run hence we get the following output

method 1 just ran
method 2 just ran

All the methods we add to the Generator class are public as so are run by yeoman. We can make them private by prefixing with the method name with an underscore (fairly standard Javascript style to suggest a field or method to be private or ignored).

The order that the methods appear is the order they’re executed in, hence switching these two methods around will result in method2 running first, followed by method1.

We’re not going to write any further code at this point, I’ll leave coding the generator for another post.

Testing our generator

At this point we don’t want to deploy our generator remotely, but want to simply test it locally. To do this we run the following command from the root folder of our generator

yarn link

This will create a symbolic link for npm/yarn and now we can run

yo --generators

which should list our new generator, named server.

Now we have our generator available to yeoman, we simply type

yo server

Obviously server is replaced by the name of your generator.

Destructing in JavaScript

In JavaScript/TypeScript, if you’re using the Prefer destructuring from arrays and objects (prefer-destructuring) eslint rule, you’ll want to use destructing syntax to get values from objects and arrays.

If we imagine we have an object like this

class Person
{
   firstName: string;
   lastName: string;
}

The to get the firstName from a Person instance, we tend to use

const firstName = person.firstName;

Instead this rule prefers that we use the following syntax

const { firstName } = person;

If for some reason (for example in React if you’re destructing state which may have been changed) you have need to get the value using destructing syntax but assigned to a new variable/value name, then we use

const { firstName: fname } = person;

Writing JavaScript templates using ejs

There are quite a few (as you’d probably expect) templating engines for use with JavaScript, such as Mustache and handlebars both of which use {{ }} syntax.

The syntax used is really not very important, what matters is the flexibility/functionality, but for nostalgia’s sake (as much as anything) this post will instead use ejs which uses syntax such as <%= %> which is very much like old ASP from my recollections (hence the nostalgia).

Note: I’m not endorsing ejs over any other engine, I just picked this to begin with, if/when I get time I will probably create posts covering using some other templating engines also.

Getting Started

We need to start by installing ejs, so run the following

yarn add ejs

We’re going to create a folder named templates (this is not a requirement of ejs) to store our .ejs files. So the template files will be saved with the .ejs extension.

Let’s just create a very standard Hello World app. so in the file templates/sample.ejs write the following

<%= message %>

Now let’s write the code to use ejs generate some output from our template and supply our data to the generator.

const ejs = require("ejs");

ejs.renderFile("./templates/sample.ejs", {
    message: "Hello World",
  },
  {},
  (err: any, result: string) => {
    if(err != null) {
      console.log(err);
    }
    // use the results of the template combined
    // with the supplied model
    console.log(result);
});

Here we show the basic usage pattern, which uses the asynchronous renderFile function. This takes the template file (we can ofcourse also supply the template as a string using the render function). The second argument is a “model” of the data being passed into the template engine and ofcourse the key’s related to the variable names used within the template file. The third argument supplies any options to the template engine, in this example I’m not applying any options. Finally, as this function is asynchronous, we will get an error or result or the processing of the template.

More…

That was simple enough but we’re not going to want to just output simple values like this, so let’s take a look at some more capabilities.

If we take a look at the Docs (https://ejs.co/#docs), Tags section we can see the tags for embedding our model data, let’s take a look at some of these in usage.

<%

The scriptlet tag is used for flow control and does not embed output into the template itself (as such). Instead it allow us to use conditionals such as if or loops such as for loops on our data model items.

type Names = <% for(let i = 0; i < names.length; i++) { %>
    | '<%= name[i].header %>'<% } %>;

In this example we’re code generating some types by taking an array of names from the model and creating a union of those header names.

Like-wise we might also use this tag for if statements within the <% %gt;

<%_

This will strip whitespace from before the tag. One of the problems we often find with templated code generation is how to control whitespace before and after the generated code. Hence this will remove the preceding whitespace.

<%=

This simply embeds an HTML escaped value into our template, in other words it simply replaces the tag with the value from message in this example below. Ofcourse message is a value stored in our data model.

<%= message %>

<%-

As per <%= but no escaping of values takes place.

<%#

Comment tags, just in case you wanted to add some comments/documentation to your template, this does not get output as part of the generated data.

<%%

Escapes the tag, i.e. outputs <%

%>

Standard end tag for use with the other start tags.

-%>

Trims the following newline.

_%>

Removes whitespace after the tag, again allows us to have some control of the whitespace following our generated output.

Splitter bars in React

I went through a load of splitter bar implementations for use within a React site, i.e. I simply wanted to partition my application into two halves (top and bottom), with a horizontal splitter bar.

The top half should be filled with a grid of data (I was using ag-grid), the bottom half filled with the query builder control. Also the button half should be capable of being collapse/hidden.

I was surprised at how hard it was to find anything that “just worked”. But this one did…

Run

yarn add m-react-splitters

Mine is “m-react-splitters”: “^1.2.0”. Now import the following

import Splitter from "m-react-splitters";
import "m-react-splitters/lib/splitters.css";

and here’s the basics for the code (obviously replacing the comment blocks with your components).

<Splitter
   position="horizontal"
   maximizedPrimaryPane={!showQueryBuilder}>
<div style={{width: "100%, height: "100%" }}>
<!-- grid -->
</div>
<div style={{width: "100%, height: "100%", overflow: "auto" }}>
<!-- query builder -->
</div>

Getting started with Electron

Let’s start by installing the latest version of electron using

  • npm i -D electron@latest

The Writing Your First Electron App goes through the process of setting up your first electron application, I’ll recreate some of these steps below…

  • Create a folder, mine’s test1
  • Run npm init, when asked for the entry point type main.js instead of index.js
  • Add main.js
  • Add index.html
  • Run npm install –save-dev electron
  • Add scripts section to package.json, i.e.
    "scripts": {
      "start": "electron ."
    }
    

Next up let’s add the following to the main.js file

const electron = require('electron');

const { app, BrowserWindow } = require('electron')

function createWindow () {
  // Create the browser window.
  let win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  })

  // and load the index.html of the app.
  win.loadFile('index.html')
}

app.on('ready', createWindow)

As you can see, we require electron, along with the app object and BrowserWindow. Next we create a BrowserWindow and load the index.html into it, so let’s supply something for it to load.

Change the index.html to the following

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
    <!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag -->
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
  </head>
  <body>
    <h1>Hello World!</h1>
    We are using node <script>document.write(process.versions.node)</script>,
    Chrome <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.
  </body>
</html>

This demonstrates HTML content as well as using JavaScript within it.

Now run npm start and view your first electron application.