5. Integration with JavaScript libraries

5.1. Three ways of integration

There are three ways to integrate Transcrypt applications with existing JavaScript libraries.

  1. The simplest way is to use the library as is, without any encapsulation. In this way all symbols of that library will be in the global namespace. While many JavaScript programmers don’t seem to mind that, many Python programmers do.
  2. Another way is to encapsulate the JavaScript library as a whole in a Transcrypt module. In the distibution this is done for the fabric module, that encapsulates fabric.js. In this way the global namespace stays clean.
  3. The third way is to write a complete Pythonic API for the JavaScript library. This is overkill in most cases and makes it harder to keep up with new versions of the library. Note that Transcrypt was desiged to make seamless cooperation between Transcrypt and JavaScript libraries possible without

In the Pong example below, approach 2 is choosen to encapsulate the fabric.js graphics library. In most cases this approach strikes a good balance between effort and yield. As can be seen below, the effort involved is minimal.

The encapsulation layer for fabric.js
fabric = __pragma__ (
    'js',
    '''
        (function () {{
            var exports = {{}};
            {}  // Puts fabric in exports and in global window
            delete window.fabric;
            return exports;
        }}) () .fabric;
    ''',
    __include__ ('com/fabricjs/__javascript__/fabric.js')
)

Note that __pragma__ (‘js’, <skeletoncode>, includes = [<file1>, <file2>, ..]) is used to achieve the encapsulation. It replaces the {} by the respective contents of the files. The fabric module is part of the download. Note that not all facilities were included in customizing fabric.js. You can drop-in replaces the fabric.js in the __javascript__ subdirectory by another customized version without changing anything. Preferably download a development version, since that enables easy debugging. Transcryp will minify it for you on the fly.

5.2. Integration example: Pong

In using the fabric.js JavaScript library this example, the only thing differing from plain JavaScipt is that new <constructor> is replaced by __new__ (<constructor>).

pong.py
__pragma__ ('skip')
window = Math = Date = 0    # Prevent complaints by optional static checker
__pragma__ ('noskip')

from com.fabricjs import fabric

orthoWidth = 1000
orthoHeight = 750
fieldHeight = 650

enter, esc, space = 13, 27, 32

class Attribute:    # Attribute in the gaming sense of the word, rather than of an object
    def __init__ (self, game):
        self.game = game                    # Attribute knows game it's part of
        self.game.attributes.append (self)  # Game knows all its attributes
        self.install ()                     # Put in place graphical representation of attribute
        self.reset ()                       # Reset attribute to start position
                    
    def reset (self):       # Restore starting positions or score, then commit to fabric
        self.commit ()      # Nothing to restore for the Attribute base class
                
    def predict (self):
        pass
                
    def interact (self):
        pass
        
    def commit (self):
        pass

class Sprite (Attribute):   # Here, a sprite is an attribute that can move
    def __init__ (self, game, width, height):
        self.width = width
        self.height = height
        Attribute.__init__ (self, game)
        
    def install (self):     # The sprite holds an image that fabric can display
        self.image = __new__ (fabric.Rect ({
            'width': self.game.scaleX (self.width), 'height': self.game.scaleY (self.height),
            'originX': 'center', 'originY': 'center', 'fill': 'white'
        }))
        
    __pragma__ ('kwargs')
    def reset (self, vX = 0, vY = 0, x = 0, y = 0):
        self.vX = vX        # Speed
        self.vY = vY
        
        self.x = x          # Predicted position, can be commit, no bouncing initially
        self.y = y
        
        Attribute.reset (self)
    __pragma__ ('nokwargs')
        
    def predict (self):     # Predict position, do not yet commit, bouncing may alter it
        self.x += self.vX * self.game.deltaT
        self.y += self.vY * self.game.deltaT

    def commit (self):      # Update fabric image for asynch draw
        self.image.left = self.game.orthoX (self.x)
        self.image.top = self.game.orthoY (self.y)
        
    def draw (self):
        self.game.canvas.add (self.image)
         
