Skip to content

Commit

Permalink
Merge pull request #26195 from totten/master-esm
Browse files Browse the repository at this point in the history
dev/core#4279 - Add support for ECMAScript Modules (ESM, part 1)
  • Loading branch information
eileenmcnaughton authored May 11, 2023
2 parents 6b605a3 + dae48fc commit ae78a69
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 2 deletions.
12 changes: 10 additions & 2 deletions CRM/Core/Region.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ public function render($default, $allowCmsOverride = TRUE) {
break;

case 'scriptUrl':
if (!$allowCmsOverride || !$cms->addScriptUrl($snippet['scriptUrl'], $this->_name)) {
// ECMAScript Modules (ESMs) are basically Javascript files, but they require a slightly different incantation.
if (!empty($snippet['esm'])) {
$html .= sprintf("<script type=\"module\" src=\"%s\">\n</script>\n", $snippet['scriptUrl']);
}
elseif (!$allowCmsOverride || !$cms->addScriptUrl($snippet['scriptUrl'], $this->_name)) {
$html .= sprintf("<script type=\"text/javascript\" src=\"%s\">\n</script>\n", $snippet['scriptUrl']);
}
break;
Expand All @@ -107,7 +111,11 @@ public function render($default, $allowCmsOverride = TRUE) {
break;

case 'script':
if (!$allowCmsOverride || !$cms->addScript($snippet['script'], $this->_name)) {
// ECMAScript Modules (ESMs) are basically Javascript files, but they require a slightly different incantation.
if (!empty($snippet['esm'])) {
$html .= sprintf("<script type=\"module\">\n%s\n</script>\n", $snippet['script']);
}
elseif (!$allowCmsOverride || !$cms->addScript($snippet['script'], $this->_name)) {
$html .= sprintf("<script type=\"text/javascript\">\n%s\n</script>\n", $snippet['script']);
}
break;
Expand Down
46 changes: 46 additions & 0 deletions CRM/Core/Resources/CollectionAdderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,52 @@ public function add($snippet);
*/
public function addMarkup(string $markup, ...$options);

/**
* Add an ECMAScript Module (ESM) to the current page (<SCRIPT TYPE=MODULE>).
*
* Ex: addScript('alert("Hello world");', ['weight' => 123]);
*
* @param string $code
* JavaScript source code.
* @param array $options
* Open-ended list of key-value options. See CollectionInterface docs.
* Positional equivalence: addScript(string $code, int $weight, string $region).
* @return static
* @see CRM_Core_Resources_CollectionInterface
*/
public function addModule(string $code, ...$options);

/**
* Add an ECMAScript Module (ESM) from file to the current page (<SCRIPT TYPE=MODULE SRC=...>).
*
* Ex: addScriptFile('myextension', 'myscript.js', ['weight' => 123]);
*
* @param string $ext
* Extension name; use 'civicrm' for core.
* @param string $file
* File path -- relative to the extension base dir.
* @param array $options
* Open-ended list of key-value options. See CollectionInterface docs.
* Positional equivalence: addScriptFile(string $code, int $weight, string $region, mixed $translate).
* @return static
* @see CRM_Core_Resources_CollectionInterface
*/
public function addModuleFile(string $ext, string $file, ...$options);

/**
* Add an ECMAScript Module (ESM) by URL to the current page (<SCRIPT TYPE=MODULE SRC=...>).
*
* Ex: addScriptUrl('http://example.com/foo.js', ['weight' => 123])
*
* @param string $url
* @param array $options
* Open-ended list of key-value options. See CollectionInterface docs.
* Positional equivalence: addScriptUrl(string $url, int $weight, string $region).
* @return static
* @see CRM_Core_Resources_CollectionInterface
*/
public function addModuleUrl(string $url, ...$options);

/**
* Export permission data to the client to enable smarter GUIs.
*
Expand Down
73 changes: 73 additions & 0 deletions CRM/Core/Resources/CollectionAdderTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,79 @@ public function addMarkup(string $markup, ...$options) {
return $this;
}

/**
* Add an ECMAScript module (ESM) to the current page (<SCRIPT TYPE=MODULE>).
*
* Ex: addModule('alert("Hello world");', ['weight' => 123]);
*
* @param string $code
* JavaScript source code.
* @param array $options
* Open-ended list of key-value options. See CollectionInterface docs.
* Positional equivalence: addModule(string $code, int $weight, string $region).
* @return static
* @see CRM_Core_Resources_CollectionInterface
* @see CRM_Core_Resources_CollectionAdderInterface::addModule()
*/
public function addModule(string $code, ...$options) {
$this->add(self::mergeStandardOptions($options, [
'esm' => TRUE,
'script' => $code,
]));
return $this;
}

/**
* Add an ECMAScript Module (ESM) from file to the current page (<SCRIPT TYPE=MODULE SRC=...>).
*
* Ex: addModuleFile('myextension', 'myscript.js', ['weight' => 123]);
*
* @param string $ext
* Extension name; use 'civicrm' for core.
* @param string $file
* File path -- relative to the extension base dir.
* @param array $options
* Open-ended list of key-value options. See CollectionInterface docs.
* Positional equivalence: addModuleFile(string $code, int $weight, string $region, mixed $translate).
* @return static
* @see CRM_Core_Resources_CollectionInterface
* @see CRM_Core_Resources_CollectionAdderInterface::addModuleFile()
*/
public function addModuleFile(string $ext, string $file, ...$options) {
$this->add(self::mergeStandardOptions($options, [
'esm' => TRUE,
'scriptFile' => [$ext, $file],
'name' => "$ext:$file",
// Setting the name above may appear superfluous, but it preserves a historical quirk
// where Region::add() and Resources::addScriptFile() produce slightly different orderings.
]));
return $this;
}

/**
* Add an ECMAScript Module (ESM) by URL to the current page (<SCRIPT TYPE=MODULE SRC=...>).
*
* Ex: addModuleUrl('http://example.com/foo.js', ['weight' => 123])
*
* @param string $url
* @param array $options
* Open-ended list of key-value options. See CollectionInterface docs.
* Positional equivalence: addModuleUrl(string $url, int $weight, string $region).
* @return static
* @see CRM_Core_Resources_CollectionInterface
* @see CRM_Core_Resources_CollectionAdderInterface::addModuleUrl()
*/
public function addModuleUrl(string $url, ...$options) {
$this->add(self::mergeStandardOptions($options, [
'esm' => TRUE,
'scriptUrl' => $url,
'name' => $url,
// Setting the name above may appear superfluous, but it preserves a historical quirk
// where Region::add() and Resources::addScriptUrl() produce slightly different orderings.
]));
return $this;
}

/**
* Export permission data to the client to enable smarter GUIs.
*
Expand Down
1 change: 1 addition & 0 deletions CRM/Core/Resources/CollectionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
* not guaranteed among versions/implementations.)
* - disabled: int, default=0
* - region: string
* - esm: bool, enable ECMAScript Module (ESM) support for "script","scriptFile","scriptUrl"
* - translate: bool|string, Autoload translations. (Only applies to 'scriptFile')
* - FALSE: Do not load translated strings.
* - TRUE: Load translated strings. Use the $ext's default domain.
Expand Down
10 changes: 10 additions & 0 deletions tests/phpunit/CRM/Core/RegionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ public function testAllTypes() {
CRM_Core_Region::instance('testAllTypes')->add([
'jquery' => '$("div");',
]);
CRM_Core_Region::instance('testAllTypes')->add([
'scriptUrl' => '/my%20module.mjs',
'esm' => TRUE,
]);
CRM_Core_Region::instance('testAllTypes')->add([
'script' => 'import foo from "./foobar.mjs";',
'esm' => TRUE,
]);
CRM_Core_Region::instance('testAllTypes')->add([
'styleUrl' => '/foo%20bar.css',
]);
Expand All @@ -131,6 +139,8 @@ public function testAllTypes() {
. "<script type=\"text/javascript\" src=\"/foo%20bar.js\">\n</script>\n"
. "<script type=\"text/javascript\">\nalert(\"hi\");\n</script>\n"
. "<script type=\"text/javascript\">\nCRM.\$(function(\$) {\n\$(\"div\");\n});\n</script>\n"
. "<script type=\"module\" src=\"/my%20module.mjs\">\n</script>\n"
. "<script type=\"module\">\nimport foo from \"./foobar.mjs\";\n</script>\n"
. "<link href=\"/foo%20bar.css\" rel=\"stylesheet\" type=\"text/css\"/>\n"
. "<style type=\"text/css\">\nbody { background: black; }\n</style>\n";
$this->assertEquals($expected, $actual);
Expand Down
40 changes: 40 additions & 0 deletions tests/phpunit/CRM/Core/Resources/CollectionTestTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,21 @@ public function getSnippetExamples() {
]
);

$addCases(
[
'add(scriptUrl,esm)' => ['add', ['scriptUrl' => 'http://example.com/foo.js', 'esm' => TRUE]],
'addModuleUrl()' => ['addModuleUrl', 'http://example.com/foo.js'],
],
[
'name' => 'http://example.com/foo.js',
'disabled' => FALSE,
'weight' => 1,
'type' => 'scriptUrl',
'scriptUrl' => 'http://example.com/foo.js',
'esm' => TRUE,
]
);

$addCases(
[
'add(styleFile)' => ['add', ['styleFile' => ['civicrm', 'css/civicrm.css']]],
Expand Down Expand Up @@ -153,6 +168,15 @@ public function getSnippetExamples() {
],
];

$addCases(
[
'add(scriptFile): esm' => ['add', ['scriptFile' => ['civicrm', 'js/foo.js'], 'esm' => TRUE]],
'addScriptFile(): esm' => ['addModuleFile', 'civicrm', 'js/foo.js', ['esm' => TRUE]],
'addModuleFile(): dfl' => ['addModuleFile', 'civicrm', 'js/foo.js'],
],
$basicFooJs + ['weight' => 1, 'translate' => TRUE, 'esm' => TRUE]
);

$addCases(
[
'add(scriptFile): dfl' => ['add', ['scriptFile' => ['civicrm', 'js/foo.js']]],
Expand Down Expand Up @@ -195,6 +219,22 @@ public function getSnippetExamples() {
]
);

$addCases(
[
'add(script): esm' => ['add', ['script' => 'window.alert("Boo!");', 'esm' => TRUE]],
'addScript(): esm' => ['addScript', 'window.alert("Boo!");', ['esm' => TRUE]],
'addModule(): dfl' => ['addModule', 'window.alert("Boo!");'],
],
[
'name' => 1 + $defaultCount,
'disabled' => FALSE,
'weight' => 1,
'type' => 'script',
'script' => 'window.alert("Boo!");',
'esm' => TRUE,
]
);

if ($allowsMarkup) {
$addCases(
[
Expand Down

0 comments on commit ae78a69

Please sign in to comment.