Email development with React and Webpack

Reading Time: 5 minutes

Email development is often an overlooked practice due to the peculiarities and constraints that most email clients impose.

If you want to deliver a delightful user experience to your product you have to maintain its design consistent across all the media it is consumed from.

An email is one way your product might be consumed from. It is therefore important that you craft your emails sticking to the same design principles you would follow when developing your product in other contexts.

The email world, though, has its own peculiarities, and the constraints the emails have to be built within often lead to design compromises.

My personal experience with email development

A project I was recently involved in had delivered a new look and feel to the product but it required an email to be sent out about it.

We wanted our email to take advantage of the new designs too and, ideally, also of the tools and components we had already built for delivering the new UI.

For this reason, we decided to build an email development pipeline that would help us achieve this goal.

In this blog post I’m going to focus on the use of React and webpack to build HTML templates to be sent as emails.

Even though I mentioned React, the key aspect of this project is really how webpack has been configured to come up with a small email development environment. I’m pretty sure the same configuration can be easily adapted to work with other frameworks.

The constraints

This project had to allow us to re-useΒ React components in our email development. We also wanted to structure it so that it would allow for more than one email template to reuse our existing components library.

Finally, we wanted to be able to produce, as output, a single, standalone HTML file.

Let’s get started

I created and pushed an example project on GitHub. Check it out if you’re impatient πŸ™‚

The project structure

We’re gonna structure the project in a way that allows multiple email templates to be crafted and hosted in the same repository.

.
β”œβ”€β”€ output/
β”œβ”€β”€ package.json
β”œβ”€β”€ README.md
β”œβ”€β”€ src/
β”‚Β Β  β”œβ”€β”€ components/
β”‚Β Β  β”‚Β Β  └── SectionOutline/
β”‚Β Β  β”‚Β Β      β”œβ”€β”€ index.js
β”‚Β Β  β”‚Β Β      └── index.scss
β”‚Β Β  β”œβ”€β”€ index.js
β”‚Β Β  └── templates/
β”‚Β Β      └── HelloWorld/
β”‚Β Β          β”œβ”€β”€ index.js
β”‚Β Β          β”œβ”€β”€ index.scss
β”‚Β Β          └── index.test.js
β”œβ”€β”€ webpack.config.js
└── yarn.lock
  • The templates folder will contain all the email templates that will be built, pre-rendered and published into the output folder
  • The components folder will contain all the reusable ReactJS components you want to reuse across templates
  • The output folder contains the resulting HTML output of the template you chose to build

webpack.config.js

The webpack configuration file is probably the most important bit in this project. The build configuration will take care of picking the right template to build by injecting all the information it needs and pre-rendering it to HTML.

Let’s start with the header of our webpack.config.js.

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const PrerenderSPAPlugin = require('prerender-spa-plugin');
const HTMLInlineCSSWebpackPlugin = require('html-inline-css-webpack-plugin').default;

As you can see, the configuration is importing a few useful plugins.

In particular, PrerenderSPAPlugin, will take care of pre-rendering the whole template and generate a static HTML out of it. This is achieved using puppeteer behind the scenes.

Another important bit, here, isΒ HTMLInlineCSSWebpackPlugin that will help us convert our css into an internal <style> node within our generated HTML. This is particularly useful for emails.

A dynamic entry

We want to be able to compile a single template out of all the ones available inΒ the templates folder. In order to do this, we will create a function that will return the configuration hash to be exported for the webpack configuration.

const config = (env) => {
  return ({
    mode: 'production',
    entry: {
      // this will be filled in dynamically
    },
    output: {
      filename: '[name].js',
      path: path.join(__dirname, 'output'),
    },
    module: {
      rules: [
        {
          test: /\.(scss|css)$/,
          use: [
            'css-loader',
            'sass-loader',
          ],
        },
        {
          test: /\.js$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
          },
        },
      ],
    },
    plugins: [
      new PrerenderSPAPlugin({
        staticDir: path.join(__dirname, 'output'),
        indexPath: path.join(__dirname, 'output', `${env.entry}.html`),
        routes: ['/'],
        postProcess: context => Object.assign(context, { outputPath: path.join(__dirname, 'output', `${env.entry}.html`) }),
      }),
      new HtmlWebpackPlugin({
        filename: `${env.entry}.html`,
        chunks: [env.entry],
      }),
      new HTMLInlineCSSWebpackPlugin(),

      // the DefinePlugin helps us defining a const
      // variable that will be 'visible' by the JS code
      // we are rendering at runtime
      new webpack.DefinePlugin({
        "EMAIL_TEMPLATE": JSON.stringify(env.entry),
      }),
    ],
  });
};