class Paddle (Sprite):
    margin = 30 # Distance of paddles from walls
    width = 10
    height = 100
    speed = 400 # / s
    
    def __init__ (self, game, index):
        self.index = index  # Paddle knows its player index, 0 == left, 1 == right
        Sprite.__init__ (self, game, self.width, self.height)
        
    def reset (self):       # Put paddle in rest position, dependent on player index
        Sprite.reset (
            self,
            x = orthoWidth // 2 - self.margin if self.index else -orthoWidth // 2 + self.margin,
            y = 0
        )
        
    def predict (self): # Let paddle react on keys
        self.vY = 0
    
        if self.index:                          # Right player
            if ord ('K') in self.game.keySet:       # Letter k pressed
                self.vY = self.speed
            elif ord ('M') in self.game.keySet:
                self.vY = -self.speed
        else:                                   # Left player
            if ord ('A') in self.game.keySet:
                self.vY = self.speed
            elif ord ('Z') in self.game.keySet:
                self.vY = -self.speed
                
        Sprite.predict (self)                   # Do not yet commit, paddle may bounce with walls

    def interact (self):    # Paddles and ball assumed infinitely thin
        # Paddle touches wall
        self.y = Math.max (self.height // 2 - fieldHeight // 2, Math.min (self.y, fieldHeight // 2 - self.height // 2))
        
        # Paddle hits ball
        if (
            (self.y - self.height // 2) < self.game.ball.y < (self.y + self.height // 2)
            and (
                (self.index == 0 and self.game.ball.x < self.x) # On or behind left paddle
                or
                (self.index == 1 and self.game.ball.x > self.x) # On or behind right paddle
            )
        ):
            self.game.ball.x = self.x               # Ball may have gone too far already
            self.game.ball.vX = -self.game.ball.vX  # Bounce on paddle
            self.game.ball.speedUp (self)
        
class Ball (Sprite):
    side = 8
    speed = 300 # / s
    
    def __init__ (self, game):
        Sprite.__init__ (self, game, self.side, self.side)
 
    def reset (self):   # Launch according to service direction with random angle offset from horizontal
        angle =  (
            self.game.serviceIndex * Math.PI    # Service direction
            +
            (1 if Math.random () > 0.5 else -1) * Math.random () * Math.atan (fieldHeight / orthoWidth)
        )
        
        Sprite.reset (
            self,
            vX = self.speed * Math.cos (angle),
            vY = self.speed * Math.sin (angle)
        )
        
    def predict (self):
        Sprite.predict (self)           # Integrate velocity to position
        
        if self.x < -orthoWidth // 2:   # If out on left side
            self.game.scored (1)        #   Right player scored
        elif self.x > orthoWidth // 2:
            self.game.scored (0)
            
        if self.y > fieldHeight // 2:   # If it hit top wall
            self.y = fieldHeight // 2   #   It may have gone too far already
            self.vY = -self.vY          #   Bounce
        elif self.y < -fieldHeight // 2:
            self.y = -fieldHeight // 2
            self.vY = -self.vY

    def speedUp (self, bat):
        factor = 1 + 0.15 * (1 - Math.abs (self.y - bat.y) / (bat.height // 2)) ** 2    # Speed will increase more if paddle hit near centre
        
        if Math.abs (self.vX) < 3 * self.speed:
            self.vX *= factor
            self.vY *= factor           

class Scoreboard (Attribute):
    nameShift = 75
    hintShift = 25
            
    def install (self): # Graphical representation of scoreboard are four labels and a separator line
        self.playerLabels = [__new__ (fabric.Text ('Player {}'.format (name), {
                'fill': 'white', 'fontFamily': 'arial', 'fontSize': '30',
                'left': self.game.orthoX (position * orthoWidth), 'top': self.game.orthoY (fieldHeight // 2 + self.nameShift)
        })) for name, position in (('AZ keys:', -7/16), ('KM keys:', 1/16))]
        
        self.hintLabel = __new__ (fabric.Text ('[spacebar] starts game, [enter] resets score', {
                'fill': 'white', 'fontFamily': 'arial', 'fontSize': '12',
                'left': self.game.orthoX (-7/16 * orthoWidth), 'top': self.game.orthoY (fieldHeight // 2 + self.hintShift)
        }))
        
        self.image = __new__ (fabric.Line ([
                self.game.orthoX (-orthoWidth // 2), self.game.orthoY (fieldHeight // 2),
                self.game.orthoX (orthoWidth // 2), self.game.orthoY (fieldHeight // 2)
            ],
            {'stroke': 'white'}
        ))
                
    def increment (self, playerIndex):
        self.scores [playerIndex] += 1
        
    def reset (self):
        self.scores = [0, 0]
        Attribute.reset (self)  # Only does a commit here
        
    def commit (self):          # Committing labels is adapting their texts
        self.scoreLabels = [__new__ (fabric.Text ('{}'.format (score), {
                'fill': 'white', 'fontFamily': 'arial', 'fontSize': '30',
                'left': self.game.orthoX (position * orthoWidth), 'top': self.game.orthoY (fieldHeight // 2 + self.nameShift)
        })) for score, position in zip (self.scores, (-2/16, 6/16))]

    def draw (self):
        for playerLabel, scoreLabel in zip (self.playerLabels, self.scoreLabels):
            self.game.canvas.add (playerLabel)
            self.game.canvas.add (scoreLabel)
            self.game.canvas.add (self.hintLabel)
        self.game.canvas.add (self.image)
        
class Game:
    def __init__ (self):
        self.serviceIndex = 1 if Math.random () > 0.5 else 0    # Index of player that has initial service
        self.pause = True                           # Start game in paused state
        self.keySet = set ()
        
        self.canvas = __new__ (fabric.Canvas ('canvas', {'backgroundColor': 'black', 'originX': 'center', 'originY': 'center'}))
        self.canvas.onWindowResize = self.resize    # Install draw callback, will be called asynch
        self.canvas.onWindowDraw = self.draw        # Install resize callback, will be called if resized
        self.canvas.lineWidth = 2
        self.canvas.clear ()    

        self.attributes = []                        # All attributes will insert themselves here
        self.paddles = [Paddle (self, index) for index in range (2)]    # Pass game as parameter self
        self.ball = Ball (self)
        self.scoreboard = Scoreboard (self)     

        window.setInterval (self.update, 10)    # Install update callback, time in ms
        window.setInterval (self.draw, 20)      # Install draw callback, time in ms
        window.addEventListener ('keydown', self.keydown)
        window.addEventListener ('keyup', self.keyup)
        
        self.time = + __new__ (Date)
 
    def update (self):                          # Note that update and draw are not synchronized
        oldTime = self.time
        self.time = + __new__ (Date)
        self.deltaT = (self.time - oldTime) / 1000.
        
        if self.pause:                          # If in paused state
            if space in self.keySet:            #   If spacebar hit
                self.pause = False              #         Start playing
            elif enter in self.keySet:      #   Else if enter hit
                self.scoreboard.reset ()        #         Reset score
        else:                                   # Else, so if in active state
            for attribute in self.attributes:   #   Compute predicted values
                attribute.predict ()
            
            for attribute in self.attributes:   #   Correct values for bouncing and scoring
                attribute.interact ()
            
            for attribute in self.attributes:   #   Commit them to pyglet for display
                attribute.commit ()
            
    def scored (self, playerIndex):             # Player has scored
        self.scoreboard.increment (playerIndex) # Increment player's points
        self.serviceIndex = 1 - playerIndex     # Grant service to the unlucky player
        
        for paddle in self.paddles:             # Put paddles in rest position
            paddle.reset ()

        self.ball.reset ()                      # Put ball in rest position
        self.pause = True                       # Wait for next round
        
    def draw (self):
        self.canvas.clear ()
        for attribute in self.attributes:
            attribute.draw ()
             
    def resize (self, width, height):
        pass
        
    def scaleX (self, x):
        return x * (self.canvas.width / orthoWidth)
            
    def scaleY (self, y):
        return y * (self.canvas.height / orthoHeight)   
        
    def orthoX (self, x):
        return self.scaleX (x + orthoWidth // 2)
        
    def orthoY (self, y):
        return self.scaleY (orthoHeight - fieldHeight // 2 - y)
                
    def keydown (self, event):
        self.keySet.add (event.keyCode)
        
    def keyup (self, event):
        self.keySet.remove (event.keyCode)
        
game = Game ()  # Create and run game
pong.mod.js
    (function () {
        ;
        ;
        var fabric = __init__ (__world__.com.fabricjs).fabric;
        var orthoWidth = 1000;
        var orthoHeight = 750;
        var fieldHeight = 650;
        var __left0__ = tuple (list ([13, 27, 32]));
        var enter = __left0__ [0];
        var esc = __left0__ [1];
        var space = __left0__ [2];
        var Attribute = __class__ ('Attribute', [object], {
            get __init__ () {return __get__ (this, function (self, game) {
                self.game = game;
                self.game.attributes.append (self);
                self.install ();
                self.reset ();
            });},
            get reset () {return __get__ (this, function (self) {
                self.commit ();
            });},
            get predict () {return __get__ (this, function (self) {
            });},
            get interact () {return __get__ (this, function (self) {
            });},
            get commit () {return __get__ (this, function (self) {
            });}
        });
        var Sprite = __class__ ('Sprite', [Attribute], {
            get __init__ () {return __get__ (this, function (self, game, width, height) {
                self.width = width;
                self.height = height;
                Attribute.__init__ (self, game);
            });},
            get install () {return __get__ (this, function (self) {
                self.image = new fabric.Rect (dict ({'width': self.game.scaleX (self.width), 'height': self.game.scaleY (self.height), 'originX': 'center', 'originY': 'center', 'fill': 'white'}));
            });},
            get reset () {return __get__ (this, function (self, vX, vY, x, y) {
                if (typeof vX == 'undefined' || (vX != null && vX .__class__ == __kwargdict__)) {;
                    var vX = 0;
                };
                if (typeof vY == 'undefined' || (vY != null && vY .__class__ == __kwargdict__)) {;
                    var vY = 0;
                };
                if (typeof x == 'undefined' || (x != null && x .__class__ == __kwargdict__)) {;
                    var x = 0;
                };
                if (typeof y == 'undefined' || (y != null && y .__class__ == __kwargdict__)) {;
                    var y = 0;
                };
                if (arguments.length) {
                    var __ilastarg0__ = arguments.length - 1;
                    if (arguments [__ilastarg0__] && arguments [__ilastarg0__].__class__ == __kwargdict__) {
                        var __allkwargs0__ = arguments [__ilastarg0__--];
                        for (var __attrib0__ in __allkwargs0__) {
                            switch (__attrib0__) {
                                case 'self': var self = __allkwargs0__ [__attrib0__]; break;
                                case 'vX': var vX = __allkwargs0__ [__attrib0__]; break;
                                case 'vY': var vY = __allkwargs0__ [__attrib0__]; break;
                                case 'x': var x = __allkwargs0__ [__attrib0__]; break;
                                case 'y': var y = __allkwargs0__ [__attrib0__]; break;
                            }
                        }
                    }
                }
                self.vX = vX;
                self.vY = vY;
                self.x = x;
                self.y = y;
                Attribute.reset (self);
            });},
            get predict () {return __get__ (this, function (self) {
                self.x += self.vX * self.game.deltaT;
                self.y += self.vY * self.game.deltaT;
            });},
            get commit () {return __get__ (this, function (self) {
                self.image.left = self.game.orthoX (self.x);
                self.image.top = self.game.orthoY (self.y);
            });},
            get draw () {return __get__ (this, function (self) {
                self.game.canvas.add (self.image);
            });}
        });
        var Paddle = __class__ ('Paddle', [Sprite], {
            get __init__ () {return __get__ (this, function (self, game, index) {
                self.index = index;
                Sprite.__init__ (self, game, self.width, self.height);
            });},
            get reset () {return __get__ (this, function (self) {
                Sprite.reset (self, __kwargdict__ ({x: (self.index ? Math.floor (orthoWidth) / Math.floor (2) - self.margin : Math.floor (-orthoWidth) / Math.floor (2) + self.margin), y: 0}));
            });},
            get predict () {return __get__ (this, function (self) {
                self.vY = 0;
                if (self.index) {
                    if (__in__ (ord ('K'), self.game.keySet)) {
                        self.vY = self.speed;
                    }
                    else {
                        if (__in__ (ord ('M'), self.game.keySet)) {
                            self.vY = -self.speed;
                        }
                    }
                }
                else {
                    if (__in__ (ord ('A'), self.game.keySet)) {
                        self.vY = self.speed;
                    }
                    else {
                        if (__in__ (ord ('Z'), self.game.keySet)) {
                            self.vY = -self.speed;
                        }
                    }
                }
                Sprite.predict (self);
            });},
            get interact () {return __get__ (this, function (self) {
                self.y = Math.max (Math.floor (self.height) / Math.floor (2) - Math.floor (fieldHeight) / Math.floor (2), Math.min (self.y, Math.floor (fieldHeight) / Math.floor (2) - Math.floor (self.height) / Math.floor (2)));
                if ((self.y - Math.floor (self.height) / Math.floor (2) < self.game.ball.y && self.game.ball.y < self.y + Math.floor (self.height) / Math.floor (2)) && (self.index == 0 && self.game.ball.x < self.x || self.index == 1 && self.game.ball.x > self.x)) {
                    self.game.ball.x = self.x;
                    self.game.ball.vX = -self.game.ball.vX;
                    self.game.ball.speedUp (self);
                }
            });}
        });
        Paddle.margin = 30;
        Paddle.width = 10;
        Paddle.height = 100;
        Paddle.speed = 400;
        var Ball = __class__ ('Ball', [Sprite], {
            get __init__ () {return __get__ (this, function (self, game) {
                Sprite.__init__ (self, game, self.side, self.side);
            });},
            get reset () {return __get__ (this, function (self) {
                var angle = self.game.serviceIndex * Math.PI + (Math.random () > 0.5 ? 1 : -1) * Math.random () * Math.atan (fieldHeight / orthoWidth);
                Sprite.reset (self, __kwargdict__ ({vX: self.speed * Math.cos (angle), vY: self.speed * Math.sin (angle)}));
            });},
            get predict () {return __get__ (this, function (self) {
                Sprite.predict (self);
                if (self.x < Math.floor (-orthoWidth) / Math.floor (2)) {
                    self.game.scored (1);
                }
                else {
                    if (self.x > Math.floor (orthoWidth) / Math.floor (2)) {
                        self.game.scored (0);
                    }
                }
                if (self.y > Math.floor (fieldHeight) / Math.floor (2)) {
                    self.y = Math.floor (fieldHeight) / Math.floor (2);
                    self.vY = -self.vY;
                }
                else {
                    if (self.y < Math.floor (-fieldHeight) / Math.floor (2)) {
                        self.y = Math.floor (-fieldHeight) / Math.floor (2);
                        self.vY = -self.vY;
                    }
                }
            });},
            get speedUp () {return __get__ (this, function (self, bat) {
                var factor = 1 + 0.15 * Math.pow (1 - Math.abs (self.y - bat.y) / Math.floor (bat.height) / Math.floor (2), 2);
                if (Math.abs (self.vX) < 3 * self.speed) {
                    self.vX *= factor;
                    self.vY *= factor;
                }
            });}
        });
        Ball.side = 8;
        Ball.speed = 300;
        var Scoreboard = __class__ ('Scoreboard', [Attribute], {
            get install () {return __get__ (this, function (self) {
                self.playerLabels = function () {
                    var __accu0__ = [];
                    var __iter0__ = tuple (list ([tuple (list (['AZ keys:', -7 / 16])), tuple (list (['KM keys:', 1 / 16]))]));
                    for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                        var __left0__ = __iter0__ [__index0__];
                        var name = __left0__ [0];
                        var position = __left0__ [1];
                        __accu0__.append (new fabric.Text ('Player {}'.format (name), dict ({'fill': 'white', 'fontFamily': 'arial', 'fontSize': '30', 'left': self.game.orthoX (position * orthoWidth), 'top': self.game.orthoY (Math.floor (fieldHeight) / Math.floor (2) + self.nameShift)})));
                    }
                    return __accu0__;
                } ();
                self.hintLabel = new fabric.Text ('[spacebar] starts game, [enter] resets score', dict ({'fill': 'white', 'fontFamily': 'arial', 'fontSize': '12', 'left': self.game.orthoX (-7 / 16 * orthoWidth), 'top': self.game.orthoY (Math.floor (fieldHeight) / Math.floor (2) + self.hintShift)}));
                self.image = new fabric.Line (list ([self.game.orthoX (Math.floor (-orthoWidth) / Math.floor (2)), self.game.orthoY (Math.floor (fieldHeight) / Math.floor (2)), self.game.orthoX (Math.floor (orthoWidth) / Math.floor (2)), self.game.orthoY (Math.floor (fieldHeight) / Math.floor (2))]), dict ({'stroke': 'white'}));
            });},
            get increment () {return __get__ (this, function (self, playerIndex) {
                self.scores [playerIndex]++;
            });},
            get reset () {return __get__ (this, function (self) {
                self.scores = list ([0, 0]);
                Attribute.reset (self);
            });},
            get commit () {return __get__ (this, function (self) {
                self.scoreLabels = function () {
                    var __accu0__ = [];
                    var __iter0__ = zip (self.scores, tuple (list ([-2 / 16, 6 / 16])));
                    for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                        var __left0__ = __iter0__ [__index0__];
                        var score = __left0__ [0];
                        var position = __left0__ [1];
                        __accu0__.append (new fabric.Text ('{}'.format (score), dict ({'fill': 'white', 'fontFamily': 'arial', 'fontSize': '30', 'left': self.game.orthoX (position * orthoWidth), 'top': self.game.orthoY (Math.floor (fieldHeight) / Math.floor (2) + self.nameShift)})));
                    }
                    return __accu0__;
                } ();
            });},
            get draw () {return __get__ (this, function (self) {
                var __iter0__ = zip (self.playerLabels, self.scoreLabels);
                for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                    var __left0__ = __iter0__ [__index0__];
                    var playerLabel = __left0__ [0];
                    var scoreLabel = __left0__ [1];
                    self.game.canvas.add (playerLabel);
                    self.game.canvas.add (scoreLabel);
                    self.game.canvas.add (self.hintLabel);
                }
                self.game.canvas.add (self.image);
            });}
        });
        Scoreboard.nameShift = 75;
        Scoreboard.hintShift = 25;
        var Game = __class__ ('Game', [object], {
            get __init__ () {return __get__ (this, function (self) {
                self.serviceIndex = (Math.random () > 0.5 ? 1 : 0);
                self.pause = true;
                self.keySet = set ();
                self.canvas = new fabric.Canvas ('canvas', dict ({'backgroundColor': 'black', 'originX': 'center', 'originY': 'center'}));
                self.canvas.onWindowResize = self.resize;
                self.canvas.onWindowDraw = self.draw;
                self.canvas.lineWidth = 2;
                self.canvas.clear ();
                self.attributes = list ([]);
                self.paddles = function () {
                    var __accu0__ = [];
                    var __iter0__ = range (2);
                    for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                        var index = __iter0__ [__index0__];
                        __accu0__.append (Paddle (self, index));
                    }
                    return __accu0__;
                } ();
                self.ball = Ball (self);
                self.scoreboard = Scoreboard (self);
                window.setInterval (self.update, 10);
                window.setInterval (self.draw, 20);
                window.addEventListener ('keydown', self.keydown);
                window.addEventListener ('keyup', self.keyup);
                self.time = +new Date;
            });},
            get update () {return __get__ (this, function (self) {
                var oldTime = self.time;
                self.time = +new Date;
                self.deltaT = (self.time - oldTime) / 1000.0;
                if (self.pause) {
                    if (__in__ (space, self.keySet)) {
                        self.pause = false;
                    }
                    else {
                        if (__in__ (enter, self.keySet)) {
                            self.scoreboard.reset ();
                        }
                    }
                }
                else {
                    var __iter0__ = self.attributes;
                    for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                        var attribute = __iter0__ [__index0__];
                        attribute.predict ();
                    }
                    var __iter0__ = self.attributes;
                    for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                        var attribute = __iter0__ [__index0__];
                        attribute.interact ();
                    }
                    var __iter0__ = self.attributes;
                    for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                        var attribute = __iter0__ [__index0__];
                        attribute.commit ();
                    }
                }
            });},
            get scored () {return __get__ (this, function (self, playerIndex) {
                self.scoreboard.increment (playerIndex);
                self.serviceIndex = 1 - playerIndex;
                var __iter0__ = self.paddles;
                for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                    var paddle = __iter0__ [__index0__];
                    paddle.reset ();
                }
                self.ball.reset ();
                self.pause = true;
            });},
            get draw () {return __get__ (this, function (self) {
                self.canvas.clear ();
                var __iter0__ = self.attributes;
                for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                    var attribute = __iter0__ [__index0__];
                    attribute.draw ();
                }
            });},
            get resize () {return __get__ (this, function (self, width, height) {
            });},
            get scaleX () {return __get__ (this, function (self, x) {
                return x * self.canvas.width / orthoWidth;
            });},
            get scaleY () {return __get__ (this, function (self, y) {
                return y * self.canvas.height / orthoHeight;
            });},
            get orthoX () {return __get__ (this, function (self, x) {
                return self.scaleX (x + Math.floor (orthoWidth) / Math.floor (2));
            });},
            get orthoY () {return __get__ (this, function (self, y) {
                return self.scaleY (orthoHeight - Math.floor (fieldHeight) / Math.floor (2) - y);
            });},
            get keydown () {return __get__ (this, function (self, event) {
                self.keySet.add (event.keyCode);
            });},
            get keyup () {return __get__ (this, function (self, event) {
                self.keySet.remove (event.keyCode);
            });}
        });
        var game = Game ();
        __pragma__ ('<use>' +
            'com.fabricjs' +
        '</use>')
        __pragma__ ('<all>')
            __all__.Attribute = Attribute;
            __all__.Ball = Ball;
            __all__.Game = Game;
            __all__.Paddle = Paddle;
            __all__.Scoreboard = Scoreboard;
            __all__.Sprite = Sprite;
            __all__.enter = enter;
            __all__.esc = esc;
            __all__.fieldHeight = fieldHeight;
            __all__.game = game;
            __all__.orthoHeight = orthoHeight;
            __all__.orthoWidth = orthoWidth;
            __all__.space = space;
        __pragma__ ('</all>')
    }) ();

5.3. Joined minification

Minification is currently performed by the Google closure compiler, that’s also part of the distribution. Rather than separately minifying libraries, the application is minified as a whole. In principle this enables a smaller total download size. Currently closures ADVANCED_OPTIMIZATIONS switch breaks the working strict code, however, so the SIMPLE_OPTIMIZATIONS switch is used by default.

As can be seen from the listings, pong.mod.js without libraries is only slightly longer than pong.py without libraries. The difference mainly comes from the expensive keyword arguments mechanism that is activated for the reset function, using __pragma__ (‘kargs’) and __pragma__ (‘nokwargs’). The minified version is about half this size. The Transcrypt runtime itself in minified form is about 9kB. So the bulk of the total size of the minified file, 148kB comes from fabric.js. From this example it becomes clear that Transcrypt is extremely lightweight.

5.4. Integration example: jQuery

In contrast to the use of the fabric.js library in the Pong example, jQuery hasn’t been encapsulated at all. It’s just downloaded on the fly from a content delivery network and used as-is. Instead of the $ (that is not a valid Python identifier), an S is used as alias. This might have been any character sequence.

jquery_demo.py
__pragma__ ('alias', 'S', '$')

def start ():
    def changeColors ():
        for div in S__divs:
            S (div) .css ({
                'color': 'rgb({},{},{})'.format (* [int (256 * Math.random ()) for i in range (3)]),
            })

    S__divs = S ('div')
    changeColors ()
    window.setInterval (changeColors, 500)
jquery_demo.mod.js
    (function () {
        var start = function () {
            var changeColors = function () {
                var __iter0__ = $divs;
                for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                    var div = __iter0__ [__index0__];
                    $ (div).css (dict ({'color': 'rgb({},{},{})'.format.apply (null, function () {
                        var __accu0__ = [];
                        var __iter1__ = range (3);
                        for (var __index1__ = 0; __index1__ < __iter1__.length; __index1__++) {
                            var i = __iter1__ [__index1__];
                            __accu0__.append (int (256 * Math.random ()));
                        }
                        return __accu0__;
                    } ())}));
                }
            };
            var $divs = $ ('div');
            changeColors ();
            window.setInterval (changeColors, 500);
        };
        __pragma__ ('<all>')
            __all__.start = start;
        __pragma__ ('</all>')
    }) ();