Tutorial – Getting started with TypeScript and Cocos2d-x JS -Part 6 – Webpack-Dev-Server – MVCS Framework – IOC – Dependency Injection

Tutorial – Getting started with TypeScript and Cocos2d-x JS -Part 6 – Webpack-Dev-Server – MVCS Framework – IOC – Dependency Injection

Welcome to Part 6  of this Cocos2d-x Tutorial Series.

Revisions

14-06-2017 – MVC code now works on JSB, Restructured so that we do not extend core cocos classes in typescript. Moved Node and Scene LifeCycle Extensions into dalste.mvc javascript IIFE package with corresponding typescript definitions.

Updated Factories to return Containers rather than Cocos2d-x derived objects directly.

 

You will find the completed source for this tutorial in the project folder named CocosTsGameMVC on the GitHub repository.

We will explore the changes made to the project configuration to install webpack-dev-server, which will help us increase productivity during development by automating the transpilation of our typescript as changes are made.

We will  also explore the code we have written to create our MVCS architecture using the dijon IOC library.

It is assumed  that you have pulled the latest source from the git repository, you have Cocos2d-x installed, and you are following along with the source contained within CocosTsGameMVC project.

Lets get started:

Merging the Project With a New Cocos2d-x Project & Installing Dependencies

The version of Cocos2d-x used in this tutorial is 3.14 , but the current version (3.15) should be fine.

Create a new Cocos2d-x project with  the following command:

cocos new YourProjectName -p com.yourcompany.YourProjectName -l js

 

Grab and merge the files from the CocosTsGameMVC  project in the tutorials Git Repository

Install the node dependencies by executing below command on you project root:

npm install

Navigate to your project root and publish the application using the below command:  (we need the resulting ‘publish/html5/game.min.js’ ).

cocos deploy -p web -m release

Your folder structure should now look something like this:

The outline of our application code is given below our entry point is Application2.ts (focus for now on studying the highlighted modules/classes):

│   Application1.ts
│   Application2.ts
│   Bootstrap.ts
│   IApplication.ts
│
├───component
│       GameComponents.ts
│
├───controller
│       GameController.ts
│
├───entity
├───events
│       ApplicationEvents.ts
│
├───factory
│   │   IAssetContainer.ts
│   │   ICreationOptions.ts
│   │   IFactory.ts
│   │
│   ├───entity
│   │       CharacterEntityFactory.ts
│   │       GameObjectEntityFactory.ts
│   │       ICreationOptions.ts
│   │
│   └───view
│           CharacterAssetFactory.ts
│           GameObjectAssetFactory.ts
│           MockAsset.ts
│
├───model
│       GameModel.ts
│
├───service
├───system
│       GameplaySystem.ts
│       GameViewSystem.ts
│       NPCAISystem.ts
│       PhysicsSystem.ts
│       PlayerInputSystem.ts
│
├───types
│       AssetTypes.ts
│       EntityTypes.ts
│       GameComponentTypes.ts
│       InputTypes.ts
│       ScreenTypes.ts
│
└───view
    │   GameView.ts
    │   GameViewController.ts
    │   SplashScreenView.ts
    │   SplashScreenViewController.ts
    │
    └───scenes
        │   HelloWorldScene.ts
        │
        └───helloworld
                HelloWorldMainLayer.ts

Webpack Dev Server

Earlier in this series we introduced webpack. We currently use webpack to  build and bundle our game code via gulp.

webpack-dev-server allows us to do the same, but introduces a “watch” mode  so that whenever we make a change to our code it will be automatically transpiled and reloaded within our browser. webpack-dev-server is pretty nifty.

If you open up package.json, you will notice a few additional dependencies (highlighted below):

