How to use Aurelia inside nopCommerce

In this article we are going to take a look how Aurelia together with TypeScript and Webpack can be used to build UI components inside of nopCommerce.

Complete source code available at GitHub.

What is Aurelia?

Aurelia is a modern JavaScript Framework to build web, mobile and desktop apps - similar to Angular 2+, React + Redux, VueJs etc. Where Aurelia embraces convention over configuration, is built around dependency injection, highly performant (1), enables two-way databinding and much more. I'm not going to go in much detail of Aurelia in this article, but a good starting point to learn more is by visiting Aurelia's website.

Why should I use Aurelia inside nopCommerce?

With nopCommerce I have never felt so productive building e-commerce solutions. With Aurelia I have never felt so productive building UI components for the web. For several projects I have been using these in combination by running Aurelia inside nopCommerce - it's been a great combination to deliver high quality e-commerce solutions.

nopCommerce comes with jQuery and although you technically can achive the very same tasks by using jQuery alone you will find that jQuery quickly becomes hard to manage as soon as the UI gets a bit more complex. With Aurelia you will quickly get extremely productive writing complex UI components.

What are we going to build?

We are going to build a simple Aurelia component inside of a nopCommerce v4.0 installation. We will use TypeScript as transpiler and let Webpack manage the build.

1-2-3 Go!

For this demo I have started with a fresh nopCommerce v4.0 installation. But any version of nopCommerce would be fine.

In order to get Aurelia up and running we need a few tools:

1. Install node modules

First off we need node and npm in order to install Aurelia, TypeScript and Webpack. If you haven't already installed node you can download it from here.

Add following package.json file in the root of your Nop.Web nopCommerce folder

If your project is under source control it's a good idea to configure the soruce control to ignore the node_modeles folder.

{
  "name": "nopcommerce-aurelia",
  "version": "1.0.0",
  "scripts": {
    "prebuild": "cross-env rimraf wwwroot/dist",
    "build": "webpack  --progress -d",
    "prebuild:prod": "cross-env rimraf wwwroot/dist",
    "build:prod": "webpack --progress -p --env.production"
  },
  "dependencies": {
    "aurelia-bootstrapper": "2.1.1",
    "aurelia-framework": "1.1.4",
    "aurelia-history-browser": "^1.1.0",
    "aurelia-loader-webpack": "2.1.0",
    "aurelia-logging-console": "1.0.0",
    "aurelia-pal-browser": "1.2.1",
    "aurelia-polyfills": "1.2.2",
    "aurelia-templating": "1.4.2",
    "aurelia-templating-binding": "1.3.0",
    "aurelia-templating-resources": "1.4.0",
    "aurelia-templating-router": "^1.2.0",
    "bluebird": "3.5.0",
    "isomorphic-fetch": "2.2.1"
  },
  "devDependencies": {
    "@types/node": "^7.0.12",
    "aurelia-webpack-plugin": "2.0.0-rc.2",
    "awesome-typescript-loader": "3.2.1",
    "cross-env": "^5.1.3",
    "expose-loader": "0.7.3",
    "html-loader": "^0.4.5",
    "rimraf": "^2.6.2",
    "setimmediate": "^1.0.5",
    "typescript": "2.4.1",
    "webpack": "3.3.0"
  }
}

Install all packages by following command inside of your Nop.Web folder

npm install  

If you are using Visual Studio 2017 the node modules will download automatically as soon as the package.json file is added.

2. Configure Webpack

We are using Webpack to transpile our TypeScript to JavaScript and to create two JavaScript bundles: one for the Aurelia framwework code and one for our own code.

Add following webpack.config.js to same Nop.Web folder

const path = require('path');  
const { AureliaPlugin } = require('aurelia-webpack-plugin');  
const { ProvidePlugin } = require('webpack');  
const { TsConfigPathsPlugin, CheckerPlugin } = require('awesome-typescript-loader');

const outDir = path.resolve(__dirname, 'wwwroot/dist');  
const srcDir = path.resolve(__dirname, 'wwwroot/aurelia');  
const nodeModulesDir = path.resolve(__dirname, 'node_modules');

