Proof By Example

Programming blog by Mark Feeney

TypeScript 2.0, pixi.js, and Webpack

This article describes how to get a minimal pixi.js project started with TypeScript 2.0 and Webpack.

Code

The working skeleton project is available at https://github.com/overthink/pixitest

Why?

I want to do a graphics thing and the browser is the natural target these days. Pixi.js is an impressive looking 2d renderer that seems relatively small and is widely used. I want TypeScript in order to add basic sanity to JavaScript. I want TypeScript 2.0 for non-nullable types (finally). Webpack… well, I have not been much of a JS/node.js/npm person in the past, so I’m not really sure what I’m doing here. However, from my flailing around in this enormous ecosystem it seems that Webpack is a relatively sane way to get apps developed using node.js to run stand-alone in the browser. If nothing else it seems to be very popular.

Prerequisites

Have the latest node.js installed and in your path. v6.3.1 at the time of writing.

$ node --version
v6.3.1

How

Install TypeScript 2.0 (currently considered beta) globally so the tsc command ends up on the path.

npm install -g typescript@beta

Install the other global commands we need. typings will give us TypeScript support for pixi.js.

npm install -g typings webpack

Setup the project directories.

mkdir pixitest
cd pixitest
mkdir src dist externals

Put a minified version of pixi.js into the externals directory. We do this to avoid having Webpack bundle the entire 1MB pixi library into our app’s .js file (more below).

curl https://github.com/pixijs/pixi.js/releases/download/v3.0.10/pixi.min.js > \
  externals/pixi.min.js

Initialize the npm project. Accept defaults for all prompts except “entry point”. For that use ./dist/bundle.js.

npm init
...
entry point: (index.js) ./dist/bundle.js
...

Add pixi.js as a dependency for the project.

npm install --save pixi.js

Add Webpack and related dev dependencies.

npm install --save-dev webpack ts-loader source-map-loader brfs transform-loader
Module WTF
webpack Earlier we installed this globally. Now we install it locally because source-map-loader won’t work if we don’t. Strangely, other docs out there suggest we don’t need to have Webpack as a dev dependency, but that seems wrong to me. Webpack is required to “build” this project, seems it should be a listed dependency (see open question).
ts-loader Helps Webpack compile TypeScript files.
brfs pixi.js expects us to be using Browserify, but we’re not. Fortunately Browserify’s fs transforming module is available a la carte, and we use it here.
transform-loader Used by Webpack to get brfs to work. I’m out of my depth on this one.

Add TypeScript to this project. Earlier we installed it globally, so we’ll just link that install to our project. See open question.

npm link typescript

Use typings to get the type info for pixi.js.

typings install --global --save-dev dt~pixi.js

This creates a Typings config file called typings.json. In the future someone can just clone the repo and run typings install to get the type definitions mentioned in this file. It should look roughly like this now:

{
  "globalDevDependencies": {
    "pixi.js": "registry:dt/pixi.js#3.0.9+20160705114540"
  }
}

At this point, package.json should look roughly like this:

{
  "name": "pixitest",
  "version": "1.0.0",
  "description": "",
  "main": "./dist/bundle.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "pixi.js": "^3.0.11"
  },
  "devDependencies": {
    "brfs": "^1.4.3",
    "source-map-loader": "^0.1.5",
    "transform-loader": "^0.2.3",
    "ts-loader": "^0.8.2",
    "webpack": "^1.13.1"
  }
}

We now need to create a tsconfig.json file so all the various tools (tsc, IDEs) can use the same TypeScript configuration.

{
    "compilerOptions": {
        "strictNullChecks": true,
        "outDir": "./dist/",
        "sourceMap": true,
        "noImplicitAny": true,
        "module": "commonjs",
        "target": "es5"
    },
    "files": [
        "./typings/index.d.ts",
        "./src/helloworld.ts"
    ]
}

Including ./typings/index.d.ts is how we make available the type info for pixi.js.

Create a skeleton index.html page in the project root to host our app.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Hello World, Pixi!</title>
    </head>
    <body>
        <script src="./externals/pixi.min.js"></script>
        <script src="./dist/bundle.js"></script>
    </body>
</html>

Now we actually write some code! Call this file ./src/helloworld.ts

import * as PIXI from 'pixi.js';

var renderer = PIXI.autoDetectRenderer(800, 600, {backgroundColor: 0x1099bb});
document.body.appendChild(renderer.view);

var stage = new PIXI.Container();
var texture = PIXI.Texture.fromImage('bunny.png');
var bunny = new PIXI.Sprite(texture);
bunny.anchor.x = 0.5;
bunny.anchor.y = 0.5;
bunny.position.x = 400;
bunny.position.y = 300;
bunny.scale.x = 2;
bunny.scale.y = 2;
stage.addChild(bunny);
animate();

function animate() {
    requestAnimationFrame(animate);
    var bunny = stage.getChildAt(0);
    bunny.rotation += 0.01;
    renderer.render(stage);
}

Last, and most ugly/confusing, configure Webpack. This file is called webpack.config.js.

var path = require('path');

module.exports = {
    entry: "./src/helloworld.ts",
    output: {
        filename: "./dist/bundle.js",
    },

    // Enable sourcemaps for debugging webpack's output.
    devtool: "source-map",

    resolve: {
        // Add '.ts' as resolvable extensions.
        extensions: ["", ".ts", ".js"]
    },

    module: {
        preLoaders: [
            // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
            { test: /\.js$/, loader: "source-map-loader" }
        ],

        loaders: [
            // All files with a '.ts' extension will be handled by 'ts-loader'.
            { test: /\.ts$/, loader: "ts-loader" },
        ],

        // Pixi expects people to be using Browserify. We're not, but we still can use
        // its brfs module to deal with pixi code using "fs". 
        postLoaders: [
          { include: path.resolve(__dirname, "node_modules/pixi.js"), loader: "transform?brfs" }
        ]
    },

    externals: [
        // Don't bundle pixi.js, assume it'll be included in the HTML via a script
        // tag, and made available in the global variable PIXI.
        {"pixi.js": "PIXI"}
    ]

};

Try it

Run the webpack command in the root of the project.

$ webpack
ts-loader: Using typescript@2.0.0 and /home/mark/dev/pixitest/tsconfig.json
Hash: a1860189d2d608061ed0
Version: webpack 1.13.1
Time: 2076ms
               Asset     Size  Chunks             Chunk Names
    ./dist/bundle.js  2.24 kB       0  [emitted]  main
./dist/bundle.js.map  3.23 kB       0  [emitted]  main
    + 2 hidden modules

Start a web server in the project directory (I use python -m SimpleHTTPServer) and check out index.html in the browser. You should have a little pixi.js rabbit rotating on a blue background.

Open question

Why don’t we npm install --save-dev typescript@beta? We seem to have to do it for webpack. We install TypeScript globally and then just assume people have them on their systems? Seems wrong to me. I’d prefer a model where you clone a repo, type npm install and you get everything you need. Is this not how it works in JS-land? If you can explain this to me, I’d be grateful.

References