Initial setup

Both Meteor and Ionic took their platform to the next level in tooling. They both provide CLI interfaces and build tools which will help you build a mobile-application.

In this post we will focus on the Ionic CLI; We will use it to serve the client and build the our project using Cordova, and we will use Meteor as a platform for our server, so we will be able to use Mongo collections and subscriptions.

The only pre-requirements for this tutorial is for you to have Node.JS version 5 or above installed. If you haven't already installed it, you can download it from its official website over here.

We will start by installing Ionic and Cordova globally:

$ npm install -g ionic cordova

We will create our Whatsapp-clone using the following command:

$ ionic start whatsapp --v2

To start our app, simply type:

$ ionic serve

For more information on how to run an Ionic-app on a mobile device, see the following link: https://ionicframework.com/docs/v2/getting-started/installation/.

Ionic 2 apps are written using Angular 2. Although Angular 2 apps can be created using plain JavaScript, it is recommended to write them using Typescript, for 2 reasons:

  • It prevents runtime errors.
  • Dependency injection is done automatically based on the provided data-types.

In order to apply TypeScript, Ionic's build system is built on top of a module bundler called Rollup.

In this tutorial we will use a custom build-config, and replace Rollup with Webpack. Both module-bundlers are great solutions for building our app, but Webpack provides us with some extra features like aliases and custom module-loaders which are crucial ingredients for our app to work properly.

Ionic 2 + Webpack

The first thing we gonna do would be telling Ionic that we're using Webpack as our module-bundler. To specify it, add the following field in the package.json file:

1.1 Added build configuration for Ionic app scripts package.json
37
38
39
40
41
42
43
44
    "ionic-plugin-keyboard"
  ],
  "cordovaPlatforms": [],
  "description": "Ionic2CLI-Meteor-Whatsapp: An Ionic project",
  "config": {
    "ionic_webpack": "./webpack.config.js"
  }
}

Ionic provides us with a sample Webpack config file that we can extend later on, and it's located under the path [email protected]/app-scripts/config/webpack.config.js. We will copy it to a newly created config dir using the following command:

$ cp [email protected]/app-scripts/config/webpack.config.js .

The configuration file should look like so:

1.2 Added webpack base file webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var path = require('path');
var webpack = require('webpack');
var ionicWebpackFactory = require(process.env.IONIC_WEBPACK_FACTORY);
 
module.exports = {
  entry: process.env.IONIC_APP_ENTRY_POINT,
  output: {
    path: '{{BUILD}}',
    filename: process.env.IONIC_OUTPUT_JS_FILE_NAME,
    devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),
  },
  devtool: process.env.IONIC_GENERATE_SOURCE_MAP ? process.env.IONIC_SOURCE_MAP_TYPE : '',
 
  resolve: {
    extensions: ['.ts', '.js', '.json'],
    modules: [path.resolve('node_modules')]
  },
 
  module: {
    loaders: [
      {
        test: /\.json$/,
        loader: 'json-loader'
      },
      {
        //test: /\.(ts|ngfactory.js)$/,
        test: /\.ts$/,
        loader: process.env.IONIC_WEBPACK_LOADER
      }
    ]
  },
 
  plugins: [
    ionicWebpackFactory.getIonicEnvironmentPlugin()
  ],
 
  // Some libraries import Node modules but don't use them in the browser.
  // Tell Webpack to provide empty mocks for them so importing them works.
  node: {
    fs: 'empty',
    net: 'empty',
    tls: 'empty'
  }
};

As we said earlier, this is only a base for our config. We would also like to add the following abilities while bundling our project:

  • The ability to load external TypeScript modules without any issues.
  • Have an alias for our Meteor server under the api dir (Which will be created later in).
  • Be able to import Meteor packages and Cordova plugins.

To achieve these abilities, this is how our extension should look like:

1.3 Updated webpack file webpack.config.js
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 
39
40
41
42
43
44
45
46
47
48
 
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
  },
  devtool: process.env.IONIC_GENERATE_SOURCE_MAP ? process.env.IONIC_SOURCE_MAP_TYPE : '',
 
  externals: [
    'cordova',
    resolveExternals
  ],
 
  resolve: {
    extensions: ['.ts', '.js', '.json'],
    modules: [path.resolve('node_modules')],
    alias: {
      'api': path.resolve(__dirname, 'api')
    }
  },
 
  module: {
...some lines skipped...
  },
 
  plugins: [
    ionicWebpackFactory.getIonicEnvironmentPlugin(),
    new webpack.ProvidePlugin({
      __extends: 'typescript-extends'
    })
  ],
 
  // Some libraries import Node modules but don't use them in the browser.
...some lines skipped...
  node: {
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    __dirname: true
  }
};
 
function resolveExternals(context, request, callback) {
  return meteorPackage(request, callback) ||
         cordovaPlugin(request, callback) ||
         callback();
}
 
function meteorPackage(request, callback) {
  var match = request.match(/^meteor\/(.+)$/);
  var pack = match && match[1];
 
  if (pack) {
    callback(null, 'window.Package && Package["' + pack + '"]');
    return true;
  }
}
 
function cordovaPlugin(request, callback) {
  var match = request.match(/^cordova\/(.+)$/);
  var plugin = match && match[1];
 
  if (plugin) {
    plugin = camelCase(plugin);
    plugin = upperFirst(plugin);
    callback(null, 'window.cordova && cordova.plugins && cordova.plugins.' + plugin);
    return true;
  }
}

In addition to the alias we've just created, we also need to tell the TypesScript compiler to include the api dir during the compilation process:

1.4 Include API files in typescript config tsconfig.json
14
15
16
17
18
19
20
21
22
23
24
25
    "target": "es5"
  },
  "include": [
    "src/**/*.ts",
    "api/**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "api/node_modules"
  ],
  "compileOnSave": false,
  "atom": {

And we will need to install the following dependencies so the Webpack config can be registered properly:

$ npm install --save-dev typescript-extends
$ npm install --save-dev lodash.camelcase
$ npm install --save-dev lodash.upperfirst

TypeScript Configuration

Now, we need to make some modifications for the TypeScript config so we can load Meteor as an external dependency; One of the changes include the specification for CommonJS:

1.6 Updated typscript compiler config tsconfig.json
8
9
10
11
12
13
14
15
16
17
18
19
20
      "dom",
      "es2015"
    ],
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "target": "es5",
    "skipLibCheck": true,
    "stripInternal": true,
    "noImplicitAny": false
  },
  "include": [
    "src/**/*.ts",

Trying It Out

By this point, you can run ionic serve and test how our application works with the new module bundler we've just configured. You might encounter the following warnings when launching the app in the browser:

Native: tried calling StatusBar.styleDefault, but Cordova is not available. Make sure to include cordova.js or run in a device/simulator
Native: tried calling Splashscreen.hide, but Cordova is not available. Make sure to include cordova.js or run in a device/simulator

This is caused due to expectation to be run in a mobile environment. To fix this warning, simply check if Cordova is defined on the global scope before calling any methods related to it:

1.7 Check if cordova exists src/app/app.component.ts
15
16
17
18
19
20
21
22
23
24
    platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      if (window.hasOwnProperty('cordova')) {
        StatusBar.styleDefault();
        Splashscreen.hide();
      }
    });
  }
}