Yii2 资源管理实战:从资源包定义、发布到组合压缩的完整指南
【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址: https://gitcode.com/gh_mirrors/yi/yii2
导读
本文以 Yii2 官方指南的「资源(Assets)」章节为骨架,系统讲解 Yii2 如何以**资源包(Asset Bundle)**为单位管理 CSS、JavaScript、图片与视频等静态资源:如何定义资源包、如何发布不可 Web 访问的源码资源、如何处理资源依赖与排序、如何接入 Bower/NPM 前端包、如何做资源映射、缓存破除、LESS/SCSS/TypeScript 等扩展语法的自动转换,以及最终如何通过asset控制台命令将资源组合压缩以提升生产环境性能。读完本文,你将能够在自己的 Yii2 应用或扩展中熟练定义、注册、自定义与优化资源包,并理解其底层实现原理。
一、什么是资源(Asset)
在 Yii2 中,资源是指可以在 Web 页面中被引用的文件:CSS 文件、JavaScript 文件、图片、视频文件等。资源位于 Web 可访问的目录中,由 Web 服务器直接提供。
理想的做法是以编程方式管理资源。例如,当页面中使用yii\jui\DatePicker图形控件时,它会在页面中自动引入所需的 CSS 和 JavaScript 文件,而不需要你手工查找并引入这些文件;当你升级该控件到新版本时,它也会自动使用新版本的资源文件。这正是 Yii2 强大的资源管理能力所实现的(参见视图与资源的使用)。
二、资源包(Asset Bundle)与定义方式
Yii2 以资源包为单位管理资源。一个资源包就是位于某个目录下的一组资源的集合。当你在视图中注册一个资源包时,该包内的 CSS 与 JavaScript 文件会被自动包含进渲染出的 Web 页面。
资源包以继承yii\web\AssetBundle的 PHP 类来表示。资源包的名称就是其对应的 PHP 类全限定名(不含开头的反斜杠)。资源包类必须可被自动加载。通常它指定:资源位于何处、包含哪些 CSS/JavaScript 文件、以及依赖哪些其他资源包。
以下代码定义了 Basic 项目模板所使用的核心资源包:
<?php namespace app\assets; use yii\web\AssetBundle; class AppAsset extends AssetBundle { public $basePath = '@webroot'; public $baseUrl = '@web'; public $css = [ 'css/site.css', ['css/print.css', 'media' => 'print'], ]; public $js = [ ]; public $depends = [ 'yii\web\YiiAsset', 'yii\bootstrap\BootstrapAsset', ]; }上面的AppAsset类表明:资源文件位于@webroot目录(对应 URL@web);包内包含一个 CSS 文件css/site.css且没有 JavaScript 文件;该包依赖另外两个包:yii\web\YiiAsset与yii\bootstrap\BootstrapAsset。
AssetBundle 核心属性详解
对照框架源码 framework/web/AssetBundle.php 中的属性声明,各属性的含义与约束如下:
sourcePath(源路径):指定包含该资源包源文件的根目录。当根目录不可被 Web 访问时必须设置此属性;否则应改设basePath与baseUrl。支持使用路径别名。从源码看,init()方法会通过Yii::getAlias()解析该路径并去除末尾斜杠(见 AssetBundle.php)。basePath(基础路径):指定一个可被 Web 访问、包含资源包文件的目录。当你设置了sourcePath时,资源管理器会在发布资源时自动改写此属性。如果你的资源文件已经在 Web 可访问目录中、无需发布,则必须设置此属性。同样支持路径别名。baseUrl(基础 URL):与basePath对应的 URL。若设置了sourcePath,资源管理器发布资源后同样会自动改写此属性。支持路径别名。css:列出资源包内 CSS 文件的数组。注意:目录分隔符只能使用正斜杠/。每个文件既可以直接写成字符串,也可以写成「数组 + HTML 属性键值对」的形式(如示例中的['css/print.css', 'media' => 'print'])。源码中registerAssetFiles()会对数组形式的条目做ArrayHelper::merge合并cssOptions与条目自身的选项后调用registerCssFile()(见 AssetBundle.php)。js:列出资源包内 JavaScript 文件的数组,格式与css相同。每个 JS 文件可以用以下两种形式之一指定:- 相对路径,表示本地 JavaScript 文件(如
js/main.js)。文件实际路径 =basePath前缀 + 相对路径;实际 URL =baseUrl前缀 + 相对路径(源码见 AssetManager::getAssetUrl())。 - 绝对 URL,表示外部 JavaScript 文件,例如
https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js或//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js(协议相对)。
- 相对路径,表示本地 JavaScript 文件(如
depends(依赖):列出本资源包所依赖的其它资源包名称的数组。jsOptions:传给yii\web\View::registerJsFile()的选项,应用于本包内每一个JavaScript 文件。cssOptions:传给yii\web\View::registerCssFile()的选项,应用于本包内每一个CSS 文件。publishOptions:传给yii\web\AssetManager::publish()的选项,仅当设置了sourcePath时才生效。
补充说明:
js/css中数组形式的条目(首个元素为文件路径、其余为键值对)可以按文件单独覆盖jsOptions/cssOptions,该能力自 2.0.7 起可用(见 AssetBundle.php 的注释说明)。
资源位置分类
根据资源所处的位置,可将其分为三类:
- 源资源(source assets):与 PHP 源码放在一起、无法被 Web 直接访问的资源文件。要在页面中使用它们,必须先复制到 Web 目录并转换为「已发布资源」。这一过程称为资源发布(asset publishing)。
- 已发布资源(published assets):位于 Web 目录、可被 Web 直接访问的资源文件。
- 外部资源(external assets):位于与当前 Web 应用不同服务器上的资源文件。
定义资源包类时:如果设置了sourcePath,则相对路径列出的资源被视为源资源;如果没有设置sourcePath,则这些资源被视为已发布资源(此时必须设置basePath与baseUrl,让 Yii 知道它们在哪里)。
推荐实践:属于应用自身的资源应放在 Web 可访问目录中,以省去不必要的发布过程——这就是前面AppAsset使用basePath而非sourcePath的原因。而对于扩展,由于其资源与源码一同位于不可 Web 访问的目录,定义资源包类时必须设置sourcePath。
注意:不要将
@webroot/assets用作sourcePath。该目录是资源管理器默认用于存放发布资源的目录,其中任何内容都被视为临时内容、可能被清理删除。
资源依赖(Asset Dependencies)
当页面中包含多个 CSS/JavaScript 文件时,它们必须遵循一定的先后顺序以避免相互覆盖问题。例如使用 jQuery UI 控件时,必须保证 jQuery 的 JS 文件先于 jQuery UI 的 JS 文件引入。这种顺序约束就是「资源依赖」。
资源依赖主要通过depends属性声明。在AppAsset示例中,资源包依赖yii\web\YiiAsset与yii\bootstrap\BootstrapAsset,意味着AppAsset中的 CSS/JS 文件会排在被依赖包的文件之后引入。
资源依赖具有传递性:若包 A 依赖包 B、B 依赖包 C,则 A 也依赖 C。这一机制在 View::registerAssetFiles() 中实现:注册某个包时,会先递归注册其全部depends依赖,再注册包自身的文件;View::registerAssetBundle()还会检测循环依赖并抛出InvalidConfigException(见 View.php)。
资源选项(Asset Options)
通过cssOptions与jsOptions可以定制 CSS/JS 文件在页面中的引入方式。这些属性值会被传给View::registerCssFile()/View::registerJsFile()。
注意:在资源包类中设置的选项作用于包内每一个CSS/JS 文件。如果希望不同文件使用不同选项,应使用上文
css/js的数组条目格式,或拆分多个资源包、每个包各用一组选项。
典型用法示例:
按浏览器条件引入 CSS(针对 IE9 及以下浏览器):
public $cssOptions = ['condition' => 'lte IE9'];该设置会让包内的 CSS 文件以如下 HTML 形式引入:
<!--[if lte IE9]> <link rel="stylesheet" href="path/to/foo.css"> <![endif]-->用<noscript>包裹生成的 CSS 链接:
public $cssOptions = ['noscript' => true];将 JS 文件引入页面<head>区域(默认 JS 位于</body>之前):
public $jsOptions = ['position' => \yii\web\View::POS_HEAD];只发布sourcePath的部分子目录:默认情况下,资源包发布时会发布sourcePath目录下的全部内容。通过publishOptions的only选项可以只发布部分子目录,例如为 FontAwesome 字体库定义资源包:
<?php namespace app\assets; use yii\web\AssetBundle; class FontAwesomeAsset extends AssetBundle { public $sourcePath = '@bower/font-awesome'; public $css = [ 'css/font-awesome.min.css', ]; public $publishOptions = [ 'only' => [ 'fonts/', 'css/', ] ]; }上述示例为 fontawesome 的文档注释),这些选项最终会透传给FileHelper::copyDirectory()。
三、Bower 与 NPM 资源的接入
大多数 JavaScript/CSS 包由 Bower 和/或 NPM 包管理器管理。在 PHP 世界中有 Composer 管理 PHP 依赖,但也可以让 Composer 像加载 PHP 包一样加载 Bower 与 NPM 包。为此需要稍微配置 Composer,有两种方案:
方式一:使用 asset-packagist 仓库
这种方式能满足大多数需要 NPM/Bower 包的项目需求。
注意:自 2.0.13 起,Basic 与 Advanced 项目模板均已默认配置为使用 asset-packagist,因此如果你使用新模板可以跳过本节。
在项目的composer.json中添加以下内容:
"repositories": [ { "type": "composer", "url": "https://asset-packagist.org" } ]并在应用配置中调整@npm与@bower两个别名:
$config = [ ... 'aliases' => [ '@bower' => '@vendor/bower-asset', '@npm' => '@vendor/npm-asset', ], ... ];方式二:使用 fxp/composer-asset-plugin
与 asset-packagist 相比,composer-asset-plugin不需要修改应用配置,但需要全局安装一个特殊的 Composer 插件:
composer global require "fxp/composer-asset-plugin:^1.4.1"该命令全局安装 composer asset plugin,从而可以通过 Composer 管理 Bower 与 NPM 包依赖。插件安装后,你计算机上的任何项目都能通过composer.json支持 Bower/NPM 包。若希望用 Yii 发布这些包,可在项目的composer.json中指定安装目录:
"config": { "asset-installer-paths": { "npm-asset-library": "vendor/npm", "bower-asset-library": "vendor/bower" } }注意:与 asset-packagist 相比,
fxp/composer-asset-plugin会显著拖慢composer update命令。
配置完成后的三步使用流程
- 修改应用或扩展的
composer.json,在require中列出包名:Bower 包用bower-asset/PackageName,NPM 包用npm-asset/PackageName; - 执行
composer update; - 创建资源包类,列出要使用的 JavaScript/CSS 文件,并将
sourcePath设为@bower/PackageName或@npm/PackageName——因为 Composer 会将对应包安装到这些别名指向的目录中。
注意:有些包会把发行文件放在子目录中,此时应将子目录作为
sourcePath的值。例如yii\web\JqueryAsset使用@bower/jquery/dist而非@bower/jquery。从框架源码可见这一实践:JqueryAsset.php 正是将sourcePath设为@bower/jquery/dist、js设为['jquery.js']。
四、在视图中使用资源包
在视图中调用yii\web\AssetBundle::register()即可注册资源包。例如在视图模板中:
use app\assets\AppAsset; AppAsset::register($this); // $this 代表视图对象(view)提示:
register()方法返回一个资源包对象,其中包含已发布资源的信息,例如basePath与baseUrl(见 AssetBundle::register(),其内部委托给View::registerAssetBundle())。
如果在其它位置注册资源包,则需要自行提供视图对象。例如在图形控件(widget)类中注册资源包,可通过$this->view取得视图对象。
当资源包在视图中注册后,Yii 在后台会自动注册其所有依赖的资源包;如果某个资源包位于 Web 不可访问的目录,它会被发布到 Web 目录。之后视图渲染页面时,会为已注册资源包css/js列表中的文件生成<link>与<script>标签。标签的顺序由已注册包之间的依赖关系以及各包css/js数组内的文件顺序共同决定。在源码层面,View::endBody()中会对所有已注册资源包依次调用registerAssetFiles()(见 View.php)。
动态资源包
资源包本身就是普通 PHP 类,可以携带额外逻辑并在运行时动态调整内部参数。例如:某个复杂的 JavaScript 库将国际化资源按语言拆分为独立文件,你需要根据当前语言向页面追加特定的.js文件。可以通过覆写yii\web\AssetBundle::init()实现:
namespace app\assets; use yii\web\AssetBundle; use Yii; class SophisticatedAssetBundle extends AssetBundle { public $sourcePath = '/path/to/sophisticated/src'; public $js = [ 'sophisticated.js' // 始终使用的文件 ]; public function init() { parent::init(); $this->js[] = 'i18n/' . Yii::$app->language . '.js'; // 动态追加的文件 } }也可以通过register()返回的实例来调整特定资源包:
use app\assets\SophisticatedAssetBundle; use Yii; $bundle = SophisticatedAssetBundle::register(Yii::$app->view); $bundle->js[] = 'i18n/' . Yii::$app->language . '.js'; // 动态追加的文件注意:虽然 Yii 支持动态调整资源包,但这属于不良实践,可能引发意想不到的副作用,应尽可能避免。
通过 assetManager 自定义资源包
Yii 通过名为assetManager的应用组件管理资源包,该组件由yii\web\AssetManager实现。通过配置其bundles属性即可自定义某个资源包的行为。例如,默认的yii\web\JqueryAsset使用本地安装的 Bower jQuery 包中的jquery.js;为了提高可用性与性能,你可能希望改用 Google 托管的版本,可在应用配置中这样设置assetManager:
return [ // ... 'components' => [ 'assetManager' => [ 'bundles' => [ 'yii\web\JqueryAsset' => [ 'sourcePath' => null, // 不发布该包 'js' => [ '//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js', ] ], ], ], ], ];可以通过bundles以类似方式配置多个资源包:数组键为资源包类名(不含前导反斜杠),数组值为对应的配置数组。从源码看,AssetManager::getBundle()在创建资源包实例时会合并应用这份配置(见 AssetManager.php)。
技巧:可以按条件选择包内使用哪些资源。例如开发环境使用
jquery.js,其它环境使用压缩版jquery.min.js:'yii\web\JqueryAsset' => [ 'js' => [ YII_ENV_DEV ? 'jquery.js' : 'jquery.min.js' ] ],
也可以禁用一个或多个资源包:将资源包名映射为false。当某个被禁用的资源包在视图中注册时,其依赖包也不会注册,视图不会在页面中包含该包的任何资源。例如禁用yii\web\JqueryAsset:
return [ // ... 'components' => [ 'assetManager' => [ 'bundles' => [ 'yii\web\JqueryAsset' => false, ], ], ], ];还可以将bundles整体设为false来禁用所有资源包。测试代码tests/framework/web/AssetManagerTest.php中的testGetBundleReturnsDummyWhenBundlesDisabled验证了这一行为:当bundles = false时,getBundle()返回一个「哑包」(dummy bundle),其js/css均被清空。
资源映射(Asset Mapping)
有时你想「修正」多个资源包中错误或不兼容的资源文件路径。例如包 A 使用jquery.min.js1.11.1,包 B 使用jquery.js2.1.1。除了逐个自定义资源包外,更简单的做法是使用资源映射特性,将错误的资源映射到期望的资源。配置assetManager的assetMap属性即可:
return [ // ... 'components' => [ 'assetManager' => [ 'assetMap' => [ 'jquery.js' => '//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js', ], ], ], ];assetMap的键是想要修正的资源名,值是期望的资源路径。注册资源包时,其css与js数组中的每个相对资源文件都会与此映射比对:如果某个键是资源文件路径的最后一段(若存在sourcePath则以其为前缀),对应值就会替换该资源并注册到视图。例如资源文件my/path/to/jquery.js会命中键jquery.js。其匹配逻辑在 AssetManager::resolveAsset() 中实现,使用的是后缀比较(substr_compare)。
注意:只有使用相对路径指定的资源才受资源映射影响。映射目标路径必须是绝对 URL,或者是相对于
basePath的路径。
五、资源发布(Asset Publishing)与缓存破除
如前所述,如果资源包位于不可 Web 访问的目录,那么当它在视图中注册时,其资源会被复制到 Web 目录。这个过程称为资源发布,由yii\web\AssetManager自动完成。
默认情况下,资源发布到目录@webroot/assets(对应 URL@web/assets)。可以通过配置basePath与baseUrl属性自定义发布位置。发布目录的子目录名由源路径的 CRC32 哈希(结合文件修改时间、框架版本与linkAssets状态)生成,见 AssetManager::hash()——这就是为什么发布后的 URL 形如/assets/5515a87c/yii.js。
使用符号链接发布:linkAssets
如果操作系统与 Web 服务器允许,可以改用符号链接而不是复制文件来发布资源。将linkAssets设为true即可启用:
return [ // ... 'components' => [ 'assetManager' => [ 'linkAssets' => true, ], ], ];采用上述配置后,资源管理器在发布时会为资源包的源路径创建符号链接。这比复制文件更快,而且能保证已发布资源始终与源码一致。源码中publishFile()与publishDirectory()均对linkAssets分支做了处理(见 AssetManager.php 与 AssetManager.php)。测试用例testPublishFileWithLinkAssets、testPublishDirectoryWithLinkAssets也验证了该行为。
注意:符号链接要求 Linux/Unix 或 Windows Vista/2008 及以上的环境;部分 Web 服务器需要额外配置才能访问链接资源,例如 Apache 需要为 Web 目录开启
Options FollowSymLinks。
缓存破除(Cache Busting):appendTimestamp
生产环境通常会对静态资源启用 HTTP 缓存,但副作用是:当你修改并重新部署资源后,浏览器可能仍因 HTTP 缓存使用旧版本。为此,Yii2 自2.0.3起引入了缓存破除功能,配置assetManager即可启用:
return [ // ... 'components' => [ 'assetManager' => [ 'appendTimestamp' => true, ], ], ];启用后,每个已发布资源的 URL 末尾都会附加其最后修改时间戳。例如yii.js的 URL 会变成/assets/5515a87c/yii.js?v=1423448645,其中v参数即yii.js文件的最后修改时间戳。此后只要修改资源,其 URL 也会随之变化,从而强制浏览器获取最新版本。该逻辑实现在 AssetManager::getAssetUrl() 中:通过@filemtime($assetPath)取文件修改时间并拼接到 URL。
六、框架内置的常用资源包
Yii2 核心代码定义了多个资源包,以下是应用或扩展代码中常用的几个:
yii\web\YiiAsset:主要包含yii.js文件,实现 JavaScript 代码模块化组织机制,并为data-method、data-confirm等属性提供特殊支持(更多信息见客户端脚本)。从源码看,它依赖yii\web\JqueryAsset,sourcePath为@yii/assets(见 YiiAsset.php,对应仓库中的 framework/assets/yii.js)。yii\web\JqueryAsset:包含 jQuery Bower 包中的jquery.js文件(见 JqueryAsset.php)。yii\bootstrap\BootstrapAsset:包含 Twitter Bootstrap 框架的 CSS 文件。yii\bootstrap\BootstrapPluginAsset:包含 Twitter Bootstrap 框架的 JavaScript 文件,用于支持 Bootstrap 的 JavaScript 插件。yii\jui\JuiAsset:包含 jQuery UI 库的 CSS 与 JavaScript 文件。
如果你的代码依赖 jQuery、jQuery UI 或 Bootstrap,应优先使用这些预定义资源包,而不是自行创建版本。若这些包的默认设置不满足需求,可按照上文「自定义资源包」小节的方式调整。
七、资源转换(Asset Conversion):LESS、SCSS、TypeScript 等
开发者往往不直接编写 CSS/JavaScript,而是使用扩展语法再借助工具转换为 CSS/JavaScript。例如 CSS 侧可使用 LESS 或 SCSS,JavaScript 侧可使用 TypeScript。
你可以在资源包的css与js属性中直接列出扩展语法文件,例如:
class AppAsset extends AssetBundle { public $basePath = '@webroot'; public $baseUrl = '@web'; public $css = [ 'css/site.less', ]; public $js = [ 'js/site.ts', ]; public $depends = [ 'yii\web\YiiAsset', 'yii\bootstrap\BootstrapAsset', ]; }在视图中注册这样的资源包时,资源管理器会自动运行预处理工具,将可识别的扩展语法资源转换为 CSS/JavaScript;页面最终引入的是转换后的文件,而非扩展语法原文。
Yii 依据文件扩展名识别资源所用语法。默认支持的语法与扩展名如下(见 AssetConverter.php 中$commands默认值):
- LESS:
.less - SCSS:
.scss - Stylus:
.styl - CoffeeScript:
.coffee - TypeScript:
.ts
Yii 依赖机器上安装的预处理工具完成转换。例如使用 LESS 需要安装lessc预处理命令。AssetConverter::convert()会检查目标文件与源文件的修改时间,仅在目标缺失或过旧时重新执行转换命令(见 AssetConverter.php);命令行通过proc_open执行,{from}/{to}占位符会被替换为转义后的源/目标文件路径(见 AssetConverter.php)。
你可以通过配置assetManager的converter属性来定制预处理命令与支持的扩展语法:
return [ 'components' => [ 'assetManager' => [ 'converter' => [ 'class' => 'yii\web\AssetConverter', 'commands' => [ 'less' => ['css', 'lessc {from} {to} --no-color'], 'ts' => ['js', 'tsc --out {to} {from}'], ], ], ], ], ];上述配置通过yii\web\AssetConverter::commands指定支持的扩展语法:数组键是文件扩展名(不含前导点),值是转换结果的扩展名以及执行转换的命令;命令中的{from}与{to}会被替换为源资源文件路径与目标资源文件路径。需要注意的是,仓库当前默认的 LESS 命令为lessc {from} {to} --no-color --source-map,SCSS 命令为sass --style=compressed {from} {to},覆盖配置时可按需保留或修改。
提示:除上述方式外,还可以使用 grunt 等构建工具监听并自动转换扩展语法资源。这种情况下,应把转换后的 CSS/JavaScript 文件列入资源包,而不是原始文件。
八、组合与压缩资源
一个 Web 页面可能包含大量 CSS 和/或 JavaScript 文件。为减少 HTTP 请求数量与总体下载体积,常见做法是把多个 CSS/JS 文件组合并压缩成一个或极少数文件,然后在页面中引入这些压缩文件。
提示:组合与压缩通常在生产模式下需要。开发模式下使用原始 CSS/JS 文件往往更便于调试。
下面介绍一种无需修改现有应用代码即可组合压缩资源的思路:
- 找出应用中计划组合压缩的所有资源包;
- 将这些包划分为一个或几个组(注意每个包只能属于一个组);
- 将每组内的 CSS 文件组合/压缩为单个文件,JS 文件同理;
- 为每个组定义一个新的资源包:
- 将
css与js属性设为组合后的 CSS/JS 文件; - 将该组内原资源包的
css/js置为空数组,并把其depends设为该组对应的新资源包。
- 将
采用这种方法后,在视图中注册某个资源包时,会自动触发其所属组的新资源包注册;页面最终引入的是组合/压缩后的文件,而非原始文件。
手动示例
假设应用有页面 X 和 Y:页面 X 使用资源包 A、B、C,页面 Y 使用资源包 B、C、D。
划分资源包有两种思路:一是把所有资源包放进单个组;二是把 A 放入组 X、D 放入组 Y、(B, C) 放入组 S。哪种更好?取决于场景。第一种方式的优点是两个页面共享同一份组合后的 CSS/JS 文件,HTTP 缓存效率更高;缺点是由于单组包含所有包,组合文件的体积更大,会增加初始传输时间。为简化示例,这里采用第一种方式(单组包含所有包)。
提示:将资源包分组并非易事,通常需要分析不同页面上各类资源的真实流量数据。初期可以从单组开始,以简化操作。
使用现有工具(例如 Closure Compiler、YUI Compressor)组合并压缩所有包中的 CSS/JS 文件。注意:文件必须按满足包间依赖的顺序组合——例如包 A 依赖 B,B 又依赖 C 与 D,则应按照 C、D → B → A 的顺序排列文件。
组合压缩后得到一个 CSS 文件与一个 JS 文件。假设命名为all-xyz.css与all-xyz.js,其中xyz是时间戳或哈希值,用于保证文件名唯一、避免 HTTP 缓存问题。
最后一步,在应用配置中如下配置assetManager:
return [ 'components' => [ 'assetManager' => [ 'bundles' => [ 'all' => [ 'class' => 'yii\web\AssetBundle', 'basePath' => '@webroot/assets', 'baseUrl' => '@web/assets', 'css' => ['all-xyz.css'], 'js' => ['all-xyz.js'], ], 'A' => ['css' => [], 'js' => [], 'depends' => ['all']], 'B' => ['css' => [], 'js' => [], 'depends' => ['all']], 'C' => ['css' => [], 'js' => [], 'depends' => ['all']], 'D' => ['css' => [], 'js' => [], 'depends' => ['all']], ], ], ], ];如「自定义资源包」小节所述,上述配置改变了每个包的默认行为:A、B、C、D 不再包含任何资源文件,全部依赖包含组合文件all-xyz.css与all-xyz.js的all包。因此页面 X 不再引入 A、B、C 的原始文件,而只引入这两个组合文件;页面 Y 同理。
还有一个让上述方案更顺滑的技巧:与其直接修改应用配置文件,不如把资源包定制数组放在独立文件中,再按条件包含进应用配置。例如:
return [ 'components' => [ 'assetManager' => [ 'bundles' => require __DIR__ . '/' . (YII_ENV_PROD ? 'assets-prod.php' : 'assets-dev.php'), ], ], ];也就是说,资源包配置数组在生产模式存放在assets-prod.php,非生产模式存放在assets-dev.php。
注意:此组合机制依赖
AssetManager::bundles对已注册资源包属性的覆盖能力。但如前所述,该能力不覆盖在AssetBundle::init()方法内或注册之后进行的动态调整;因此组合压缩时应避免使用这类动态资源包。
使用 asset 控制台命令
Yii 提供了名为asset的控制台命令来自动化上述过程(实现位于 framework/console/controllers/AssetController.php)。
使用该命令前,需要先创建一个配置文件,描述哪些资源包将被组合、如何分组。可以先通过子命令asset/template生成模板,再按需修改:
yii asset/template assets.php该命令会在当前目录生成assets.php文件,内容大致如下:
<?php /** * 用于 "yii asset" 控制台命令的配置文件。 * 注意:在控制台环境中,一些路径别名如 '@webroot' 与 '@web' 可能不存在, * 请记得定义这些缺失的路径别名。 */ return [ // 调整 JavaScript 文件压缩的命令/回调: 'jsCompressor' => 'java -jar compiler.jar --js {from} --js_output_file {to}', // 调整 CSS 文件压缩的命令/回调: 'cssCompressor' => 'java -jar yuicompressor.jar --type css {from} -o {to}', // 压缩后是否删除资源源文件(自 2.0.10 起支持,默认 false): 'deleteSource' => false, // 要压缩的资源包列表: 'bundles' => [ // 'yii\web\YiiAsset', // 'yii\web\JqueryAsset', ], // 压缩输出的资源包: 'targets' => [ 'all' => [ 'class' => 'yii\web\AssetBundle', 'basePath' => '@webroot/assets', 'baseUrl' => '@web/assets', 'js' => 'js/all-{hash}.js', 'css' => 'css/all-{hash}.css', ], ], // 资源管理器配置: 'assetManager' => [ ], ];需要修改此文件,在bundles选项中指定计划组合的包;在targets选项中指定包如何划分为组(可以指定一个或多个组)。JavaScript 文件将被组合、压缩并写入js/all-{hash}.js,其中{hash}会被替换为结果文件的哈希值(源码见 AssetController.php 中targets属性的说明)。
注意:由于
@webroot与@web别名在控制台应用中不可用,需要在配置中显式定义它们。
jsCompressor与cssCompressor选项指定执行 JS/CSS 组合压缩的控制台命令或 PHP 回调。默认情况下,Yii 使用 Closure Compiler 组合 JavaScript 文件、使用 YUI Compressor 组合 CSS 文件(见 AssetController.php)。需要自行安装这些工具,或调整选项改用你偏好的工具。
注意:如果你通过
AssetManager::bundles或AssetManager::assetMap自定义了资源包,并希望这些自定义也作用于压缩源文件,应把相应选项放入 asset 命令配置文件内的assetManager段中。注意:指定压缩源时应避免使用参数会动态调整的资源包(例如在
init()中或注册后调整的包),因为它们压缩后可能工作异常。
有了配置文件,即可运行asset命令组合压缩资源文件,并生成新的资源包配置文件assets-prod.php:
yii asset assets.php config/assets-prod.php生成的配置文件可以像上一小节描述的那样包含进应用配置。另外,AssetController的默认动作是compress(见 AssetController.php),因此yii asset等价于执行压缩动作。
提示:
asset命令并不是自动化组合压缩的唯一选择,也可以使用 grunt 任务运行器达到相同效果。
资源包分组(Grouping Asset Bundles)
上一小节介绍了将所有资源包合并为单包以最小化 HTTP 请求的做法,但这并不总是可取的。例如,应用同时拥有「前端(frontend)」与「后台(backend)」,二者使用不同的 CSS/JS 文件集合。此时把两个端的资源包合并为一个意义不大:前端资源包不会被后台使用,请求前端页面时还要传输后台资源,纯属浪费带宽。
解决方法是把资源包划分成组、按组各自组合压缩。下面的配置演示了如何分组:
return [ ... // 按组指定输出资源包: 'targets' => [ 'allShared' => [ 'js' => 'js/all-shared-{hash}.js', 'css' => 'css/all-shared-{hash}.css', 'depends' => [ // 包含 'backend' 与 'frontend' 共享的所有资源 'yii\web\YiiAsset', 'app\assets\SharedAsset', ], ], 'allBackEnd' => [ 'js' => 'js/all-{hash}.js', 'css' => 'css/all-{hash}.css', 'depends' => [ // 仅包含 'backend' 资源: 'app\assets\AdminAsset' ], ], 'allFrontEnd' => [ 'js' => 'js/all-{hash}.js', 'css' => 'css/all-{hash}.css', 'depends' => [], // 包含所有其余资源 ], ], ... ];可以看到资源包被划分为三个组:allShared、allBackEnd与allFrontEnd,各自依赖一组合适的资源包。例如allBackEnd依赖app\assets\AdminAsset。用该配置运行asset命令时,资源包会按上述规则组合。
提示:可以让某个目标包的
depends配置为空。这样该资源包将依赖所有「没有被其它目标包依赖」的剩余资源包。
九、资源管理器(AssetManager)与资源发布
总结一下:资源管理器的核心职责由yii\web\AssetManager承担(见 framework/web/AssetManager.php),它作为应用组件通过Yii::$app->assetManager访问,主要功能包括:
getBundle():按名称加载资源包,应用bundles配置、按需发布(publish = true),并缓存实例;bundles = false时返回空的哑包(见 AssetManager.php);publish():将文件或目录发布到basePath,返回[目标路径, 目标URL];文件按修改时间增量复制,目录发布支持only/except/beforeCopy/afterCopy/forceCopy等选项(见 AssetManager.php);getAssetUrl()/getAssetPath():结合assetMap与appendTimestamp计算资源的最终 URL 与路径(见 AssetManager.php);getConverter():惰性创建yii\web\AssetConverter实例,用于扩展语法资源转换(见 AssetManager.php)。
发布目录的子目录名通过hash()生成(CRC32 + 文件修改时间 + 框架版本 + linkAssets 状态),因此同一份源码在不同部署中会得到一致的目录名;@webroot/assets下的已发布内容被视为临时数据,可安全清理后由下次请求重新生成。
十、总结
Yii2 的资源管理以「资源包」为核心抽象,贯穿从开发到生产的完整链路:
- 定义:继承
yii\web\AssetBundle声明sourcePath/basePath/baseUrl/css/js/depends等属性; - 接入前端生态:通过 asset-packagist 或 fxp/composer-asset-plugin 让 Composer 管理 Bower/NPM 包,配合
@bower/@npm别名使用; - 注册:在视图或控件中调用
AssetBundle::register(),依赖、发布、标签顺序由 Yii 自动处理; - 定制:通过
assetManager的bundles、assetMap、linkAssets、appendTimestamp等配置实现 CDN 替换、路径修正、符号链接发布与缓存破除; - 转换:借助
AssetConverter自动将 LESS/SCSS/Stylus/CoffeeScript/TypeScript 编译为 CSS/JS; - 优化:使用
asset控制台命令(asset/template生成配置、asset执行压缩)按组组合压缩资源,减少 HTTP 请求与传输体积。
掌握这套体系后,无论是应用开发、扩展开发还是生产性能调优,你都能以统一、可维护的方式管理前端资源。相关实现与测试可进一步阅读仓库源码:AssetBundle.php、AssetManager.php、AssetConverter.php、AssetController.php、View.php 以及 AssetManagerTest.php。
【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址: https://gitcode.com/gh_mirrors/yi/yii2
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考