adobe / aem-boilerplate

Use this repository template for new AEM projects.
https://main--aem-boilerplate--adobe.hlx.page
Apache License 2.0
111 stars 284 forks source link

feat: a (revised) minimal plugin and template system #374

Open ramboz opened 2 weeks ago

ramboz commented 2 weeks ago

✏️ This is a revised version of #254 that relies more on custom DOM events and reduces the custom JS APIs a bit.

Use case

As a developer, I want to be able to easily organize my code into different files, and execute some of the logic in each phase of the page load as needed.

In particular, I want a way to:

Technical Requirements

Proposal

Introduce a new custom event dispatching logic with the capability to await its listeners so we can have blocking logic if needed (i.e. experiments need to replace the content before the page continues rendering). Event handlers can call the ev.await(…) method with a promise.

document.addEventListener('aem:eager', (ev) => {
  ev.await(new Promise((res) => setTimeout(res, 1000)));
});

Introduce new page lifecycle events:

Introduce 2 new namespaces on window.hlx.*:

Plugins and templates would follow the same API:

Extracting a new loadModule(name, cssPath, jsPath, args) method that both the loadBlock and the new plugin system use.

Usage

For ease of use, we export 2 convenience methods in aem.js, namely withPlugin & withTemplate.

Adding Templates

Add a new template to the project:

withTemplate('foo', '/templates/foo.js');

or the shorthand version:

withTemplate('/templates/bar.js');

or the bulk version:

withTemplate([
  '/templates/bar.js',
  '/templates/baz.js'
]);

or the the module approach that loads both JS and CSS:

withTemplate('/templates/bar');
// loads both /templates/bar/bar.css and /templates/bar/bar.js

Adding Plugins

Add a new inline plugin to the project:

withPlugin('inline-plugin-baz', {
  condition: () => true, // if defined, the condition is evaluated before running any code in the plugin
  eager: () => { … }, // these attach as listeners for the respective `aem:eager` event
  lazy: () => { … },
  delayed: () => { … },
});

Add a regular plugin to the project that will always load (no condition):

withPlugin('plugin-qux', { url: '/plugins/qux/src/index.js' });

or the module approach that loads both JS and CSS:

withPlugin('plugin-qux', { url: '/plugins/qux' });

or the shorthand version:

withPlugin('/plugins/qux');

All plugins load by default in the lazy phase, to offer best performance out of the box. If a plugin needs to load in another phase, this can be done via:

withPlugin('plugin-qux', {
  url: '/plugins/qux/src/index.js',
  load: 'eager', // can be `eager`, `lazy` or `delayed`. defaults to `lazy` if omitted
  options: { corge: 'grault' },
});

Plugin Template

/plugins/qux.js

// document: to keep it consistent with the loadEager in the scripts.js file
// options: additional options passed to the plugin when it was added
// context: passes a plugin context which gives access to the main lib-franklin.js APIs and avoids cyclic dependencies
//          it also turns all of those into pure functions that are easier to unit test
export default (document, options) => {
  console.log('plugin qux: init', options, context);
};

document.addEventListener('aem:eager', (ev) => {
  console.log('plugin qux: eager');
});

document.addEventListener('aem:lazy', (ev) => {
  console.log('plugin qux: lazy');
});

document.addEventListener('aem:delayed', (ev) => {
  console.log('plugin qux: delayed');
});

Examples

A barebone demo site built for this PR:

Test URLs:

aem-code-sync[bot] commented 2 weeks ago
Page Scores Audits Google
/ PERFORMANCE A11Y SEO BEST PRACTICES SI FCP LCP TBT CLS PSI
ramboz commented 2 weeks ago

@rofe @trieloff here is a revised version of the plugin proposal based on:

  1. @trieloff's initial feedback that this should be more event-based
  2. learnings from the v1 version we deployed to simplify the APIs