{
  "name": "CocosTsGame",
  "version": "1.0.0",
  "description": "base project for typescript based cocos2dx games ( external module )",
  "main": "./tsdist/main.js",
  "scripts": {
    "test": "mocha -w"
  },
  "keywords": [
    "typescript",
    "cocos2dxjs"
  ],
  "author": "Steven Daley - www.dalste.co.uk",
  "license": "ISC",
  "devDependencies": {
    "@types/javascript-state-machine": "^2.4.0",
    "chai": "^3.5.0",
    "gulp": "^3.9.1",
    "gulp-mocha": "^4.3.1",
    "gulp-tslint": "^8.0.0",
    "gulp-typescript": "^3.1.6",
    "gulp-webpack": "^1.5.0",
    "karma": "^1.7.0",
    "karma-chai": "^0.1.0",
    "karma-chrome-launcher": "^2.1.1",
    "karma-cli": "^1.0.1",
    "karma-firefox-launcher": "^1.0.1",
    "karma-ie-launcher": "^1.0.0",
    "karma-mocha": "^1.3.0",
    "karma-mocha-reporter": "^2.2.3",
    "karma-phantomjs-launcher": "^1.0.4",
    "karma-safari-launcher": "^1.0.0",
    "karma-webpack": "^2.0.3",
    "source-map-loader": "^0.2.1",
    "ts-loader": "^2.1.0",
    "tslint": "^5.2.0",
    "typescript": "^2.3.2",
    "webpack": "^2.6.0",
    "webpack-dev-server": "^2.4.5",
    "webpack-stream": "^3.2.0",
    "write-file-webpack-plugin": "^4.0.2"
  },
  "dependencies": {
    "javascript-state-machine": "^2.4.0"
  }
}

You will notice that we have added dependencies for webpack-dev-server in addition to the @type dependencies for the javascript-state-machine.

Open up gulpfile.js and note the changes highlighted below:

'use strict'
var gulp = require("gulp");
var ts = require("gulp-typescript");
var tsProject = ts.createProject("tsconfig.json");

var tsTestProject = ts.createProject("tsconfig.test.json");
const mocha = require('gulp-mocha');

var gulpTslint = require("gulp-tslint");
var tslint = require("tslint");

gulp.task('lint', function () {
    var program = tslint.Linter.createProgram("./tsconfig.json");

    gulp.src('tssrc/**/*.ts', { base: '.' })
        .pipe(gulpTslint({ program })).pipe(gulpTslint.report());
});


gulp.task('test', function () {

    return gulp.src(['./tslib/**/*.ts', './tssrc/**/*.ts', './tstest/**/*.spec.ts'],
        {
            base: '.'
        })
        /*transpile*/
        .pipe(tsTestProject())
        /*flush to disk*/
        .pipe(gulp.dest('tsdist'))
        /*execute tests*/
        .pipe(mocha())
        .on("error", function (err) {
            console.log(err)
        });
});


var tsconfig = require(process.cwd() + '/tsconfig.json');

gulp.task('typescript', function () {
    var tsResult = tsProject.src() // instead of gulp.src(...) 
        .pipe(tsTestProject());

    return tsResult.js.pipe(gulp.dest(tsconfig.compilerOptions.outDir));
});

var gutil = require('gulp-util');
var webpack = require('webpack');
var WebpackDevServer = require("webpack-dev-server");
var webpackConfig = require(process.cwd() + '/webpack.config.js');
gulp.task('webpack', ['typescript'], function (callback) {
    // run webpack
    webpack(webpackConfig, function (err, stats) {
        if (err) throw new gutil.PluginError('webpack', err);
        gutil.log('[webpack]', stats.toString({
            // output options
        }));
        callback();
    });
});

gulp.task("webpack-dev-server", function (callback) {
    // modify some webpack config options
    var myConfig = Object.create(webpackConfig);
    myConfig.devtool = "eval";
    //	myConfig.debug = true;

    // Start a webpack-dev-server
    new WebpackDevServer(webpack(myConfig), {
        publicPath: "/" + myConfig.output.publicPath,
        stats: {
            colors: true
        }
    }).listen(8080, "localhost", function (err) {
        if (err) throw new gutil.PluginError("webpack-dev-server", err);
        gutil.log("[webpack-dev-server]", "http://localhost:8080/webpack-dev-server/index.html");
    });
});


//ES6
//import { Server } from 'karma';

// ES5
var karma = require('karma').Server;


gulp.task('karma:watch', function (done) {
    karma.start({
        configFile: __dirname + '/karma.config.js',
        singleRun: false,
        autoWatch: true,
    }, function () {
        done();
    });
});
gulp.task('karma', function (done) {
    karma.start({
        configFile: __dirname + '/karma.config.js',
        singleRun: true
    }, function () {
        done();
    });
});

We now have a gulp command named webpack-dev-server.

Navigate to your project root and execute below command:

gulp webpack-dev-server

(hint: press ctrl+c  to exit the command at any time)

Open up your browser and navigate to:

http://localhost:8080/webpack-dev-server/

Editing any one of your files while this command is running will result in the project auto-magically being recompiled and reloaded within the browser. Cool!

 

