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..c0182e3b5 100644 --- a/system/src/Grav/Common/Assets.php +++ b/system/src/Grav/Common/Assets.php @@ -32,12 +32,16 @@ class Assets extends PropertyObject const CSS = 'css'; const JS = 'js'; + const JS_MODULE = 'js_module'; const CSS_COLLECTION = 'assets_css'; const JS_COLLECTION = 'assets_js'; + const JS_MODULE_COLLECTION = 'assets_js_module'; 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,6 +52,9 @@ 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 */ @@ -57,6 +64,8 @@ class Assets extends PropertyObject protected $assets_css = []; /** @var array */ protected $assets_js = []; + /** @var array */ + protected $assets_js_module = []; // Following variables come from the configuration: /** @var bool */ @@ -66,19 +75,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 = []; @@ -193,6 +200,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 +231,7 @@ class Assets extends PropertyObject return $this; } - if (($type === $this::CSS_TYPE || $type === $this::JS_TYPE) && isset($this->collections[$asset])) { + if (($type === $this::CSS_TYPE || $type === $this::JS_TYPE || $type === $this::JS_MODULE_TYPE) && isset($this->collections[$asset])) { $this->addType($collection, $type, $this->collections[$asset], $options); return $this; } @@ -230,7 +239,20 @@ 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; + + switch ($type) { + case $this::JS_TYPE: + case $this::INLINE_JS_TYPE: + $exclude_type = $this::JS; + break; + case $this::JS_MODULE_TYPE: + case $this::INLINE_JS_MODULE_TYPE: + $exclude_type = $this::JS_MODULE; + break; + default: + $exclude_type = $this::CSS; + } + $excludes = strtolower($exclude_type . '_pipeline_before_excludes'); if ($this->{$excludes}) { $default = 'after'; @@ -309,6 +331,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. @@ -446,6 +487,7 @@ class Assets extends PropertyObject */ public function js($group = 'head', $attributes = []) { - return $this->render('js', $group, $attributes); + return $this->render('js', $group, $attributes) . $this->render('js_module', $group, $attributes); + } } diff --git a/system/src/Grav/Common/Assets/BaseAsset.php b/system/src/Grav/Common/Assets/BaseAsset.php index 21d9eef63..2b107a5ee 100644 --- a/system/src/Grav/Common/Assets/BaseAsset.php +++ b/system/src/Grav/Common/Assets/BaseAsset.php @@ -69,7 +69,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..c59a38104 --- /dev/null +++ b/system/src/Grav/Common/Assets/InlineJsModule.php @@ -0,0 +1,38 @@ + 'js_module', + 'attributes' => ['type' => 'module'], + 'position' => 'after' + ]; + + $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements); + + parent::__construct($merged_attributes, $key); + } + +} 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..e453a0fcc --- /dev/null +++ b/system/src/Grav/Common/Assets/JsModule.php @@ -0,0 +1,36 @@ + 'js_module', + 'attributes' => ['type' => 'module'] + ]; + + $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements); + + parent::__construct($merged_attributes, $key); + } +} 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/user/config/system.yaml b/user/config/system.yaml index 7a8ffe58c..b074f3af1 100644 --- a/user/config/system.yaml +++ b/user/config/system.yaml @@ -28,7 +28,8 @@ assets: css_pipeline: false css_minify: true css_rewrite: true - js_pipeline: false + js_pipeline: true + js_module_pipeline: true js_minify: true errors: