Skip to content

A nano-sized high performance js/css resource bundler targeting WordPress Gutenberg.

License

Notifications You must be signed in to change notification settings

IONOS-WordPress/cm4all-wp-bundle

Repository files navigation

Introduction

cm4all-wp-bundle is a nano-sized JS/CSS bundler for WordPress/Gutenberg related projects.

The provided bundler transpiles JavaScript and JSX (including CSS import statements) into bundled Javascript/CSS runnable in the Browser.

What means Transpilation/bundling ?

JavaScript imports of @wordpress/* and react NPM packages will be transformed to their matching global pendants (window.wp.* and window.react). JSX/PostCSS/SCSS support is built-in.

It can act as a lightweight (and expotential faster) alternative to the build script provided by @wordpress/scripts.

cm4all-wp-bundle uses esbuild and sass under the hood to provide JavaScript/SCSS/Sass transpilation capabilities instead of webpack. And nothing more.

Target audience

If you are developing Javascript/CSS for the WordPress/Gutenberg Ecosystem like Wordpress plugins/themes or WordPress blocks this package will give you the opportunity to much faster build times and less project dependencies.

Installation

The package can be used as NPM package cm4all-wp-bundle or preinstalled in Docker image 'lgersman/cm4all-wp-bundle'.

If you are not planning to use additional esbuild plugins I would suggest you to use the 'lgersman/cm4all-wp-bundle' Docker image since it minimizes the hassle (NodeJS version/package dependency bloating) of your project configuration.

Docker

You don't need to install anything when using the docker image - just execute a container, the docker image will be downloaded on demand :

docker run
  -i \
  --rm \
  --mount type=bind,source=$(pwd)/your-repo,target=/app \
  lgersman/cm4all-wp-bundle:latest \
  --outdir=dist/ \
  ./src/gutenberg-extension.js
  ./src/blocks/my-block/edit.js

More Docker image specific documentation can be found at the official 'lgersman/cm4all-wp-bundle' docker hub page.

NPM

Executing npm install --save-dev cm4all-wp-bundle will install the package as development dependency in your project.

The NPM package provides a same named package script cm4all-wp-bundle which can be used in your build scripts.

  ...
  "scripts": {
      ...
      "build": "cm4all-wp-bundle --outdir=./dist ./src/gutenberg-extension.js ./src/blocks/my-block/edit.js"
      ...
    },
  ...

If you want to execute the bundler directly in your Javascript Code (or planning to use additional esbuild plugins) you can even use the JavaScript API:

import bundle from 'cm4all-wp-bundle';

...

await bundle({
  entryPoints: ['./src/gutenberg-extension.js', './src/blocks/my-block/edit.js'],
  outdir: './test/fixtures/wordpress/build',
});

Configuration

CLI

The bundler script cm4all-wp-bundle provided by the package supports various commandline arguments :

Arguments

  • --watch/-w

    (optional)

    Listen for changes on the file system and to rebuild whenever a file changes that could invalidate the build.

  • --verbose/-v

    (optional)

    Produces more verbose log output.

  • --mode=[production|development]

    (default=production)

    Produces a production (*.min.*) or development version of bundled js/css code.

    In case of mode == 'production' the transpiled sources will also be minifed.

  • --banner=<string>

    (default='')

    Use this to insert an arbitrary string at the beginning of generated JavaScript and CSS files.

  • --footer=<string>

    (default='')

    Use this to insert an arbitrary string at the end of generated JavaScript and CSS files.

  • --target=<string>

    (default='esnext') May occur multiple times.

    Target environments for the generated JavaScript and/or CSS code (see https://esbuild.github.io/api/#target).

  • --global-name=<string>

    (optional)

    Sets the name of the global variable which is used to store the exports from the entry point.

    Can only be used if a single file gets transpiled (see https://esbuild.github.io/api/#global-name)

  • --analyze=<boolean>

    (default=false) Generates an easy-to-read report about the contents of your bundle

  • --outdir=<path>

    (default=[current working directory])

    This option sets the output directory for the build operation.

Advanced CLI configuration

In advance to the commandline arguments you reconfigure almost any esbuild option using STDIN to the bundler.

The STDIN CLI interface let's you override presets preconfigured using the commandline arguments.

You can provide the advanced configuration

  • using a file

    // cm4all-wp-bundle.json
    {
      "wordpress": {
        // adds additional mapping from import statements => global variable (window.*)
        "mappings": {
          "@cm4all-impex/debug": "wp.impex.debug",
          "@cm4all-impex/filters": "wp.impex.filters",
          "React": "React"
        }
      }
    }

    and execute

    cat ./cm4all-wp-bundle.json | \
    cm4all-wp-bundle --global-name=wp.impex.store --outdir=./dist ./src/wp.impex.store.mjs

    Same same using the docker image:

    cat ./cm4all-wp-bundle.json | \
    docker run -i --rm -v $(pwd):/app lgersman/cm4all-wp-bundle --global-name=wp.impex.store --outdir=./dist ./src/wp.impex.store.mjs
  • using echo

    echo '{ "wordpress" : { "mappings" : { "@cm4all-impex/debug" : "wp.impex.debug", "@cm4all-impex/filters" : "wp.impex.filters", "React": "React" } }}' | \
    cm4all-wp-bundle --global-name=wp.impex.store --outdir=./dist ./src/wp.impex.store.mjs

    The docker command looks quite similar as you might guess:

    echo '{ "wordpress" : { "mappings" : { "@cm4all-impex/debug" : "wp.impex.debug", "@cm4all-impex/filters" : "wp.impex.filters", "React": "React" } }}' | \
    docker run -i --rm -v $(pwd):/app lgersman/cm4all-wp-bundle --global-name=wp.impex.store --outdir=./dist ./src/wp.impex.store.mjs
  • using cat and shell HEREDOC syntax :

    cat << EOF | cm4all-wp-bundle --global-name=wp.impex.store --outdir=./dist ./src/wp.impex.store.mjs
    {
      "wordpress" : {
        "mappings" : {
          "@cm4all-impex/debug" : "wp.impex.debug",
          "@cm4all-impex/filters" : "wp.impex.filters",
          "React": "React"
        }
      }
    }
    EOF

    Same same using the docker image:

    cat << EOF | docker run -i --rm -v $(pwd):/app lgersman/cm4all-wp-bundle --global-name=wp.impex.store --outdir=./dist ./src/wp.impex.store.mjs
    {
      "wordpress" : {
        "mappings" : {
          "@cm4all-impex/debug" : "wp.impex.debug",
          "@cm4all-impex/filters" : "wp.impex.filters",
          "React": "React"
        }
      }
    }
    EOF

Advanced JSON Schema structure

The JSON provided via STDIN must match the cm4all-wp-bundle JSON Schema (can be found here : bundle-configuration.schema.json).

The cm4all-wp-bundle JSON Schema declares 3 top level properties :

  • "wordpress" : { ... }

    This property may be used to define additional import-package => global_variable mappings using the sub property "mappings". Using this property you can declare additional import-package => global_variable mappings to apply during transpilation. Each entry in "mappings" declares a single mappings where the key is the import-package and the value the global variable to map to.

    See https://github.com/IONOS-WordPress/cm4all-wp-bundle/blob/develop/src/plugins/wordpress.js to get a picture of all by default mapped packages.

    An example:

    Suppose you want to use NPM package "debug". But this package should not be bundled in your code. And you've already managed that this package is already in the browser in global variable window.myapp.debug;

    This scenario can simply expressed using the Advanced JSON Schema configuration :

    {
      "wordpress": {
        "mappings": {
          "debug": "myapp.debug"
        }
      }
    }

    The mapping target will automatically be prefixed with "window."

  • "sass" : { ... }

    The "sass" property can be used to express a custom sass configuration to the bundler.

    An example :

    You have some shared Sass files located somewhere else in your project repository and use them in your code :

    ...
    /*
      this file is located in ./src/components/my-component.scss
    */
    ...
    /*
      common.sass is located in ./shared-css/
    */
    @import common.sass;
    ...

    To get the Sass compiler a chance to find these shared files you could use the Sass option "loadPaths"

    {
      "sass": {
        "loadPaths": ["./shared-css/"]
      }
    }

    You can provide Sass any configuration property as stated in the Sass compiler documentation.

  • "esbuild" : { ... }

    Using this property it's possible to customize the esbuild's transpilation process.

    An example:

    Suppose you want

    • configuring a loader for a given file type lets you load that file type with an import statement or a require call. For example, configuring the .png file extension to use the data URL loader means importing a .png file gives you a data URL containing the contents of that image:

      import url from './example.png';
      let image = new Image();
      image.src = url;
      document.body.appendChild(image);
      
      import svg from './example.svg';
      let doc = new DOMParser().parseFromString(svg, 'application/xml');
      let node = document.importNode(doc.documentElement, true);
      document.body.appendChild(node);

      The above code can be bundled using the the following advanced configuration :

      {
        "esbuild": {
          "loader": {
            ".png": "dataurl",
            ".svg": "text"
          }
        }
      }
    • let's add another condition : We want accidental console.* and debugger statements to be removed. This can be done using esbuild's drop option :

      {
        "esbuild": {
          "loader": {
            ".png": "dataurl",
            ".svg": "text"
          },
          "drop": ["console", "debugger"]
        }
      }

    Please note that cm4all-wp-bundle already need to preconfigure some esbuild options to get your sources correctly transformed. So it might be that case that you override preconfigured options using the cm4all-wp-bundle JSON Schema configuration. Use can use the bundlers verbose options esbuild options get computed by cm4all-wp-bundle.

    You can provide esbuild any of it's configuration options using the "esbuild" property.

Editing/Validation support

You can get editing/validation support for your file based cm4all-wp-bundle JSON Schema configuration. Create a new JSON file with following contents:

{
  "$schema": "https://github.com/IONOS-WordPress/cm4all-wp-bundle/blob/develop/bundle-configuration.schema.json"
}

If you've installed the cm4all-wp-bundle NPM package you can also reference the locally installed JSON schema file contained in the package (a lot of JSON Schema supporting editor alternatives to VSCode is available online).

Voilà - VSCode (and any other Schema aware editor) provides you autocompletion, documentation and so on.

Javascript API

The JavaScript API of this package let's you integrate the bundler into custom JavaScript build scripts.

The package exports a single function async bundle(options) exposing the bundler.

Example (see tests for real live usage) :

...
await bundle({
  mode: 'development',
  entryPoints: ['./test/fixtures/wordpress/mylib.js', './test/fixtures/wordpress/figure.js'],
  wordpress: {
    mappings: {
      './mylib.js': 'window.my.lib',
    },
  },
  outdir: './test/fixtures/wordpress/build',
});
...

The options argument has the same shape as esbuild's build/buildSync(...) function.

Please refer to esbuild's build options documentation to see all available options.