Model View Controller (MVC)

The utilities:

Navigate to tslib/dalste/mvc you will find the below utility classes & Interfaces:

IModel.ts
IView.ts
IViewController.ts
Model.ts
View.ts
ViewController.ts
DalsteMVC.d.ts
    INodeLifeCycleExtensions
    NodeExtended
    SceneExtended

Model

The Model  class  provides utility functions for  setting and getting nested data within a single JavaScript object. It is recommend that you utilise this class for application wide models and View Models.

An application wide model may for examples contain information about a level or the players progress. see  See tssrc/model/GameModel.ts for an example.

A View Model may for example hold temporary data for a form during editing. Upon submittal of said form, the View model data could be copied into a wider application Model.

A Models’s data can be set and get via period delimited paths (such as “my.path.to.the.value”) using the set(path:string, value:any ), and get(path:string) functions.

The set function will handle creating empty objects that satisfy a given path if one does not already exist. (see the source code comments for more info)

The bind function allows clients to subscribe to changes made within the Model via the set(path:string, value:any ) function . see bind(path:string);

While it is quite possible to the Model class directly, it is recommend that you override it with your own classes and wrap its get and set functions with you own type-safe accessors. This will allow you to mask the underlying data schema to the rest of the application. Your type safe accessors will also make your code more maintainable.  See tssrc/model/GameModel.ts for an example.

View

The view class is designed to wrap a cc.Node implementing the INodeLifeCycleExtensions interface as its main asset. It provides convenient wrappers around said asset’s life cycle events, and allows the  simplification of communication  with the View’s associated View Controller.

The idea is that a View should not really communicate directly with the wider application. A View will simply fire events that are handled by it’s associated View Controller. A View Controller will tie a View in to the wider application by providing logic and communicating with application wide Controllers and Services implementing reusable application logic. See the tsSrc/contoller/GameController for an example of an application wide Controller.

Provision has been made for you to provide a View with a View Model. It can either be injected via dijon using a _viewModel outlet (recommended). or you can pass it via the  setViewModel(val:IModel) function.

cc.Node & cc.Scene life-cycle events

There are a number of important events in a nodes life-cycle.

onEnter()
Event callback that is invoked every time the cc.Node enters the ‘stage’.
onEnterTransitionDidFinish()
Event callback that is invoked when the cc.Node enters the ‘stage’.
onExit()
callback that is called every time the cc.Node leaves the ‘stage’.
onExitTransitionDidStart()
callback that is called every time the cc.Node leaves the ‘stage’.

We provide utility classes implementing INodeLifeCycleExtensions to provide subscribable signals around these events, see:

tslib/dalste/mvc/DalsteMVC.d.ts

To ensure full compatibility with JSB its is advisable to extend cocos2dx functionality via a compositional approach and to not extend the cococs2dx classes directly within your  typescript code . If you really need to extend the classes directly then is is advisable that you do this in pure javascript. Utilising the cc.Class.extend functionality. This will ensure that you classes will integrate fully with JSB and HTML5. You can then provide type definitions around your JavaScript classes and use them within you typescript taking care not to extend them.

You can see how we implemented our NodeExtended and SceneExtened classes using JavaScript IIFE namespaces and typescript definitions via studying the following files:

src/jslib/dalste/mvc/NodeExtended.js

src/jslib/dalste/mvc/SceneExtened.js

src/jslib/dalste/Dalste.js

tslib/dalste/mvc/DalsteMVC.d.ts

 

dalste.mvc.NodeExtended and dalste.mvc.SceneExtened provide concrete implementations of the INodeLifeCycleExtensions interface. Although it is not recommend to extend these classes directly, you can  extend these classes for your own view assets or provide your own implementation of INodeLifeCycleExtensions. 

After providing a View with an asset  you can call its initialiseLifeCycleEventListeners() function. This will ensure that the below virtual handlers are called on your View derived class as the view moves through its life-cycle. You can override these as necessary.

protected onEnterHandler(): void;
protected onEnterTransitionDidFinishHandler(): void;
protected onExitHandler(): void;
protected onExitTransitionDidStartHandler(): void;

Be sure to call the corresponding removeLifeCycleListeners(); function upon cleanup.

You can see an example implementation of View in the SplashScreenView see: tssrc/view/SplashScreenView.ts see bellow (areas discussed have been highlighted):

