How to make a TypeScript webapp in IntelliJ without frameworks

This post outlines how to create a webapp with multiple files and third-party dependencies in TypeScript without relying on Node or Require, using the IntelliJ IDE. For such a seemingly simple task, there is a dearth of useful tutorial material online; most TypeScript guides assume a level of familiarity with JavaScript that I don’t think is entirely reasonable, and most information about third-party libraries assume the use of Node at runtime. Therefore, in this post I intend to lay out a path to a simple functioning product for a relatively lay audience. As a disclaimer, this is likely not the best way to accomplish this; I’m presenting here the only way I’ve found to do it after two months of troubleshooting. If you know of ways this guide can be improved (or, conversely, if any of it doesn’t work for you), please comment and let me know!

First, let’s set the scene. Suppose I want to make a webpage that runs some code. It’s not a lot of code, so I don’t need to set up a back-end or anything; I can just host a bit of JavaScript on GitHub Pages or some similarly straightforward service and let the user’s browser handle any computation. This means that I don’t need to use Node, or any other runtime frameworks.

Suppose, moreover, that I would like to be able to organize my code into multiple files and external libraries without cluttering up my namespace with global values of confusing origin. This calls for the use of modules, which are usually facilitated by a framework like Node or Require, but can also be used with vanilla JavaScript if it’s done right. This relies on ECMAScript 6 (ES6), which is supported by default on many but not all browsers, meaning anyone using a browser that doesn’t support ES6 can’t use my webpage. I feel comfortable with this sacrifice because every browser I’ve heard of has supported ES6 since at least 2017, and avoiding frameworks will reduce the structural complexity of my project substantially.

Finally, suppose that I want to do it in a programming language that isn’t terrible. That means I’m using TypeScript. If you’re don’t know what TypeScript is, this may seem confusing, since I just mentioned JavaScript. Technically, the webpage will be running JavaScript; that’s the only language that browsers generally support. But the thing about JavaScript is that it’s deeply, deeply terrible. Its scoping is a pain, its type coersions are out of control, and it almost never throws runtime errors. It makes Java look bad by implied association. That’s why there are multiple programming languages that are compile into JavaScript to be run on a browser. One of these, and the one I’ve decided to describe here, is TypeScript, made by Microsoft. TypeScript is far from perfect, and it suffers from still ultimately being JavaScript at runtime, but it’s a far cry more tolerable than coding directly in JS.

The only thing left is an Integrated Development Environment (IDE). This part is optional; you can write TypeScript in a text editor and compile it in the command line, but I don’t really understand how and also hate working in the command line. So for the purposes of this tutorial, I’m going to be using JetBrains’ IntelliJ IDEA (/ɪn.ˌtɛ.lɪ.dʒeɪ ɑɪ.ˈdi.ə/). It’s free for students and teachers and has a lot of very handy features, like HTML syntax highlighting in string literals. It also has TypeScript functionality built in. You should still be able to use this guide if you would rather use something else, since the project structure and code will be the same; you’ll just have to find your tool1s equivalents for any GUI elements I reference.

With that road-map in place, let’s get started! The complete example webapp outlined below can be found in its entirety on my GitHub, and viewed at jkunimune15.github.io/misali-netoloke/.

Step 0. Install and start IntelliJ IDEA.

I’m not going to go over this part in detail because I found IntelliJ’s installation procedures pretty straightforward. You can download it from jetbrains.com/idea. Just make sure to get the free educational license if you apply (a university email address or a GitHub Student Developer Pack account are the easiest ways to do so). Start it up and you should see a window that looks something like this:

Step 1. Create a new project.

Go ahead and click “Create New Project”. This will spawn a prompt asking you what kind of project you want to make. You’ll notice “TypeScript” isn’t an option. Go ahead and select “JavaScript” as the project type and click “Next”. Next you’ll be prompted for a name. We’ll title our example project “misali-netoloke”. Click “Next”. Now you should see your empty project.

Step 2. Create the HTML file.

For this example, we will create a single HTML webpage with two TypeScript files: one to handle the GUI elements, and one to handle some mathematical routines. This is purely to illustrate how code can be split up among multiple files.

Start by creating the HTML file. Navigate to “File” > “New” > “HTML File”. Sometimes if the wrong thing is selected, you won’t have an “HTML File” option in the “New” submenu; if that’s the case, you should be able to get it by clicking on the project name. At the prompt, name it index.html. This tells whatever ends up serving this website to serve this HTML file by default. IntelliJ will fill in the structure of the file automatically, so you just need to add some content to the <body>:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
salam! me netoloke, e me wana xowa yi kuli xey pa tote! pliza puxa yi botone:
<button type="button" class="btn" id="botone">botone</button>
<div id="grafe"></div>
<script src="./src/xefe.js" type="module"></script>
</body>
</html>

I won’t go over HTML syntax in detail because the internet has a surplus of fine HTML tutorials, and I would like to focus on how it interacts with TypeScript. Suffice it to say that this code will produce some placeholder text, a button that says “botone” on it, and an empty <div> element for Plotly to use later, before referencing the code that we are about to create.

Note that the <script> element references a .js file, not a .ts file. This is because browsers only understand JavaScript, and thus will only know about the compiled JavaScript files, not the TypeScript files we will actually be writing. Note also the type="module". This is what allows us to import other source code files into xefe.js without using frameworks.

Step 3. Start coding.

Next, we need the TypeScript files. Instead of xefe.ts, let’s start with the other one—we’ll call it matike.ts—because it’s simpler. In this file, we’ll implement a simple algorithm to find the first n Fibonacci numbers. That algorithm can then be called by xefe.ts. Go to “File” > “New” > “TypeScript File” and type in src/matike.ts (the src/ puts it in a separate source code folder to avoid cluttering up the main directory).

This time, the file is blank by default. Let’s drop in our routine as a simple function definition.

export function jena_fibonaci(n: number): number[] {
    const serye = [];
    for (let i = 0; i < n; i ++) {
        if (i == 0)
            serye.push(1);
        else if (i == 1)
            serye.push(1);
        else
            serye.push(serye[i-2] + serye[i-1]);
    }
    return serye;
}

As with HTML, I won’t discuss TypeScript syntax too deeply. As far as low-level syntax and structure go, the TypeScript tutorials that exist online are actually quite good. There is one higher-level thing I want to note here, though—the keyword export before function. This is how we tell ES6 that the function jena_fibonaci can be used by other files that import matike.js as a module. Variables and classes can also be exported this way.

Step 4. Import dependencies.

Now, we move onto the main source file, xefe.ts. Go ahead and create another TypeScript file called xefe.ts (it should automatically place this one in the src folder). This file is going to contain the logic that connects the HTML to the code we’ve already written. Specifically, it will attach an event to the button such that when it is pressed, the first few values of the Fibonacci sequence are graphed. We will use matike.js for the Fibonacci numbers, Plotly for the graph, and JQuery to make some of the DOM syntax more concise. That means that all three of these will need to be imported. It should look like this.

import "./lib/jquery.min.js";
// @ts-ignore
const $ = window.$;
import "./lib/plotly.min.js";
// @ts-ignore
const Plotly = window.Plotly;

import {jena_fibonaci} from "./matike.js";

$('#botone').on('click', function () {
    Plotly.newPlot(
        $('#grafe')[0],
        [{
            y: jena_fibonaci(10)
        }]
    );
});

The lower half is pretty basic JQuery and Plotly (when the element called “botone” is clicked, plot the results of jena_fibonaci in the element called “grafe”), so let’s focus on the imports. The local import on line 8 is the cleanest looking, and is the recommended ES6 import syntax. It tells the browser to load the jena_fibonaci function from matike.js. If we had several things to import, we could do them all on the same line, separating them with commas in the curly braces, as long as they were all exported in their files of origin. Note that the filename must have the .js extension.

The JQuery and Plotly imports are much more unfortunate. Both of them, as well as most JavaScript libraries, are written to be imported using Node or React, not ES6 alone, so attempting to use proper ES6 syntax to import names directly from them yields a Module '"./libs/jquery.min.js"' has no exported member '$' error. The only configuration I have found to avoid errors both in the TypeScript compiler and the JavaScript runtime environment is that shown above: importing the entire file and then reading the desired value off window. Since the desired name is brought into the namespace implicitly, against TypeScript’s will (the @ts-ignore comments are the only reason TS allows it), this unfortunately means that you can’t get any useful type checking on imported values, even if the library comes with a Definitely Typed file. That’s life, I guess.

Step 5. Configure TypeScript.

There’s one more file we need to write, and that’s tsconfig.json. This file contains all of the configuration details for the TypeScript compiler. You’ll find “tsconfig.json File” in the list of new file options in IntelliJ. The default is mostly sufficient, but for a couple of key modifications:

{
  "compilerOptions": {
    "module": "es6",
    "target": "es5",
    "sourceMap": true,
    "lib": ["es2016", "DOM"]
  },
  "exclude": [
    "node_modules"
  ]
}

The most important parts here are "module", which must be set to "es6" if you don’t want to use Node, and "lib", which should contain both "es2016" and "DOM" to give us access to modern syntax and HTML DOM functionality. The "exclude" argument isn’t strictly necessary since we won’t be using the node_modules folder, but it’s there by default and not hurting anyone, so I just left it.

Step 6. Obtain dependencies.

Naturally, the successful execution of your code depends on the presence of dependencies. In this case there are two: JQuery and Plotly. Both are registered with NPM, so in principle IntelliJ can manage those dependencies for you, but only in a way that assumes you have Node at runtime. Since we’re avoiding that, we’ll have to get our libraries the old fashioned way. Download the compressed JQuery and Plotly JavaScript files at jquery.com and plotly.com, respectively. Rename them to jquery.min.js and plotly.min.js and put them in a subdirectory of src called lib. That wasn’t so hard.

Well, there is one more step. I included Plotly in my example knowing that there is an additional bug that is specific to it, but that is indicative of the web development community’s greater apparent disdain for native ES6 modules. Plotly throws a TypeError on import unless it is being imported by Node or React, though this can be corrected by changing a single line of code within plotly.js. I don’t know where that line is in the compressed JavaScript file, but it can be located in the uncompressed version by searching for }();, going to the second hit (immediately after an if statement that says something about define.amd and module.exports), and replacing it with }.apply(self);. If you don’t want to do that, you can download my modified copy from my GitHub (I named it plotly.min.js, even though it’s not actually minified, so that it fits with the import statement I used above in xefe.ts).

Step 6. Compile.

Now that everything is in place, you can go ahead and compile the TypeScript. Click “TypeScript” in the lower left, the green hammer that says “TS” above that, and then “Compile All”. You won’t notice anything immediately, but IntelliJ will create JavaScript files in the src directory and file them under their TypeScript equivalents. Your code is now ready to run in a browser!

Step 7. Test in browser.

If you’ve worked with JavaScript in the past, you may recall this part being trivial: just open the HTML file in a browser. Since we’re using ES6 modules, it’s a little more complicated than that. Attempting to simply load index.html from disk will fail to load the JavaScript due to the CORS policy. This is a security protocol that forbids loading local files from an HTML file, and applies to scripts but not modules for some reason. I don’t know; I don’t really understand it. The way around it is to host the HTML page as a website on localhost. If you have Python 3 installed, this isn’t as hard as it may sound. If you don’t, then it’s probably about as hard as it sounds, but the easiest way is still to install Python. You can get the installer from python.org.

Once you have Python 3, you can host your site locally with the built-in HTTP server module. Open a command prompt, navigate to the folder with index.html in it (if you’re in Windows, you can do these simultaneously by clicking “File” > “Open Command Prompt” or “Open Windows PowerShell” from File Explorer while viewing the folder), and call

python -m http.server

This should start a simple HTTP server on port 8000. Visit by going to the URL localhost:8000 in your browser of choice.

Step 8. Profit.

Clicking on the button that appears should reveal an exponential-looking graph that you can interact with by mousing and clicking!

From here, you’re free to expand and develop your site however you want. Hopefully the basis I’ve laid out here helps you do that. Just remember to compile your code every time you make a change (alternatively, IntelliJ has an option under “File” > “Settings” > “Languages & Frameworks” > “TypeScript”called “Recompile on changes” that makes it do this automatically). I’ve found that I sometimes also need to clear my cache when making a change, though not always for some reason. You can do this by deleting cookies in the privacy settings of most browsers, or avoid the issue entirely by always testing in a private/incognito window.

When you’re ready to put your webapp on the web, simply host your project directory on your platform of choice. I recommend GitHub Pages because it’s free and easy if you already use GitHub (it’s how I’m hosting my example).

Again, please let me know in the comments if you know of any way to improve this process, or if it doesn’t work for you for whatever reason. I think that this is much harder than it should be (like I said, I spent two months figuring all this out) and sincerely hope the web development community finds ways to improve it in the future. I hope I can save ye from some of the same pain.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.