If you got directly into here, please read the whole intro section explaining the goals for this tutorial and project.
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 tutorial we will focus on the Ionic
CLI; We will use it to serve the client and build 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.
If you are interested in the Meteor CLI, the steps needed to use it with Meteor are almost identical to the steps required by the Ionic CLI
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 blank --cordova --skip-link
Then we will add an empty declarations file, to later allow third party libraries to be used in our app even if they don't provide their own type declarations:
1
2
3
4
5
6
7
8
9
10
/*
Declaration files are how the Typescript compiler knows about the type information(or shape) of an object.
They're what make intellisense work and make Typescript know all about your code.
A wildcard module is declared below to allow third party libraries to be used in an app even if they don't
provide their own type declarations.
To learn more about using third party libraries in an Ionic app, check out the docs here:
http://ionicframework.com/docs/v2/resources/third-party-libs/
For more info on type definition files, check out the Typescript docs here:
https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html
*/
To start our app, simply type:
$ ionic serve
To prevent ionic-app-scripts bug #1052 let's upgrade Typescript to 2.4:
$ npm install --save typescript@~2.4
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:
In order to apply TypeScript
, Ionic
's build system is built on top of a module bundler called Webpack.
In this tutorial we will use a custom build-config for 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:
38
39
40
41
42
43
44
45
"@ionic/app-scripts": "3.0.0",
"typescript": "^2.4.2"
},
"description": "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 node_modules/@ionic/app-scripts/config/webpack.config.js
. We will copy it to a newly created config
dir using the following command:
$ cp node_modules/@ionic/app-scripts/config/webpack.config.js .
The configuration file should look like so:
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
45
46
47
48
49
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
* The webpack config exports an object that has a valid webpack configuration
* For each environment name. By default, there are two Ionic environments:
* "dev" and "prod". As such, the webpack.config.js exports a dictionary object
* with "keys" for "dev" and "prod", where the value is a valid webpack configuration
* For details on configuring webpack, see their documentation here
* https://webpack.js.org/configuration/
*/
var path = require('path');
var webpack = require('webpack');
var ionicWebpackFactory = require(process.env.IONIC_WEBPACK_FACTORY);
var ModuleConcatPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
var PurifyPlugin = require('@angular-devkit/build-optimizer').PurifyPlugin;
var optimizedProdLoaders = [
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.js$/,
loader: [
{
loader: process.env.IONIC_CACHE_LOADER
},
{
loader: '@angular-devkit/build-optimizer/webpack-loader',
options: {
sourceMap: true
}
},
]
},
{
test: /\.ts$/,
loader: [
{
loader: process.env.IONIC_CACHE_LOADER
},
{
loader: '@angular-devkit/build-optimizer/webpack-loader',
options: {
sourceMap: true
}
},
{
loader: process.env.IONIC_WEBPACK_LOADER
}
]
}
];
function getProdLoaders() {
if (process.env.IONIC_OPTIMIZE_JS === 'true') {
return optimizedProdLoaders;
}
return devConfig.module.loaders;
}
var devConfig = {
entry: process.env.IONIC_APP_ENTRY_POINT,
output: {
path: '{{BUILD}}',
publicPath: 'build/',
filename: '[name].js',
devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),
},
devtool: 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$/,
loader: process.env.IONIC_WEBPACK_LOADER
}
]
},
plugins: [
ionicWebpackFactory.getIonicEnvironmentPlugin(),
ionicWebpackFactory.getCommonChunksPlugin()
],
// 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'
}
};
var prodConfig = {
entry: process.env.IONIC_APP_ENTRY_POINT,
output: {
path: '{{BUILD}}',
publicPath: 'build/',
filename: '[name].js',
devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),
},
devtool: process.env.IONIC_SOURCE_MAP_TYPE,
resolve: {
extensions: ['.ts', '.js', '.json'],
modules: [path.resolve('node_modules')]
},
module: {
loaders: getProdLoaders()
},
plugins: [
ionicWebpackFactory.getIonicEnvironmentPlugin(),
ionicWebpackFactory.getCommonChunksPlugin(),
new ModuleConcatPlugin(),
new PurifyPlugin()
],
// 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'
}
};
module.exports = {
dev: devConfig,
prod: prodConfig
}
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:
TypeScript
modules without any issues.Meteor
server under the api
dir (Which will be created later in).Meteor
packages and Cordova
plugins.To achieve these abilities, this is how our extension should look like:
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
99
100
101
102
103
104
105
106
107
108
110
111
112
113
114
115
116
117
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
145
146
147
148
149
150
151
152
153
154
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
resolve: {
extensions: ['.ts', '.js', '.json'],
modules: [path.resolve('node_modules')],
alias: {
'api': path.resolve(__dirname, 'api/server')
}
},
externals: [
resolveExternals
],
module: {
loaders: [
{
...some lines skipped...
plugins: [
ionicWebpackFactory.getIonicEnvironmentPlugin(),
ionicWebpackFactory.getCommonChunksPlugin(),
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
}
};
...some lines skipped...
resolve: {
extensions: ['.ts', '.js', '.json'],
modules: [path.resolve('node_modules')],
alias: {
'api': path.resolve(__dirname, 'api/server')
}
},
externals: [
resolveExternals
],
module: {
loaders: getProdLoaders()
},
...some lines skipped...
ionicWebpackFactory.getIonicEnvironmentPlugin(),
ionicWebpackFactory.getCommonChunksPlugin(),
new ModuleConcatPlugin(),
new PurifyPlugin(),
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 resolveMeteor(request, callback) ||
callback();
}
function resolveMeteor(request, callback) {
var match = request.match(/^meteor\/(.+)$/);
var pack = match && match[1];
if (pack) {
callback(null, 'Package["' + pack + '"]');
return true;
}
}
module.exports = {
dev: devConfig,
prod: prodConfig
}
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:
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
"target": "es5"
},
"include": [
"src/**/*.ts",
"api/**/*.ts"
],
"exclude": [
"node_modules",
"api/node_modules"
],
"compileOnSave": false,
"atom": {
"rewriteTsconfig": false
}
}
And we will need to install the following dependencies so the Webpack
config can be registered properly:
$ npm install --save-dev typescript-extends
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
2
3
4
5
6
7
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
29
30
31
32
33
34
35
36
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
...some lines skipped...
"dom",
"es2015"
],
"module": "commonjs",
"moduleResolution": "node",
"paths": {
"api/*": ["./api/server/*"]
},
"sourceMap": true,
"target": "es5",
"skipLibCheck": true,
"stripInternal": true,
"noImplicitAny": false,
"types": [
"@types/meteor"
]
},
"include": [
"src/**/*.ts",
...some lines skipped...
],
"exclude": [
"node_modules",
"api/node_modules",
"api"
],
"compileOnSave": false,
"atom": {
This configuration requires us to install the declaration files specified under the types
field:
$ npm install --save-dev @types/meteor
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 the expectation to be run in a mobile environment. To fix this warning, simply check if the current platform supports Cordova
before calling any methods related to it:
14
15
16
17
18
19
20
21
22
23
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 (platform.is('cordova')) {
statusBar.styleDefault();
splashScreen.hide();
}
});
}
}