import { View } from "./../../tslib/dalste/mvc/View";
import { Display } from "./../../tslib/dalste/util/Display";
import { ApplicationEvents } from "./../events/ApplicationEvents";

declare var ccui: any;
export class SplashScreenView extends View {

    //inject
    protected _display: Display = undefined;

    show(parent?: cc.Node): void {
        this.setAsset(new dalste.mvc.SceneExtended());
        this.initLifecycleListeners();
        cc.director.runScene(this.getAsset());
    }

    protected onEnterHandler(): void {
        cc.log("SplashScreenView:onEnterHandler");
        var button = new ccui.Text("Play Game", "Arial", 30);

       // button.setTitleText("Play Game");
        button.setTouchEnabled(true);
        button.setTouchScaleChangeEnabled(true);
         button.setName("playGameButton");
        button.addTouchEventListener(this.touchEvent, this);
       
        button.setPosition(this._display.middleMiddle().x, this._display.middleMiddle().y);

        button.setColor(cc.color("#00ff00"));

        this.addChild(button, 0);

        var instructionLabel = new cc.LabelTTF("Swipe the screen to move the player. Click in the direction you wish to fire", "Arial", 30, cc.size(this._display.screenWidth()-100,200),cc.ALIGN_CENTER);
        // position the label on the center of the screen
        instructionLabel.x = this._display.middleMiddle().x;
        instructionLabel.y = this._display.middleMiddle().y + 200;
        // add the label as a child to this layer
        this.addChild(instructionLabel, 5);

    }

    protected onEnterTransitionDidFinishHandler(): void {
        cc.log("SplashScreenView::onEnterTransitionDidFinishHandler");
    }

    protected onExitHandler(): void {
        cc.log("SplashScreenView:onExitHandler");
        this.removeLifeCycleListeners();
        this.setAsset(null);
    }

    protected onExitTransitionDidStartHandler(): void {
        cc.log("SplashScreenView::onExitTransitionDidStartHandler");
    }

    protected touchEvent(sender:cc.Node, type: any) {
  
        switch (type) {
            case ccui.Widget.TOUCH_BEGAN:
                break;
            case ccui.Widget.TOUCH_MOVED:
                break;
            case ccui.Widget.TOUCH_ENDED:
            
                        this.getUIEventBus().dispatch("playGameButtonPressed");
                
            
                break;
            case ccui.Widget.TOUCH_CANCELED:
                break;

        }
    }
}

View Controller

A View Controller should handle the logic for its  associated View.  It also has access to the View Model.

We have implemented convenience functions so that a View Controller can subscribe to its associated View’s life-cycle and UI events.

It is not essential that you use the provided UIEventBus signal to dispatch events from your view. It has been provided for  convenience and you may want provide a separate signal for individual aspects of your View.

You can see an example View Controller implementation in the SplashScreenViewController see: tssrc/view/SplashScreenViewController.ts:

import { ViewController } from "./../../tslib/dalste/mvc/ViewController";
import { ScreenTypes } from "./../types/ScreenTypes";
import { ApplicationEvents } from "./../events/ApplicationEvents";
import { Display } from "./../../tslib/dalste/util/Display";


export class SplashScreenViewController extends ViewController {


    //inject
    private _display: Display = undefined;

    onViewReady(): void {
        this.getView().getUIEventBus().add(this.onViewEvent, this);
        this.getView().getEnterSignal().add(this.onViewEnter, this);
        this.getView().getExitSignal().add(this.onViewExit, this);
        this.getView().getEnterTransitionDidFinishSignal().add(this.onViewEnterTransitionDidFinish, this);
        this.getView().getExitTransitionDidStartSignal().add(this.onViewExitTransitionDidStart, this);
    }

    onViewEnter(): void {
        cc.log("SplashScreenViewController::onViewEnter");
    }

    onViewEnterTransitionDidFinish(): void {
        cc.log("SplashScreenViewController::onViewEnterTransitionDidFinish");
    }

    /**
     * @function onViewExit 
     * @description recommended here that you free any resources allocated for the view/scene 
     * ---- TIP here you could also save the game state
     * @see cc.Node onEnter
     */
    onViewExit(): void {
        cc.log("SplashScreenViewController::onViewExit");
    }

    onViewExitTransitionDidStart(): void {
        cc.log("SplashScreenViewController::onViewExitTransitionDidStart");
    }

