Webpack Starter for Enonic XP

Contents

Best practice project setup for building apps using Webpack.

This starter requires Enonic XP 7.0.0 or later

Introduction

Once initiated, you’ll have the bare minimum needed to create a new Enonic application with Webpack. You’ll have all the folders set up, and can get straight to creating what you’re creating.

The project will support:

  • Code minification;

  • Production and development environments;

  • SASS and Less support;

  • Transpilation of ECMAScript code with Babel (client- and server-side);

  • Transpilation of TypeScript code (client- and server-side).

Create project

To setup a project locally, run the following command:

enonic project create -r starter-webpack

Remember to create a new XP sandbox when completing the project wizard.

Don’t have the Enonic CLI? Visit the Getting started guide to install it.

Processing of client-side resources

Project structure

In the selected folder, you should now have a project structure, looking something like this (only client-side resources residing in the assets folder):

Selected files from the project structure:
src/
 main/
  resources/
   assets/
    js/
      main.es6 (1)
      example.es6 (2)
    styles/
      main.less (3)
      main.sass (4)
      main.scss (5)
    ts/
      main.ts (6)
      example.ts (7)
    tsconfig.client.json  (8)
1 Entry point of the bundle that will be transpiled from ES6 to Javascript at assets/js/bundle.js.
2 2nd level asset imported from main.es6. Will together with main.es6 be bundled in assets/js/bundle.js
3 Project styles in Less syntax.
4 Project styles in SASS syntax.
5 Project styles in SCSS syntax.
6 Entry point of the bundle that will be transpiled from Typescript to Javascript at assets/ts/bundle.js.
7 2nd level asset imported from main.ts. Will together with main.ts be bundled in assets/ts/bundle.js
8 Configuration of TypeScript transpilation

Project bundling

Common configuration of the starter is set in the config variable at the top of webpack.client.config.js:

const config = {
  context: path.join(__dirname, '/src/main/resources/assets'), (1)
  entry: {}, (2)
  output: { (3)
    path: path.join(__dirname, '/build/resources/main/assets'),
    filename: '[name].js',
  },
  resolve: {
    extensions: [],
  },
  optimization: {
    minimizer: [
      new TerserPlugin({
        sourceMap: true,
        terserOptions: {
          compress: {
            drop_console: false,
          },
        },
      }),
    ],
    splitChunks: {
      minSize: 30000,
    },
  },
  plugins: [], (4)
  mode: env.type,
  devtool: isProd ? false : 'inline-source-map',
}
1 A path containing assets
2 Main entry point (typically main.js) for the dependency chain. Webpack will unwind this chain to bundle of the assets together. Empty by default, will be set further down in the config (see addBabelSupport and addTypeScriptSupport functions).
3 Target path where the bundle will be generated. Bundle name is typically named after main entry point.
4 Plugins will be injected by addLessSupport, addSassSupport, addFontSupport. You can add your own plugins here.

To showcase its capabilities, current configuration of the starter (webpack.client.config.js) enables processing of ES6, Typescript, Less and SASS assets at the very end of the file:

module.exports = R.pipe(
  addBabelSupport,
  addTypeScriptSupport,
  addLessSupport,
  addSassSupport,
  addFontSupport
)(config);

You will most likely not use all of them, so just leave the steps for processing assets used by your application.

Transpilation of ECMAScript with Babel

Let’s say you use main.es6 as the main file which imports other assets (which import other assets and so on). Then you specify it like this:

module.exports = {
  context: path.join(__dirname, '/src/main/resources/assets'),
  entry: {
    js/bundle: './js/main.es6',
  },
  exclude: /node_modules/,
  loader: 'babel-loader',
  // ...
}
Instead of configuring the bundle manually you can make changes to config inside addBabelSupport function in webpack.client.config.js

The main entry point can import other ES6 files in your project, like example.es6:

import example from './example.es6';

or even the assets like styles and fonts:

import '../styles/main.less';
import '../styles/main.sass';
import '../styles/main.scss';

Starting from this entry point, webpack will recursively build a dependency graph that includes every module your application needs, then bundle all of those modules into a small number of bundles (bundle.js and bundle.css) inside the build folder to be loaded by the browser.

Build folder:
build/
 resources/
  main/
   assets/
    js/
      bundle.js
    styles/
      bundle.css

Transpilation of TypeScript code