/**
 * @return {webpack.Configuration}
 */
module.exports = ({production} = {}) => ({  
  resolve: {
    extensions: ['.ts', '.js'],
    modules: [srcDir, nodeModulesDir],
  },  
  devtool: production ? 'source-map' : 'cheap-module-eval-source-map',
  entry: {
    app: ['aurelia-bootstrapper'],
    vendor: ['bluebird'],
  },
  output: {
    path: outDir,
    filename: '[name].bundle.js',
    sourceMapFilename: '[name].bundle.map'
  },
  module: {
    rules: [
      { test: /\.html$/i, loader: 'html-loader' },
      { test: /\.ts$/i, loader: 'awesome-typescript-loader', exclude: nodeModulesDir },
      // use Bluebird as the global Promise implementation:
      { test: /[\/\\]node_modules[\/\\]bluebird[\/\\].+\.js$/, loader: 'expose-loader?Promise' }
    ]
  },
  plugins: [
    new AureliaPlugin({ aureliaApp: 'main' }),
    new ProvidePlugin({
      'Promise': 'bluebird'
    }),
    new TsConfigPathsPlugin(),
    new CheckerPlugin()
  ],
})
3. Configure TypeScript

Configure the TypeScript compiler by adding following tsconfig.json to Nop.Web

{
  "compilerOptions": {
    "moduleResolution": "node",
    "target": "es5",
    "sourceMap": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "skipDefaultLibCheck": true,
    "skipLibCheck": true,
    "lib": [ "es2015", "dom" ],
    "types": [ "node" ]
  },
  "exclude": [ "bin", "node_modules" ]
}

Configure, build and run Aurelia

With all that configuration in place we are ready to start builing our Aurelia component. Very much like ASP.NET Core has an entry point in Startup.cs, Aurelia follows similar concept:

Create a new folder: aurelia inside of Nop.Web/wwwroot and create a new TypeScript file: main.ts:

import { Aurelia, PLATFORM } from 'aurelia-framework';

export async function configure(aurelia: Aurelia) {  
    aurelia.use
        .defaultBindingLanguage()
        .defaultResources()
        .developmentLogging();

    await aurelia.start();
}

Inside of the configure function we do all the global Aurelia configuration such installing and configure plugins, setting up the dependency injection container etc. This is all we need to get Aurelia running. To read more about Aurelia configuration check out App configuration and startup of the Aurelia documentation.

Webpack will upon build produce two JavaScript bundles inside wwwroot/dist: vendor.bundle.js which contins our framework and libraries code and app.bundle.js containing our own application code. Add these to bundles in Nop.Web/Views/Shared/_Root.head.cshtml next to all other scripts:

Html.AppendScriptParts(ResourceLocation.Footer, "~/dist/vendor.bundle.js");  
Html.AppendScriptParts(ResourceLocation.Footer, "~/dist/app.bundle.js");  

All we need now is to tell Aurelia that main.ts is our main entry point. We do so by adding an aurelia-app html attribute on the page we want to load Aurelia. In this case I add it to the body tag inside Nop.Web/Views/Shared/_Root.Head.cshtml:

<body aurelia-app="main">  

Build the application by following command

npm run build  

Start a web browser, open up it's developer and start the nopCommerce site. If everything went well you should see INFO [aurelia] Aurelia Started get logged in the console, indication Aurelia successfully started.

Creating the welcome component

The Aurelia team has a GitHub repository Skeleton Navigation that is a great starting point for setting up an Aurelia SPA-application. From this repository we will copy the welcome component and include it in our application:

Inside wwwroot/aurelia create welcome.ts and paste following code:

//import {computedFrom} from 'aurelia-framework';

export class Welcome {  
  heading: string = 'Welcome to Aurelia';
  firstName: string = 'John';
  lastName: string = 'Doe';
  previousValue: string = this.fullName;