    onViewEvent(event: string): void {

        switch (event) {
            case "playGameButtonPressed":
                this._system.notify(ApplicationEvents.APP_GOTO_PLAY_SCENE);
                break;
        }

    }




}

 

The splash screen View Controller uses the provided onViewReady() pure virtual hook to to add listeners for the provided View’s UI and life-cycle events. the onViewReady() hook is called after the view has been constructed by the dijon IOC  container and its dependencies resolved.

You can observe that the View Controller handles the “playGameButtonPressed” event dispatched from its View, after catching this event the View Controller dispatches an application wide event which is handled by our singleton GameController. (we will show an example in the IOC wiring discussion below)

 

Application Wide Controllers, Services & Models

It is recommended that you wire Application Wide Controllers, Services and Models as singletons via the IOC container. Their functionality can be invoked via application events. Or they may be injected into your View Contollers via as dijon outlets. (discussed below)

 

IOC Wiring And Dependency Injection

We have ensured that an instance of a View Controller is created and injected into its associated View each time an instance of that view is instantiated. The same can be done a views View Model.

Please study the comments in the tssrc/Application2 class to get a better understanding of how our application has been wired together via the dijon container.

It is also worthwhile studying the dijon documentation to get a better understanding of the key concepts used.

The tssrc/Application2  code is provided below for convenience.

import { IApplication } from "./IApplication";
import { PhysicsSystem } from "./system/PhysicsSystem";
import { GameObjectEntityCreationOptions } from "./factory/entity/GameObjectEntityFactory";
import { GameObjectAssetCreationOptions } from "./factory/view/GameObjectAssetFactory";
import { NPCAISystem } from "./system/NPCAISystem";
import { GameplaySystem } from "./system/GameplaySystem";
import { CharacterEntityFactory } from "./factory/entity/CharacterEntityFactory";
import { GameObjectEntityFactory } from "./factory/entity/GameObjectEntityFactory";
import { GameModel } from "./model/GameModel";
import { ApplicationEvents } from "./events/ApplicationEvents";
import { SplashScreenViewController } from "./view/SplashScreenViewController";
import { SplashScreenView } from "./view/SplashScreenView";
import { Display } from "./../tslib/dalste/util/Display";
import { CharacterAssetCreationOptions } from "./factory/view/CharacterAssetFactory";
import { GameView } from "./view/GameView";
import { GameController } from "./controller/GameController";
import { GameViewController } from "./view/GameViewController";
import { CharacterAssetFactory } from "./factory/view/CharacterAssetFactory"
import { GameObjectAssetFactory } from "./factory/view/GameObjectAssetFactory";

/**
 * @class Application2 
 * @description this Application shows and example of setting up an MVC framework using the dijon 
 * IOC container.
 * 
 * - we map a number of singleton utility classes such as Display to be injected as required
 * 
 * - we map our factories as singletons 
 * 
 * - we tie together our views and view controllers 
 * 
 * - we map our game model as a singleton
 * 
 * - we map a singleton GameController to handle application wide events and handle the wider application logic 
 * for example: loading the applications core scenes
 * 
 * @see https://github.com/creynders/dijon
 */
export class Application2 implements IApplication {


    /**
     * our ioc container
     */
    _system: dijon.System;


    _config: {
        isdebug: boolean
    }

