dojo / cli-build-app

Command for building Dojo applications
Other
9 stars 32 forks source link
app build cli command dojo

@dojo/cli-build-app

Build Status Build status codecov npm version

The official CLI command for building optimized Dojo applications.

Usage

To use @dojo/cli-build-app in a single project, install the package:

npm install @dojo/cli-build-app

Features

@dojo/cli-build-app is a command for the @dojo/cli.

Building

There are four modes available to build a Dojo application, dist, dev, unit, and functional. The mode required can be passed using the --mode flag:

dojo build app --mode dist

The built application files are written to the output/{dist/dev} directory. The built test files are written to the output/test/{unit|functionl} directory.

Note: dist is the default mode and so can be run without any arguments, dojo build app.

Dist Mode (default)

The dist mode creates a production-ready build.

Bundle Analyzer

When building your application in dist mode, the build process will generate a webpack bundle analyzer that can be used to inspect the content of your application's bundles. The webpack bundle analyzer is outputted to the output/info/analyzer directory of your project. To view the analyzer, open the index.html file contained in this directory.

webpack bundle analyzer

Dev mode

The dev mode creates an application build that has been optimized for debugging and development.

Unit mode

The unit mode creates bundles that can be used to run the unit tests of the application.

Functional mode

The functional mode creates bundles that can be used to run the functional tests of the application.

Polyfills

The build command conditionally loads polyfills from @dojo/framework/shim based on your application's usage and the user's browser capabilities.

Legacy Browser Support

By default, the build will support the last two versions of the latest browsers. To support IE 11, run the build with the --legacy (-l) flag.

Asset Management

While most assets will be imported by modules in the src/ directory and therefore handled by the main build pipeline, it is often necessary to serve static assets or include assets in the HTML file itself (e.g., the favicon).

Static assets can be added to an assets/ directory at the project root. At build time, these assets are copied as-is without file hashing to output/{mode}/assets, and can be accessed using the absolute /assets/ path. For example, if users need access to a static terms of service document named terms.pdf, that file would added to assets/terms.pdf and accessed via the URL /assets/terms.pdf.

The build also parses src/index.html for CSS, JavaScript, and image assets, hashing them and including them in the output/{mode}/ directory. For example, it is common for applications to display a favicon in the URL bar. If the favicon is named favicon.ico, it can be added to the src/ directory and included in src/index.html with <link rel="icon" href="https://github.com/dojo/cli-build-app/blob/master/favicon.ico">. The build will then hash the file and copy it to output/{mode}/favicon.[hash].ico.

Code Splitting By Route

The build command will automatically code split your application based on its dojo routing configuration.

To enable the code automatic splitting by route:

1) The dojo routing configuration needs to be the default export from a routes.ts module in the src directory. 2) Widgets must by the default export of their module. 3) When defining the Outlet, the renderer function must be defined inline.

// routes.ts
export default [
    {
        path: 'foo',
        outlet: 'foo',
        children: [
            {
                path: 'bar',
                outlet: 'bar'
            }
        ]
    },
    {
        path: 'bar',
        outlet: 'bar'
    }
];
// widget
import WidgetBase from '@dojo/framework/widget-core/WidgetBase';
import { v, w } from '@dojo/framework/widget-core/d';
import Outlet from '@dojo/framework/routing/Outlet';

import FooWidget from './FooWidget';
import BarWidget from './BarWidget';

export default class App extends WidgetBase {
    protected render() {
        return v('div', [
            w(Outlet, { id: 'foo', renderer: () => w(FooWidget, {})}),
            w(Outlet, { id: 'bar', renderer: () => w(BarWidget, {})})
        ]);
    }
}

The output will result in a separate bundle for each of the application's top level routes. In this example, there will be a main application bundle and two further bundles for src/FooWidget and src/BarWidget.

Note: The configuration can be further refined using the bundle configuration in the .dojorc, see bundles configuration.

Serving the Application

A web server can be started with the --serve flag while running in dev or dist modes. By default, the application is served on port 9999, but this can be changed with the --port (-p) flag:

# build once and then serve the app on port 3000
dojo build -s -p 3000

By default, the files will be served via HTTP. HTTPS can be enabled by placing server.crt and server.key files in a .cert directory in the root of your project:

|-- my-project
    |-- .cert
        |-- .server.crt
        |-- .server.key

When these files are detected, dojo build -s will automatically serve files via HTTPS.

Proxy Configuration

The development server can be configured to act as a simple proxy. Add a proxy section to your .dojorc containing the paths you want to proxy. The key of the object is the path you want to proxy and the value of the object is the proxy configuration.

{
    "build-app": {
        "proxy": {
            "/api": {
                "target": "http://example.com",
                "changeOrigin": true,
                "pathRewrite": {
                    "^/api": "/api/v1"
                }
            },
            "/simple": "http://example.com"
        }
    }
}

Proxy configuration can take the following options:

Property Description
target The source URL to proxy from
changeOrigin true to rewrite the origin header (required for named based virtual hosts)
ws true to proxy WebSockets
pathRewrite key/value pairs of paths that will get rewritten during the proxy. The key is a regular expression to be matched, the value is the replacement.

Note: Setting the proxy configuration as a string is equivelant to { target: "string" }.

Watching

Building with the --watch option observes the file system for changes and when with the development server (--serve) will automatically reload you browser.

# start a build with watch
dojo build --mode=dev --watch

# start a build using the development server and live reload
dojo build --mode=dev --serve --watch

Eject

Ejecting @dojo/cli-build-app will produce the following files under the config/build-app directory:

As already noted, the dojorc's build-app options are moved to config/build-app/build-options.json after ejecting. Further, the modes are specified using webpack's env flag (e.g., --env.mode=dev), defaulting to dist. You can run a build using webpack with:

node_modules/.bin/webpack --config=config/build-app/ejected.config.js --env.mode={dev|dist|unit|functional}

Configuration

Applications use a .dojorc file at the project root to control various aspects of development such as testing and building. This file, if provided, MUST be valid JSON, and the following options can be used beneath the "build-app" key:

bundles: object

Useful for breaking an application into smaller bundles, the bundles option is a map of webpack bundle names to arrays of modules that should be bundled together. For example, with the following configuration, both src/Foo and src/Bar will be grouped in the foo.[hash].js bundle.

Widget modules defined used with w() will be automatically converted to a lazily imported, local registry item in the parent widget. This provides a mechanism for declarative code splitting in your application.

{
    "build-app": {
        "bundles": {
            "foo": [
                "src/Foo",
                "src/Bar"
            ]
        }
    }
}

The bundles configuration supports globs for matching against modules, this can be useful for scenarios such as grouping all nls internationalization modules by locale:

{
    "build-app": {
        "bundles": {
            "fr": [
                "src/**/nls/fr/**"
            ],
            "de": [
                "src/**/nls/de/**"
            ]
        }
    }
}

Note: The precedence for bundle configuration is 1) An exact match wins against a glob match 2) Order based with the last config winning.

cldrPaths: string[]

An array of paths to CLDR JSON files. Used in conjunction with the locale and supportedLocales options (see below). If a path contains the string {locale}, that file will be loaded for each locale listed in the locale and supportedLocales properties. For example, with the following configuration the numbers.json file will be loaded for the "en", "es", and "fr" locales:

{
    "build-app": {
        "locale": "en",
        "supportedLocales": [ "es", "fr" ]
        "cldrPaths": [
            "cldr-data/main/{locale}/numbers.json"
        ]
    }
}

compression: Array<'gzip' | 'brotli'>

Options for compression when running in dist mode. Each array value represents a different algorithm, allowing both gzip and brotli builds to be output side-by-side. When used in conjunction with the --serve flag (in dist mode without memory watch), the compressed files will be served, with brotli preferred over gzip when available.

imageOptimization: boolean | object

If this is set to true, image optimization will be performed when running in dist mode using default settings. This uses several different libraries depending on the type of image being optimized. imageOptimization can be an object with individual configuration for these libraries in nested objects. They can also be individually disabled by passing { enabled: false }. Webp optimization is disabled by default and can be enabled by passing webp: { enabled true }. The options available for each library can be found at the links below:

externals: object

Non-modular libraries or standalone applications that cannot be bundled normally can be included in a Dojo application by providing an implementation of require or define when needed, and some configuration in the project's .dojorc file.

Configuration for external dependencies can be provided under the externals property of the build-app config. externals is an object with two allowed properties:

Property Type optional Description
from string false A path relative to the root of the project specifying the location of the files or folders to copy into the build application.
to string true A path that replaces from as the location to copy this dependency to. By default, dependencies will be copied to ${externalsOutputPath}/${to} or ${externalsOutputPath}/${from} if to is not specified. If there are any . characters in the path and it is a directory, it needs to end with a forward slash.
name string true Either the module id or the name of the global variable referenced in the application source.
inject string, string[], or boolean true This property indicates that this dependency defines, or includes, scripts or stylesheets that should be loaded on the page. If inject is set to true, then the file at the location specified by to or from will be loaded on the page. If this dependency is a folder, then inject can be set to a string or array of strings to define one or more files to inject. Each path in inject should be relative to ${externalsOutputPath}/${to} or ${externalsOutputPath}/${from} depending on whether to was provided.
type 'root' or 'umd' or 'amd' or 'commonjs' or 'commonjs2' true Force this module to a specific method of resolution. For AMD style require use umd or amd. For node style require use commonjs, and to access the object as a global use root

As an example the following configuration will inject src/legacy/layer.js into the application page, inject the file that defines the MyGlobal global variable, declare that modules a and b are external and should be delegated to the external layer, and then copy the folder node_modules/legacy-dep, from which several files are injected. All of these files will be copied into the externals folder, which could be overridden by specifying the outputPath property in the externals configuration.

"externals": {
    "dependencies": [
        "a",
        "b",
        { "from": "node_modules/GlobalLibrary.js", "to": "GlobalLibrary.js", "name": "MyGlobal", "inject": true },
        { "from": "src/legacy/layer.js", "to": "legacy/layer.js", "inject": true },
        {
            "from": "node_modules/legacy-dep",
            "to": "legacy-dep/",
            "inject": [ "moduleA/layer.js", "moduleA/layer.css", "moduleB/layer.js" ]
        }
    ]
}

Types for any dependencies included in externals can be installed in node_modules/@types, like any other dependency.

Because these files are external to the main build, no versioning or hashing will be performed on files in a production build, with the exception of the links to any injected assets. The to property can be used to specify a versioned directory to copy dependencies to in order to avoid different versions of files being cached.

features: object

A map of has features to boolean flags that can be used when building in dist mode to remove unneeded imports or conditional branches. See the static-build-loader documentation for more information.

locale: string

The default locale for the application. When the application loads, the root locale is set to the user's locale if it supported (see below), or to the default locale as a fallback.

pwa: object

A parent map that houses settings specific to creating progressive web applications.

pwa.manifest: object

Specifies information for a web app manifest. If provided, the following <meta> tags are injected into the application's index.html:

For example:

{
    "build-app": {
        "pwa": {
            "manifest": {
                "name": "Todo MVC",
                "description": "A simple to-do application created with Dojo",
                "icons": [
                    { "src": "./favicon-16x16.png", "sizes": "16x16", "type": "image/png" },
                    { "src": "./favicon-32x32.png", "sizes": "32x32", "type": "image/png" },
                    { "src": "./favicon-48x48.png", "sizes": "48x48", "type": "image/png" },
                    { "src": "./favicon-256x256.png", "sizes": "256x256", "type": "image/png" }
                ]
            }
        }
    }
}

pwa.serviceWorker: object

Generates a fully-functional service worker that is activated on startup, complete with precaching and custom route handling. Alternatively, you can create your own service worker file and @dojo/cli-build-app will ensure it is copied to the correct output directory. Under the hood, the ServicerWorkerPlugin from @dojo/webpack-contrib is used to generate the service worker, and all of its options are valid pwa.serviceWorker properties. Note that if pwa.serviceWorker.cachePrefix is not included, it defaults to the name property from the application's package.json.

