Meteor Boilerplate for Typescript Projects

Meteor Boilerplate for Typescript Projects

Meteor loves Typescript with this new boilerplate!

Quickstart

  1. Clone the meteor boilerplate: git clone git://github.com/tomitrescak/meteor-boilerplate-typescript.git
  2. Run npm install in the main directory (possibly sudo npm install)
  3. Add your files (see section Files).
  4. If you want to use modules use gulp build-debug-modules or gulp build-release-modules gulp task (see section Modules). You can easily run these tasks in your editor (e.g. Webstorm, Sublime)
  5. If you do not want modules use gulp build gulp task.
  6. Run gulp tslint for that extra linting that makes your project shine!
  7. Done!

Modules

To get modules running, you need to register only top level modules in modules:export package. For example, if your modules are MyModule.SubModule1, MyModule.SubModule2.SubSubModule, MyOtherModule.SubModule1 then the top level module is MyModule.
We register this module as following:

//file: /src/packages/exports/exports.js
MyModule = {}
MyOtherModule = {} // another example

and

//file: /src/packages/exports/package.js

...
api.export('MyModule');
api.export('MyOtherModule');
...

Registering module in package assures that the top level module definition is loaded before any other module extension. In the next section, we explain what options we have in building the project.

Building With Gulp

All the magic needed to run the project is hidden inside the gulp files.
Just choose your favourite editor (i.e. Sublime, Webstorm, Visual Studio Code) and run the gulp files to compile your project. These are the tasks that are currently supported:

  • build – Builds the project into /src/js directory, ignoring modules
  • build-debug-modules – Builds the project into /src/js with multiple files, where files are split into three directories (client, server and lib). Files are prefixed by number assuring the correct load order. The load order is detected from references within “*.ts” files.
  • build-release-modules – Builds the project into three files: client/client.js, lib/lib.js and server/server.js.
  • tslint – Runs a tslint

Editors

This boilerplate has been pre-configured for:

  1. Visual Studio Code
  2. WebStorm

Files

Please check out the Typescript-Utils page to see how to write
awesome statically typed template helpers, events and routes.

Packages

We have chosen Semantic UI as the main front end framework.
You can control which components are used in ‘/src/client/lib/semanticui/custom.semantic.json’

  • semantic:ui – Beautifully crafted web pages with Semantic UI
  • useraccounts:semantic-ui – User accounts manipulation in semantic ui
  • accounts-password – Authentification package
  • flemay:autoprefixer – Vital helper for Semantic UI package (can be removed if Semantic UI is removed)
  • iron:router – Popular routing solution
  • multiply:iron-router-progress – Visual progress display (progress colors are defined in /client/stylesheets/progress.css)
  • meteorhacks:subs-manager – Subscription manager to save traffic
  • meteorhacks:fast-render – Blazing fast page load, no more waiting
  • alanning:roles – Roles management
  • dataflows:typescript-utils – Typescript goodies

We leave the rest of the packages up to you, not to annoy you too much.

Karma

This project has been configured to run with Karma (e.g. in Webstorm) for super fast unit testing.
Just click on Run Configurations -> add -> Karma.

You can install karma cli if you plan to run karma tests in terminalsudo npm install -g karma-cli.
You can now simply run karma start.

Structure

  • gulp — Definition of gulp tasks and jobs
  • node_modules
  • src — Your Meteor App
    • js — Generated files
  • typescript — Typescript definition files

License

This project is provided on the MIT license.

Advertisements

Meteor Typescript – Classes and Modules

Typescript is slowly making its name, when only recently Google announced joning forces with Microsoft in creating Typescript 1.5 as the main scripting language for Angular 2.0. Also, Webstorm 10 now supports typescript 1.4 and make Typescript programming a breeze.

Meteor is making it particulary difficult to use typescript as the scripting language due to the following reasons:

  1. The load order of javascript files
  2. Isolation of the code within defined files with var

Imagine following scenario with two classes belonging into the same module, split across two files:

//file:foo.ts
module Hugo {
  export class Foo {
    fooMe():string {
      return 'foo'
    }
  }
}

and

/// <reference path="foo.ts"/>
module Hugo {
 export class Bar extends Foo {
    bar () : string {
      return 'bar1';
    }
  }
}

Load Order

While previous example compiles typescript just fine, you will end up with the following error in the console:

TypeError: Cannot read property 'prototype' of undefined
at __extends (app/models/bar.js:5:21)