    startUp() {
        this._system = new dijon.System();



        this._system.autoMapOutlets = true;


        /**
         * map the dijon containner to a global outlet named _system so that it may be injected into any class
         * that has the "_system" mapping
         */
        this._system.mapValue("_system", this._system);
        //this._system.mapOutlet('_system'); - not needed as we have set autoMapOutlets to true above


        /**
         * map the game controller as a singleton 
         * the game controller will provide application wide functionality
         */
        this._system.mapSingleton("GameController", GameController);
        var gc = this._system.getObject("GameController");


        /**
         * map the game model as a singleton 
         * the main data model for the game
         */
        this._system.mapSingleton("_gameModel", GameModel);

        /**
         * map the character and game object asset factories as a singletons
         * 
         */
        this._system.mapSingleton("_characterAssetFactory", CharacterAssetFactory);
        this._system.mapSingleton("_gameObjectAssetFactory", GameObjectAssetFactory);

        /**
        * map the character and game object entity  factories as singletons 
        * 
        */
        this._system.mapSingleton("_characterEntityFactory", CharacterEntityFactory);
        this._system.mapSingleton("_gameObjectEntityFactory", GameObjectEntityFactory);

        /**
        * map the display utility class as a singleton 
        * 
        */
        this._system.mapSingleton("_display", Display);


        /**
        * map a signal as a singleton to dispatch collision events - 
        *these events will be fired by physics system and handled primarily by Gameplaysystem 
        * 
        */
        this._system.mapSingleton("_collisionEventBus", signals.Signal);


        /**
         * map the systems that we need to inkect dependencies into (other systems are just instantiated as normal)
         */

        this._system.mapClass("GameplaySystem", GameplaySystem);
        this._system.mapClass("NPCAISystem", NPCAISystem);
        this._system.mapClass("PhysicsSystem", PhysicsSystem);

        /**
        * initialise the splash screen view and its controller
        * first we map our SplashScreenView and SplashScreenViewController classes to class identifiers holding the same name 
        * then we map the SplashScreenViews _viewcontroller to SplashScreenViewController
        * When an instance of game view is created a corresponding instance of SplashScreenViewController is injected into its _viewController property
        */
        this._system.mapClass("SplashScreenView", SplashScreenView);
        this._system.mapClass("SplashScreenViewController", SplashScreenViewController);
        this._system.mapOutlet("SplashScreenViewController", "SplashScreenView", "_viewController");


        /**
         * initialise the game view and its controller
         * first we map our GameView and GameViewController classes to class identifiers holding the same name 
         * then we map the GameViews _viewcontroller to GameViewController
         * When an instance of game view is created a corresponding instance of GameViewController is injected into its _viewController property
         */
        this._system.mapClass("GameView", GameView);
        this._system.mapClass("GameViewController", GameViewController);
        this._system.mapOutlet("GameViewController", "GameView", "_viewController");

        /**
         * map the GameController::onAppStartupComplete function as a handler for the ApplicationEvents.APP_STARTUP_COMPLETE event
         */
        this._system.mapHandler(ApplicationEvents.APP_STARTUP_COMPLETE, 'GameController', 'onAppStartupComplete');



        /**
         * map the GameController::onAppGoToPlayScene function as a handler for the ApplicationEvents.APP_GOTO_PLAY_SCENE event
         */
        this._system.mapHandler(ApplicationEvents.APP_GOTO_PLAY_SCENE, 'GameController', 'onAppGoToPlayScene');

        /**
         * map the GameController::onAppGoToPlayScene function as a handler for the ApplicationEvents.APP_GOTO_PLAY_SCENE event
         */
        this._system.mapHandler(ApplicationEvents.APP_GOTO_SPLASH_SCENE, 'GameController', 'onAppGoToSplashScene');


        /**
         * fire app:startup event
         */
        this._system.notify(ApplicationEvents.APP_STARTUP);

        /**
      * fire app:startupComplete event
      */
        this._system.notify(ApplicationEvents.APP_STARTUP_COMPLETE);


    }
}

 

Further reading

There are other IOC libraries available for TypeScript such as inversify.JS.

InversifyJS uses newer (ES7 metadata ) and more advanced features of JavaScript to address the current lack of RTTI (Run time Type Information ). I believe that InversifyJS  can also work with  an es5 target and cocos2d-x JSB but discussion of these topics are outside the scope of this tutorial series.

 

Thats is for now, Ill see you in the final Part 7 of this tutorial series where we discuss our gameplay engine written upon a modified version of the  MoonCES Entity Framework.

The code for the upcoming tutorial is already present in the CocosTsGameMVC  project presented in this tutorial so feel free to browse  through the code and in addition, familiarise yourself with Moon CES.

 

References

https://github.com/creynders/dijon

http://inversify.io/

http://blog.wolksoftware.com/introducing-inversify-2

https://github.com/inversify/InversifyJS

http://webpack.github.io/docs/configuration.html#output-publicpath

https://github.com/webpack/webpack-dev-server/issues/24

https://github.com/webpack/webpack-dev-server/issues/62

https://github.com/gajus/write-file-webpack-plugin