As you can see, the mandatory entry field has not been set yet. We will handle it later, and we will require it to be passed in by command line.

The entry will be used to load the right template by passing its value to the PrerenderSPAPlugin. It will also be used to tell HtmlWebpackPlugin how to name the result file.

Finally, we export the configuration the way webpack expects it to be exported:

module.exports = (env) => {
  const entry = {};
  entry[env.entry] = './src/index.js';
  const cfg = config(env);
  cfg.entry = entry;
  return (cfg);
};

Whatever entry we specify, we will always associate it with our entrypoint: index.js.

The index.js file is what is reponsible of loading the template and embedding it into our email layout.

Check out the full webpack.config.js for more information.

A dynamic template

This is the content of the index.js file:

import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
import { Box, Item } from 'react-html-email';

/*
 * The EmailContainer will be the body of your Email HTML body.
 * It will receive the right template to load and inject from Webpack
 * and will attempt to load it here and include it in the DOM.
 * 
 * This class expects the EMAIL_TEMPLATE const to be defined.
 */
class EmailContainer extends PureComponent {
  render() {
    // EMAIL_TEMPLATE is defined by the webpack configuration and enables us
    // to include the right template at compile time
    const Template = require(`./templates/${EMAIL_TEMPLATE}`).default;
    return (
      <Box width="600px" height="100%" bgcolor='#f3f3f3' align='center'>
        <Item align='center' valign='top'>
          <Template />
        </Item>
      </Box>
    );
  }
}

ReactDOM.render(<EmailContainer />, document.body);

This is where the magic happens. Every template will be embedded into this code that will produce the final HTML output for the email. As you can see, I am importing a couple of components from the convenient react-html-email project that takes care of providing a few useful components for the email world.

The Template object is dynamically loaded from the EMAIL_TEMPLATE const string that’s expected to be defined when this code is executed. We’re able to do this because we’re using the webpack DefinePlugin:

// the DefinePlugin helps us defining a const
// variable that will be 'visible' by the JS code
// we are rendering at runtime
new webpack.DefinePlugin({
  "EMAIL_TEMPLATE": JSON.stringify(env.entry),
}),

The plugin will take care of setting the const to the full path of the email template I am interested in rendering. In our case the HelloWorld template.

Run it

yarn webpack --env.entry=HelloWorld

The resulting HTML will be stored in the output folder.

browser window html hello world
hello world template rendered result

I know. Not the most beautiful email, but I’ll leave the design to you. πŸ™‚

I hope you enjoyed the post. Let me know if you have any feedback. Don’t forget to check out the full project onΒ GitHub.

A native iOS app with a Qt third-party library

Reading Time: 4 minutes

I’m wrapping up here pieces of information that I used to setup a working native iOS project taking advantage of a library I wrote mainly using Qt. The library I wrote has nothing to do with GUI, it just helps me dealing with connectivity, REST API interaction, file system access, compression… Also, the library was born for the Desktop but the porting effort to mobile is feasible due to the lack of GUI interaction.
In particular, the following has been tested using Qt 5.3.

Despite the huge integration level now Qt has reached over the year even on the iOS platform, I still prefer having the UI developed using the native SDK. I’m a rather experienced Cocoa developer, not that experienced with Cocoa Touch, but I managed to have everything I needed working. Since the information I found is rather sparse regarding this topic, I thought it could be nice to have everything here in a blog post.

Project info

In the following post I’m assuming you want to link a native iOS Xcode project to a third party library written using C++ and Qt. I’m also assuming you are using statically linked Qt for this project (which is the default for the iOS solution).

So what we have:

  • A native Xcode project for iOS: NativeHelloWorld.xcodeproj
  • A static lib Qt project: FancyStaticLib.pro

What we are going to have

  • NativeHelloWorld.xcodeproj
  • FancyStaticLib.xcodeproj as a subproject to NativeHelloWorld
  • Qt for iOS properly linked to make NativeHelloWorld run and use FancyStaticLib

Let’s get things started

So, first of all let’s instruct Xcode about where to find Qt on our system.
Go to Xcode -> Preferences -> Locations (Tab) -> Source Trees (Sub Tab).
Add the path to your Qt for iOS packages and name it as you wish. I just chose QTLIB_IOS.
Screen Shot 2014-12-18 at 18.25.05

I like setting my paths this way in order to keep my project as “exportable” as possible. This way, other devs can join my project pretty easily.

Now, if you haven’t already, you should create an Xcode project for your static library you want to link into your native iOS project.
In order to do so you have to run something like this:

/path/to/your/Qt/ios/bin/qmake -spec macx-xcode -r /path/to/FancyStaticLib.pro CONFIG+=staticlib CONFIG+=whatever_you_need

This will output FancyStaticLib.xcodeproj file for your static library. You can drag it to your our NativeHelloWorld.xcodeproj inside Xcode and add its product (the static lib) as a link dependency to your project.
NOTE: You will have to re-generate the FancyStaticLib.xcodeproj each time you change your static library .pro file

Link to Qt

Now that we have the project feeling more like a completely native Xcode one we have to set a few things up in order to easily keep developing directly from Xcode through our NativeHelloWorld.xcodeproj

First of all, look for the Headers Search path section in the Build Settings section of your Xcode project:
Screen Shot 2014-12-18 at 18.54.07

We want to make it easy for Xcode to find Qt headers, and also our static lib headers.
Now the variable we previously defined through the Source Trees section in the Xcode preferences comes in handy.
Let’s add the following to the Headers Search Path section:

  • $(QTLIB_IOS)/include
  • /path/to/FancyStaticLib/headers

Now, the actual linker flags.
You will probably need to start your project inside the emulator. When doing so bare in mind that the simulator has a different architecture from your iOS device. Simulator runs an i386 architecture and we want to link our project both to the static Qt lib files compiled for such architecture and for arm. This way we will be able to run our project both in the simulator and on the native device.

Scroll down to the Other Linker Flags section: you should at least have a Debug section. Under Debug, as a child item, you should have Any iOS Simulator SDK. If you don’t, click the little “+” icon on the side of the Debug item and add Any iOS Simulator SDK as child item.Screen Shot 2014-12-18 at 19.06.30

Our project dependencies are satisfied by the following modules:

  • QtCore
  • QtGui
  • QtNetwork
  • QtScript

The Debug section will host the option for running our app on a native device with debug symbols:

-L$(QTLIB_IOS)/lib -lQt5Core_debug -lQt5Gui_debug -lQt5Network_debug -lQt5Script_debug
Also, don’t forget to include proper platform support with:
-lQt5PlatformSupport_debug -L$(QTLIB_IOS)/plugins/ -lqios_debug
You’ll also need -lz -lqtharfbuzzng_debug most probably.
Also, if you are taking advantage of the bearer plugin to handle connectivity, add the following:
-L$(QTLIB_IOS)/plugins/bearer -lqgenericbearer_debug

Now the Any iOS Simulator SDK section:
Simply replace what you typed in the previous section changing “_debug” with “_iphonesimulator_debug” and you are good to go.

The last touch

Your Qt lib will most probably need an instance of QGuiApplication. This usually requires you to replace the default main coming with your project template with a custom one that actually calls QGuiApplication::exec(). Luckily, Qt has made things relatively easy and you won’t need a custom main body. Looks like the Qt guys are cool enough to inject their Qt Event Dispatcher inside the main CocoaTouch run loop making it easy to spawn QTimers and queued methods invocations from Objective-C(++).
Just make sure you initialize a QGuiApplication instance (but you won’t need to call .exec()).

We are going to add the following piece of code inside your application delegate - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions after having renamed the application delegate file from .m to .mm. Renaming to .mm enables Objective-C++ which helps us mix C++ and Objective-C in the same source.

Conclusions

This is pretty much what is needed to mix a native iOS project with a Qt library. If you encounter linker issues you should be able to easily address them by inspecting the .a symbols you can find inside your $(QTLIB_IOS)/{lib,plugins} directories.
Otherwise please post here your issues so that we can try address them together.

Cheers.