Tom Newby

Adding Aurelia components to Symfony 4

03 Feb 2018

Aurelia is a modern JavaScript framework that leverages simple conventions to empower your creativity. The framework supports operation as a layer which “enhances” existing pages with dynamic, interactive components.

In my opinion, this is a great way to create to build great web applications, that leverage server-side rendering for a majority of boring pages (think login, user profile) and a frontend JavaScript framework to create richer, interactive experiences (think search pages and live updating content).

In this tutorial, we’ll integrate Aurelia into Symfony using Webpack Encore, so that we could use it to create dynamic components on an existing Twig template.

Earlier in 2017, the Symfony community released Webpack Encore. Pulling directly from the documentation:

Webpack Encore is a simpler way to integrate Webpack into your application. It wraps Webpack, giving you a clean & powerful API for bundling JavaScript modules, pre-processing CSS & JS and compiling and minifying assets. Encore gives you professional asset system that’s a delight to use.

This articles assumes you have a working installation of Symfony 4 already — if you need to get setup, you can follow the Symfony guide.

Setting up Webpack Encore

To install Encore, simply run the following:

composer require encore
yarn install

Whilst Encore is a Javascript library, using Composer will also setup some more things for you — the magic of Flex! What this will do for you:

  • Create a package.json
  • Create a default webpack.config.js
  • Create the assets/ directory
  • Add node_modules directory to your .gitignore

And once you’ve run yarn install, it will install the dependencies required to build!

How Encore Works

Encore provides a more human-friendly approach to configuring Webpack. If you check the default webpack.config.js that is created, you will see that the last line is simply getting Encore to generate a Webpack configuration:

module.exports = Encore.getWebpackConfig();

Because of this, it is quite easy to make further modifications to the configuration that Encore generates if you need.

Directory structures

The recommended approach is to add your assets to the assets/ directory in the root, and then building with yarn build will place the built assets in public/build/ 


Creating our first Aurelia components

We’re going to create a simple <hello-world> component, so you’ll need to create a few files within the assets/ directory.

assets
├── aurelia
│   ├── components
│   │   ├── hello-world.html
│   │   ├── hello-world.js
│   │   └── index.js
│   └── main.js

main.js is the main Aurelia ‘app’ loader, which starts the framework to enhance existing pages, rather than running as a full-blown single page application. The framework is configure to load our components directory as a feature, which means we don’t need to manually specify imports for components we produce.

// main.js
import 'babel-polyfill';
import {PLATFORM} from 'aurelia-pal';

export function configure(aurelia) {
    aurelia.use
        .standardConfiguration()
        .developmentLogging()
        .feature(PLATFORM.moduleName('components/index'))
    ;

    aurelia.start().then(() => aurelia.enhance());
}

components/ is where we can keep our components listed, and the index file is simply the configuration for the resource loader.

// components/index.js
import {PLATFORM} from 'aurelia-pal';

export function configure(config) {
    config.globalResources([
        PLATFORM.moduleName('./hello-world')
    ]);
}

Then we create a basic Aurelia component:

// components/hello-world.js
export class HelloWorld {}
// components/hello-world.html
<template>
Hello world :)
</template>

Note how we didn’t use the word “Aurelia” when we created the component? Plain ES6! It’s one of my favourite parts of Aurelia.


Building the Aurelia app with Webpack Encore 

Now that we’ve created our Aurelia code, let’s get Webpack Encore building the application. First, we’re going to add the dependencies we’ll need to get Aurelia running:

yarn add aurelia-bootstrapper

This includes the core Aurelia libraries required to run the Aurelia code in this example.

Then, we add the Aurelia Webpack plugin which we’ll use with Webpack Encore:

yarn add aurelia-webpack-plugin --dev

When you first did a composer require of the Encore library, it created a webpack.config.js file for you — we now need to modify this to configure Webpack to build our Aurelia app.

// webpack.config.js
var Encore = require('@symfony/webpack-encore');

Encore
    // the project directory where compiled assets will be stored
    .setOutputPath('public/build/')
    // the public path used by the web server to access the previous directory
    .setPublicPath('/build')
    .cleanupOutputBeforeBuild()
    .enableSourceMaps(!Encore.isProduction())
    // Add these below lines:
    .addEntry('app', 'aurelia-bootstrapper')
    .enableAureliaPlugin((options) => {
        options.aureliaApp = 'main'
    })
    .configureResolveModules([
        './assets/aurelia',
        'node_modules'
    ])
;

module.exports = Encore.getWebpackConfig();

To break down what we just added:

.addEntry() configured a new entrypoint for app.js so that when we include it in the page, aurelia-bootstrapper will be started and it will scan the page for aurelia-app DOM attributes to kickstart the loader

.enableAureliaPlugin() configured aurelia-webpack-plugin to then scan the Aurelia modules to build the application. The first argument is a callback which allows you to configure the options passed into the plugin, but is optional.  The value of aurelia-app is the name of our file which ran aurelia.start() , and this lets the Webpack plugin know which file to start with to begin tracing for modules to be bundled together.

.configureResolveModules() lets the Webpack plugin know where it can search for modules to resolve them. Aurelia leans heavily on dynamic module loading, so this is required.

Now that we’ve configured Encore, we should be able to build our assets:

yarn dev
...
2 files written to public/build
Done in 4.68s.

Now in public/build you should have both app.js and manifest.json. The manifest file can be read by Symfony to accomodate cache-busting versioning, which we’ll explore later on, but for now you’ll want to add the following to your config/packages/framework.yaml

# config/packages/framework.yaml

framework:

    ...
    
    assets:
        json_manifest_path: "%kernel.root_dir%/../public/build/manifest.json"

Using the Aurelia component in a Twig template To use the component, we need to include the app.js and add the DOM attribute [aurelia-app="main"]. Below is an example Twig template demonstrating how you could achieve this.

{% extends 'base.html.twig' %}

{% block body %}
    <div aurelia-app="main">
        <hello-world>This content will be replaced once Aurelia loads the page and enhances this component.</hello-world>
    </div>

    <script src="{{ asset('build/app.js') }}"></script>
{% endblock %}

Load the page and you’ve loaded your first Aurelia component with Symfony!

Voila!

Follow me on Twitter: @tomnewbyau