I would love to tell you that there exists an automatic method of solving these issues, but till Meteor releases a more sophisticated build mechanism, we are either depending on some dirty hacks, or as I prefer to do this, is to check the current load order and adjust my application accordingly. If you do not mind to do this, you are down to three options.

Option 1: Flat Directory Structure

With the flat directory structure, you depend on alphabetical ordering of loaded files, and therefore you rename foo.ts to _foo.ts so that it will load before bar.ts.

Option 2: Hierarchical Directories

Meteor (for some reason) loads first the child directories, so you can place the foo.ts into a sub-directory, e.g. /dependencies/foo.ts.

Option 3: Libs

Since meteor loads lib directories first, you can place foo.ts into the lib directory and your problem is solved.

All three options suffer from major problem, when in complex projects it becomes very difficult to maintain.

Modules

I have spent significant amount of time trying to figure out the best way of dealing with modules and classes across multiple files. The problem lies with Typescript compiler automatically emitting the var ModuleName making the module private to the given script file. Following is the compiled code for the Foo class:

var Hugo;
(function (Hugo) {
    var Foo = (function () {
        function Foo() {
        }
        Foo.prototype.fooMe = function () {
            return 'fooMe1';
        };
        return Foo;
    })();
    Hugo.Foo = Foo;
})(Hugo || (Hugo = {}));

Finding inspiration on StackOverflow we can easily deal with exporting the module to the global namespace by using this.Hugo = Hugo leading to the following source:

//file:foo.ts
module Hugo {
  export class Foo {
    fooMe():string {
      return 'foo'
    }
  }
}
this.Hugo = Hugo;

Yet, we still keep receiving the Cannot read property 'prototype' of undefined error. The reason for this is, that var Hugo is also emitted into the compiled Bar.js file, ignoring the value in this.Hugo and using empty object {} in the constructor.

var Hugo; // ignores this.Hugo
(function (Hugo) {
    var Bar = (function (_super) {
        __extends(Bar, _super);
        function Bar() {
            _super.apply(this, arguments);
        }
        Bar.prototype.bar = function () {
            return 'bar1';
        };
        return Bar;
    })(Hugo.Foo);
    Hugo.Bar = Bar; // Hugo here is {}
})(Hugo || (Hugo = {}));

Solution

I have checked several options of how to deal with this problem, among which was creating external modules with commonjs and importing them with mrt:exports package, but that solution was not producing any good results. Therefore, I decided to use the evil eval command and redefine the module variable before it gets redefined again. Here is the code:

eval('var Hugo = (this.Hugo || (this.Hugo = {})'); // this will override the automatically emitted var Hugo and assigns it with globally defined Hugo module 

module Hugo {
  export class Foo {
    fooMe():string {
      return 'foo'
    }
  }
}
/// <reference path="lib/foo.ts"/>
eval('var Hugo = (this.Hugo || (this.Hugo = {})'); // this will override the automatically emitted var Hugo and assigns it with globally defined Hugo module 

module Hugo {
 export class Bar extends Foo {
    bar () : string {
      return 'bar1';
    }
  }
}

While neither of these solutions are particularly pretty, they allow to confortably use the Typescript within Meteor, and once Meteor releases more sophisticated support for load ordering anf modules, it can be easily replaced.

If you have any better solution please share it with me.

Growl with Semantic UI

I have modified a jQuery plugin bootstrap growl jquery plugin to be used with Semantic UI.

Extra option is:

  • header: String – renders the header of the growl message

Forked from https://github.com/TimHeckel/meteor-bootstrap-growl

How to use?

Simply install with meteor add tomi:semantic-ui-growl

Example use:

$.semanticUiGrowl('My message', {
    header: 'My Header'
  });

Default Options

$.semanticUiGrowl.defaultOptions = {
  ele: 'body',
  type: 'info',
  offset: {
    from: 'top',
    amount: 20
  },
  align: 'right',
  width: 250,
  delay: 4000,
  allow_dismiss: true,
  stackup_spacing: 10
};

Written with StackEdit.

Meteor Upload File with jQuery File Upload

[EDIT 18/2/2014] Full Cordova support straight out of the box! Follow the guidelines at the project Github page

[EDIT 20/12/2014]: Due to big amount of requests I have added the drag and drop support for file uploads. Currently upload of drag and dropped files cannot be canceled

[EDIT 10/11/2014]: For the most updated documentation to this upload control please go to the project Github page.

In this post I’ll introduce two new packages using which you can easily setup a file upload service to your Meteor server. This solution has following perks:

  1. Uses the famous jQuery file upload system from blueimp.
    1. As a result you can upload any file size (default limit 11GB)
    2. Displays upload progress
    3. Uploads can be canceled
    4. You can upload multiple files
    5. Images can be resized to various sizes using the imagemagick
  2. Saves and serves file from arbitrary folder on your server. This solves problems with hot-code reload, when files are saved into Meteor’s public directory
    1. Possibility to save files to subfolders
    2. Possibility to rename files on the server
  3. Simple configuration and installation! No need to install 10+ packages like with Collection-FS solution

Here is a screenshot of the interface:

Screenshot

Please note that since we are using blueimp’s juery solution, this solution has a full potential of performing client image resizes, chunked uploads, upload resume and more. These features will be gradually added. Pull requests are welcome!

Demo application can be found on Github

Quick Start

Install packages

$ meteor add tomi:upload-server
$ meteor add tomi:upload-jquery

Create directory in the application root

mkdir -p .uploads/tmp

Configure server

//file:/server/init.js
Meteor.startup(function () {
  UploadServer.init({
    tmpDir: process.env.PWD + '/.uploads/tmp',
    uploadDir: process.env.PWD + '/.uploads/'
  })
});

Use template

<template name="yourTemplate">
    {{> upload_bootstrap }}
</template>

DONE!

Installation

The installation is very simple and consists of installing two packages:

$ meteor add tomi:upload-server
$ meteor add tomi:upload-jquery

I have separated these two packages, because often you will want to run your upload service as a separate application. There are several options supported by blueimp, such as Java, ASP, Ruby and more or even node.js Express server installed via NPM. If you wish to use self standing server, install only the tomi:upload-jquery package.

Configuration

Server

First, we need to initialise the server and configure upload paths (see below for explanation of the different options):

//file:/server/init.js
Meteor.startup(function () {
  UploadServer.init({
    tmpDir: '/Users/tomi/Documents/Uploads/tmp',
    uploadDir: '/Users/tomi/Documents/Uploads/',
    getDirectory: function(file, formData) {
      return formData.contentType;
    },
    finished: function(file, folder, formFields) {
      console.log('Write to database: ' + folder + '/' + file);
    }
  })
});

Following options are available for UploadServer.init(options):

Field Type Default Description
tmpDir String null Temporary upload directory
uploadDir String null Path to the upload directory
uploadUrl String ‘/upload/’ Upload route
maxPostSize int 11000000000 Maximum post size (11 GB)
minFileSize int 1 Minimum file size
maxFileSize int 10000000000 Maximum file size (10 GB)
acceptFileTypes RegEx /.+/i, Accepted types of files (e.g. prohibit .exe)
imageTypes RegEx Images which can be resized with Imagemagick (e.g. /\.(gif\|jpe?g\|png)$/i)
imageVersions Object {} Defines the sizes of images which will be converted and saved to upload directory. For example {thumbnailBig: {width: 400, height: 300}, thumbnailSmall: {width: 200, height: 100}}
getDirectory function functions which decides the subdirectory in which the file will be saved. In this function is not defined, no sub-directory is created. For example: function(file, formData) { return '/my/sub/directory'; }
getFileName function Renames the file on the server. In no function is specified, file is saved with the original file name. For example: `function(file, formData) { return ‘Saved-‘ + file; }
finished function Callback

Client

On client we have a possibility to use pre-defined templates, existing for semantic-ui and bootstrap. We can use several upload forms on the page. We can distinguish what kind of content we are uploading, by providing the contentType parameter. We can also limit the file type available for the file picker using parameter fileTypes. Following is an example of how you can include the upload form on your page:

<template name="home">
    <h3>PDFs</h3>
    {{> upload_semantic_ui contentType='pdfs' fileTypes='.pdf' }}
    <h3>Images</h3>
    {{> upload_bootstrap contentType='images' fileTypes='.jpg' multiple=true }}
</template>

If you wish to create your own template for uploading, you can use following call in the rendered callback to set up uploading functionality:

Template['your_upload_template'].rendered = function () {
  Uploader.render.call(this);
};

When using this approach, your template MUST contain following:

  • element with class .uploadFilePicker holding the file picker
  • element with class .uploadProgressHolder holding the file picker
  • element with class .uploadProgressBar showing the progress bar
  • element with class .uploadProgressLabel showing the progress label

If you wish to use custom URL for your uploads this can be configured as following:

Uploader.uploadUrl = 'http://yoururl';