8 Replies to “Tutorial – Getting started with TypeScript and Cocos2d-x JS -Part 6 – Webpack-Dev-Server – MVCS Framework – IOC – Dependency Injection”

  1. Hi dalste:

    I’m trying your tutorials. Good job!

    I’ve an issue with webpack-dev-server. Could you help me, please?

    When I execute:

    gulp webpack-dev-server

    The first time it calls to ts-loader and applicationbundle.js is built.

    However, after it, when I modify some TS code ts-loader isn’t called and applicationbundle.js isn’t rebuilt.

    I’ve found a workaround adding “watch: true” in webpack.config.js and executing 1) gulp webpack 2) gulp webpack-dev-server

    Do you know how could I use only webpack-dev-server?

    Thanks and best!

    1. Hi,

      I believe webpack-dev-server does not write the bundle to disk it just serves the code directly to the browser as you write.

      Thus the use of https://github.com/gajus/write-file-webpack-plugin.

      The issue you raised is not something I have explored as yet, you could perhaps look into the configuration of above plugin.

      Also check out this thread where this topic is discussed in detail : https://github.com/webpack/webpack-dev-server/issues/62

      Best,

      Steven

      1. Thanks for your quick reply, dalste.

        You are right! I’d created my project step by step following the tutorials and I had my webpack.config.js file outdated (without WriteFilePlugin plugin entry).

        Now it works fine!

        Thanks and best,

        Patricio

        1. You are welcome, I am glad to see people making use of this work.

          There is much room for improvement, but this should serve as a good starting place.

  2. Hi Steven:

    I think it’s a good starting place and I would like to use and improve it. However I haven’t decided yet, because I would like to know about the maintainability.

    – Could we update the npm packages without problems?
    – Is the ts-file generation from JS libraries (like cocos2dx or another) easy?

    Thanks and best!

    1. I tend to create ts files on the fly as I need them. It is relatively easy. the best way to utilize existing JavaScript libraries is to load them into your game as usual and create your own typescript definitions for the apis you wish to use.

      Updating the npm packages can come with problems, I would tread with caution, and ensure you read change logs etc.

      Best,

      Steven

  3. Hi Steven,
    Your tutorial is awesome!
    Thanks for the great work.

    All work fine till I try to run code from “CocosTsGameMVC” on device.
    The screen only shows cocos debug info – fps, draw calls, vertexes.

    Log from device shows that no errors.
    And log have this string:
    >Hello Application1
    But NOT have this string:
    >Hello World Scene

    This indicates that the scene does not start.

    I tried to compile with
    cocos run -p android
    And with Android studio 3.4.2 – same result

    Can you suggest where is the problem?

    Here is the full log in case it will helps:

    07-23 13:05:08.886 28952 29044 D cocos2d-x debug info: cocos2d: fullPathForFilename: No file found at script/jsb_prepare.jsc. Possible missing file.
    07-23 13:05:08.984 28952 29044 D cocos2d-x debug info: cocos2d: fullPathForFilename: No file found at script/jsb_boot.jsc. Possible missing file.
    07-23 13:05:09.256 28952 29044 D cocos2d-x debug info: Cocos2d-JS v3.14
    07-23 13:05:09.271 28952 29044 D cocos2d-x debug info: JS: assets/src/applicationbundle.js:318:mutating the [[Prototype]] of an object will cause your code to run very slowly; instead create the object with the correct initial [[Prototype]] value using Object.create
    07-23 13:05:09.271 28952 29044 D cocos2d-x debug info:
    07-23 13:05:09.282 28952 29044 D cocos2d-x debug info: Hello Application1
    07-23 13:05:09.289 28952 29044 D cocos2d-x debug info: create rendererRecreatedListener for GLProgramState
    07-23 13:05:09.290 28952 29044 D cocos2d-x debug info: cocos2d: QuadCommand: resizing index size from [-1] to [2560]

    1. Yes it looks like the hello world scene derives directly from a cocos2dx class “cc.Scene”. it is recommend to only use the cocos2dx classes via composition, as we are currently using the typescript inheritance system which is incompatible with the cocos2dx jsb engine, meaning that although it will work with the web engine which uses the typical Javascript protoype chain for inheritance it wont work with JSB as JSb has internal functionality to map the core cocos2dx classes to JSB c++ objects.

      If you look at Application2 and how the views are constructed you will find that they have a member called node (or asset) which can hold either a cc.Scene or other cc.Node derivative, this object is then run via the cc.director upon startup of the view.

      think composition over inheritance when working with cocos2dx within this framework.

      In honesty there is probably a way using typescript decorators to get around this current limitation but I’ve not had time to look into it.

      It may be helpful to know that I have built and released an app on the web, IOS and Andriod, with a slightly updated version of this stack.
      hope this helps…

Leave a Reply

Your email address will not be published. Required fields are marked *