How to run TypeScript in the browser

Short answer

You can’t, that’s not a thing (at least so far).

Longer answer:

By building an overly complicated front-end tool chain! Seriously, it’s crazy how much this is not out-of-box. Preface, as always, is that I don’t really know what I’m doing, so I certainly wouldn’t recommend this for any real projects – I’m just using it for experimentation.

Tools needed

  • NodeJS
    • JavaScript run-time environment
    • Needed to actually run JavaScript and all the tooling
  • TypeScript
    • Typed language that compiles to JavaScript, comes with a compiler
    • This is what we want to write!
  • Babel
    • JavaScript to JavaScript compiler with ability to polyfill new features into older versions
    • Needed to convert the version of JavaScript we’re writing to a version browsers can execute
  • Webpack
    • Asset bundler – decouples your development project structure from the deliverable
    • Not strictly needed, but extremely useful for any “real” project

Steps

It’ll look something like this when all done:

TypeScriptProcess(1)

  1. Write code in TypeScript
  2. Use TypeScript compiler to compile TypeScript into a recent version of JavaScript, without providing backwards compatibility or browser polyfilling
  3. Use Babel compiler to turn recent version of JavaScript, which browsers can’t natively execute, into a version browsers can execute
  4. Use Webpack to grab your assortment of JavaScript files, organized however you want for development, and create a more easily deliverable “bundle” of everything

From the beginning, that means:

    1. Install NodeJS (use the latest version unless you have reason to do otherwise)
    2. Create your project
$ yarn init
yarn init v0.27.5
question name (typescript-front-end-seed): 
question version (1.0.0): 
question description: Seed project for TypeScript in the browser
question entry point (index.js): 
question repository url (https://github.com/tobymurray/typescript-front-end-seed.git): 
question author (Toby Murray <murray.toby+github@gmail.com>): 
question license (MIT): 
success Saved package.json
Done in 34.38s.
    1. Add all the dependencies we’ll need – TypeScript, Babel (note Babel by itself doesn’t really do anything, you need to include a plugin), and Webpack
$ yarn add -D typescript babel-cli babel-preset-env webpack
    1. Create whatever project structure you want. I’ll do something like src/ for TypeScript code, and public/ for static files (e.g. HTML).
$ mkdir src public
$ touch public/index.html src/index.ts
    1. Create the configuration files you’ll need for all the tools – tsconfig.json for TypeScript, .babelrc for Babel and webpack.config.js for Webpack
$ touch tsconfig.json .babelrc webpack.config.js

Tool configuration

Now comes either the interesting part or the awful part, depending on your perspective – configuring all the tools to do what we want! To keep things clear, we’ll place the output of the TypeScript compiler into a tsc folder, then we’ll feed that as input into Babel. The output of Babel will go into a babel folder. We’ll then use Webpack to consume the contents of the Babel folder and put it in a dist folder (this is what we’d actually serve up to a client browser).

TypeScript

Keeping this as simple as possible (there are plenty of options to play with), the two big decisions are what to use as the target and module version to use. Fortunately, we don’t really have to care too much, it just has to be consumable by Babel. To get all the features possible (mmm, delicious features), we can target e.g. ES2017, and use commonjs.

{
  "compilerOptions": {
    "target": "es2017",      /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
    "module": "commonjs",    /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    "rootDir": "./src",      /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    "outDir": "./build-tsc", /* Redirect output structure to the directory. */
  }
}

Babel

Again, doing as little as possible, we’ll tell Babel to do whatever it needs to do to target apparently 95% of user’s browsers. For some reason, Babel does not support setting the output directory in the configuration file (see options here), it has to be passed as an argument to the invocation of Babel.

{
  "presets": [
    ["env", {
      "targets": {
        "browsers": ["last 2 versions", "safari >= 7"]
      }
    }]
  ]
}

Webpack

Likewise, for the start Webpack doesn’t have to be that complicated. We’ll include source maps here, don’t feel obliged to do so though.

const path = require('path');

module.exports = {
  devtool: "source-map",
  entry: './build-babel/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};

package.json

To avoid having to remember anything, a few scripts in package.json can be useful. Breaking them out so it’s clear what step is doing what, it could look like this:

"scripts": {
  "clean": "yarn run clean-build-steps && rm -rf dist",
  "tsc": "./node_modules/.bin/tsc",
  "babel": "./node_modules/.bin/babel build-tsc --out-dir build-babel --source-maps",
  "webpack": "webpack && cp public/* dist",
  "clean-build-steps": "rm -rf build-tsc build-babel",
  "build": "yarn run clean && yarn run tsc && yarn run babel && yarn run webpack && yarn run clean-build-steps"
}

Build

Running yarn build (after the initial install) will:

  1. Clean anything from previous executions of the script
    1. This includes any leftover build artifacts, as well as the dist directory
  2. Use the TypeScript compiler to take everything from the src directory, transpile it to ES2017 JavaScript, and output it into the build-tsc directory
  3. Use Babel to convert everything in the build-tsc directory from ES2017 to ES2015 and output it into build-babel
  4. Use Webpack:
    1. Look in the build-babel folder
    2. Find index.js
    3. Parse index.js as an entrypoint, and resolve dependencies
    4. Add everything needed into one big bundle.js
  5. Create the “deployable” directory
    1. Copy the static HTML into the dist directory
    2. Copy the bundle.js into the dist directory

Serve

With something like http-server and serving the dist directory, we can see the product of our work!

$ http-server dist
Starting up http-server, serving dist
Available on:
  http://127.0.0.1:8080
  http://10.0.2.15:8080
  http://172.17.0.1:8080
Hit CTRL-C to stop the server

See the GitHub repository here and the deployed example here.

Advertisements

2 thoughts on “How to run TypeScript in the browser

    1. tobymurray Post author

      You’re absolutely right. I don’t remember why I used Babel at the time. Possible reasons I can think of (without validating them):
      – Babel supported more polyfills than TSC, so allowed for usage of newer features
      – I may have been mucking around with a mix of TypeScript code and vanilla JavaScript, and at the time (not sure if anything has changed or this ever actually happened) I don’t think the TypeScript compiler transpiled non-TypeScript files.

      I’m not sure if either of these cases ever existed, or if they did if they’re still relevant at all. Definitely for the simplest case it looks like Babel could be excluded to make this quite a bit easier!

      Reply

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