{
    "build-app": {
        "pwa": {
            "serviceWorker": {
                "cachePrefix": "my-app",

                // exclude the "admin" bundle from caching
                "excludeBundles": [ "admin" ],

                "routes": [
                    // Use the cache-first strategy for loading images, adding them to the "my-app-images" cache.
                    // Only the first ten images should be cached, and for one week.
                    {
                        "urlPattern": ".*\\.(png|jpg|gif|svg)",
                        "strategy": "cacheFirst",
                        "cacheName": "my-app-images",
                        "expiration": { "maxEntries": 10, "maxAgeSeconds": 604800 }
                    },

                    // Use the cache-first strategy to cache up to 25 articles that expire after one day.
                    {
                        "urlPattern": "http://my-app-url.com/api/articles",
                        "strategy": "cacheFirst",
                        "expiration": { "maxEntries": 25, "maxAgeSeconds": 86400 }
                    }
                ]
            }
        }
    }
}

build-time-render(BTR): object

Renders the application to HTML during the build and in-lines the critical CSS. This allows the application to effectively render static HTML pages and provide some advantages of SSR (server side rendering) such as performance, SEO etc without the complexities of running a server to support full SSR.

{
    "build-app": {
        "build-time-render": {
            "root": "app",
            "paths": [
                "#home",
                {
                    "path": "#comments/9999",
                    "match": [ "#comments\/.*" ]
                }
            ]
        }
    }
}

BTR generates a screenshot for each of the paths rendered during the build in the output/info/screenshots directory of your project.

Build time rendering exposes a has flag build-time-render that can be used to skip functionality that cannot be executed at build time, for example fetching external data.

if (!has('build-time-render')) {
    fetch( /* ... */ );
}

Note: The index.html of your application needs to contain a DOM node with the id specified as the root in the configuration. This DOM node needs to be used when mounting the renderer for application:

const r = renderer(() => w(YourAppWidget, {}));
r.mount({ domNode: document.getElementById('app')! });

supportedLocales: string[]

An array of supported locales beyond the default. When the application loads, the user's locale is checked against the list of supported locales. If the user's locale is compatible with the supported locales, then the user's locale is used throughout the application. Otherwise, the default locale is used. For example, with the following configuration, the application locale will be set to Pashto or Arabic if either is listed as the user's locale, with Farsi used as the default.

Example:

{
    "build-app": {
        "locale": "fa",
        "supportedLocales": [ "ps", "ar" ],
        "compression": "gzip",
        "bundles": {
            "widgets": [
                "src/widgets/Header",
                "src/widgets/Footer"
            ]
        }
    }
}

How do I contribute?

We appreciate your interest! Please see the Dojo Meta Repository for the Contributing Guidelines. This repository uses prettier for code style and is configured with a pre-commit hook to automatically fix formatting issues on staged .ts files before performing the commit. If you are changing the .dojorc configuration behaviour for build-app, please make sure to update the schema.json (a JSON Schema description which is used to validate the build-app configuration) to match the changes.

Installation

To start working with this package, clone the repository and run:

npm install

In order to build the project, you can run all the build steps via:

npm run build

Scripts

watch

Will run a watcher process which looks for changes in the source code TypeScript files and runs the build scripts to update the contents of the built files in dist with latest changes made.

clean

Runs the clean up script which removes any built files like output, dist, coverage which get created on build and testing steps.

lint

Runs the ts-lint and prettier on all .ts files in the src and tests directories. ts-lint will ensure that all linting rules have been abided by and prettier will fix any detected code style violations in the code.

Testing

Test cases MUST be written using Intern using the BDD test interface and Assert assertion interface.

90% branch coverage MUST be provided for all code submitted to this repository, as reported by istanbul’s combined coverage results for all supported platforms.

The command is tested by running via the Dojo CLI and asserting the build output against known fixtures. To do this, a test artifact needs to be built and installed into the test-app:

npm test

Licensing information

© 2018 JS Foundation. New BSD license.