It’s been just a hair over 500 days since the last Ionic Show.

That’s far too long. Today, we’re excited to be back with a vengeance: a movie-length Ionic Show episode covering all the latest Ionic news! 🎉

In this episode of the Ionic Show, Max and Ben bring on Josh Thomas and Matt Netkow to cover Ionic 4, Capacitor, Stencil One, and a community Q&A. Some of the highlights include…

Highlights

Watch Now

Watch the recorded discussion video below. While lengthy, it’s a perfect listen for a long commute, a coding session, or even an after work jog. Guaranteed to be better than the series finale of Game of Thrones.

Or, listen to the podcast version:

Read more

One of the very special things about Ionic and Capacitor is that a huge bulk of your app development can happen right in a browser on your desktop. That means full access to your traditional desktop web development tools (Chrome/Safari/Firefox dev tools) and the development velocity of building without having to recompile or deploy to a simulator or device.

One of the guiding design goals of Capacitor, Ionic’s new native web app container project that runs your web app natively on iOS, Android, Electron, and the web as a Progressive Web App, was to increase the amount of time you can spend developing your app on desktop before having to mess with simulators or devices.

On top of that, building web-first means that your app will likely run well as a Progressive Web App with minimal additional work, assuming you’re able to achieve the functionality your app needs with Web APIs.

Let’s take a look at how that works.

A Fake Native Plugin

Chances are, your Ionic/Capacitor app will have some native functionality that it needs, that goes beyond what is available out of the box in Capacitor (such as integrating with a 3rd party SDK).

Let’s say we have this simple Capacitor plugin:

import Foundation
import Capacitor

@objc(ThirdPartyPlugin)
public class ThirdPartyPlugin: CAPPlugin {
  @objc func connect(_ call: CAPPluginCall) {
    let service = ThirdPartySDK(apiKey)
    service.connect()
    call.resolve(service.getUser())
  }
}

This plugin connects to the ThirdPartySDK and then returns some user information. Unfortunately, this theoretical SDK is only available on iOS and Android, so when we’re building the app in the browser, we will need to either add custom code to detect which platform we’re running on (not ideal), or mock out the service so we can keep building quickly (much better!)

Mocking Plugins on the Web

In order to mock out this plugin, we need to create what is known as a Web Plugin for Capacitor. Web Plugins have a lot of power and aren’t just for mocking though! For example, Capacitor ships with a full Filesystem implementation for the web that uses IndexedDB so you can work with files just like on iOS, Android, and Electron.

In this case, our web plugin will be pretty basic and just return some fake data, so let’s create it:

import { WebPlugin } from '@capacitor/core';

export class ThirdPartyPluginWeb extends WebPlugin {
  constructor() {
    // Register the plugin with Capacitor so it is aware of it
    super({
      name: 'ThirdPartyPlugin',
      platforms: ['web']
    });
  }

  async connect(options: any) {
    return {
      id: 123,
      name: 'Barnaby Jones',
      email: This email address is being protected from spambots. You need JavaScript enabled to view it.'
    }
  }
}

export const ThirdPartyPlugin = new ThirdPartyPluginWeb();

Next, we need to register this plugin with Capacitor so it is aware of it. This makes your plugin available at runtime, but only when running on the platforms specified in the constructor (in this case, web):

import { ThirdPartyPlugin } from './plugins';
import { registerWebPlugin } from '@capacitor/core';
registerWebPlugin(ThirdPartyPlugin);

Where you put this code does matter, to ensure it loads before this plugin is accessed, so we recommend putting it in your root index JS/TS file.

Testing the Plugin

Once you have your plugin built, using it is as simple as just serving your app normally (npm start, for example), and then invoking the plugin like so:

import { Plugins } from '@capacitor/core';

function connectToThirdParty() {
  const { ThirdPartyPlugin } = Plugins;

  const user = await ThirdPartyPlugin.connect();
}

You should see your mock web data come back when running on the web, otherwise see the normal full connection to the Third Party SDK when running natively!

If you’re having trouble, try accessing your plugin on Plugins as close to where it is used. Unfortunately, with modern bundlers and module loaders, your code may be referencing Plugins.ThirdPartyPlugin before the web plugin has loaded, so be mindful of that.

Conclusion

Web Plugins are wonderful for building out consistent cross-platform APIs (for example, the Geolocation and Share APIs have the same code on all platforms), but they can also be used for mocking out functionality that is only used on native platforms.

If you end up building a web plugin for Capacitor and find this guide helpful, let us know below!

Read more

Today I am thrilled to announce the 1.0.0 release of Stencil—or what we’re calling “Stencil One.” 🎉

Last week at JS Conf EU, we had the pleasure of announcing the Stencil One final release on stage, just a few weeks after we released the beta. Now I’m excited to go in-depth and explain what this moment means for Stencil and Ionic.

Stencil’s creation is the direct result of years spent working on Ionic, developing a reusable UI component library for millions of developers and applications, and the many lessons learned along the way. So let’s dig into Stencil’s origin story, and where we’re going next.

First, The Problem ☝

The story of Stencil starts with a very specific problem. The Ionic team wanted to make it easy for anybody to use our component library, whether they preferred Angular, React, Vue, or some other framework. But at the time, Ionic components were only compatible with Angular.

In our quest for universal compatibility, it occurred to us that, as far as the framework logic is concerned, an <ion-button> element is no different than a <button>. Both are just elements in the DOM, and in both cases, the developer is able to interact with elements using properties, attributes, and events.

When we accept that <ion-button> is no different than any other element on the page, it immediately expands our universe of possibilities. The low-level framework actually doesn’t matter. In fact, without a dependency on any specific framework, using an Ionic component can be as simple as:

const btn = document.createElement('ion-button');
btn.textContext = 'Hello Ionic';
document.body.appendChild(btn);

This gave us enough to believe that Ionic could, in fact, decouple itself from unnecessary requirements, dependencies, and direct ties to a specific framework, or framework version — but not without some help, as you’ll see later.

Enter Web Components 🧩

The first step in our journey, then, was to move our component library to Web Components instead. More specifically, Custom Elements.

Web Components allow Ionic to use web-standard APIs already built into all modern web browsers, rather than framework-specific APIs that are version-restricted and may change over time. Essentially, this enables Ionic components to be created and connected to the DOM, no different than any other element.

The Second Problem 🤔

However, Web Components have purposely been designed as a very low-level web API, and as they scale and become more complex, things start to get a little trickier. Common scenarios include growing application complexity, team members moving on and off the project, business requirements changing, and creeping tech debt.

The complexity of maintaining apps can quickly escalate, which in my opinion is why frameworks such as React, Vue, and Angular have become so popular. They solve a real problem. Frameworks are the glue for components and are able to provide a common structure and best practices to help teams develop and maintain large applications.

So, while we saw the immense value of porting our components to Web Components in order to become framework agnostic, we immediately realized that in doing so we would lose out on all the great features and developer experience that come with frameworks. The questions quickly piled up:

How would we…

  • Setup a dev server that’s integrated with watching the file system?
  • Perform Hot Module Replacement or reload styles on file changes?
  • Prerender components in each framework?
  • Run unit and end-to-end tests?
  • Integrate these components in React, Vue, Angular, etc.?
  • Export component types for frameworks, JSX and HTML?
  • Document component properties, attributes, events, CSS variables, etc.?
  • Set up differential bundling for modern and legacy browsers?
  • Efficiently bundle modules using modern ESM standards?
  • Minify ES2017 builds and separately minify ES5 legacy builds?
  • Print error messages pointing directly to the source?
  • Precompile Sass and minify CSS?
  • Configure the package entry points for ESM, CommonJS, and CDNs?
  • Set up linting rules?
  • Take screenshots and perform image comparisons against the last commit?

The list above only scratches the surface problems that need to be solved without a framework. And by solve I mean, “figure this out entirely on our own”, because Web Components themselves do not provide any answers to the above questions. Each one is certainly doable, and there are many dependencies developers can piecemeal together.

However, the R&D time just to figure out how to answer all of the above questions with the wild west of external dependencies is a problem in itself. And not just to work on our development machines, but Ionic’s package setup must be capable of being imported by all bundlers, frameworks and external tools, which is something that’s not exactly cut-and-dried.

Enter Stencil 👋

We had to find a better way. And that’s what led us to Stencil.

Stencil is a compiler that generates standards-compliant Web Components, while also delivering the best concepts of the most popular frameworks into a simple build-time tool.

Stencil takes features such as

  • Async rendering
  • Reactive data-binding
  • TypeScript
  • JSX

and then generates standards-based Web Components with all these features baked in.

Since Stencil generates standards-compliant Web Components, we solved our original problem of making Ionic compatible with all popular frameworks right out of the box. They could even be used without a framework because they are just Web Components. And on top of that, Stencil also enabled a number of key capabilities, in particular, prerendering, and objects-as-properties (instead of just strings).

Compared to using Custom Elements directly, Stencil provides extra APIs that makes writing fast components simpler. APIs like JSX and async rendering make fast, powerful components easy to create, while still maintaining compatibility.

The developer experience is also tuned and comes with live reload and a small dev server baked into the compiler.

An Unexpected Benefit: Size & Speed ⚡

While our journey started with a desire to build a framework-agnostic component library, Stencil delivered some incredible – and somewhat unexpected – benefits. By making Stencil a build-time tool, and using the built-in capabilities of the browser via Web Components, we found that our bundle sizes for Stencil apps were incredibly small. Like, insanely small. And the speed of our components blew away our expectations.

Since Stencil is able to statically analyze the source of every component, the compiler is able to apply heavy optimizations and only include exactly what the components require. This means smaller file sizes, which are perfect for PWAs, and extremely fast startup times. Since every Stencil build will be different, below are commonly used examples in order to have a comparison.

Hello World: 83 bytes compressed, 133 bytes uncompressed
Todo MVC: 2.0KB compressed, 4.88KB uncompressed

And these same numbers scale quite well for all of Ionic’s components!

Design Systems 🧬

One of the reasons you’re even reading this blog is that we eventually made the decision to release Stencil as its own open source (MIT) project. Up until now, we’ve talked only about what Stencil has meant for the Ionic team, but the Stencil project has taken on a life of its own. In fact, we’re always excited when we hear from teams that are passionate about Stencil, and have no clue it’s made by Ionic.

Perhaps the most popular use case for Stencil is Design Systems. Design System initiatives have attempted to centralize brand, voice, componentry, and more for decades at large enterprises. However, when attempting to distribute actual code-based components for use in any project across the company, many enterprises have failed at gaining critical adoption due to having chosen one specific framework to make components in. Write your component library in React, and you’re only helping teams that use React in your company.

These teams have started to use Stencil to build their own custom component libraries, and have found success because the components they build will now work with any framework – helping to ensure their universal adoption within the organization – while also bringing the benefits of reduced bundle sizes and increased performance to their apps. Our team has since taken this a step further by developing specific solutions to address Design Systems directly. We call this offering StencilDS, and you can learn about it here.

What’s Next: Stencil Roadmap 🗺

Ionic has been built with Stencil since Ionic v4, so in a sense, Stencil has been in production for quite some time now. Before we released version 1.0 of the Stencil compiler though, there were a few areas of Ionic Framework’s development which helped discover more areas for improvement.

Because Stencil is a compiler that can do so many powerful things, surprisingly enough one of the biggest challenges is deciding what it should not do! Keeping the API tiny and continually aligned with web-standards is extremely important to us.

Some additional goals:

  • Stability: Continue to close out bugs while avoiding breaking changes at all costs.
  • Documentation: Continue to iterate on our docs by adding new content and focusing on writing clear and concise information.
  • Demos: Build more demos showcasing best practices we’ve discovered.
  • Prerendering and SSR: Stencil One can already prerender components, but the next step is to demonstrate how they can be used in other projects such as Angular Universal, Gatsby, and Next.js.
  • Community: We’re thrilled to have such a large, vibrant and helpful community already, but will keep working to further improve the ecosystem, become more transparent with our roadmap, and keep the community up to date via Twitter, Slack and soon a dedicated new blog on Stencil’s website.

Start Building! 🛠

Hopefully, this helped explain a little bit more of Stencil’s story, and where we’re going next. If you haven’t tried out Stencil yet, we encourage you to take five minutes and toy around with it:

npm init stencil

Follow our full Getting Started guide in the docs.

Thank You ❤

Lastly, on behalf of the whole Stencil team, I’d like to give a huge thanks to everyone in the community who has helped make Stencil One possible. Throughout this nearly two-year journey, your feedback, patience, contributions, and encouragement have helped us bring Stencil from an ambitious vision to an incredible reality.

We’re looking forward to hearing how Stencil One is working for you, and we’re excited to hear your thoughts, feedback, and questions.

Happy building! 🤙

Read more

How to manage state in your app can often be the biggest and most impactful architectural decision you make.

Unfortunately, there is no standard practice for state management. Developers have to choose between a wide variety of techniques and libraries (many of them 3rd party), including Redux, MobX, state “tunneling,” singleton state services, or just hacking it together. Some of these solutions are optimized for large apps, and some for small ones.

With React Hooks, however, we finally have a state management technique that is both native to the framework, and a good fit for a huge swathe of apps (except, perhaps, very large ones).

If you aren’t familiar with Hooks in React, go read our introduction to Using React Hooks in Ionic React, it offers are primer on the new APIs and how to build basic apps with them. We will gloss over that in this post.

Let’s jump in.

State Management With React Hooks

React now ships with a number of hooks, including two that we can use to build a powerful state management system right into our app: useContext and useReducer.

At the risk of oversimplifying, a simple state management system has a few desirable properties: 1) it’s global, so state is managed in one place instead of all over your app and 2) individual components don’t modify or mutate state themselves, but rather emit “actions” to the state management system which can then mutate the state, causing the tree of components to update if necessary.

If you recognize redux in the above, congratulations! That’s effectively what we’re going to build with React Hooks.

The Pattern

Okay, let’s get to the pattern. We’re going to build our state management system in one file called State.jsx (or tsx if using TypeScript):

import React, { createContext, useReducer } from "react";

let AppContext = createContext();

const initialState = {
  count: 0
}

let reducer = (state, action) => {
  switch(action.type) {
    case "setCount": {
      return { ...state, count: action.user }
    }
  }
  return state;
};

function AppContextProvider(props) {
  const fullInitialState = {
    ...initialState,
  }

  let [state, dispatch] = useReducer(reducer, fullInitialState);
  let value = { state, dispatch };


  return (
    <AppContext.Provider value={value}>{props.children}</AppContext.Provider>
  );
}

let AppContextConsumer = AppContext.Consumer;

export { AppContext, AppContextProvider, AppContextConsumer };

In this file, we set up our Context, which our child components will access with the useContext hook. When they do this, they will have access to two things that we’ve set as the value on our AppContext.Provider: state and our dispatch function. Which are returned from calling the useReducer hook. state is the current global state, which can be used for rendering/etc., and dispatch allows components to emit actions that our reducer function will process to turn into a new state object.

The reducer function takes two arguments: the current state, and the action that was performed. It then returns a new state object that contains any differences after processing the action.

Let’s take a look at an example component to see how we’d use this:

import React, { useContext } from 'react';
import { IonButton } from '@ionic/react';
import { AppContext } from '../State';

export const MyComponent = () => {
  const { state, dispatch } = useContext(AppContext);

  return (
    <div>
      <IonButton onClick={() => dispatch({
        type: 'setCount',
        count: state.count + 1
      })}>
        Add to Order
      </IonButton>
      <h2>You have {state.count} in your cart</h2>
    </div>
  )
}

That’s pretty much it for the basic state management pattern! Our components access state from the Context and dispatch actions to the reducer, which in turn updates the global state, which causes components to re-render. Pretty simple!

There are a few other things we can add to our state management system to make it even more powerful, though.

Logging

A common need for state management is logging actions for debugging purposes.

Logging can be done very simply by wrapping the reducer function with a simple logging function and using that function as the argument to useReducer instead of the original reducer function:

const logger = (reducer) => {
  const reducerWithLogger = (state, action) => {
    console.log("%cPrevious State:", "color: #9E9E9E; font-weight: 700;", state);
    console.log("%cAction:", "color: #00A7F7; font-weight: 700;", action);
    console.log("%cNext State:", "color: #47B04B; font-weight: 700;", reducer(state,action));
    return reducer(state,action);
  };
  return reducerWithLogger;
}

const loggerReducer = logger(reducer);

function AppContextProvider(props) {
  // ...
  let [state, dispatch] = useReducer(loggerReducer, fullInitialState)
  // ...
}

Resulting in helpful log info like this:

Persistence

Another common need for a state management system is persistence, either of the entire state or a subset of it.

We can achieve this in a simple way using localStorage and adding a few lines of code to our state system:


const initialState = {...} const persistedState = JSON.parse(window.localStorage['persistedState']); function AppContextProvider(props) { const fullInitialState = { ...initialState, ...persistedState } // ... }

This first setups up the initial state to contain any data we’ve persisted in persistedState.

Then, to keep the persisted data up to date when state changes, we can use useEffect which will run every time our state is updated. In this example we’re persisting a new state.user field which might contain a user’s session token:

function AppContextProvider(props) {
  const fullInitialState = {
    ...initialState,
    ...persistedState
  }

  let [state, dispatch] = useReducer(loggerReducer, fullInitialState);

  useEffect(() => {
    // Persist any state we want to
    window.localStorage['persistedState'] = JSON.stringify({
      user: state.user
    });
  }, [state]);
  // ...
}

This will let us keep specific fields in our state persisted if they change, and load them back when the app starts up again. In that sense, the persistence is reactive and we don’t have to think about it. Note: using localStorage is bad for anything that needs to live for a long time as the browser/OS may clean it up. It’s perfectly fine for temporary data, however.

Conclusion

There you have it, a simple pattern for state management in Ionic React with React hooks. There are simpler state management patterns, to be sure, but I feel this strikes a nice balance between being simple enough for basic apps, and complex enough for decent sized ones, too. If I were going to build a Very Serious app, I’d probably still use Redux to benefit from the various libraries and techniques available there.

I like this pattern so much I’ve used it now on three different Ionic React apps. Much like a sourdough starter, I copy this state management system for each new app I build.

What do you think? Do you like this pattern? Could something be improved or tweaked? Let us know in the comments!

Read more

The latest version of the Ionic CLI has dropped, containing some great updates. To see the full list of changes, review the changelog. Read on for update instructions and the highlights.

NOTE: The Ionic CLI has a different versioning scheme than the Ionic Framework. Ionic Framework 5 doesn’t exist… yet. 🤓Run ionic info in your Ionic project for details.

Updating to Ionic CLI 5 🧗‍♀️

First, ensure that you have the latest NodeJS installed, which at this time is version 10. It’s easy to check which version you’re currently using:

$ node -v

To update the Ionic CLI to version 5.0:

$ npm install -g ionic

Next, install Cordova Resources (used to generate Cordova resources locally) and Native Run (used to deploy app binaries to devices):

$ npm install -g cordova-res native-run

For Angular projects, update to the latest @ionic/angular-toolkit:

cd my-ionic-app
npm install @ionic/angular-toolkit@latest

Notable Changes and Additions ☝🏻

native-run is now used to deploy app binaries to devices for ionic cordova run.

Native-run is a new tool we created to help ease the pain of deploying to simulators and real devices. Native-run is also platform independent, meaning it can work with both Cordova and Capacitor. Finally, it’s written entirely in JavaScript, so there’s no compilation step or platform specific code that could cause bugs on different platforms.

cordova-res is used to generate Cordova resources locally for ionic cordova resources.

Cordova-res takes the same great resource generation tools we had as part of our Ionic services, but moves them back onto the developer’s machine. This means you no longer need an Ionic account to generate splash screens and icons, resources can be generated offline, and since it’s local to the file system, you don’t have to wait for resources to download.

localhost is now the default host for ionic serve, ionic cordova run -l, and ionic capacitor run -l

--devapp is now a required flag if you want to use DevApp with ionic serve.

Now, something we’ve all been waiting for.

Ionic React Support (Beta) ⚛

The Ionic Framework team has been heads down on the Ionic React beta. A huge thanks to everyone in the Community who have submitted issues and provided feedback! It’s helping us shape version 1.0, which is right around the corner.

Some great Community articles have been published recently, too:
Creating a Medium clone with Ionic React
Build a news app with Ionic React and Axios
Ionic 4 and React: Navigation
Build a Chat App with Ionic React and Stream

Writing about Ionic React? Tweet us the link: @IonicFramework.

To use an Ionic React starter, ensure you have updated to the latest Ionic CLI (npm install ionic -g), then run:

$ ionic start myApp --type=react

Next, select a starter template, from a completely blank template to a complete conference example app:

Official documentation is coming soon. In the meantime, if you want to try Ionic React while it’s in beta, you should be comfortable with React and how things are done in a React app. Happy app building!

Read more

© 2020 Extly, CB - All rights reserved.