diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04e88091a..b43e1dcb4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,10 @@
# v1.7.27
## mm/dd/2022
-1. [](#improved)
+1. [](#new)
+ * Added support for generic `assets.link()` for external references. No pipeline support.
+ * Added support for `assets.addJSmodule()` with full pipeline support.
+2. [](#improved)
* Improved `Utils::download()` method to allow overrides on download name, mime and expires header
* Improved `onPageFallBackUrl` event
3. [](#bugfix)
diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml
index aff54b46c..c5427d125 100644
--- a/system/blueprints/config/system.yaml
+++ b/system/blueprints/config/system.yaml
@@ -992,6 +992,39 @@ form:
validate:
type: bool
+ assets.js_module_pipeline:
+ type: toggle
+ label: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE
+ help: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ assets.js_module_pipeline_include_externals:
+ type: toggle
+ label: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE_INCLUDE_EXTERNALS
+ help: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE_INCLUDE_EXTERNALS_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ assets.js_module_pipeline_before_excludes:
+ type: toggle
+ label: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE_BEFORE_EXCLUDES
+ help: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE_BEFORE_EXCLUDES_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
assets.js_minify:
type: toggle
label: PLUGIN_ADMIN.JAVASCRIPT_MINIFY
diff --git a/system/config/system.yaml b/system/config/system.yaml
index 60abcee34..2de075b26 100644
--- a/system/config/system.yaml
+++ b/system/config/system.yaml
@@ -127,6 +127,9 @@ assets: # Configuration for Assets Mana
js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file
js_pipeline_include_externals: true # Include external URLs in the pipeline by default
js_pipeline_before_excludes: true # Render the pipeline before any excluded files
+ js_module_pipeline: false # The JS Module pipeline is the unification of multiple JS Module resources into one file
+ js_module_pipeline_include_externals: true # Include external URLs in the pipeline by default
+ js_module_pipeline_before_excludes: true # Render the pipeline before any excluded files
js_minify: true # Minify the JS during pipelining
enable_asset_timestamp: false # Enable asset timestamps
enable_asset_sri: false # Enable asset SRI
diff --git a/system/src/Grav/Common/Assets.php b/system/src/Grav/Common/Assets.php
index 36dc79dd0..2956ea45d 100644
--- a/system/src/Grav/Common/Assets.php
+++ b/system/src/Grav/Common/Assets.php
@@ -30,14 +30,21 @@ class Assets extends PropertyObject
use TestingAssetsTrait;
use LegacyAssetsTrait;
+ const LINK = 'link';
const CSS = 'css';
const JS = 'js';
+ const JS_MODULE = 'js_module';
+ const LINK_COLLECTION = 'assets_link';
const CSS_COLLECTION = 'assets_css';
const JS_COLLECTION = 'assets_js';
+ const JS_MODULE_COLLECTION = 'assets_js_module';
+ const LINK_TYPE = Assets\Link::class;
const CSS_TYPE = Assets\Css::class;
const JS_TYPE = Assets\Js::class;
+ const JS_MODULE_TYPE = Assets\JsModule::class;
const INLINE_CSS_TYPE = Assets\InlineCss::class;
const INLINE_JS_TYPE = Assets\InlineJs::class;
+ const INLINE_JS_MODULE_TYPE = Assets\InlineJsModule::class;
/** @const Regex to match CSS and JavaScript files */
const DEFAULT_REGEX = '/.\.(css|js)$/i';
@@ -48,15 +55,16 @@ class Assets extends PropertyObject
/** @const Regex to match JavaScript files */
const JS_REGEX = '/.\.js$/i';
+ /** @const Regex to match JavaScriptModyle files */
+ const JS_MODULE_REGEX = '/.\.mjs$/i';
+
/** @var string */
protected $assets_dir;
/** @var string */
protected $assets_url;
/** @var array */
- protected $assets_css = [];
- /** @var array */
- protected $assets_js = [];
+ protected $assets= [];
// Following variables come from the configuration:
/** @var bool */
@@ -66,19 +74,17 @@ class Assets extends PropertyObject
/** @var bool */
protected $css_pipeline_before_excludes;
/** @var bool */
- protected $inlinecss_pipeline_include_externals;
- /** @var bool */
- protected $inlinecss_pipeline_before_excludes;
- /** @var bool */
protected $js_pipeline;
/** @var bool */
protected $js_pipeline_include_externals;
/** @var bool */
protected $js_pipeline_before_excludes;
/** @var bool */
- protected $inlinejs_pipeline_include_externals;
+ protected $js_module_pipeline;
/** @var bool */
- protected $inlinejs_pipeline_before_excludes;
+ protected $js_module_pipeline_include_externals;
+ /** @var bool */
+ protected $js_module_pipeline_before_excludes;
/** @var array */
protected $pipeline_options = [];
@@ -113,6 +119,12 @@ class Assets extends PropertyObject
$this->assets_dir = $locator->findResource('asset://');
$this->assets_url = $locator->findResource('asset://', false);
+ // Initialize asset collection storage
+ $this->assets[self::LINK_COLLECTION] = [];
+ $this->assets[self::CSS_COLLECTION] = [];
+ $this->assets[self::JS_COLLECTION] = [];
+ $this->assets[self::JS_MODULE_COLLECTION] = [];
+
$this->config($asset_config);
// Register any preconfigured collections
@@ -193,6 +205,8 @@ class Assets extends PropertyObject
call_user_func_array([$this, 'addCss'], $args);
} elseif ($extension === 'js') {
call_user_func_array([$this, 'addJs'], $args);
+ } elseif ($extension === 'mjs') {
+ call_user_func_array([$this, 'addJsModule'], $args);
}
}
}
@@ -222,7 +236,7 @@ class Assets extends PropertyObject
return $this;
}
- if (($type === $this::CSS_TYPE || $type === $this::JS_TYPE) && isset($this->collections[$asset])) {
+ if ($this->isValidType($type) && isset($this->collections[$asset])) {
$this->addType($collection, $type, $this->collections[$asset], $options);
return $this;
}
@@ -230,7 +244,9 @@ class Assets extends PropertyObject
// If pipeline disabled, set to position if provided, else after
if (isset($options['pipeline'])) {
if ($options['pipeline'] === false) {
- $exclude_type = ($type === $this::JS_TYPE || $type === $this::INLINE_JS_TYPE) ? $this::JS : $this::CSS;
+
+ $exclude_type = $this->getBaseType($type);
+
$excludes = strtolower($exclude_type . '_pipeline_before_excludes');
if ($this->{$excludes}) {
$default = 'after';
@@ -263,12 +279,22 @@ class Assets extends PropertyObject
// If exists
if ($asset_object->init($asset, $options)) {
- $this->$collection[md5($asset)] = $asset_object;
+ $this->assets[$collection][md5($asset)] = $asset_object;
}
return $this;
}
+ /**
+ * Add a CSS asset or a collection of assets.
+ *
+ * @return $this
+ */
+ public function addLink($asset)
+ {
+ return $this->addType($this::LINK_COLLECTION, $this::LINK_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::LINK_TYPE));
+ }
+
/**
* Add a CSS asset or a collection of assets.
*
@@ -309,6 +335,25 @@ class Assets extends PropertyObject
return $this->addType($this::JS_COLLECTION, $this::INLINE_JS_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::INLINE_JS_TYPE));
}
+ /**
+ * Add a JS asset or a collection of assets.
+ *
+ * @return $this
+ */
+ public function addJsModule($asset)
+ {
+ return $this->addType($this::JS_MODULE_COLLECTION, $this::JS_MODULE_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::JS_MODULE_TYPE));
+ }
+
+ /**
+ * Add an Inline JS asset or a collection of assets.
+ *
+ * @return $this
+ */
+ public function addInlineJsModule($asset)
+ {
+ return $this->addType($this::JS_MODULE_COLLECTION, $this::INLINE_JS_MODULE_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::INLINE_JS_MODULE_TYPE));
+ }
/**
* Add/replace collection.
@@ -390,17 +435,17 @@ class Assets extends PropertyObject
$pipeline_output = '';
$after_output = '';
- $assets = 'assets_' . $type;
+ $collection = 'assets_' . $type;
$pipeline_enabled = $type . '_pipeline';
$render_pipeline = 'render' . ucfirst($type);
- $group_assets = $this->filterAssets($this->$assets, 'group', $group);
+ $group_assets = $this->filterAssets($this->assets[$collection], 'group', $group);
$pipeline_assets = $this->filterAssets($group_assets, 'position', 'pipeline', true);
$before_assets = $this->filterAssets($group_assets, 'position', 'before', true);
$after_assets = $this->filterAssets($group_assets, 'position', 'after', true);
// Pipeline
- if ($this->{$pipeline_enabled}) {
+ if ($this->{$pipeline_enabled} ?? false) {
$options = array_merge($this->pipeline_options, ['timestamp' => $this->timestamp]);
$pipeline = new Pipeline($options);
@@ -432,9 +477,29 @@ class Assets extends PropertyObject
* @param array $attributes
* @return string
*/
- public function css($group = 'head', $attributes = [])
+ public function css($group = 'head', $attributes = [], $include_link = true)
{
- return $this->render('css', $group, $attributes);
+ $output = '';
+
+ if ($include_link) {
+ $output = $this->link($group, $attributes);
+ }
+
+ $output .= $this->render(self::CSS, $group, $attributes);
+
+ return $output;
+ }
+
+ /**
+ * Build the CSS link tags.
+ *
+ * @param string $group name of the group
+ * @param array $attributes
+ * @return string
+ */
+ public function link($group = 'head', $attributes = [])
+ {
+ return $this->render(self::LINK, $group, $attributes);
}
/**
@@ -444,8 +509,58 @@ class Assets extends PropertyObject
* @param array $attributes
* @return string
*/
- public function js($group = 'head', $attributes = [])
+ public function js($group = 'head', $attributes = [], $include_js_module = true)
{
- return $this->render('js', $group, $attributes);
+ $output = $this->render(self::JS, $group, $attributes);
+
+ if ($include_js_module) {
+ $output .= $this->jsModule($group, $attributes);
+ }
+
+ return $output;
+ }
+
+ /**
+ * Build the Javascript Modules tags
+ *
+ * @param $group
+ * @param $attributes
+ * @return string
+ */
+ public function jsModule($group = 'head', $attributes = [])
+ {
+ return $this->render(self::JS_MODULE, $group, $attributes);
+ }
+
+ public function all($group = 'head', $attributes = [])
+ {
+ $output = $this->css($group, $attributes, false);
+ $output .= $this->link($group, $attributes);
+ $output .= $this->js($group, $attributes, false);
+ $output .= $this->jsModule($group, $attributes);
+ return $output;
+ }
+
+ protected function isValidType($type)
+ {
+ return in_array($type, [self::CSS_TYPE, self::JS_TYPE, self::JS_MODULE_TYPE]);
+ }
+
+ protected function getBaseType($type)
+ {
+ switch ($type) {
+ case $this::JS_TYPE:
+ case $this::INLINE_JS_TYPE:
+ $base_type = $this::JS;
+ break;
+ case $this::JS_MODULE_TYPE:
+ case $this::INLINE_JS_MODULE_TYPE:
+ $base_type = $this::JS_MODULE;
+ break;
+ default:
+ $base_type = $this::CSS;
+ }
+
+ return $base_type;
}
}
diff --git a/system/src/Grav/Common/Assets/BaseAsset.php b/system/src/Grav/Common/Assets/BaseAsset.php
index 21d9eef63..4f74f55f9 100644
--- a/system/src/Grav/Common/Assets/BaseAsset.php
+++ b/system/src/Grav/Common/Assets/BaseAsset.php
@@ -26,8 +26,9 @@ abstract class BaseAsset extends PropertyObject
{
use AssetUtilsTrait;
- protected const CSS_ASSET = true;
- protected const JS_ASSET = false;
+ protected const CSS_ASSET = 1;
+ protected const JS_ASSET = 2;
+ protected const JS_MODULE_ASSET = 3;
/** @var string|false */
protected $asset;
@@ -69,7 +70,7 @@ abstract class BaseAsset extends PropertyObject
* @param array $elements
* @param string|null $key
*/
- public function __construct(array $elements = [], $key = null)
+ public function __construct(array $elements = [], ?string $key = null)
{
$base_config = [
'group' => 'head',
diff --git a/system/src/Grav/Common/Assets/Css.php b/system/src/Grav/Common/Assets/Css.php
index 701c94235..ea6b388fa 100644
--- a/system/src/Grav/Common/Assets/Css.php
+++ b/system/src/Grav/Common/Assets/Css.php
@@ -22,7 +22,7 @@ class Css extends BaseAsset
* @param array $elements
* @param string|null $key
*/
- public function __construct(array $elements = [], $key = null)
+ public function __construct(array $elements = [], ?string $key = null)
{
$base_options = [
'asset_type' => 'css',
diff --git a/system/src/Grav/Common/Assets/InlineCss.php b/system/src/Grav/Common/Assets/InlineCss.php
index 943cef6b6..4984db4d1 100644
--- a/system/src/Grav/Common/Assets/InlineCss.php
+++ b/system/src/Grav/Common/Assets/InlineCss.php
@@ -22,7 +22,7 @@ class InlineCss extends BaseAsset
* @param array $elements
* @param string|null $key
*/
- public function __construct(array $elements = [], $key = null)
+ public function __construct(array $elements = [], ?string $key = null)
{
$base_options = [
'asset_type' => 'css',
diff --git a/system/src/Grav/Common/Assets/InlineJs.php b/system/src/Grav/Common/Assets/InlineJs.php
index 9ad365574..e38a51aee 100644
--- a/system/src/Grav/Common/Assets/InlineJs.php
+++ b/system/src/Grav/Common/Assets/InlineJs.php
@@ -22,7 +22,7 @@ class InlineJs extends BaseAsset
* @param array $elements
* @param string|null $key
*/
- public function __construct(array $elements = [], $key = null)
+ public function __construct(array $elements = [], ?string $key = null)
{
$base_options = [
'asset_type' => 'js',
diff --git a/system/src/Grav/Common/Assets/InlineJsModule.php b/system/src/Grav/Common/Assets/InlineJsModule.php
new file mode 100644
index 000000000..42ce6f14a
--- /dev/null
+++ b/system/src/Grav/Common/Assets/InlineJsModule.php
@@ -0,0 +1,46 @@
+ 'js_module',
+ 'attributes' => ['type' => 'module'],
+ 'position' => 'after'
+ ];
+
+ $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
+
+ parent::__construct($merged_attributes, $key);
+ }
+
+ /**
+ * @return string
+ */
+ public function render()
+ {
+ return '\n";
+ }
+
+}
diff --git a/system/src/Grav/Common/Assets/Js.php b/system/src/Grav/Common/Assets/Js.php
index fb67491f3..8687a86b1 100644
--- a/system/src/Grav/Common/Assets/Js.php
+++ b/system/src/Grav/Common/Assets/Js.php
@@ -22,7 +22,7 @@ class Js extends BaseAsset
* @param array $elements
* @param string|null $key
*/
- public function __construct(array $elements = [], $key = null)
+ public function __construct(array $elements = [], ?string $key = null)
{
$base_options = [
'asset_type' => 'js',
diff --git a/system/src/Grav/Common/Assets/JsModule.php b/system/src/Grav/Common/Assets/JsModule.php
new file mode 100644
index 000000000..5c2a836c2
--- /dev/null
+++ b/system/src/Grav/Common/Assets/JsModule.php
@@ -0,0 +1,49 @@
+ 'js_module',
+ 'attributes' => ['type' => 'module']
+ ];
+
+ $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
+
+ parent::__construct($merged_attributes, $key);
+ }
+
+ /**
+ * @return string
+ */
+ public function render()
+ {
+ if (isset($this->attributes['loading']) && $this->attributes['loading'] === 'inline') {
+ $buffer = $this->gatherLinks([$this], self::JS_MODULE_ASSET);
+ return '\n";
+ }
+
+ return '\n";
+ }
+}
diff --git a/system/src/Grav/Common/Assets/Link.php b/system/src/Grav/Common/Assets/Link.php
new file mode 100644
index 000000000..ecafcea90
--- /dev/null
+++ b/system/src/Grav/Common/Assets/Link.php
@@ -0,0 +1,43 @@
+ 'link',
+ ];
+
+ $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
+
+ parent::__construct($merged_attributes, $key);
+ }
+
+ /**
+ * @return string
+ */
+ public function render()
+ {
+ return 'renderAttributes() . $this->integrityHash($this->asset) . ">\n";
+ }
+}
diff --git a/system/src/Grav/Common/Assets/Pipeline.php b/system/src/Grav/Common/Assets/Pipeline.php
index a41e924a5..4e7348942 100644
--- a/system/src/Grav/Common/Assets/Pipeline.php
+++ b/system/src/Grav/Common/Assets/Pipeline.php
@@ -29,8 +29,9 @@ class Pipeline extends PropertyObject
{
use AssetUtilsTrait;
- protected const CSS_ASSET = true;
- protected const JS_ASSET = false;
+ protected const CSS_ASSET = 1;
+ protected const JS_ASSET = 2;
+ protected const JS_MODULE_ASSET = 3;
/** @const Regex to match CSS urls */
protected const CSS_URL_REGEX = '{url\(([\'\"]?)(.*?)\1\)}';
@@ -169,7 +170,7 @@ class Pipeline extends PropertyObject
* @param array $attributes
* @return bool|string URL or generated content if available, else false
*/
- public function renderJs($assets, $group, $attributes = [])
+ public function renderJs($assets, $group, $attributes = [], $type = self::JS_ASSET)
{
// temporary list of assets to pipeline
$inline_group = false;
@@ -198,7 +199,7 @@ class Pipeline extends PropertyObject
}
// Concatenate files
- $buffer = $this->gatherLinks($assets, self::JS_ASSET);
+ $buffer = $this->gatherLinks($assets, $type);
// Minify if required
if ($this->shouldMinify('js')) {
@@ -223,6 +224,19 @@ class Pipeline extends PropertyObject
return $output;
}
+ /**
+ * Minify and concatenate JS files.
+ *
+ * @param array $assets
+ * @param string $group
+ * @param array $attributes
+ * @return bool|string URL or generated content if available, else false
+ */
+ public function renderJs_Module($assets, $group, $attributes = [])
+ {
+ $attributes['type'] = 'module';
+ return $this->renderJs($assets, $group, $attributes, self::JS_MODULE_ASSET);
+ }
/**
* Finds relative CSS urls() and rewrites the URL with an absolute one
diff --git a/system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php b/system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php
index ec00a6079..4e8524cce 100644
--- a/system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php
+++ b/system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php
@@ -30,7 +30,7 @@ trait TestingAssetsTrait
*/
public function exists($asset)
{
- return isset($this->collections[$asset]) || isset($this->assets_css[$asset]) || isset($this->assets_js[$asset]);
+ return isset($this->collections[$asset]) || isset($this->assets[self::CSS_COLLECTION][$asset]) || isset($this->assets[self::JS_COLLECTION][$asset]);
}
/**
@@ -69,10 +69,10 @@ trait TestingAssetsTrait
if (null !== $key) {
$asset_key = md5($key);
- return $this->assets_css[$asset_key] ?? null;
+ return $this->assets[self::CSS_COLLECTION][$asset_key] ?? null;
}
- return $this->assets_css;
+ return $this->assets[self::CSS_COLLECTION];
}
/**
@@ -88,10 +88,10 @@ trait TestingAssetsTrait
if (null !== $key) {
$asset_key = md5($key);
- return $this->assets_js[$asset_key] ?? null;
+ return $this->assets[self::JS_COLLECTION][$asset_key] ?? null;
}
- return $this->assets_js;
+ return $this->assets[self::JS_COLLECTION];
}
/**
@@ -102,7 +102,7 @@ trait TestingAssetsTrait
*/
public function setCss($css)
{
- $this->assets_css = $css;
+ $this->assets[self::CSS_COLLECTION] = $css;
return $this;
}
@@ -115,7 +115,7 @@ trait TestingAssetsTrait
*/
public function setJs($js)
{
- $this->assets_js = $js;
+ $this->assets[self::JS_COLLECTION] = $js;
return $this;
}
@@ -129,8 +129,8 @@ trait TestingAssetsTrait
public function removeCss($key)
{
$asset_key = md5($key);
- if (isset($this->assets_css[$asset_key])) {
- unset($this->assets_css[$asset_key]);
+ if (isset($this->assets[self::CSS_COLLECTION][$asset_key])) {
+ unset($this->assets[self::CSS_COLLECTION][$asset_key]);
}
return $this;
@@ -145,8 +145,8 @@ trait TestingAssetsTrait
public function removeJs($key)
{
$asset_key = md5($key);
- if (isset($this->assets_js[$asset_key])) {
- unset($this->assets_js[$asset_key]);
+ if (isset($this->assets[self::JS_COLLECTION][$asset_key])) {
+ unset($this->assets[self::JS_COLLECTION][$asset_key]);
}
return $this;
@@ -201,7 +201,7 @@ trait TestingAssetsTrait
*/
public function resetJs()
{
- $this->assets_js = [];
+ $this->assets[self::JS_COLLECTION] = [];
return $this;
}
@@ -213,7 +213,7 @@ trait TestingAssetsTrait
*/
public function resetCss()
{
- $this->assets_css = [];
+ $this->assets[self::CSS_COLLECTION] = [];
return $this;
}
diff --git a/user/config/system.yaml b/user/config/system.yaml
index 7a8ffe58c..2e992778d 100644
--- a/user/config/system.yaml
+++ b/user/config/system.yaml
@@ -29,6 +29,7 @@ assets:
css_minify: true
css_rewrite: true
js_pipeline: false
+ js_module_pipeline: false
js_minify: true
errors: