All About HTML5 Game Development, from Publishing to Monetization

Typescript and Webpack setup for an HTML5 game in ExcaliburJS engine

Typescript and Webpack setup for an HTML5 game in ExcaliburJS engine

This is an open-source project on game development with a focus on the HTML5 Excalibur game engine. Check out this feature implementation on this commit in our Github repository!

You can read here the list of features we are going to develop for our Excalibur game, such as Twine integration for characters interactive dialogs If you're interested in game development, follow us on Twitter and Instagram to be caught up on our latest news!

Table of Contents

In this tutorial

In this tutorial we set up an npm project with:

We are going to set up two different Webpack configurations, one for development and one for production.

Typescript configuration

Create a folder named sword-adventure in your workspace. From the terminal run


npm init
            

and answer the questions which are prompted in the terminal. Here’s the package.json file that we’ve obtained:


{
  "name": "sword-adventure",
  "version": "1.0.0",
  "description": "A template for developing games with Excalibur engine",
  "main": "index.ts",
  "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
   },
  "author": "Facondia Games"
}
        

Install the Typescript dependency by running


npm install typescript
            

You should see now the Typescript dependency in your package.json.

Now run tsc --init to generate the Typescript configuration file, tsconfig.json. In our file, we've added the exclude field to exclude the test files from the compilation. Here's our configuration file:


{
  "compileOnSave": false,
  "compilerOptions": {
    "skipLibCheck": true,
    "module": "esnext",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "baseUrl": "./src",
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "allowSyntheticDefaultImports": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "emitDecoratorMetadata": true,
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "strictPropertyInitialization": false,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2017",
    "lib": [
      "es2018",
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "esModuleInterop": true,
    "allowJs": true
  },
  "include": [
    "src"
  ],
  "exclude": [
    "**/*.test.ts",
    "**/*.spec.ts",
    "**/*.test.tsx"
  ]
}
    

This is our project folder structure until now:


sword-adventure
    /package.json
    /tsconfig.json

The very last step of Typescript configuration is creating the entry point: the index.ts file. Create in the project folder a directory named src. We are going to put here all the classes that we need to build our game. In this folder, create the index.ts file. It can be empty for now.

At this stage, our project structure is:


sword-adventure
    /src
        /index.ts
    /package.json
    /tsconfig.json
    /webpack.common.js
    

Webpack configuration

We are going to have two different configurations, one for development and another for production. The two modes will have some configuration in common. To get started, create a webpack.common.js file in the project folder:


sword-adventure
    /src
        /index.ts
    /package.json
    /tsconfig.json
    /webpack.common.js
    

Our webpack.common.js configuration will be:


module.exports = {
    experiments: {
        topLevelAwait: true,
    },
    entry: "./src/index.ts",
    target: "web",
    output: {
        filename: 'sword-adventure.js',
        sourceMapFilename: "[file].map",
        path: path.resolve(__dirname, "dist"),
        clean: true
    },
    devtool: "source-map",
    module: {
        rules: [
            {
                test: /\.(png|svg|jpg|jpeg|gif)$/i,
                type: "asset/resource",
            },
            {
                test: /\.mp3$/,
                loader: 'file-loader'
            },
            {
                test: /\.js$/,
                use: ["source-map-loader"],
                exclude: [path.resolve(__dirname, "node_modules/excalibur"), path.resolve(__dirname, "node_modules/@capacitor-community")],
                enforce: "pre",
            },
            {
                test: /\.tsx?$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: "ts-loader",
                        options: {
                            compilerOptions: {
                                noEmit: false,
                            }
                        }
                    }
                ]
            },
            {
                test: /\.css$/i,
                use: ['style-loader', 'css-loader'],
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/i,
                type: 'asset/resource',
            },
        ],
    },
    resolve: {
        extensions: [".tsx", ".ts", ".js"],
        modules: ['node_modules', 'src/**/*'],
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebPackPlugin({
            title: "Sword Adventure",
            template: "src/index.html"
        }),
    ],
};
       

Let's see step by step this configuration.

This option


entry: "./src/index.ts"
            

let Webpack knows that the entry point for the compilation is the index.ts file that we've created in the previous step.

The output config


output: {
    filename: 'sword-adventure.js',
    sourceMapFilename: "[file].map",
    path: path.resolve(__dirname, "dist"),
    clean: true
}
        

sets the configurations for the output file that Webpack will create. Our file will be called sword-adventure.js, it will be placed under the dist directory of the project folder, and it will have a sourceMap file. Sourcemaps are very useful when debugging code in the console. Thanks to Sourcemaps files, it's possible to work with Typescript files in the browser console instead of compiled js output (remember that browsers work with javascript files, not with Typescript ones!) The clean option is related to the CleanWebpackPlugin imported at the beginning of Webpack config file:


const {CleanWebpackPlugin} = require("clean-webpack-plugin");
        

