如何使用Assetic进行资源管理

3.4 版本
维护中的版本

安装和开启Assetic 

从Symfony 2.8开始,Assetic不再是Symofny标准版框架的自带内容。在使用其功能之前,请先在你的项目中执行以下命令,安装AcceticBundle:

1
$  composer require symfony/assetic-bundle

然后,在你Symfony程序的 AppKernel.php 文件中开启bundle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// app/AppKernel.php
 
// ...
class AppKernel extends Kernel
{
    // ...
 
    public function registerBundles()
    {
        $bundles = array(
            // ...
            new Symfony\Bundle\AsseticBundle\AsseticBundle(),
        );
 
        // ...
    }
}

最后,添加下列最小化配置以开启程序对Assetic的支持:

1
2
3
4
5
6
7
8
# app/config/config.yml
assetic:
    debug:          '%kernel.debug%'
    use_controller: '%kernel.debug%'
    filters:
        cssrewrite: ~
 
# ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:assetic="http://symfony.com/schema/dic/assetic"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/assetic
        http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
 
    <assetic:config debug="%kernel.debug%" use-controller="%kernel.debug%">
        <assetic:filters cssrewrite="null" />
    </assetic:config>
 
    <!-- ... -->
</container>
1
2
3
4
5
6
7
8
9
10
11
// app/config/config.php
$container->loadFromExtension('assetic', array(
    'debug' => '%kernel.debug%',
    'use_controller' => '%kernel.debug%',
    'filters' => array(
        'cssrewrite' => null,
    ),
    // ...
));
 
// ...

Assetic简介 

Assetic包括两项主要功能: assetsfilters。asset(资源)是指CSS, JavaScript 和 image 文件。调节器(filters)则是在这些文件被送到浏览器之前,可以作用于它们的工具。这能达成一种 “存于程序中(某处)的资源文件” 和 “真实呈现给用户的文件” 之间的分离。

如果没有Assetic,你在程序中只能提供“直接存储在程序中(的web目录)”的文件:

1
<script src="{{ asset('js/script.js') }}"></script>
1
<script src="<?php echo $view['assets']->getUrl('js/script.js') ?>"></script>

但如果 使用 Assetic,在它们被提供(给浏览器)之前,你可以随需操作这些资源(或从任意位置加载它们)。这意味着你可以:

  • 最小化与合并全部CSS和JS文件

  • 通过某些compiler,例如LESS,SASS或CoffeeScript,运行全部(或一部分)CSS和JS文件

  • 对图片进行优化

Assets 

使用Assetic,相比直接提供文件,有多项优势。文件不需要存放在显示它们的地方,而是可以从各种源头(比如一个bundle)中拖出来(使用)。

你可以使用Assetic来处理 CSS样式表JavaScript文件图片。它们的背后思想大致相同,但语法细节略有不同。

包容JavaScript文件 

要包容JavaScript文件,可在任意模板中使用 javascripts 标签:

1
2
3
{% javascripts '@AppBundle/Resources/public/js/*' %}
    <script src="{{ asset_url }}"></script>
{% endjavascripts %}
1
2
3
4
5
<?php foreach ($view['assetic']->javascripts(
    array('@AppBundle/Resources/public/js/*')
) as $url): ?>
    <script src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach ?>

如果你的程序模板使用的是Symfony标准版框架中的默认块名(block names),javascripts 标签则最常被置于 javascripts block之中:

