Moodle's standard way to build AMD JavaScript is its grunt toolchain, which pulls in Node, Babel and a fair amount of configuration. If you just want to compile a plugin's amd/src/ files into the amd/build/*.min.js that Moodle serves, esbuild is a much lighter alternative. The key to doing it cleanly is to author named AMD modules so the output loads exactly like a grunt-built module. This guide covers that setup, configuration, and the build workflow.

Using esbuild to Build AMD Modules in a Moodle Plugin

Purpose

To compile JavaScript source files in amd/src/ into the AMD .min.js files in amd/build/ that Moodle loads, using esbuild instead of grunt.

Directory Structure

Your Moodle plugin should contain:

mod/yourplugin/
 amd/
    src/
       editor.js
       runtime.js
    build/      (created automatically by esbuild)

Each source file must begin with a named AMD define() block. The module name has to be plugintype_pluginname/filename, matching the plugin's component name and the file's path under amd/src/. This is the crucial detail: baking the name in yourself is what lets Moodle load the module directly, without relying on any server-side fix-up (see the note at the end).

// amd/src/editor.js  (plugin mod_yourplugin)
define('mod_yourplugin/editor', ['jquery'], function($) {
   return {
        init: function(cfg) {
            console.log('Hello from editor.js', cfg);
        }
    };
});

Step 1: Initialize NPM (once)

From your plugin root:

npm init -y

This creates a package.json file.

Step 2: Install esbuild

npm install --save-dev esbuild

This installs esbuild locally into your plugin.

Step 3: Create build.js file

Create a file named build.js in the plugin root:

touch build.js

Paste in:

// build.js
const esbuild = require('esbuild');
esbuild.build({
  entryPoints: ['amd/src/editor.js', 'amd/src/runtime.js'],
  outdir: 'amd/build',
  outExtension: { '.js': '.min.js' },
  bundle: false,        // keep each module separate; do not bundle dependencies
  minify: true,
  sourcemap: true,      // emit .min.js.map, matching what the grunt build ships
  target: ['es2015'],
}).then) => {
  console.log('JS build complete.');
}).catcherr) => {
  console.error('Build failed:', err);
  process.exit(1);
});

Step 4: Run the build

From your plugin root:

node build.js

You should see:

JS build complete.

Then verify:

ls amd/build/

You should see:

editor.min.js
editor.min.js.map
runtime.min.js
runtime.min.js.map

Optional: Add NPM build script

Edit package.json to add:

"scripts": {
  "build": "node build.js"
}

Now you can build with:

npm run build

TL;DR Summary

  • npm init -y
  • npm install --save-dev esbuild
  • Author each source file as a named module: define('plugintype_name/file', [...], ...)
  • Create build.js (minify + sourcemap, no format override)
  • Run node build.js or npm run build
  • Moodle loads your AMD modules from amd/build/*.min.js

Why the module name matters

Moodle's AMD loader expects every module to declare its own name (define('plugintype_name/file', ...)). The grunt toolchain injects that name automatically during its Babel step. esbuild does not, so you add it yourself in the source. If you omit the name and emit an anonymous define([...]), Moodle will still load it, but only because lib/requirejs.php runs a fallback (requirejs_fix_define()) that patches the name in at serve time. That fallback is explicitly marked in core as deprecated and slated for removal, so do not rely on it: name your modules and the output loads on its own merits.

A caveat on the plugins directory

If you submit a plugin to the moodle.org Plugins directory, its automated checks include a grunt build-state step that rebuilds amd/src/ and compares against the committed amd/build/. esbuild output will not byte-match grunt's, so a submitted plugin built this way may be flagged for mismatched build files. For in-house or client plugins this is a non-issue; for directory submissions, run the standard grunt build for the final committed artifacts.

Solin specializes in Moodle plugin development and JavaScript tooling.

Contact us