网页资源管理器
概念
在前端世界中,许多资源是相关的。例如,我们的保持活动脚本依赖于 core.js 文件进行选项管理。在 Joomla 中,从来没有一种简单的方法来指定这一点,你只需要包含多个文件。Joomla 4 通过网页资源的概念改变了这一点。
您可能觉得观看此 网页资源管理器视频 会很有用。
概述
网页资源管理器使您能够轻松管理与扩展相关的 javascript 和 css。
组件 com_example
的网页资源管理器工作原理概述如示意图所示,需要考虑 3 个阶段。
阶段 1 注册资源
初始化和路由后,Joomla 知道要运行哪个组件,网页资源管理器读取文件 media/<component>/joomla.asset.json
,例如 media/com_example/joomla.asset.json
。
在 joomla.asset.json
文件中,您列出
- 组件的 js 和 css 文件,
- 它们关联的依赖项,
- 它们版本信息,
- 以及任何关联的属性(例如 defer、async),
在下一小节中将详细介绍。
在此阶段,网页资源管理器读取组件的 joomla.asset.json
文件,并据此在其注册表中创建资源。
网页资源管理器还会读取以下文件中的条目:
- 系统文件:media/system/joomla.asset.json
- 供应商文件:media/vendor/joomla.asset.json
- 遗留文件:media/legacy/joomla.asset.json
- 以及您的模板文件:例如 templates/cassiopeia/joomla.asset.json
阶段 2 使用资源
您通过在代码中调用 useScript
(对于 javascript)或 useStyle
(对于 css)来使用资源,例如
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->useScript('field.modal-fields');
这将导致资源在网页资源管理器注册表中被标记为已使用。如果有任何依赖项,则这些依赖项也将被标记为已使用。
这些在上面的图中以绿色表示。红色的资源已注册,但未使用。
阶段 3 呈现资源
在通常情况下,Joomla 根据您模板文件夹中的 index.html 文件(例如 /templates/cassiopeia/index.html)生成输出 HTML。如果您查看此文件中的 HTML,您会发现其中存在“空洞”。
<head>
...
<jdoc:include type="styles" />
<jdoc:include type="scripts" />
</head>
Joomla 解析此文件中的 HTML,当它遇到上述情况时,它会请求网页资源管理器填充“空洞”,方法是在 HTML 中提供适当的 <link rel="stylesheet" ...>
和 <script ...>
元素。
网页资源管理器为其注册表中已标记为使用的资源生成 HTML 元素。至关重要的是,它会考虑依赖关系,从而确保浏览器首先加载任何依赖项。与资源关联的任何属性也会作为 HTML 元素上的属性输出。
网页资源管理器还会考虑资源的版本信息,并确保如果版本号更改,则浏览器将从服务器请求更新的 js 或 css 文件,而不是使用缓存的副本。
已弃用的函数
顺便说一句,以前通过 Document::addScript
、Document::addStyleSheet
等添加 js 和 css 文件的方法现在已弃用(将在 Joomla 6 中删除),应改为使用网页资源管理器。
但是,Text::script
和 Document::addScriptOptions
未弃用,应继续使用它们将语言翻译后的字符串和变量传递到 javascript 中。
定义
相关资源在 JSON 文件中定义,例如 system/joomla.asset.json#L14-L21
它的结构是具有架构定义(用于验证)、名称、版本、许可证,然后是一个或多个资源定义。资源由与资源相关的 js 文件和 css 文件列表以及任何依赖项组成。依赖项部分只是资源正常运行所需的资源名称列表。示例
{
"$schema": "https://developer.joomla.net.cn/schemas/json-schema/web_assets.json",
"name": "com_example",
"version": "4.0.0",
"description": "Joomla CMS",
"license": "GPL-2.0+",
"assets": [
{
"name": "bar",
"type": "style",
"uri": "com_example/bar.css"
},
{
"name": "bar",
"type": "script",
"uri": "com_example/bar.js"
},
{
"name": "beer",
"type": "style",
"uri": "com_example/beer.css",
"dependencies": [
"bar"
]
},
{
"name": "beer",
"type": "script",
"dependencies": [
"core",
"bar"
],
"uri": "com_example/beer.js",
"attributes": {
"defer": true,
"data-foo": "bar"
}
}
]
}
$schema
属性是架构定义文件,允许您使用 JSON Schema 验证文件。阅读 官方网站 以获取有关 json 架构验证工作原理的更多信息。
下面解释了主要资源字段
- “name” - 这就像注册表中资源的键;
- “type” - 可以是 css 的“style”,js 的“script”,或者如果您想将 css 和 js 文件组合在一起,则可以是“preset”。在这种情况下,您将实际的 css 和 js 资源指定为依赖项,每个依赖项后缀为“#style”或“#script”。
- “uri” - **警告:这可能不是您期望的!**您应该将 js 文件放在 js 子目录中,安装后将是 media/com_example/js/myjsfile.js。但是,您在不使用 js 子目录的情况下指定 uri
"uri": "com_example/myjsfile.js"
同样,您将 css 文件放入 css 子目录中(例如 media/com_example/css/mycssfile.js),但省略该子目录
"uri": "com_example/mycssfile.js"
网页资源管理器打开 uri 并根据资源类型插入 /js
或 /css
。
- “dependencies” - 在数组中列出每个依赖项的“name”
- “attributes” - 列出您希望映射到
<link>
或<script>
元素的任何属性。
对于依赖项,您如何知道需要哪个特定的 Joomla 资源?不幸的是,没有神奇的解决方案,您可能需要阅读一些 Joomla 代码才能确定需要哪个资源。一些常见的资源是
- “core” - Joomla 核心 js 库
- “jquery” - 用于 jQuery 库
- “form.validate” - 用于验证表单
- “field.modal-fields” - 如果您的组件使用模式窗口
建议为您的扩展或模板提供 joomla.asset.json,但这不是 WebAsset 工作的必要条件(请参阅下一节)。
不建议将内联资源添加到 json 文件中,最好使用文件。
解释资源阶段
每个资源有 2 个阶段:**已注册**和**已使用**。
已注册
已注册是指资源加载到 WebAssetRegistry
中的阶段。这意味着 WebAssetManager
知道这些资源的存在,但在呈现时不会将它们附加到文档中。
从 joomla.asset.json
加载的所有资源最初都处于 registered
阶段。
已使用
已使用是指通过 $wa->useAsset()
(->useScript()、->useStyle()、->registerAndUseX() 等)启用资源的阶段。这意味着 WebAssetManager
将在呈现时将这些资源及其依赖项附加到文档中。
如果资源之前未注册,则无法使用它,这将导致未知资源异常。
注册资源
加载所有已知资源,然后存储在 WebAssetRegistry
中。(要启用/禁用资源项,您必须使用 WebAssetManager
,请参阅下一节)。
Joomla! 将在运行时自动查找以下资源定义(按以下顺序)
media/vendor/joomla.asset.json (on first access to WebAssetRegistry)
media/system/joomla.asset.json
media/legacy/joomla.asset.json
media/{com_active_component}/joomla.asset.json (on dispatch the application)
templates/{active_template}/joomla.asset.json
并将它们加载到已知资源的注册表中。
加载资源时,如果 Web 资源管理器遇到与注册表中已存在资源同名的资源,则新的资源定义将覆盖旧的资源定义。
您可以通过 WebAssetRegistry
注册自己的资源定义。
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wr = $wa->getRegistry();
$wr->addRegistryFile('relative/path/to/your/joomla.asset.json');
例如,对于 Joomla 模块,不会自动尝试读取 joomla.asset.json
文件,但您可以使用 addRegistryFile
函数来处理它。
在运行时添加自定义资源项
$wr->add('script', new Joomla\CMS\WebAsset\WebAssetItem('foobar', 'com_foobar/file.js', ['type' => 'script']));
或者更简单地,使用 WebAssetManager
$wa->registerScript('foobar', 'com_foobar/file.js');
新的资源项 foobar
将添加到已知资源的注册表中,但在您的代码(布局、模板等)请求它之前,不会附加到文档。
检查资源是否存在
if ($wa->assetExists('script', 'foobar'))
{
var_dump('Script "foobar" exists!');
}
启用资源
当前文档 $doc
中的所有资源管理都由 WebAssetManager
处理,可以通过 $doc->getWebAssetManager()
访问。
通过使用 Web 资源管理器,您可以通过标准方法轻松地在 Joomla! 中启用或禁用资源。
要在页面中启用资源,请使用 useAsset 函数,例如
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->useScript('keepalive');
// Or multiple
$wa->useScript('keepalive')
->useScript('fields.validate')
->useStyle('foobar')
->useScript('foobar');
// Add new asset item with dependency and use it
$wa->registerAndUseScript('bar', 'com_foobar/bar.js', [], [], ['core', 'foobar']);
WebAssetManager
将在 WebAssetRegistry
中查找请求的资源是否存在,并将其为当前文档实例启用。否则,它将抛出 UnknownAssetException
异常。
要在页面中禁用资源,请使用 disableAsset 函数。以下示例将禁用加载 jquery-noconflict 资源。
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->disableScript('jquery-noconflict');
如果禁用的资源有任何依赖项,则无论如何,此资源都将自动重新启用。
检查资源是否启用以及资源状态
// Checking whether an asset are active (enabled manually or automatically as dependency)
if ($wa->isAssetActive('script', 'foobar'))
{
var_dump('Script "foobar" is active!');
}
// Checking state
switch($wa->getAssetState('script', 'foobar')){
case Joomla\CMS\WebAsset\WebAssetManager::ASSET_STATE_ACTIVE:
var_dump('Active! Was enabled manually');
break;
case Joomla\CMS\WebAsset\WebAssetManager::ASSET_STATE_DEPENDENCY:
var_dump('Active! Was enabled automatically while resolving dependencies');
break;
default:
var_dump('not active!');
}
覆盖资源
当您需要重新定义资源项或其依赖项的 URI 时,覆盖可能很有用。如前所述,来自 joomla.asset.json 的每个后续资源定义都将通过项目名称覆盖先前资源定义中的资源项。
这意味着如果您提供的 joomla.asset.json 包含已加载的资源项,它们将被您的项目替换。另一种在代码中覆盖的方法是注册同名的项目。
例如,我们有一个 "foobar" 脚本,它加载 com_example/foobar.js 库,我们想为此库使用 CDN
系统最初是如何定义的
...
{
"name": "foobar",
"type": "script",
"uri": "com_example/foobar.js",
"dependencies": ["core"]
}
...
为了覆盖 URI,我们在我们的 joomla.asset.json 中定义了名为 "foobar" 的资源项。
...
{
"name": "foobar",
"type": "script",
"uri": "http://foobar.cdn.blabla/foobar.js",
"dependencies": ["core"]
}
...
或者,使用 AssetManager 注册一个新的资源项。
$wa->registerScript('foobar', 'http://fobar.cdn.blabla/foobar.js', [], [], ['core']);
使用样式
资源管理器允许您管理样式表文件。样式表资源项的类型为 "style"。
joomla.asset.json 中项目的示例 JSON 定义
...
{
"name": "foobar",
"type": "style",
"uri": "com_example/foobar.css"
}
...
使用样式的方法
资源管理器提供以下方法来处理样式文件
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
// Attach foobar to the document
$wa->useStyle('foobar');
// Disable foobar from being attached
$wa->disableStyle('foobar');
// Register custom item without json definition
$wa->registerStyle('bar', 'com_example/bar.css', [], ['data-foo' => 'some attribute'], ['some.dependency']);
// And use it later
$wa->useStyle('bar');
// Register and attach a custom item in one run
$wa->registerAndUseStyle('bar', 'com_example/bar.css', [], ['data-foo' => 'some attribute'], ['some.dependency']);
添加内联样式
除了样式文件之外,WebAssetManager 还允许您添加内联样式,并维护其与文件资源的关系。内联样式可以放置在依赖项之前、之后,或者(通常情况下)在所有样式之后。
内联资源可以像其他资源一样具有 "name",但不是必需的。名称可用于从注册表中检索资源项,或设置为另一个内联资源的依赖项。如果未指定名称,则将使用基于内容哈希的生成名称。
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
// Add an inline content as usual, will be rendered in flow after all assets
$wa->addInlineStyle('content of inline1');
// Add an inline content that will be placed after "foobar" asset
$wa->addInlineStyle('content of inline2', ['position' => 'after'], ['data-foo' => 'bar'], ['foobar']);
// Add an inline content that will be placed before "foobar" asset
$wa->addInlineStyle('content of inline3', ['position' => 'before'], [], ['foobar']);
// Named inline asset
$wa->addInlineStyle('content of inline4', ['name' => 'my.inline.asset']);
foobar
资源(在上面的代码中设置为依赖项)应该存在于资源注册表中,否则您将收到未满足的依赖项异常。
以上示例将生成
...
<style>content of inline3</style>
<link rel="stylesheet" href="foobar.css" />
<style data-foo="bar">content of inline2</style>
...
...
<style>content of inline1</style>
<style>content of inline4</style>
...
如果内联资源具有多个依赖项,则将使用最后一个依赖项进行定位。例如
$wa->addInlineStyle('content of inline1', ['position' => 'before'], [], ['foo', 'bar']);
$wa->addInlineStyle('content of inline2', ['position' => 'after'], [], ['foo', 'bar']);
将生成
...
<link rel="stylesheet" href="foo.css" />
<style>content of inline1</style>
<link rel="stylesheet" href="bar.css" />
<style>content of inline2</style>
...
命名内联资源可以作为另一个内联资源的依赖项,但是不建议将内联资源用作非内联资源的依赖项。这可以工作,但此行为将来可能会更改。建议使用 "position" 代替。
使用脚本
资源管理器允许您管理脚本文件。脚本资源项的类型为 "script"。joomla.asset.json 中项目的示例 JSON 定义
...
{
"name": "foobar",
"type": "script",
"uri": "com_example/foobar.js",
"dependencies": ["core"]
}
...
ES6 模块脚本的示例 JSON 定义,并回退到旧版本
...
{
"name": "foobar-legacy",
"type": "script",
"uri": "com_example/foobar-as5.js",
"attributes": {
"nomodule": true,
"defer": true
},
"dependencies": ["core"]
},
{
"name": "foobar",
"type": "script",
"uri": "com_example/foobar.js",
"attributes": {
"type": "module"
},
"dependencies": [
"core",
"foobar-legacy"
]
}
...
使用脚本的方法
资源管理器提供以下方法来处理脚本文件
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
// Attach foobar to the document
$wa->useScript('foobar');
// Disable foobar from being attached
$wa->disableScript('foobar');
// Register custom item without json definition
$wa->registerScript('bar', 'com_example/bar.js', [], ['defer' => true], ['core']);
// And use it later
$wa->useScript('bar');
// Register and attach a custom item in one run
$wa->registerAndUseScript('bar','com_example/bar.js', [], ['defer' => true], ['core']);
添加内联脚本
除了脚本文件之外,WebAssetManager 还允许您添加内联脚本,并维护其与文件资源的关系。内联脚本可以放置在依赖项之前、之后,或者(通常情况下)在所有脚本之后。
内联资源可以像其他资源一样具有 "name",但不是必需的。名称可用于从注册表中检索资源项,或设置为另一个内联资源的依赖项。如果未指定名称,则将使用基于内容哈希的生成名称。
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
// Add an inline content as usual, will be rendered in flow after all assets
$wa->addInlineScript('content of inline1');
// Add an inline content that will be placed after "foobar" asset
$wa->addInlineScript('content of inline2', ['position' => 'after'], ['data-foo' => 'bar'], ['foobar']);
// Add an inline content that will be placed before "foobar" asset
$wa->addInlineScript('content of inline3', ['position' => 'before'], [], ['foobar']);
// Named inline asset
$wa->addInlineScript('content of inline4', ['name' => 'my.inline.asset']);
// Specify script type
$wa->addInlineScript('content of inline5', [], ['type' => 'module']);
foobar
资源(在上面的代码中设置为依赖项)应该存在于资源注册表中,否则您将收到未满足的依赖项异常。
以上示例将生成
...
<script>content of inline3</script>
<script src="foobar.js"></script>
<script data-foo="bar">content of inline2</script>
...
...
<script>content of inline1</script>
<script>content of inline4</script>
<script type="module">content of inline5</script>
...
如果内联资源具有多个依赖项,则将使用最后一个依赖项进行定位。例如
$wa->addInlineScript('content of inline1', ['position' => 'before'], [], ['foo', 'bar']);
$wa->addInlineScript('content of inline2', ['position' => 'after'], [], ['foo', 'bar']);
将生成
...
<script src="foo.js"></script>
<script>content of inline1</script>
<script src="bar.js"></script>
<script>content of inline2</script>
...
命名内联资源可以作为另一个内联资源的依赖项,但是不建议将内联资源用作非内联资源的依赖项。这可以工作,但此行为将来可能会更改。建议使用 "position" 代替。
使用 Web 组件
Joomla! 允许您根据需要使用 Web 组件。在 Joomla! 中,Web 组件不会像常规脚本那样加载,而是通过 Web 组件加载器加载,以便异步加载。
在所有其他方面,在资源管理器中使用 Web 组件与使用 script
资源项相同。
joomla.asset.json 中一些 Web 组件的示例 JSON 定义(作为 ES6 模块)
...
{
"name": "webcomponent.foobar",
"type": "style",
"uri": "com_example/foobar-custom-element.css",
},
{
"name": "webcomponent.foobar",
"type": "script",
"uri": "com_example/foobar-custom-element.js",
"attributes": {
"type": "module"
},
}
...
带有回退的示例,适用于不支持 ES6 module
功能的浏览器。请注意,旧版脚本应具有 wcpolyfill
依赖项,而模块脚本应具有来自旧版脚本的依赖项。
...
{
"name": "webcomponent.foobar",
"type": "style",
"uri": "com_example/foobar-custom-element.css",
},
{
"name": "webcomponent.foobar-legacy",
"type": "script",
"uri": "com_example/foobar-custom-element-es5.js",
"attributes": {
"nomodule": true,
"defer": true
},
"dependencies": [
"wcpolyfill"
]
},
{
"name": "webcomponent.foobar",
"type": "script",
"uri": "com_example/foobar-custom-element.js",
"attributes": {
"type": "module"
},
"dependencies": [
"webcomponent.foobar-legacy"
]
}
...
或者,您可以在 PHP 中注册它们(作为 ES6 模块)
$wa->registerStyle('webcomponent.foobar', 'com_example/foobar-custom-element.css')
->registerScript('webcomponent.foobar', 'com_example/foobar-custom-element.js', ['type' => 'module']);
附加到文档
$wa->useStyle('webcomponent.foobar')
->useScript('webcomponent.foobar');
最好在资源名称前加上 "webcomponent." 前缀,以便轻松发现,并将其与布局中的常规脚本区分开来。
使用 Web 组件的方法
所有使用 Web 组件的方法与使用脚本资源项的方法相同。
使用预设
Preset
是一种特殊的资源项,它保存必须启用的项目列表,与直接调用每个项目的 useAsset()
函数相同。预设可以保存混合类型的资源(脚本、样式、另一个预设等),类型应在 #
符号后提供,并跟随资源名称,例如:foo#style
、bar#script
。
joomla.asset.json 中项目的示例 JSON 定义
...
{
"name": "foobar",
"type": "preset",
"uri": "",
"dependencies": [
"core#script",
"foobar#style",
"foobar#script",
]
}
...
使用预设的方法
资源管理器提供以下方法来处理预设项目
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
// Attach all items from foobar preset to the document
$wa->usePreset('foobar');
// Disable all items from foobar preset from being attached
$wa->disablePreset('foobar');
// Register custom item without json definition
$wa->registerPreset('bar', '', [], [], ['core#script', 'bar#script']);
// And use it later
$wa->usePreset('bar');
// Register and attach a custom item in one run
$wa->registerAndUsePreset('bar','', [], [], ['core#script', 'bar#script']);
高级:自定义 WebAssetItem 类
所有 WebAsset 项目的默认类是 Joomla\CMS\WebAsset\WebAssetItem
。
您还可以使用自定义类,该类必须实现 Joomla\CMS\WebAsset\WebAssetItemInterface
或扩展 Joomla\CMS\WebAsset\WebAssetItem
。
自定义类允许您执行高级操作,例如,根据活动语言包含脚本文件。
class MyComExampleAssetItem extends WebAssetItem
{
public function getUri($resolvePath = true): string
{
$langTag = Factory::getApplication()->getLanguage()->getTag();
// For script asset use ".js", for style we would use ".css"
$path = 'com_example/bar-' . $langTag . '.js';
if ($resolvePath)
{
// For script asset use "script", for style we would use "stylesheet"
$path = $this->resolvePath($path, 'script');
}
return $path;
}
}
此外,实现 Joomla\CMS\WebAsset\WebAssetAttachBehaviorInterface
允许您在启用资源并将其附加到文档时添加脚本选项(可能取决于环境)。
class MyFancyFoobarAssetItem extends WebAssetItem implements WebAssetAttachBehaviorInterface
{
public function onAttachCallback(Document $doc): void
{
$user = Factory::getApplication()->getIdentity();
$doc->addScriptOptions('com_example.fancyfoobar', ['userName' => $user->username]);
}
}
实现 WebAssetAttachBehaviorInterface
的资源项应在 onBeforeCompileHead 事件之前启用,否则 onAttachCallback
将被忽略。
在 joomla.asset.json 中定义自定义 WebAssetItem 类
在 joomla.asset.json 中,您可以定义哪个类应与特定 AssetItem 一起使用。为此,您可以使用 2 个属性 namespace
和 class
。namespace
可以在根级别定义(然后它将用作 joomla.asset.json 中所有资源项目的默认命名空间)或在项目级别定义。例如
{
"$schema": "https://developer.joomla.net.cn/schemas/json-schema/web_assets.json",
"name": "com_example",
"version": "4.0.0",
"namespace": "Joomla\Component\Example\WebAsset",
"assets": [
{
"name": "foo",
"type": "script",
"class": "FooAssetItem",
"uri": "com_example/foo.js"
},
{
"name": "bar",
"type": "script",
"namespace": "MyFooBar\Library\Example\WebAsset",
"class": "BarAssetItem",
"uri": "com_example/bar.js"
}
]
}
这里资源 foo
将与类 Joomla\Component\Example\WebAsset\FooAssetItem
关联,而 bar
将与类 MyFooBar\Library\Example\WebAsset\BarAssetItem
关联。
如果未定义 namespace
,则默认情况下将使用 Joomla\CMS\WebAsset
。当 namespace
定义为空时,则不使用命名空间,只使用 class
。例如
{
"$schema": "https://developer.joomla.net.cn/schemas/json-schema/web_assets.json",
"name": "com_example",
"assets": [
{
"name": "foo",
"type": "script",
"class": "FooAssetItem",
"uri": "com_example/foo.js"
},
{
"name": "bar",
"type": "script",
"namespace": "",
"class": "BarAssetItem",
"uri": "com_example/bar.js"
}
]
}
这里资源 foo
将与类 Joomla\CMS\WebAsset\FooAssetItem
关联,而 bar
将与类 BarAssetItem
关联(不带命名空间)。