{"id":1178,"date":"2016-07-07T16:29:23","date_gmt":"2016-07-07T16:29:23","guid":{"rendered":"https:\/\/ionic.io\/blog\/?p=1178"},"modified":"2017-04-03T19:51:09","modified_gmt":"2017-04-03T19:51:09","slug":"advanced-workflows-for-building-rock-solid-ionic-apps-part-3","status":"publish","type":"post","link":"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3","title":{"rendered":"Advanced Workflows for Building Rock-Solid Ionic Apps, Part 3"},"content":{"rendered":"<p><em><a href=\"https:\/\/github.com\/gruppjo\">Jonathan Grupp<\/a> is a software engineer at <a href=\"http:\/\/www.mwaysolutions.com\/en\/enterprise-mobility-management\/\">M-Way Solutions<\/a>, the owner and maintainer of Generator-M-Ionic, and a frequent contributor to Ionic. He has written a three-part series on advanced workflows for building rock-solid Ionic apps. This is Part 3.<\/em><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1283\" height=\"721\" data-src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03.png\" alt=\"blog-03\" class=\"aligncenter size-full wp-image-1185 lazyload\" data-srcset=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03.png 1283w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03-300x169.png 300w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03-768x432.png 768w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03-1024x575.png 1024w\" data-sizes=\"auto, (max-width: 1283px) 100vw, 1283px\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" style=\"--smush-placeholder-width: 1283px; --smush-placeholder-aspect-ratio: 1283\/721;\" \/><noscript><img loading=\"lazy\" decoding=\"async\" width=\"1283\" height=\"721\" src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03.png\" alt=\"blog-03\" class=\"aligncenter size-full wp-image-1185\" srcset=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03.png 1283w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03-300x169.png 300w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03-768x432.png 768w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03-1024x575.png 1024w\" sizes=\"auto, (max-width: 1283px) 100vw, 1283px\" \/><\/noscript><\/p>\n<p>The air is getting a little thin in app-development orbit! Luckily, Part 3 of this series comes with a sweet oxygen mask full of life-saving elements: environments, CORS proxies, and build tools for app icons, splash screens, build variables, and app delivery. Take a deep breath and extend the project we made space-ready in <a href=\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-2\/\">Part 2<\/a>.<br \/>\n<!--more--><\/p>\n<h3>Gulp Environments<\/h3>\n<p>You have your app all set up, and you added plugins and other components, while implementing your app logic. At some point, you will probably want to exchange data with an external backend. No problem: Just inject the good old Angular <a href=\"https:\/\/docs.angularjs.org\/api\/ng\/service\/$http\">$http service<\/a>, handle all the HTTP calls with that nice Angular <a href=\"https:\/\/docs.angularjs.org\/api\/ng\/service\/$q\">promise API<\/a>, and you&#8217;re done. For instance, let&#8217;s suppose I want to shoot some requests at the <a href=\"https:\/\/echo.getpostman.com\">Postman Echo API<\/a>.<\/p>\n<pre><code class=\"js\">$http.get(&#039;https:\/\/echo.getpostman.com\/get&#039;)\n.then(function (response) {\n  console.log(response);\n});\n<\/code><\/pre>\n<p>Yeah! It&#8217;s as simple as that!<\/p>\n<blockquote><p>\n  The customer changes the URL of the endpoint (that&#8217;s the beta version of the API): <code>https:\/\/blabber.getpostman.com<\/code>.\n<\/p><\/blockquote>\n<p>Urgh. <em>&#8220;OK, if that keeps happening, I&#8217;m going to put the URL in a constant,&#8221;<\/em> you might think. Clever! Luckily, your project already comes with a <code>Config<\/code> constant in <code>app\/main\/constants\/config-const.js<\/code>. Just adapt your code.<\/p>\n<pre><code class=\"js\">.constant(&#039;Config&#039;, {\n  API_ENDPOINT: &#039;https:\/\/blabber.getpostman.com&#039;\n  \/\/ ..\n});\n<\/code><\/pre>\n<pre><code class=\"js\">$http.get(Config.API_ENDPOINT + &#039;\/get&#039;)\n.then(function (response) {\n  console.log(response);\n});\n<\/code><\/pre>\n<blockquote><p>\n  The customer tells you that some of the features you need to implement only work with the beta API <code>https:\/\/blabber.getpostman.com<\/code>, and some only work with the production API <code>https:\/\/echo.getpostman.com<\/code>.\n<\/p><\/blockquote>\n<p>You might worry that you&#8217;ll need to exchange the endpoint URL all the time, but worry not; <a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\/blob\/master\/docs\/guides\/environments.md\">Environments<\/a> to the rescue! There&#8217;s two JSON files in <code>app\/main\/constants\/<\/code>, which you will fill with the endpoints:<\/p>\n<pre><code class=\"js\">\/\/ env-dev.json\n{\n  &quot;API_ENDPOINT&quot;: &quot;https:\/\/blabber.getpostman.com&quot;\n}\n<\/code><\/pre>\n<pre><code class=\"js\">\/\/ env-prod.json\n{\n  &quot;API_ENDPOINT&quot;: &quot;https:\/\/echo.getpostman.com&quot;\n}\n<\/code><\/pre>\n<p>Modify your controller code:<\/p>\n<pre><code class=\"js\">$http.get(Config.ENV.API_ENDPOINT + &#039;\/get&#039;)\n.then(function (response) {\n  console.log(response);\n});\n<\/code><\/pre>\n<p>Now, if you run one of these:<\/p>\n<pre><code class=\"sh\">gulp watch\n# or\ngulp build\n# or\ngulp --cordova &quot;run &lt;platform&gt;&quot;\n# or anything similar\n<\/code><\/pre>\n<p>Without any flags, it will default to the dev environment, and your HTTP calls will end up against the <code>API_ENDPOINT<\/code> of your <code>env-dev.json<\/code>:  <code>https:\/\/blabber.getpostman.com<\/code>. How? Because the environments task injects the contents of the JSON file in the <code>ENV<\/code> object of your <code>Config<\/code> constant:<\/p>\n<pre><code class=\"js\">angular.module(&#039;main&#039;)\n.constant(&#039;Config&#039;, {\n\n  \/\/ gulp environment: injects environment vars\n  ENV: {\n    \/*inject-env*\/\n    &#039;API_ENDPOINT&#039;: &#039;https:\/\/blabber.getpostman.com&#039;\n    \/*endinject*\/\n  },\n  \/\/ ..\n});\n<\/code><\/pre>\n<p>On the other hand, adding the <code>--env=prod<\/code> flag like below will send your HTTP calls to <code>https:\/\/echo.getpostman.com<\/code>, the <code>API_ENDPOINT<\/code> of your <code>env-prod.json<\/code>.<\/p>\n<pre><code>gulp watch --env=prod\n<\/code><\/pre>\n<p>YEAH!<\/p>\n<p>What&#8217;s this sorcery? Environments come in handy for a variety of tasks: managing different API keys, tokens, logging levels, feature switches, and, as we just found out, API endpoints. And the best part: you can have as many environments as you want! A more detailed explanation of this incomprehensible, yet so appealing, piece of magic is available in our <a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\/blob\/master\/docs\/guides\/environments.md\">Environments Guide<\/a>.<\/p>\n<h3>CORS &amp; Proxying<\/h3>\n<p>Phew, we took care of that API-spaghetti. But wait, what&#8217;s that? Suddenly, something like this shows up in your development console as you send an HTTP request. And it is in radiant red!<\/p>\n<pre><code>XMLHttpRequest cannot load https:\/\/echo.getpostman.com\/. The\n&#039;Access-Control-Allow-Origin&#039; header contains the invalid value &#039;&#039;.\nOrigin &#039;http:\/\/localhost:3000&#039; is therefore not allowed access.\n<\/code><\/pre>\n<p>Most of us have been there: <em>&#8220;Hello, improperly configured REST API!&#8221;<\/em>. And believe me, my team and I have been beaten by issues with <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Access_control_CORS\">CORS<\/a> (Cross-Origin Resource Sharing) MANY times, so I wrote down a little guide on how to deal with <a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\/blob\/master\/docs\/guides\/cors_proxy.md\">CORS issues &amp; Proxying<\/a> in general. There are a variety of options, and depending in your project, some might work better than others.<\/p>\n<p>One of the most straightforward ones, which is also covered in the guide I just mentioned, is the <a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\/blob\/master\/docs\/guides\/cors_proxy.md#built-in-proxy\">built-in proxy<\/a> that comes with your Generator-M-Ionic project.<\/p>\n<p>Remember that piece of code from earlier? This will actually run into a CORS error using that API endpoint:<\/p>\n<pre><code class=\"js\">$http.get(&#039;https:\/\/echo.getpostman.com\/get&#039;)\n.then(function (response) {\n  console.log(response);\n});\n<\/code><\/pre>\n<p>In order to configure the built-in proxy, I&#8217;ll just start <code>gulp watch<\/code> with the following parameters:<\/p>\n<pre><code class=\"sh\">gulp watch --proxyPath=\/proxy --proxyMapTo=https:\/\/echo.getpostman.com\n<\/code><\/pre>\n<p>And now, the following request to my proxy will give me the response from <code>https:\/\/echo.getpostman.com\/get<\/code> that I want.<\/p>\n<pre><code class=\"js\">$http.get(&#039;\/proxy\/get&#039;)\n.then(function (response) {\n  console.log(response);\n});\n<\/code><\/pre>\n<p>In fact, now I can send any request of the pattern <code>\/proxy\/**\/*<\/code>, and it&#8217;ll be mapped to <code>https:\/\/echo.getpostman.com\/**\/*<\/code> through the proxy. This goes especially nicely with the generator&#8217;s <a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\/blob\/master\/docs\/guides\/environments.md\">Environments feature<\/a>, because then you can just code it like this:<\/p>\n<pre><code class=\"js\">$http.get(Config.ENV.API_ENDPOINT + &#039;\/get&#039;)\n.then(function (response) {\n  console.log(response);\n});\n<\/code><\/pre>\n<p>With the contents of your environment JSON files being:<\/p>\n<pre><code class=\"js\">\/\/ env-dev.json\n{\n  &quot;API_ENDPOINT&quot;: &quot;\/proxy&quot;\n}\n<\/code><\/pre>\n<pre><code class=\"js\">\/\/ env-prod.json\n{\n  &quot;API_ENDPOINT&quot;: &quot;https:\/\/echo.getpostman.com&quot;\n}\n<\/code><\/pre>\n<p>And then switch between firing against the proxy or directly at the API using the <code>--env<\/code> flag:<\/p>\n<pre><code class=\"sh\"># will default to --env=dev and thus use the proxy\ngulp watch --proxyPath=\/proxy --proxyMapTo=https:\/\/echo.getpostman.com\n\n# the built version will fire direclty against the API\ngulp build --env=prod\n<\/code><\/pre>\n<p>As simple as that! No more manual, tedious, and error-prone changing of the endpoints.<\/p>\n<p>Quite useful, isn&#8217;t it?<\/p>\n<h3>Default tasks<\/h3>\n<p>But yes, I get it, typing that whole thing also is kind of tedious:<\/p>\n<pre><code class=\"sh\">gulp watch --proxyPath=\/proxy --proxyMapTo=https:\/\/echo.getpostman.com\n<\/code><\/pre>\n<p>Especially if you add <code>--env=prod<\/code> or <code>--no-open<\/code> to the list, which I like to use to prevent the browser from opening a new window every time. Wouldn&#8217;t it be nice if my project just <em>knew<\/em> that I want those flags to run with <code>gulp watch<\/code>? We thought so, too! So that&#8217;s why we built this neat little task called <code>gulp defaults<\/code>. The syntax is fairly easy:<\/p>\n<pre><code class=\"sh\">gulp defaults --set=&quot;watch --no-open --proxyPath=\/proxy --proxyMapTo=https:\/\/echo.getpostman.com&quot;\n<\/code><\/pre>\n<p>Now, every time I run <code>gulp watch<\/code>, Gulp will add those three flags for me. Woohoo! It even reminds me which flags I&#8217;ve set on every run, so I don&#8217;t get confused.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1424\" height=\"128\" data-src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/gulp_defaults.png\" alt=\"gulp_defaults\" class=\"aligncenter size-full wp-image-1187 lazyload\" data-srcset=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/gulp_defaults.png 1424w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/gulp_defaults-300x27.png 300w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/gulp_defaults-768x69.png 768w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/gulp_defaults-1024x92.png 1024w\" data-sizes=\"auto, (max-width: 1424px) 100vw, 1424px\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" style=\"--smush-placeholder-width: 1424px; --smush-placeholder-aspect-ratio: 1424\/128;\" \/><noscript><img loading=\"lazy\" decoding=\"async\" width=\"1424\" height=\"128\" src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/gulp_defaults.png\" alt=\"gulp_defaults\" class=\"aligncenter size-full wp-image-1187\" srcset=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/gulp_defaults.png 1424w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/gulp_defaults-300x27.png 300w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/gulp_defaults-768x69.png 768w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/gulp_defaults-1024x92.png 1024w\" sizes=\"auto, (max-width: 1424px) 100vw, 1424px\" \/><\/noscript><\/p>\n<p>Learn more about Gulp defaults and how to clear, overwrite, and share defaults across your team via Git in our <a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\/blob\/master\/docs\/guides\/gulp_defaults.md\">Gulp defaults Guide<\/a>.<\/p>\n<h3>Building and delivering your app<\/h3>\n<p>The first testable version of your app is ready. You&#8217;re proud, and the customer is eagerly waiting to test your app. The customer has the following requirements for the hand-over:<br \/>\n&#8211; deliver two apps<br \/>\n&#8211; one for each of the APIs and to not confuse the customer:<br \/>\n&#8211; a different app icon and splash screen for each version<br \/>\n&#8211; show the date and time of the build in the app<\/p>\n<p><a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\">Generator-M-Ionic<\/a> has tools for all of that.<\/p>\n<h4>Building your app<\/h4>\n<p>Building your app with Cordova is just as easy as running your app on a device:<\/p>\n<pre><code class=\"sh\">gulp --cordova &quot;build ios&quot;\n<\/code><\/pre>\n<p>Remember that this implicitly runs <code>gulp build<\/code> first and then triggers Cordova&#8217;s <a href=\"https:\/\/cordova.apache.org\/docs\/en\/latest\/reference\/cordova-cli\/index.html#cordova-build-command\">build command<\/a>, as explained in our <a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\/blob\/master\/docs\/guides\/development_intro.md#using-the-cordova-cli\">Development Introduction<\/a>, and is the equivalent of running these two one after another:<\/p>\n<pre><code class=\"sh\">gulp build\ngulp --cordova &quot;build ios&quot; --no-build\n<\/code><\/pre>\n<p>However, for proper code-signing, it&#8217;s sometimes necessary to go with Cordova&#8217;s <a href=\"https:\/\/cordova.apache.org\/docs\/en\/latest\/reference\/cordova-cli\/index.html#cordova-prepare-command\">prepare command<\/a> and then open the platform&#8217;s project located in <code>platforms\/<\/code> and perform the code-signing, building and all that, for instance, in <a href=\"https:\/\/developer.apple.com\/xcode\/\">Xcode<\/a> when building for iOS.<\/p>\n<pre><code class=\"sh\">gulp --cordova &quot;prepare ios&quot; # also runs `gulp build` first\n<\/code><\/pre>\n<h4>Build for different APIs<\/h4>\n<p>Since we&#8217;ve already configured our <a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\/blob\/master\/docs\/guides\/environments.md\">environments<\/a> properly, building for two different APIs is just as easy as developing for two different APIs. And we already know how to do that using <code>gulp watch<\/code> and the <code>--env<\/code> flag. To build one app for each environment is just as easy:<\/p>\n<pre><code class=\"sh\"># build with dev environment\ngulp --cordova &quot;build ios&quot; --env=dev\n\n# build with prod environment\ngulp --cordova &quot;build ios&quot; --env=prod\n<\/code><\/pre>\n<p>The <code>--env=dev<\/code> flag is not necessary, since the environment always defaults to the dev environment with every command but I like to be explicit here to make it more obvious what&#8217;s happening.<\/p>\n<p>Ok, done! Building different apps for each API. Next challenge!<\/p>\n<h4>Build with different app icons and splash screens<\/h4>\n<p>I&#8217;m not going to go into the particulars of this one, since there&#8217;s a perfectly nice <a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\/blob\/master\/docs\/guides\/icons_splash_screens.md\">Icons and splash screens Guide<\/a> in our documentation. Whether you just have one set of icons and splash screens or ten, it&#8217;s fairly simple to set up and use. After proper configuration all you need to do in order to build with different resources is to add the <code>--res<\/code> flag like so:<\/p>\n<pre><code class=\"sh\"># build with dev set of resources\ngulp --cordova &quot;build ios&quot; --res=dev\n\n# build with prod set of resources\ngulp --cordova &quot;build ios&quot; --res=prod\n<\/code><\/pre>\n<p>So, final frontier! How do we display the date and time of the build in our app?<\/p>\n<h4>Injecting variables from the command line<\/h4>\n<p>At some point, especially when you&#8217;re automating the build process of your apps by <a href=\"https:\/\/en.wikipedia.org\/wiki\/Continuous_integration\">continuously integrating<\/a> with <a href=\"https:\/\/jenkins.io\/\">Jenkins<\/a> or <a href=\"https:\/\/travis-ci.org\/\">Travis<\/a> you might want to inject data into your app through the command line. Granted, showing the date and time of a build in the app is not a very useful use-case. In reality, you&#8217;re more likely to find yourself wanting to append build numbers into your app&#8217;s version number, which in turn is read from the <code>config.xml<\/code> and inject both of them in the app. And guess what? There&#8217;s a <a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\/blob\/master\/docs\/guides\/programmatically_change_configxml.md\">guide for doing both of those things<\/a>. But for now, to keep things simple and for the lack of a better example, we&#8217;ll just stick with injecting something simple, like a date.<\/p>\n<p>You may have noticed that in your <code>Config<\/code> constant is another block that looks similar to the one where the whole <a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\/blob\/master\/docs\/guides\/environments.md\">environment variables<\/a> get injected. That block is exactly the place where you can inject variables into your app from the command line using <code>gulp watch<\/code> and any tasks that uses <code>gulp build<\/code> implicitly. See how:<\/p>\n<pre><code class=\"sh\"># running $(date +%s) will only work on Unix\ngulp watch --buildVars=&quot;buildTime:$(date +%s),buildNo:12&quot;\n<\/code><\/pre>\n<p>Running the above command will produce this output in the <code>Config<\/code> const:<\/p>\n<pre><code class=\"js\">angular.module(&#039;main&#039;)\n.constant(&#039;Config&#039;, {\n\n  \/\/ gulp environment: injects environment vars\n  ENV: {\n    \/*inject-env*\/\n    &#039;API_ENDPOINT&#039;: &#039;https:\/\/blabber.getpostman.com&#039;\n    \/*endinject*\/\n  },\n\n  \/\/ gulp build-vars: injects build vars\n  BUILD: {\n    \/*inject-build*\/\n    &#039;buildTime&#039;: &#039;1462972664&#039;,\n    &#039;buildNo&#039;: &#039;12&#039;\n    \/*endinject*\/\n  }\n\n});\n<\/code><\/pre>\n<p>So you can map in one of your controllers:<\/p>\n<pre><code class=\"js\">this.build = Config.BUILD;\n<\/code><\/pre>\n<p>And using the Angular <a href=\"https:\/\/docs.angularjs.org\/api\/ng\/filter\/date\">date filter<\/a> in the matching template show the date and build number in your app:<\/p>\n<pre><code class=\"html\">&lt;ion-item class=&quot;item item-icon&quot;&gt;\n  &lt;!-- multiply by 1000 to convert from unix to JS timestamp --&gt;\n  {{ctrl.build.buildTime * 1000 | date:&#039;medium&#039;}}\n&lt;\/ion-item&gt;\n&lt;ion-item class=&quot;item item-icon&quot;&gt;\n  {{ctrl.build.buildNo}}\n&lt;\/ion-item&gt;\n<\/code><\/pre>\n<h4>Putting it all together<\/h4>\n<p>The only thing left to do is to merge all of what we just did for our build. Build the app with different environments, different sets of resources, and inject variables from the command line:<\/p>\n<pre><code class=\"sh\"># build dev version\ngulp build --env=dev --res=dev --buildVars=&quot;buildTime:$(date +%s),buildNo:12&quot;\ngulp --cordova &quot;build ios&quot; --no-build\n\n# build prod version\ngulp build --env=prod --res=prod --buildVars=&quot;buildTime:$(date +%s),buildNo:12&quot;\ngulp --cordova &quot;build ios&quot; --no-build\n<\/code><\/pre>\n<p>At this point, I like to explicitly separate the web app build from the Cordova build, so it doesn&#8217;t get messy, and it&#8217;s a little more apparent what we&#8217;re actually doing. Don&#8217;t forget the <code>--no-build<\/code> option when running the Cordova part of your build, or it will implicitly run <code>gulp build<\/code> and overwrite your previous <code>gulp build<\/code> without any of the options you supplied earlier.<\/p>\n<h3>Deliver<\/h3>\n<p>Now that you have built two runnable apps, how do you get them to your customer for testing purposes?<\/p>\n<ul>\n<li>an easy way to do so is via the Ionic Platform services, once you&#8217;ve integrated them into your project using the <a href=\"http:\/\/ionicframework.com\/docs\/cli\/\">Ionic CLI<\/a>. See how, in our <a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\/blob\/master\/docs\/ecosystems\/ionic_platform.md\">Ionic Platform Integration Guide<\/a><\/li>\n<\/ul>\n<h3>Bang!<\/h3>\n<p>**&#42;confettiandglitter rainingdownonyou***<\/p>\n<p>You&#8217;ve done it!<\/p>\n<p>You learned how to set up a project with <a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\">Generator-M-Ionic<\/a>, learned about its file structure, Git, quality assurance with linting &amp; testing, sub-generators, Sass, Plugins, a lot of Gulp tasks, ecosystems, environments, CORS, proxies, build vars, and many many other things.<\/p>\n<h3>Sparkly future<\/h3>\n<p>We&#8217;re living in exiting times. Angular 2 and Ionic 2 are around the corner, Typescript and ES6 are becoming viable choices, and we&#8217;re on it! Would you like to see a version of <a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\">Generator-M-Ionic<\/a> with those technologies? What features would you like with that? Let us know!<\/p>\n<h3>Get in touch<\/h3>\n<p>Feedback, ideas, and comments regarding this blog post or any of the features discussed here are very welcome in either the comments section below, at our <a href=\"https:\/\/github.com\/mwaylabs\/generator-m-ionic\">Generator-M-Ionic&#8217;s Github repository<\/a> or the <a href=\"https:\/\/gitter.im\/mwaylabs\/generator-m-ionic\">Generator-M-Ionic Gitter Chat<\/a>.<\/p>\n<h3>Credits<\/h3>\n<p>Author: <a href=\"https:\/\/github.com\/gruppjo\">Jonathan Grupp<\/a><br \/>\nHeadline illustrations: <a href=\"http:\/\/www.art-noir.net\/\">Christian Kahl<\/a><br \/>\nSpecial thanks to Volker Hahn, Mathias Maier, and Tim Lancina<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Jonathan Grupp is a software engineer at M-Way Solutions, the owner and maintainer of Generator-M-Ionic, and a frequent contributor to Ionic. He has written a three-part series on advanced workflows for building rock-solid Ionic apps. This is Part 3. The air is getting a little thin in app-development orbit! Luckily, Part 3 of this series [&hellip;]<\/p>\n","protected":false},"author":39,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"publish_to_discourse":"","publish_post_category":"","wpdc_auto_publish_overridden":"","wpdc_topic_tags":"","wpdc_pin_topic":"","wpdc_pin_until":"","discourse_post_id":"","discourse_permalink":"","wpdc_publishing_response":"","wpdc_publishing_error":"","_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[6,3],"class_list":["post-1178","post","type-post","status-publish","format-standard","hentry","category-all","tag-cloud","tag-ionic"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v23.0 (Yoast SEO v23.0) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Advanced Workflows for Building Rock-Solid Ionic Apps, Part 3 - Ionic Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Advanced Workflows for Building Rock-Solid Ionic Apps, Part 3\" \/>\n<meta property=\"og:description\" content=\"Jonathan Grupp is a software engineer at M-Way Solutions, the owner and maintainer of Generator-M-Ionic, and a frequent contributor to Ionic. He has written a three-part series on advanced workflows for building rock-solid Ionic apps. This is Part 3. The air is getting a little thin in app-development orbit! Luckily, Part 3 of this series [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3\" \/>\n<meta property=\"og:site_name\" content=\"Ionic Blog\" \/>\n<meta property=\"article:published_time\" content=\"2016-07-07T16:29:23+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2017-04-03T19:51:09+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03.png\" \/>\n<meta name=\"author\" content=\"Jonathan Grupp\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@ionicframework\" \/>\n<meta name=\"twitter:site\" content=\"@ionicframework\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Jonathan Grupp\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#article\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3\"},\"author\":{\"name\":\"Jonathan Grupp\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/8ac19c487b42e3cf084a53c32bbdca65\"},\"headline\":\"Advanced Workflows for Building Rock-Solid Ionic Apps, Part 3\",\"datePublished\":\"2016-07-07T16:29:23+00:00\",\"dateModified\":\"2017-04-03T19:51:09+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3\"},\"wordCount\":1709,\"commentCount\":6,\"publisher\":{\"@id\":\"https:\/\/ionic.io\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03.png\",\"keywords\":[\"Cloud\",\"Ionic\"],\"articleSection\":[\"All\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3\",\"url\":\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3\",\"name\":\"Advanced Workflows for Building Rock-Solid Ionic Apps, Part 3 - Ionic Blog\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#primaryimage\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03.png\",\"datePublished\":\"2016-07-07T16:29:23+00:00\",\"dateModified\":\"2017-04-03T19:51:09+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#primaryimage\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03.png\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03.png\",\"width\":1283,\"height\":721},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/ionic.io\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Advanced Workflows for Building Rock-Solid Ionic Apps, Part 3\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/ionic.io\/blog\/#website\",\"url\":\"https:\/\/ionic.io\/blog\/\",\"name\":\"ionic.io\/blog\",\"description\":\"Build amazing native and progressive web apps with the web\",\"publisher\":{\"@id\":\"https:\/\/ionic.io\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/ionic.io\/blog\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/ionic.io\/blog\/#organization\",\"name\":\"Ionic\",\"url\":\"https:\/\/ionic.io\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png\",\"width\":1920,\"height\":854,\"caption\":\"Ionic\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/x.com\/ionicframework\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/8ac19c487b42e3cf084a53c32bbdca65\",\"name\":\"Jonathan Grupp\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/IMG_7259_small-150x150.jpg\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/IMG_7259_small-150x150.jpg\",\"caption\":\"Jonathan Grupp\"},\"url\":\"https:\/\/ionic.io\/blog\/author\/jon\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Advanced Workflows for Building Rock-Solid Ionic Apps, Part 3 - Ionic Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3","og_locale":"en_US","og_type":"article","og_title":"Advanced Workflows for Building Rock-Solid Ionic Apps, Part 3","og_description":"Jonathan Grupp is a software engineer at M-Way Solutions, the owner and maintainer of Generator-M-Ionic, and a frequent contributor to Ionic. He has written a three-part series on advanced workflows for building rock-solid Ionic apps. This is Part 3. The air is getting a little thin in app-development orbit! Luckily, Part 3 of this series [&hellip;]","og_url":"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3","og_site_name":"Ionic Blog","article_published_time":"2016-07-07T16:29:23+00:00","article_modified_time":"2017-04-03T19:51:09+00:00","og_image":[{"url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03.png"}],"author":"Jonathan Grupp","twitter_card":"summary_large_image","twitter_creator":"@ionicframework","twitter_site":"@ionicframework","twitter_misc":{"Written by":"Jonathan Grupp","Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#article","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3"},"author":{"name":"Jonathan Grupp","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/8ac19c487b42e3cf084a53c32bbdca65"},"headline":"Advanced Workflows for Building Rock-Solid Ionic Apps, Part 3","datePublished":"2016-07-07T16:29:23+00:00","dateModified":"2017-04-03T19:51:09+00:00","mainEntityOfPage":{"@id":"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3"},"wordCount":1709,"commentCount":6,"publisher":{"@id":"https:\/\/ionic.io\/blog\/#organization"},"image":{"@id":"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#primaryimage"},"thumbnailUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03.png","keywords":["Cloud","Ionic"],"articleSection":["All"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#respond"]}]},{"@type":"WebPage","@id":"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3","url":"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3","name":"Advanced Workflows for Building Rock-Solid Ionic Apps, Part 3 - Ionic Blog","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#primaryimage"},"image":{"@id":"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#primaryimage"},"thumbnailUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03.png","datePublished":"2016-07-07T16:29:23+00:00","dateModified":"2017-04-03T19:51:09+00:00","breadcrumb":{"@id":"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#primaryimage","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03.png","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/blog-03.png","width":1283,"height":721},{"@type":"BreadcrumbList","@id":"https:\/\/ionic.io\/blog\/advanced-workflows-for-building-rock-solid-ionic-apps-part-3#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/ionic.io\/blog"},{"@type":"ListItem","position":2,"name":"Advanced Workflows for Building Rock-Solid Ionic Apps, Part 3"}]},{"@type":"WebSite","@id":"https:\/\/ionic.io\/blog\/#website","url":"https:\/\/ionic.io\/blog\/","name":"ionic.io\/blog","description":"Build amazing native and progressive web apps with the web","publisher":{"@id":"https:\/\/ionic.io\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/ionic.io\/blog\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/ionic.io\/blog\/#organization","name":"Ionic","url":"https:\/\/ionic.io\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png","width":1920,"height":854,"caption":"Ionic"},"image":{"@id":"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/ionicframework"]},{"@type":"Person","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/8ac19c487b42e3cf084a53c32bbdca65","name":"Jonathan Grupp","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/IMG_7259_small-150x150.jpg","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2016\/07\/IMG_7259_small-150x150.jpg","caption":"Jonathan Grupp"},"url":"https:\/\/ionic.io\/blog\/author\/jon"}]}},"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/1178","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/users\/39"}],"replies":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/comments?post=1178"}],"version-history":[{"count":0,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/1178\/revisions"}],"wp:attachment":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/media?parent=1178"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/categories?post=1178"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/tags?post=1178"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}