Configuration of Typescript processing is very similar to Babel. You specify main Typescript class file that will be used as the entry point:

module.exports = {
  context: path.join(__dirname, '/src/main/resources/assets'),
  entry: {
    ts/bundle: './ts/main.ts',
  },
  exclude: /node_modules/,
  loader: 'ts-loader',
  // ...
}
Instead of configuring the bundle manually you can make changes to config inside addTypescriptSupport function in webpack.client.config.js

Import of resources in Typescript is identical to ES6 example from above.

Build folder:
build/
 resources/
  main/
   assets/
    styles/
      bundle.css
    ts/
      bundle.js
Additional config of Typescript processing can be adjusted in /src/main/resources/assets/tsconfig.client.json`

The resulting client-side bundles are now ready be included/imported in the HTML templates.

Learn more about the webpack from the official docs.

Server-side processing

Both ES6 and Typescript are supported where processing server-side resources, but the biggest difference between client-side and server-side processing in the Starter is that you don’t want to merge server-side assets in one bundle, you want to transpile them one-to-one, so that’s how the Starter is configured to process resources outside of '/src/main/resources/assets'.

Selected files from the project structure:
src/
 main/
  resources/
   assets/
    lib/
      observe/
        observer.es6 (1)
    services/
      check/
        check.ts (2)
    webapp/
      webapp.ts (3)
    main.es6 (4)
    tsconfig.server.json (5)
    types.ts (6)
1 Sample lib in ECMAScript
2 Sample service in TypeScript
3 webapp/webapp.ts will be transpiled to webapp/webapp.js to enable opening the app via URL
4 main.es6 will be transpiled to main.js by Babel and called at app startup
5 Configuration of TypeScript transpilation
6 Type definitions required for TypeScript transpilation

Configuration

Config for the Webpack processing of server-side assets is done in webpack.server.config.js. It’s similar to the client-side config except that it only enables processing of ES6 and Typescript resources (choose which one to use or combine both).

Building and Deploying

To build the app, run this command from your shell:

$ enonic project build

To delete the older build before you run a new one :

$ enonic project clean

To deploy the app, run this command from your shell:

$ enonic project deploy

After accepting starting the sandbox, your brand new app should now be up and running on http://localhost:8080

To verify that your app started successfully, you should find an entry similar to this in the sandbox log:

2019-04-30 14:26:30,856 INFO ... Application [com.acme.example.webpack] installed successfully

You can also combine several commands into one:

$ enonic project gradle clean build deploy

Dev mode

It’s also possible to start your sandbox in detached dev mode and have the build system watch for changes as you develop your app.

To start your sandbox in that mode and have it watch for changes as you code, run this command from your shell:

$ enonic dev

For further details on Enonic CLI’s dev mode, read the docs.

Customizing build

Browserslist

The JS code will be transpiled by the Babel, according to the Browserslist environment settings. That means, that you can use the latest EcmaScript syntax and the Babel will automatically transpile your code to Javascript supported by the browsers listed in the config. The CSS will be optimized, and all vendor prefixes will be automatically added, according to the supported browsers from the Browserslist configuration. By default, the starter extends the Enonic Browserslist configuration:

package.json
{
  ...
  "browserslist": [
    "extends browserslist-config-enonic"
  ],
  ...
}

See the official Browserslist documentation, if you want to change the configuration.

Less & Sass (Scss)

The starter supports Less, Sass, and Scss. But you probably won’t be needing all of them. Just go to the webpack.config.js, drop the obsolete rule from the module.rules array and rename the remaining one. Also, don’t forget to remove the corresponding node modules with the npm from the package.json for Less (npm r less less-loader) or Sass (npm r node-sass sass-loader).

Optimization

In the "production" mode, the Webpack will do multiple default optimizations to the resulting JS, except removing the console methods calls from the code, because the corresponding options (drop_console) is set to false.

Building for different environments

The project can also be built for different environments. To set the environment type, call the build with the env parameter. This parameter can be either prod ("production"), or dev ("development"). If the environment is not set explicitly, the "production" will be used by default. The environment can be accessed from Gradle and will also be passed to the webpack configuration.

Here is how you can run build in "development" mode:

$ enonic project gradle build -Penv=dev

In the "production" mode, all your code is minimized, dead code is removed, and no mappings are available.


Contents

Contents