You'll need to add it to the projects devDependencies by running


npm install --save-dev clean-webpack-plugin
            

This plugin remove the dist folder content every time a new build is run. If it wasn't done, the dist directory would soon be crowded with build files.

Going back to webpack.commmon.js, the rules under the module field are required so that Webpack knows how to manage assets: images, sounds, css, and font files. It also includes a rule to load *.tsx (React) files with ts-loader.

The last interesting configuration option is under plugins: it's the HTMLWebpackPlugin


new HtmlWebPackPlugin({
    title: "Sword Adventure",
    template: "src/index.html"
}),
            

The configuration here says to Webpack that we are going to use our own index.html file. If it wasn't for this plugin, Webpack would build a index.html file on its own. When building the code, thanks to HtmlWebPackPlugin, Webpack will add to our index.html file the script to load the javascript bundle containing our game. We don't have yet an index.html file. Let's create it in our src folder:


sword-adventure
    /src
        /index.ts
        /index.html
    /package.json
    /tsconfig.json
    /webpack.common.js
    

The content of index.html file will be:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <title>Sword Adventure</title>

    <meta content="light dark" name="color-scheme"/>
    <meta
            content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
            name="viewport"
    />
    <meta content="telephone=no" name="format-detection"/>
    <meta content="no" name="msapplication-tap-highlight"/>

    <!-- add to homescreen for ios -->
    <meta content="yes" name="apple-mobile-web-app-capable"/>
    <meta content="Sword Adventure" name="apple-mobile-web-app-title"/>
    <meta content="black" name="apple-mobile-web-app-status-bar-style"/>
</head>

<body>
<div class="absolute-bottom full-width flex-row flex--justify-center">
    <div class='white-text' id="loader-progress"></div>
</div>
<canvas id="game" style="display: block; margin: auto auto"></canvas>
</body>

</html>

            

Let's focus on the content of the body:


<body>
<div class="absolute-bottom full-width flex-row flex--justify-center">
<div class='white-text' id="loader-progress"></div>
</div>
<canvas id="game" style="display: block; margin: auto auto"></canvas>
</body>
        

In the next tutorial, we are going to use the canvas element to render the Excalibur game. We will also use the div element with id loader-progress to start customizing the default options of the built-in Excalibur loader. Before proceeding with Excalibur setup, we just need to finish with Webpack configuration: the development and production ones.

Development configuration for Webpack

We'll start by creating a webpack.development.js file:


const {merge} = require("webpack-merge");
const common = require("./webpack.common");

module.exports = merge(common, {
    mode: "development",
    devtool: "inline-source-map",
    devServer: {
        static: {
            directory: "./dist",
        },
        compress: true,
        port: 9000,
    },
});
        

To run this configuration we first need to install the webpack-merge package by running in the terminal


npm install --save-dev webpack-merge
            

You should see the new dependency under devDependencies in package.json file. This package, as its doc says, provides a merge function that concatenates arrays and merges objects creating a new object. In our case, it will merge the common Webpack configuration with the specific development and production ones.

Your project structure should look now as


sword-adventure
    /src
        /index.ts
        /index.html
    /package.json
    /tsconfig.json
    /webpack.common.js
    /webpack.development.js
    

With this configuration we can add a couple of scripts to our package.json:


"scripts": {
    "serve": "webpack serve --config webpack.development.js",
    "build:dev": "webpack --config webpack.development.js",
},
    

By running npm run serve we'll start a live development server, available at localhost:9000. By running npm run build:dev we'll build the compiled code bundle and we'll find the output under the dist directory.

Production configuration for Webpack

We'll start by creating a webpack.production.js file:


const {merge} = require("webpack-merge");
const TerserPlugin = require("terser-webpack-plugin");
const common = require("./webpack.common");

module.exports = merge(common, {
    mode: "production",
    optimization: {
        minimize: true,
        minimizer: [new TerserPlugin()],
    }
});
        

To run this configuration we first need to install the terser-webpack-plugin package by running in the terminal


npm install --save-dev terser-webpack-plugin
            

You should see the new dependency under devDependencies in package.json file. As its doc says,

This plugin uses terser to minify/minimize your JavaScript.

This means that the Webpack output under the dist directory will be minified and will therefore occupy less space.

Your project structure should look now as


sword-adventure
    /src
        /index.ts
        /index.html
    /package.json
    /tsconfig.json
    /webpack.common.js
    /webpack.development.js
    /webpack.production.js
    

With this configuration we can add the last scripts to our package.json:


"scripts": {
    "serve": "webpack serve --config webpack.development.js",
    "build:dev": "webpack --config webpack.development.js",
    "build:prod": "webpack --config webpack.production.js",
},
    

By running


npm run build:prod
                

we'll build the compiled code bundle in production mode. We'll find the output under the dist directory.

Everything is set up!

We are finally ready to add Excalibur to our project! Let's go on by setting up Excalibur!

Related Articles