1
2
3
4
5
6
7
{# ... #}
{% block javascripts %}
    {% javascripts '@AppBundle/Resources/public/js/*' %}
        <script src="{{ asset_url }}"></script>
    {% endjavascripts %}
{% endblock %}
{# ... #}

你也可以包容CSS样式:参考包容CSS样式表

本例中,AppBundle的 Resources/public/js/ 目录下的所有文件将被加载,然后从另外一个地方来提供。真实渲染后的标签可能是下面这样:

1
<script src="/app_dev.php/js/abcd123.js"></script>

这里有个关键点:一旦你让Assetic处理资源,文件就会从另外一个地方被提供出来。这 引发那些内含相对路径的CSS文件的问题。参考 使用cssrewrite调节器修复CSS路径

包容CSS样式表 

要引入CSS样式,你可以使用和上例相同的技巧,除了使用 stylesheets 标签:

1
2
3
{% stylesheets 'bundles/app/css/*' filter='cssrewrite' %}
    <link rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}
1
2
3
4
5
6
<?php foreach ($view['assetic']->stylesheets(
    array('bundles/app/css/*'),
    array('cssrewrite')
) as $url): ?>
    <link rel="stylesheet" href="<?php echo $view->escape($url) ?>" />
<?php endforeach ?>

如果你的程序模板使用的是Symfony标准版框架中的默认块名(block names),stylesheets 标签则最常被置于 stylesheets block之中:

1
2
3
4
5
6
7
{# ... #}
{% block stylesheets %}
    {% stylesheets 'bundles/app/css/*' filter='cssrewrite' %}
        <link rel="stylesheet" href="{{ asset_url }}" />
    {% endstylesheets %}
{% endblock %}
{# ... #}

但由于Assetic改变了你的资源路径,这 破坏使用了“相对路径”的背景图片(或其他路径),除非你 使用cssrewrite调节器修复CSS路径

注意原来那个包容了JavaScript文件,你引用的文件使用了类似 @AppBundle/Resources/public/file.js 的路径,但那种情况在本例中,你所引用的CSS文件用是它们自己的真实的、可公开访问的路径:bundles/app/css。你用哪种都行,除了当你对CSS样式表使用 @AppBundle 语法时,存在已知的问题会导致 cssrewrite 调节器失效。

包容图片 

要包容图片你应使用 image 标签:

1
2
3
{% image '@AppBundle/Resources/public/images/example.jpg' %}
    <img src="{{ asset_url }}" alt="Example" />
{% endimage %}
1
2
3
4
5
<?php foreach ($view['assetic']->image(
    array('@AppBundle/Resources/public/images/example.jpg')
) as $url): ?>
    <img src="<?php echo $view->escape($url) ?>" alt="Example" />
<?php endforeach ?>

你也可以使用Assetic来优化图片。更多信息参考如何配合Twig函数使用Assetic来对图片进行优化

不同于用Assetic来包容图片,你也可以考虑使用 LiipImagineBundle 社区bundle,它可以让你在提供图片之前对其进行压缩和操作(翻转、缩小、加水印,等等)。

使用cssrewrite调节器修复CSS路径 

由于Assetic为你的资源生成了新的URLs,CSS文件中的任何相对路径都将失效。要修复此点,确保在 stylesheets 中使用了 cssrewrite 调节器。这会解析你的CSS文件并在内部修正路径,以反射到全新位置。

在前面小节中的你可以看到一个示例。

当使用 cssrewrite filter时,不要通过 @AppBundle 语法引用你的CSS文件。 参考上面小节的 “note” 区域内容。

合并资源 

Assetic的一个功能,是把多个文件合并成一个。这可以减少HTTP请求次数,提高了前端性能。它还能让你更轻松地维护那些”被分割成可管理的若干部分”的文件。这可以提高可复用性,因为你可以轻松地把“特定项目”的文件分割之后“用在其他程序中”,但还是会以一个单一文件提供出来:

1
2
3
4
5
6
{% javascripts
    '@AppBundle/Resources/public/js/*'
    '@AcmeBarBundle/Resources/public/js/form.js'
    '@AcmeBarBundle/Resources/public/js/calendar.js' %}
    <script src="{{ asset_url }}"></script>
{% endjavascripts %}
1
2
3
4
5
6
7
8
9
<?php foreach ($view['assetic']->javascripts(
    array(
        '@AppBundle/Resources/public/js/*',
        '@AcmeBarBundle/Resources/public/js/form.js',
        '@AcmeBarBundle/Resources/public/js/calendar.js',
    )
) as $url): ?>
    <script src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach ?>

dev 环境下,每个文件仍然会被独立提供,以方便调试。但是在 prod 环境 (或者更特殊的, debug 旗标被设为 false时),这会以一个单一的 script 标签进行渲染,里面有全部JavaScript文件的内容。

如果你刚接触Assetic,并且程序是 prod 环境(使用的是 app.php 控制器),你可能会看到所有CSS和JS无法执行。别急,这是有意为之的。在 prod 环境使用Assetic的细节,参考 剥离Asset文件

合并文件时不只是适用于 你的 文件。还可以使用Assetic来合并第三方bundle的资源,例如JQuery,连同你自己的资源,合并为单一文件:

1
2
3
4
5
{% javascripts
    '@AppBundle/Resources/public/js/thirdparty/jquery.js'
    '@AppBundle/Resources/public/js/*' %}
    <script src="{{ asset_url }}"></script>
{% endjavascripts %}
1
2
3
4
5
6
7
8
<?php foreach ($view['assetic']->javascripts(
    array(
        '@AppBundle/Resources/public/js/thirdparty/jquery.js',
        '@AppBundle/Resources/public/js/*',
    )
) as $url): ?>
    <script src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach ?>

使用命名资源 

AsseticBundle的配置指令,允许你定义命名资源集。你可以通过在配置信息中 assetic 根键下定义输入文件,调节器和输出文件来实现。参考 AsseticBundle配置信息

1
2
3
4
5
6
7
# app/config/config.yml
assetic:
    assets:
        jquery_and_ui:
            inputs:
                - '@AppBundle/Resources/public/js/thirdparty/jquery.js'
                - '@AppBundle/Resources/public/js/thirdparty/jquery.ui.js'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:assetic="http://symfony.com/schema/dic/assetic"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/assetic
        http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
 
    <assetic:config>
        <assetic:asset name="jquery_and_ui">
            <assetic:input>@AppBundle/Resources/public/js/thirdparty/jquery.js</assetic:input>
            <assetic:input>@AppBundle/Resources/public/js/thirdparty/jquery.ui.js</assetic:input>
        </assetic:asset>
    </assetic:config>
</container>
1
2
3
4
5
6
7
8
9
10
11
// app/config/config.php
$container->loadFromExtension('assetic', array(
    'assets' => array(
        'jquery_and_ui' => array(
            'inputs' => array(
                '@AppBundle/Resources/public/js/thirdparty/jquery.js',
                '@AppBundle/Resources/public/js/thirdparty/jquery.ui.js',
            ),
        ),
    ),
);

在你定义了命名资源之后,可以在模板中使用 @named_asset 注释来引用它们:

1
2
3
4
5
{% javascripts
    '@jquery_and_ui'
    '@AppBundle/Resources/public/js/*' %}
    <script src="{{ asset_url }}"></script>
{% endjavascripts %}
1
2
3
4
5
6
7
8
<?php foreach ($view['assetic']->javascripts(
    array(
        '@jquery_and_ui',
        '@AppBundle/Resources/public/js/*',
    )
) as $url): ?>
    <script src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach ?>

Filters(调节器) 

一旦(assets资源)被Assetic管理,你就可以在它们被供给(到客户端)之前对其使用调节器。包括为了获得更小容量而对资源的输出进行压缩的调节器(更好的前端优化)。其他调节器,可以编译CoffeeScript文件至JavaScript,以及处理SASS至CSS。实际上,Assetic有很多可用的调节器。

很多调节器并不直接参与工作,而是利用即存的三方类库来处理重载。这意味着你经常需要安装第三方类库才能使用一个调节器。引入这些类库(因为反对直接使用)而使用Assetic最大的好处是,避免在操作资源文件时被迫地手动让它们运行,Assetic将帮你打点这些,并从开发环境和部署进程中一并移除此步骤。

要使用一个调节器,你先要在Assetic的配置区块指定它。这里添加的调节器并不意味着它会被使用——只表明它是可以使用的(你将在后面例子中使用它)。

例如,要使用UglifyJS这个JS最小化工具,应对配置作如下定义:

1
2
3
4
5
# app/config/config.yml
assetic:
    filters:
        uglifyjs2:
            bin: /usr/local/bin/uglifyjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:assetic="http://symfony.com/schema/dic/assetic"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/assetic
        http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
 
    <assetic:config>
        <assetic:filter
            name="uglifyjs2"
            bin="/usr/local/bin/uglifyjs" />
    </assetic:config>
</container>
1
2
3
4
5
6
7
8
// app/config/config.php
$container->loadFromExtension('assetic', array(
    'filters' => array(
        'uglifyjs2' => array(
            'bin' => '/usr/local/bin/uglifyjs',
        ),
    ),
));

现在,为了对一组JavaScript文件真正 使用 这个调节器,把它添加到你的模板中:

1
2
3
{% javascripts '@AppBundle/Resources/public/js/*' filter='uglifyjs2' %}
    <script src="{{ asset_url }}"></script>
{% endjavascripts %}
1
2
3
4
5
6
<?php foreach ($view['assetic']->javascripts(
    array('@AppBundle/Resources/public/js/*'),
    array('uglifyjs2')
) as $url): ?>
    <script src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach ?>

关于配置和使用Assetic过滤器以及Assetic调试模式的细节内容,可以参考 如何最小化CSS/JS文件(使用UglifyJS和UglifyCSS)

控制所使用的URL 

如果你愿意,可以控制Assetic生成的URL。这可在模板中完成,并且(输出内容)是相对于公开的web根目录。

1
2
3
{% javascripts '@AppBundle/Resources/public/js/*' output='js/compiled/main.js' %}
    <script src="{{ asset_url }}"></script>
{% endjavascripts %}
1
2
3
4
5
6
7
<?php foreach ($view['assetic']->javascripts(
    array('@AppBundle/Resources/public/js/*'),
    array(),
    array('output' => 'js/compiled/main.js')
) as $url): ?>
    <script src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach ?>

Symfony也包含了一个用于cache busting(缓存击破)的方法,其被Assetic生成的最终URL中带有一个query参数,可以在每次部署时随着配置信息而递增。更多信息,参考 version 配置选项。

剥离Asset文件 

dev 环境下,Assetic所生成的CSS和JavaScript文件路径并非是电脑上的“物理存在”。而是渲染出来,因为Symfony内部的控制器会打开这些文件并送回给内容(在运行任何调节器之后)。

这一类处理assets时的“动态伺服”是非常好的,因为这意味着你可以立即看到任何一个资源在发生改变后的新状态。同时其缺点是运行极慢。如果你使用了多个filters,有可能彻底崩溃掉。

幸运的是,Assetic提供了一种方式来剥离出你的资源到“真实文件”,而不是被即时动态生成。

在生产环境下剥离资源文件 

prod 环境下,你的JS和CSS调节器是通过各自的独立标签来呈现的。换言之,不能看到你包容进来的每个JavaScript文件,你可能只看到类似下面的东西:

1
<script src="/js/abcd123.js"></script>

再者,那个文件 并不 真实存在,也不被Symfony动态渲染(因为资源文件是在 dev 环境中)。这是设计本意——让Symfony在生产环境中动态生成这些文件实在太慢了。

取而代之的是,每一次当你在 prod 环境使用程序时(也就是,每一次在你部署时),你应该运行如下命令:

1
$  php bin/console assetic:dump --env=prod --no-debug

这会物理生成并写入每一个你需要的文件(如 /js/abcd123.js)。如果你更新了资源中的任何一个,需要重新运行此命令以再次生成文件。

在开发环境下剥离资源文件 

默认时,dev 环境下被生成的asset路径,由Symfony负责动态处理。这并无欠点(你可以立即看到你做出的改变),除了资源加载时显著变慢。如果你觉得资源的加载太过缓慢,遵循以下指南。

首先,告之Symfony停止尝试动态处理这些文件。在 config_dev.yml 做出如下改变:

1
2
3
# app/config/config_dev.yml
assetic:
    use_controller: false
1
2
3
4
5
6
7
8
9
10
11
12
<!-- app/config/config_dev.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:assetic="http://symfony.com/schema/dic/assetic"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/assetic
        http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
 
    <assetic:config use-controller="false" />
</container>
1
2
3
4
// app/config/config_dev.php
$container->loadFromExtension('assetic', array(
    'use_controller' => false,
));

接下来,由于Symfony不再为你生成这些assets,你需要手动剥离之。要这么做,运行以下命令:

1
$  php bin/console assetic:dump

这会为你的 dev 环境物理写入全部所需的资源文件。最大欠点是,你需要在每一次的资源更新时运行命令。幸运的是,使用 assetic:watch 命令,资源会在 它们改变时 自动重新生成:

1
$  php bin/console assetic:watch

assetic:watch 命令自AsseticBundle 2.4起被引入。之前的版本,你要在 assetic:dump 命令中使用 --watch 选项来实现相同的目的。

由于在 dev 环境下运行此命令会生成一堆文件,通常,把这些资源文件映射到一个单独的目录是个好办法(如 /js/compiled ),以令事情井井有条:

1
2
3
{% javascripts '@AppBundle/Resources/public/js/*' output='js/compiled/main.js' %}
    <script src="{{ asset_url }}"></script>
{% endjavascripts %}
1
2
3
4
5
6
7
<?php foreach ($view['assetic']->javascripts(
    array('@AppBundle/Resources/public/js/*'),
    array(),
    array('output' => 'js/compiled/main.js')
) as $url): ?>
    <script src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach ?>

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)