node package manager

frontend-app

frontend-app

Start using latest ES syntax and features, import, export, async, await etc. and Livereload and Hot Module Replacement.

This includes babel-polyfill and whatwg-fetch polyfill and transpiles code using env and stage-0 Babel presets, so you may use many new features of the language out-of-the-box.

JSX and AngularJS dependency injection annotations (babel-plugin-ng-annotate, ng-annotate) are also supported.

Contents

Getting Started

one Make sure you have at least Node.js v6 and npm v3 installed:

node -v
npm -v

two Create package.json and install frontend-app:

mkdir my-app
cd my-app
npm init -y
npm install --save-dev frontend-app

three Install one of the examples or have your app entry point(s) in src directory and then:

npm run bundle

Commands

init

Initialize example project. Replace <example-name> with the name of the example, either react, react-complete or angular.

>= npm@5.2.0

npx frontend-app init <example-name>

< npm@5.2.0

npm run init -- <example-name>
bundle

Files are transpiled, bundled and minified to the build directory and are ready to be served. Source maps are also generated.

You may pass additional arguments to Webpack using --. For example, don't show progress information:

npm run bundle -- --progress false
dev

Bundle files to disk in watch mode (see Livereload).

dev-server

Bundle files in watch mode (see Dev Server).

build

Library build (see Library).

Options

You may use package.json config field to set options. Example:

{
  "name": "my-app",
  "config": {
    "frontendApp": {
      "type": "react",
      "port": "8080",
      "devServer": {
        "port": "8081"
      }
    }
  }
}
type
  • react: Adds JSX support.
  • angular: Adds ng-annotate support.
  • all: Support JSX and ng-annotate. ng-strict-di is not supported in livereload mode.
  • slim (default): Just ES2015. No React or Angular specific transformations.
  • portlet: Sets output.publicPath in Spring MVC Liferay plugin project built with Maven. publicPath is determined by artifactId in pom.xml.

Multiple values are also supported. package.json config.frontendApp property example:

{
  "type": [
    "angular",
    "portlet"
  ]
}
port

Your backend server port on localhost. Webpack Dev Server is configured to use this value to proxy all non-build related requests to your actual backend server. Default: 8080.

devServer.port

Port for Livereload/HMR enabled version of your app. Default: port + 1.

reactIntl

Set "reactIntl": "true" to use React Intl.

<Localize> prop Default value
defaultLocale 'en-US'
getBrowserLanguage true

Example ./src/components/App/index.js:

import {BrowserRouter} from 'react-router-dom';
import Localize, {Selector} from 'frontend-app/lib/react/intl';
import {FormattedMessage} from 'react-intl';
 
export default () =>
    <BrowserRouter>
        <Localize defaultLocale="fi-FI" getBrowserLanguage={false}>
            <h1>
                <FormattedMessage id={`${module.id}.title`} />
            </h1>
            <Selector
                locales={[
                    { locale: 'fi-FI', children: 'Suomeksi' },
                    { locale: 'en-US', children: 'English' }
                ]}
                style={{ marginRight: '.4em' }}
            />
        </Localize>
    </BrowserRouter>;

Example default messages ./resources/intl/messages.properties:

./src/components/App/index.js.title = Intl Example

Example localized messages ./resources/intl/messages_fi.properties:

./src/components/App/index.js.title = Intl Esimerkki

List of Preconfigured Loaders

Extension Loader
JS
.js (ng-annotate-loader,) babel-loader
HTML
.html raw-loader
.hbs handlebars-template-loader
Styles
.css css-loader
.scss sass-loader
.less less-loader
Fonts & Media
.woff, .woff2 url-loader
.ttf url-loader
.eot file-loader
.svg url-loader
.png url-loader
.gif file-loader
.jpg file-loader
Other
.txt raw-loader
.properties properties-loader

Livereload

Start build process, watch changes and refresh browser automatically on changes.

npm run dev

If you wish to serve files from different directory while developing (i.e. server content base is elsewhere):

npm run dev -- --output-path /some/other/content/base/build

Livereload in Liferay

Replace <liferay-path> with the path to the Liferay installation directory, e.g. /opt/liferay. In that directory there should be tomcat directory (leave that out of the path).

npm run dev --liferayPath=<liferay-path>

Dev Server

Use this for Hot Module Replacement with React or as an alternative to Livereload.

Have your backend server running. Then start frontend build with Webpack Dev Server:

npm run dev-server

Go to http://localhost:<devServer.port>. <devServer.port> defaults to 8081.