  //Getters can't be directly observed, so they must be dirty checked.
  //However, if you tell Aurelia the dependencies, it no longer needs to dirty check the property.
  //To optimize by declaring the properties that this getter is computed from, uncomment the line below
  //as well as the corresponding import above.
  //@computedFrom('firstName', 'lastName')
  get fullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }

  submit() {
    this.previousValue = this.fullName;
    alert(`Welcome, ${this.fullName}!`);
  }

  canDeactivate(): boolean | undefined {
    if (this.fullName !== this.previousValue) {
      return confirm('Are you sure you want to leave?');
    }
  }
}

export class UpperValueConverter {  
  toView(value: string): string {
    return value && value.toUpperCase();
  }
}

The component will have a corresponding view: in the same folder add welcome.html with following markup:

<template>  
  <section class="au-animate">
    <h2>${heading}</h2>
    <form role="form" submit.delegate="submit()">
      <div>
        <label for="fn">First Name</label>
        <input type="text" value.bind="firstName" id="fn" placeholder="first name">
      </div>
      <div>
        <label for="ln">Last Name</label>
        <input type="text" value.bind="lastName" id="ln" placeholder="last name">
      </div>
      <div>
        <label>Full Name</label>
        <p>${fullName | upper}</p>
      </div>
      <button type="submit">Submit</button>
    </form>
  </section>
</template>  

These two files (the view-model and the view) will automatically be bound together. Where Aurelia by default convention assumes a script file and html file with the same name is a complete component with view and view-model. This convention can be overridden, either on application level in main.ts or on component level.

What is going on here is that properties in welcome.ts are bound to the view, for example:

heading: string = 'Welcome to Aurelia';  

is bound to

<h2>${heading}</h2>  

and

firstName: string = 'John';  

is bound to

<input type="text" value.bind="firstName" id="fn" placeholder="first name">  

Pretty straight forward, right? No weird html-markup and just plain JavaScript (TypeScript). Actually, as you might have noticed, welcome.ts doesn't have any Aurelia API code at all, only plain old JavaScript - everything is handled by conventions, your application code is not mixed up by framework code like most other frameworks.

Another thing worth notice is the difference between the binding methods of heading and firstName - where heading is bound one-way. Where updates only flow from the view-model to the view, not the other way around. This is the default binding mode in Aurelia for everything but form elements which need the be two-way bound to update in both directions, as seen on the firstName property that is bount to an input element.

Enhence the component

In our case we won't have a full blown Aurelia application with client-side routing, instead we just want to let Aurelia handle certain components inside nopCommerce. We can easily do that by using enhence - a very nice feature for scenarios like this. We also want to make the component a global resource: meaning it will be available from anywhere in our Aurelia application.

Open up main.ts and update accordingly:

import { Aurelia, PLATFORM } from 'aurelia-framework';

export async function configure(aurelia: Aurelia) {  
    aurelia.use
        .defaultBindingLanguage()
        .defaultResources()
        .developmentLogging()
        .globalResources(PLATFORM.moduleName('welcome')); // <- Make the component a global resource
    await aurelia.start();
    await aurelia.enhance(document.querySelector('welcome')); // <- Enhence the component
}

This will tell Aurelia to handle any <welcome></welcome> html tags as an Aurelia component.

In the feature (hopefully pretty soon) Aurelia will have official support for server-side rendering and nopCommerce will be migrated to the .NET Core platform, then we will not need to use enhance but instead let the component be rendered on the server.

More on Aurelia's enhence can be read in this article.

For just the ease of this tutorial we will be adding the component to the home page of nopCommerce: open up Nop.Web/Views/Home/Index.cshtml (or the corresponding razor file if you are using a theme) and inside the page-body tag add <welcome></welcome>. Build the application with npm run build.

Browse to your nopCommerce application and the welcome component should be loaded and the functionality be working then using the form.

You homepage should look something like this:

Maybe not an extremely useful component to have in an e-commerce store, but from here you can build upon the same concept to create really awesome UI components taking advantage of the entire Aurelia framework.

Complete source code available at GitHub.

"

Leave your comment