From 591fef3a7021a3ebc2a3b16181970ea8557cbbd2 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Mon, 22 May 2023 17:05:59 -0700 Subject: [PATCH] ImportMap - Change from "array of URLs" to "object with relpaths" --- CRM/Utils/Hook.php | 22 ++++--------- Civi/Esm/BasicLoaderTrait.php | 17 +++++++++- Civi/Esm/ImportMap.php | 61 ++++++++++++++++++++++++++--------- Civi/Esm/README.md | 6 ++-- 4 files changed, 71 insertions(+), 35 deletions(-) diff --git a/CRM/Utils/Hook.php b/CRM/Utils/Hook.php index 69e5f6b5b8..4754078cb8 100644 --- a/CRM/Utils/Hook.php +++ b/CRM/Utils/Hook.php @@ -2846,25 +2846,17 @@ abstract class CRM_Utils_Hook { * * Subscribers should assume that the $importMap will be cached and re-used. * - * @link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap - * @link https://github.com/WICG/import-maps - * @see \Civi\Esm\ImportMap + * Example usage: * - * @param array $importMap - * Ex: ['imports' => ['square/' => 'https://example.com/square/']] + * function my_civicrm_esmImportMap($importMap): void { + * $importMap->addPrefix('geolib/', E::LONG_NAME, 'packages/geometry-library-1.2.3/'); + * } * - * This data-structure is defined by the browser-vendors. In the future, browser-vendors - * may update the supported features. It is the subscribers' responsibility to conform - * with browser standards. - * @param array $context - * In the future, the `$context` may provide hints about the usage environment. Based - * on these hints, you may omit unnecessary mappings. However, in the absence of a clear - * hint, listeners should tend to over-communicate (i.e. report all the mappings that - * you can). + * @param \Civi\Esm\ImportMap $importMap */ - public static function esmImportMap(array &$importMap, array $context): void { + public static function esmImportMap(\Civi\Esm\ImportMap $importMap): void { $null = NULL; - self::singleton()->invoke(['importMap', 'context'], $importMap, $context, $null, + self::singleton()->invoke(['importMap'], $importMap, $null, $null, $null, $null, $null, 'civicrm_esmImportMap' ); diff --git a/Civi/Esm/BasicLoaderTrait.php b/Civi/Esm/BasicLoaderTrait.php index c086922e87..c63a5e34f7 100644 --- a/Civi/Esm/BasicLoaderTrait.php +++ b/Civi/Esm/BasicLoaderTrait.php @@ -75,7 +75,7 @@ trait BasicLoaderTrait { return; } - $importMap = $this->importMap->get(); + $importMap = $this->buildImportMap(); $region->add([ 'name' => 'import-map', 'markup' => empty($importMap) ? '' : $this->renderImportMap($importMap), @@ -83,6 +83,21 @@ trait BasicLoaderTrait { ]); } + /** + * @return array + * The import-map, as defined by the browser-vendor or browser-based polyfill. + * For BasicLoaderTrait, we do a straight-through mapping (read extension-names, generate extension-URLs). + * @link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap + * @link https://github.com/WICG/import-maps + */ + protected function buildImportMap(): array { + $result = []; + foreach ($this->importMap->getPrefixes() as $prefix) { + $result['imports'][$prefix['prefix']] = Civi::resources()->getUrl($prefix['ext'], $prefix['relPath']); + } + return $result; + } + /** * Format the list of imports as an HTML tag. * diff --git a/Civi/Esm/ImportMap.php b/Civi/Esm/ImportMap.php index d74856f471..24d678c2a0 100644 --- a/Civi/Esm/ImportMap.php +++ b/Civi/Esm/ImportMap.php @@ -25,14 +25,44 @@ use Civi\Core\HookInterface; class ImportMap extends \Civi\Core\Service\AutoService implements HookInterface { /** - * Get the list of imports. + * @var array|null + * Ex: [['prefix' => 'lodash/', 'ext' => 'civicrm', 'relPath' => 'bower_components/lodash/']] + */ + protected $prefixes = NULL; + + /** + * @param string $prefix + * @param string $ext + * Ex: 'civicrm', 'org.example.foobar' + * @param string $relPath + * Relative path within $ext. + * Ex: '/', '/packages/foo-1.2.3/' + * @return $this + * @see \CRM_Core_Resources::getUrl() + * @see \CRM_Core_Resources::getPath() + */ + public function addPrefix(string $prefix, string $ext, string $relPath = ''): ImportMap { + $this->prefixes[$prefix] = [ + 'prefix' => $prefix, + 'ext' => $ext, + 'relPath' => $relPath, + ]; + return $this; + } + + public function getPrefixes(): array { + if ($this->prefixes === NULL) { + $this->load(); + } + return $this->prefixes; + } + + /** + * Load the list of imports, as declared by CiviCRM and its extensions. * - * @return array - * Ex: ['imports' => ['square/' => 'https://example.com/square/']] - * @link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap - * @link https://github.com/WICG/import-maps + * @return $this */ - public function get(): array { + protected function load(): ImportMap { // Just how dynamic is the import-map? Perhaps every page-view would have a different // import-map? Perhaps there should be one static import-map used for all page-views? // Can we cache the import-map? @@ -48,24 +78,23 @@ class ImportMap extends \Civi\Core\Service\AutoService implements HookInterface // We cannot predict whether their approach will be static or dynamic. If our // map is static, then we can safely feed it into UFs either way. - $importMap = \Civi::cache('long')->get('import-map'); - if ($importMap === NULL) { - $importMap = []; - \CRM_Utils_Hook::esmImportMap($importMap, []); - \Civi::cache('long')->set('import-map', $importMap); + $this->prefixes = \Civi::cache('long')->get('esm.import_map.prefix'); + if ($this->prefixes === NULL) { + $this->prefixes = []; + \CRM_Utils_Hook::esmImportMap($this); + \Civi::cache('long')->set('esm.import_map.prefix', $this->prefixes); } - return $importMap; + return $this; } /** * Register default mappings on behalf of civicrm-core. * - * @param array $importMap - * @param array $context + * @param ImportMap $importMap * @return void */ - public function hook_civicrm_esmImportMap(array &$importMap, array $context): void { - $importMap['imports']['civicrm/'] = \Civi::paths()->getUrl('[civicrm.root]/'); + public function hook_civicrm_esmImportMap(ImportMap $importMap): void { + $importMap->addPrefix('civicrm/', 'civicrm'); } } diff --git a/Civi/Esm/README.md b/Civi/Esm/README.md index 9b8a6716de..e19115de34 100644 --- a/Civi/Esm/README.md +++ b/Civi/Esm/README.md @@ -37,9 +37,9 @@ Below, we consider a few perspectives on this functionality. If you develop an extension which includes ESM files, then you may want to add items to the import-map: ```php -function myext_civicrm_esmImportMap(array &$importMap, array $context): void { - $importMap['imports']['foo/'] = E::url('js/foo/'); - $importMap['imports']['bar/'] = E::url('packages/bar/dist/'); +function myext_civicrm_esmImportMap(\Civi\Esm\ImportMap $importMap): void { + $importMap->addPrefix('foo/', E::LONG_NAME, 'js/foo/'); + $importMap->addPrefix('bar/', E::LONG_NAME, 'packages/bar/dist/'); } ``` -- 2.25.1