Options
  • port (optional): Your backend server port on localhost. Webpack Dev Server is configured to use this value to proxy all non-build related requests to your actual backend server. Default: 8080.
  • devServer.port (optional): Port for Livereload/HMR enabled version of your app. Default: port + 1.
{
  "name": "my-app",
  "config": {
    "frontendApp": {
      "port": "8080",
      "devServer": {
        "port": "8081"
      }
    }
  }
}

Single Entry (src/entry.js)

my-app/
  build/                                    // generated
    bundle.aa4915533a5b5f53c072.css
    bundle.aa4915533a5b5f53c072.css.map
    bundle.aa4915533a5b5f53c072.js
    bundle.aa4915533a5b5f53c072.js.map
  src/
    entry.js                                // entry point
    other.js
  package.json
<link href="/build/bundle.css" rel="stylesheet">
<script src="/build/bundle.js"></script>

Multiple Entries (src/entries)

my-app/
  build/                                    // generated
    commons.ee0ce0879c8b5aaefae4.css
    commons.ee0ce0879c8b5aaefae4.css.map
    commons.ee0ce0879c8b5aaefae4.js
    commons.ee0ce0879c8b5aaefae4.js.map
    entries/pageA/bundle.8036db3af1be1831e295.css
    entries/pageA/bundle.8036db3af1be1831e295.css.map
    entries/pageA/bundle.8036db3af1be1831e295.js
    entries/pageA/bundle.8036db3af1be1831e295.js.map
    entries/pageB/bundle.19596c286128bce14cf9.css
    entries/pageB/bundle.19596c286128bce14cf9.css.map
    entries/pageB/bundle.19596c286128bce14cf9.js
    entries/pageB/bundle.19596c286128bce14cf9.js.map
  src/
    entries/                                // entry points e.g. for each page
      pageA.js
      pageB.js
    other.js
  package.json

You must load commons bundle before the entry point. pageA.html:

<link href="/build/commons.css" rel="stylesheet">
<link href="/build/pageA.css" rel="stylesheet">
<script src="/build/commons.js"></script>
<script src="/build/pageA.js"></script>

Examples

React

one Install example project with React Router and React-Bootstrap:

>= npm@5.2.0

npx frontend-app init react

< npm@5.2.0

npm run init -- react

two Start Webpack Dev Server:

npm run dev-server

three Go to http://localhost:8081/.

React (complete)

one Install example project with React Router, React-Bootstrap, Bootstrap 3 custom build, React Intl and mock API:

>= npm@5.2.0

npx frontend-app init react-complete

< npm@5.2.0

npm run init -- react-complete

two Start Webpack Dev Server:

npm run dev-server

three Go to http://localhost:8081/.

React (multiple entries)

one Install React example project for multi-portlet use, with Bootstrap 3 custom build, React Intl and mock API:

>= npm@5.2.0

npx frontend-app init react-multiple-entries

< npm@5.2.0

npm run init -- react-multiple-entries

two Start Webpack Dev Server:

npm run dev-server

three Go to http://localhost:8081/.

Angular

one Install example project with AngularUI Router and Bootstrap:

>= npm@5.2.0

npx frontend-app init angular

< npm@5.2.0

npm run init -- angular

two Start Webpack Dev Server:

npm run dev-server

three Go to http://localhost:8081/.

Cookbook

Custom Webpack Configuration

If you have webpack.config.js in root directory, it will be merged into the built-in configuration using webpack-merge in smartStrategy { 'devServer.proxy': 'replace' } mode.

const {join} = require('path');
 
module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                include: join(process.cwd(), 'node_modules/my-lib')
            }
        ]
    },
    output: {
        publicPath: '/build/'
    }
};

Hot Module Replacement with React

No need for additional dependecies. frontend-app has react-hot-loader as its dependency.

one Modify src/entry.js:

import {render} from 'react-dom';
import {AppContainer} from 'react-hot-loader';
import App from './App';
 
const root = document.getElementById('root');
 
function renderer() {
    render(
        <AppContainer>
            <App />
        </AppContainer>,
        root
    );
}
 
if (process.env.npm_lifecycle_event === 'dev-server') {
    renderer();
    module.hot.accept('./App', renderer);
} else {
    render(<App />, root);
}

two Set output.publicPath in webpack.config.js:

module.exports = {
    output: {
        publicPath: '/build/'
    }
};

three Start Webpack Dev Server:

npm run dev-server

four Go to http://localhost:<devServer.port>. <devServer.port> defaults to 8081.

History API Fallback with HTML Webpack Plugin

Suppose there is no backend or it's on another machine, and you just need to test some frontend code with Webpack Dev Server.

one Install dependencies:

npm install --save-dev html-webpack-plugin

two Edit webpack.config.js:

