This is a CiviCRM extension with some simple examples of loading ECMAScript Modules (ESMs).
To view the demos, install the extension and navigate to civicrm/esmdemo
.
Basic ESM support allows you to load an ESM file. This resembles the traditional loading of Javascript files. Compare:
-Civi::resources()->addScriptFile('com.example.foo', 'my-script.js');
+Civi::resources()->addModuleFile('com.example.foo', 'my-script.js');
By switching from addScriptFile()
to addModuleFile()
, you gain the ability to import
dependencies.
There are important limitations. With basic ESM support, the import
must specify a physical path.
This limits you to scenarios where the physical path is easy to identify, such as:
- Importing dependencies from the same folder.
import { myOtherStuff } from './my-other-module.js'
- Importing dependencies from a specific CDN.
import { myOtherStuff } from 'https://unpkg.com/foo/my-other-module.js'
We have a few examples of these techniques.
- Hello World: Relative Path (
civicrm/esmdemo/hello-relpath
)- The page-controller loads the first file:
Civi::resources()->addModuleFile(E::LONG_NAME, 'js/hello-relpath.js');
- hello-relpath.js imports the second file, display-o-tron.js.
import displayotron from './display-o-tron.js';
- The page-controller loads the first file:
- Hello World: VueJS CDN (
civicrm/esmdemo/hello-vuejs-cdn
)- The page-controller loads the first file and some static HTML.
Civi::resources()->addModuleFile(E::LONG_NAME, 'js/hello-vuejs-cdn.js'); CRM_Core_Region::instance('page-body')->addMarkup('<div id="app"><h2>{{ message }}</h2></div>');
- hello-vuejs-cdn.js imports additional files from CDN.
import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
- The page-controller loads the first file and some static HTML.
- Hello World: ReactJS CDN (
civicrm/esmdemo/hello-reactjs-cdn
)- The page-controller loads the first file and some static HTML.
Civi::resources()->addModuleFile(E::LONG_NAME, 'js/hello-reactjs-cdn.js'); CRM_Core_Region::instance('page-body')->addMarkup('<div id="root"></div>');
- hello-reactjs-cdn.js imports additional files from CDN.
const React = await import('https://unpkg.com/@esm-bundle/react@17.0.2-fix.1/esm/react.production.min.js'); const ReactDOM = await import ('https://unpkg.com/@esm-bundle/react-dom@17.0.2-fix.0/esm/react-dom.resolved.production.min.js');
- The page-controller loads the first file and some static HTML.
If you want to share modules between different parts of the system (e.g. between civicrm/js/*
and my-extension/js/*
), then
the basic support is not sufficient. You also need to support logical paths based on an import map.
Here is an example:
- Hello World: Import Map Hook (
civicrm/esmdemo/hello-import-map-hook
)- The module declares that
geolib/
maps to the physical path for{MY_EXTENSION}/packages/geometry-library-1.2.3/
.function esmdemo_civicrm_esmImportMap(array &$importMap, array $context): void { $importMap['imports']['geolib/'] = E::url('packages/geometry-library-1.2.3/'); }
- The page-controller loads the first file:
Civi::resources()->addModuleFile(E::LONG_NAME, 'js/hello-import-map-hook.js');
- hello-import-map-hook.js imports a file from
geolib/
and uses it:import Square from 'geolib/Square.js'; var myShape = new Square(2.5);
- Square.js imports more files:
import Rectangle from './Rectangle.js'; export default class Square extends Rectangle { ... }
- The module declares that