const HtmlWebpackPlugin = require('html-webpack-plugin');
 
module.exports = {
    output: {
        publicPath: '/build/'
    },
    devServer: {
        // Disable frontend-app built-in proxy options (optionally add own proxy options here). 
        proxy: {},
        // Webpack Dev Server needs index.html in /build directory for historyApiFallback to work. 
        historyApiFallback: { index: '/build/' }
    },
    plugins: [new HtmlWebpackPlugin({
        title: 'My App',
        filename: process.env.npm_lifecycle_event === 'dev-server'
            // Webpack Dev Server needs index.html in /build directory for historyApiFallback to work. 
            ? 'index.html'
            // Otherwise generate index.html in project root. 
            : '../index.html'
    })]
};

CKEditor from /libs/ckeditor

one Install dependencies:

npm install --save-dev html-webpack-plugin html-webpack-template

two Edit webpack.config.js:

const HtmlWebpackPlugin = require('html-webpack-plugin');
 
module.exports = {
    plugins: [new HtmlWebpackPlugin({
        inject: false,
        template: require('html-webpack-template'),
        scripts: [
            '/libs/ckeditor/ckeditor.js'
        ],
        window: {
            CKEDITOR_BASEPATH: '/libs/ckeditor/'
        }
    })]
};

three Use CKEDITOR in your app:

// CKEDITOR is available. 
window.CKEDITOR;

CKEditor from node_modules

one Install dependencies:

npm install --save-dev html-webpack-plugin html-webpack-template ckeditor

two Edit webpack.config.js:

const HtmlWebpackPlugin = require('html-webpack-plugin');
 
module.exports = {
    plugins: [new HtmlWebpackPlugin({
        inject: false,
        template: require('html-webpack-template'),
        window: {
            CKEDITOR_BASEPATH: '/node_modules/ckeditor/'
        }
    })]
};

three Use CKEDITOR in your app:

import 'ckeditor';
 
// CKEDITOR is available. 
window.CKEDITOR;

Font Awesome

one Install dependencies:

npm install --save-dev font-awesome font-awesome-webpack@0.0.5-beta.2

two Usage example:

import 'font-awesome-webpack';
<span class="fa fa-camera-retro"></span>

Different output.publicPath in production

process.env.NODE_ENV is set correctly. You may use it, and for example process.env.npm_lifecycle_event to customize your configuration. webpack.config.js:

const production = process.env.NODE_ENV === 'production';
// process.env.npm_lifecycle_event => "bundle" | "dev" | "dev-server" 
 
module.exports = {
    output: {
        publicPath: production ? '/somepath/build/' : '/build/'
    }
};

Maven

one Install in webapp directory:

cd .../src/main/webapp
npm init
npm install --save-dev frontend-app

two Add bundle step to pom.xml:

<plugin>
    <groupId>com.github.eirslett</groupId>
    <artifactId>frontend-maven-plugin</artifactId>
    <version>1.0</version>
    <executions>
        <execution>
            <id>::install node and npm::</id>
            <goals>
                <goal>install-node-and-npm</goal>
            </goals>
        </execution>
        <execution>
            <id>::npm install::</id>
            <goals>
                <goal>npm</goal>
            </goals>
            <configuration>
                <arguments>update</arguments>
            </configuration>
        </execution>
        <execution>
            <id>::npm run bundle::</id>
            <goals>
                <goal>npm</goal>
            </goals>
            <configuration>
                <arguments>run bundle</arguments>
            </configuration>
        </execution>
    </executions>
    <configuration>
        <nodeVersion>v6.5.0</nodeVersion>
        <npmVersion>3.10.6</npmVersion>
        <workingDirectory>src/main/webapp</workingDirectory>
    </configuration>
</plugin>

three Add exclude rules to pom.xml:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <configuration>
        <warSourceExcludes>src/,node/,node_modules/,package.json</warSourceExcludes>
        <webResources>
            <resource>
                <directory>src/main/webapp</directory>
                <filtering>false</filtering>
                <excludes>
                    <exclude>src/**</exclude>
                    <exclude>node/**</exclude>
                    <exclude>node_modules/**</exclude>
                    <exclude>package.json</exclude>
                </excludes>
            </resource>
        </webResources>
    </configuration>
</plugin>

.gitignore

/build/
/node_modules/
/src/.babelrc

Library

Compile src directory to lib directory:

npm run build

You may pass additional arguments to Babel CLI using --. For example:

npm run build -- --watch

lib is cleaned on each build. If you don't want that, use script inbuild instead of build. E.g., when using different output directory:

npm run inbuild -- --out-dir ../my-app/node_modules/my-lib/lib