Introducing Assetic   Asset Management for PHP 5.3  Kris Wallsmith   February 9, 2011
@kriswallsmith•   Symfony core team member•   Doctrine contributor•   Symfony Guru at•   10+ years experience with PHP and...
OpenSky connects you with innovators,trendsetters and tastemakers.You choose   the ones you like and each week they  invit...
ShopOpenSky.com•   PHP 5.3 + Symfony2•   MongoDB + Doctrine MongoDB ODM•   MySQL + Doctrine2 ORM•   Less CSS•   jQuery
Agenda•   Strawman•   The code•   Twig Integration•   Symfony2 Integration
Symfony2 is FAST
But you can still f*** that up
We build tools thatencourage best practices
Best practices like…•   Dependency injection (DI)•   Proper caching, edge side includes (ESI)•   Test-driven development (...
If you haven’t optimized yourfrontend, you haven’t optimized
Get your assets in line.
A poorly optimized frontend     can destroy UX
…and SEO!http://googlewebmastercentral.blogspot.com/2010/04/using-site-speed-in-web-search-ranking.html
Asset Management
Lots of awesome tools•   CoffeeScript              •   LESS•   Compass Framework         •   Packer•   CSSEmbed           ...
The ones written in PHP…
This is a difficult problem
Assetic makes it easy
Are you ready tokick some Assetic!?!
# /path/to/web/js/core.php$core = new FileAsset(/path/to/jquery.js);$core->load();header(Content-Type: text/javascript);ec...
# /path/to/web/js/core.php$core = new AssetCollection(array(    new FileAsset(/path/to/jquery.js),    new GlobAsset(/path/...
# /path/to/web/js/core.php$core = new AssetCollection(array(    new FileAsset(/path/to/jquery.js),    new GlobAsset(/path/...
<script src="js/core.php"></script>
Assetic isAssets & Filters
Inspired by Python’s webassets        https://github.com/miracle2k/webassets
Assets have lazy, mutable content
Filters act on asset contents during         “load” and “dump”
Assets can be gathered in       collections
A collection is an asset
Load   Filter   Filter   Asset            Dump
Asset CollectionFilter              FilterFilter              FilterAsset               Asset
Asset Collection   Filter                                 Filter     Asset Collection            AssetFilter              ...
# /path/to/web/css/styles.php$styles = new AssetCollection(    array(new FileAsset(/path/to/main.sass)),    array(new Sass...
# /path/to/web/css/styles.php$styles = new AssetCollection(array(    new AssetCollection(        array(new FileAsset(/path...
# /path/to/web/css/styles.php$styles = new AssetCollection(array(    new AssetCollection(          array(new FileAsset(/pa...
Basic Asset Classes•   AssetCollection•   AssetReference•   FileAsset•   GlobAsset•   StringAsset
Core Filter Classes•   CallablesFilter                   •   SassSassFilter•   CoffeeScriptFilter                •   SassS...
Asset Manager
$am = new AssetManager();$am->set(jquery,    new FileAsset(/path/to/jquery.js));
$plugin = new AssetCollection(array(    new AssetReference($am, jquery),    new FileAsset(/path/to/jquery.plugin.js),));
jQuery will only be included once       $core = new AssetCollection(array(           $jquery,           $plugin1,         ...
Filter Manager
$yui = new YuiCompressorJs();$yui->setNomunge(true);$fm = new FilterManager();$fm->set(yui_js, $yui);
jQuery will only be compressed once   $jquery = new FileAsset(/path/to/core.js);   $jquery->ensureFilter($fm->get(yui_js))...
Asset Factory
# /path/to/asset_factory.php$fm = new FilterManager();$fm->set(coffee, new CoffeeScriptFilter());$fm->set(closure, new Goo...
include /path/to/asset_factory.php;$asset = $factory->createAsset(    array(js/src/*.coffee),    array(coffee, closure));h...
Debug Mode
Debugging compressed   Javascript sucks
Mark filters for omission    in debug mode
// new AssetFactory(/path/to/web, true);include /path/to/asset_factory.php;$asset = $factory->createAsset(    array(js/src...
// new AssetFactory(/path/to/web, true);include /path/to/asset_factory.php;$asset = $factory->createAsset(    array(js/src...
// new AssetFactory(/path/to/web, false);include /path/to/asset_factory.php;$asset = $factory->createAsset(    array(js/sr...
Factory Workers
Everything passes through the       workers’ hands
$worker = new EnsureFilterWorker(    /.css$/,          // the output pattern    $fm->get(yui_css), // the filter    false ...
$worker = new EnsureFilterWorker(    /.css$/,          // the output pattern    $fm->get(yui_css), // the filter    false ...
Not Lazy Enough?
Asset Formulae and the Lazy Asset Manager
$asset = $factory->createAsset(    array(js/src/*.coffee),    array(coffee, ?closure),    array(output => js));
$formula = array(    array(js/src/*.coffee),    array(coffee, ?closure),    array(output => js));
$am = new LazyAssetManager($factory);$am->setFormula(core_js, $formula);header(Content-Type: text/javascript);echo $am->ge...
Good: Basic Caching
# /path/to/web/css/styles.php$styles = new AssetCollection(    array(new FileAsset(/path/to/main.sass)),    array(new Sass...
# /path/to/web/css/styles.php$styles = new AssetCache(new AssetCollection(    array(new FileAsset(/path/to/main.sass)),   ...
Better: HTTP Caching
# /path/to/web/js/core.php$mtime = gmdate($core->getLastModified());if ($mtime == $_SERVER[HTTP_IF_MODIFIED_SINCE]) {    h...
Best: Static Assets
# /path/to/scripts/dump_assets.php$am = new AssetManager();$am->set(foo, $foo);// etc...$writer = new AssetWriter(/path/to...
# /path/to/scripts/dump_assets.php$am = new AssetManager();$am->set(foo, $foo);// etc...$writer = new AssetWriter(/path/to...
Best-est:Content Distribution Network
new AssetWriter(s3://my-bucket)                 A CloudFront S3 bucket
Twig Integration
$twig->addExtension(new AsseticExtension($factory));
{% assetic js/*.coffee, filter=coffee %}<script src="{{ asset_url }}"></script>{% endassetic %}
<script src="assets/92429d8"></script>
{% assetic js/*.coffee, filter=coffee %}<script src="{{ asset_url }}"></script>{% endassetic %}
{% assetic js/*.coffee, filter=coffee,   output=js %}<script src="{{ asset_url }}"></script>{% endassetic %}
<script src="js/92429d8.js"></script>
{% assetic js/*.coffee, filter=coffee,   output=js %}<script src="{{ asset_url }}"></script>{% endassetic %}
{% assetic js/*.coffee, filter=coffee,?closure,   output=js, name=core_js %}<script src="{{ asset_url }}"></script>{% enda...
Formula LoaderUses the Twig parser to extract asset formulae from templates
$loader = new FormulaLoader($twig);// loop through your templates$formulae = array();foreach ($templates as $template) {  ...
if (!file_exists($cache = /path/to/formulae.php)) {    $loader = new FormulaLoader($twig);    // loop through your templat...
AsseticBundle
{% assetic filter=scss,?yui_css, output=css/all.css,   @MainBundle/Resources/sass/main.scss,   @AnotherBundle/Resources/sa...
<link href="css/all.css" rel="stylesheet" />
{% assetic filter=scss,?yui_css, output=css/all.css,   @MainBundle/Resources/sass/main.scss,   @AnotherBundle/Resources/sa...
{% assetic filter=scss,?yui_css, output=css/all.css,   @MainBundle/Resources/sass/main.scss, debug=true,   @AnotherBundle/...
Each "leaf" asset is referenced individually<link href="css/all_part1.css" rel="stylesheet" /><link href="css/all_part2.cs...
Configuration
assetic.config:    debug:          %kernel.debug%    use_controller: %kernel.debug%    document_root: %kernel.root_dir%/.....
{# when use_controller=true #}<script src="{{ path(route, { name: core_js }) }}"...
# routing_dev.yml_assetic:    resource: .    type:     assetic
{# when use_controller=false #}<script src="{{ asset(js/core.js) }}"></script>                 Lots for free
The Symfony2 Assets Helper•   Multiple asset domains•   Cache buster
app.config:    templating:        assets_version: 1.2.3        assets_base_urls:            - http://assets1.domain.com   ...
{% assetic filter=scss,?yui_css, output=css/all.css,   @MainBundle/Resources/sass/main.scss,   @AnotherBundle/Resources/sa...
<link href="http://assets3.domain.com/css/all.css?1.2.3" ...
assetic:dump
$ php app/console assetic:dump web/
$ php app/console assetic:dump s3://my-bucket                 Register a stream wrapper in boot()
PHP templates   Coming soon…
<?php foreach ($view[assetic]->urls(    array(@MainBundle/Resources/sass/main.scss,          @AnotherBundle/Resources/sass...
Fork me!http://github.com/kriswallsmith/symfony-sandbox
What’s Next?•   Finish Symfony2 helpers for PHP templates•   Filter configuration•   Image sprites, embedded image data•   ...
Assetic is a killer feature of        Symfony2…
…but is only one month old,        so be nice :)
Questions?
Assetichttp://github.com/kriswallsmith/assetic
Upcoming SlideShare
Loading in...5
×

Introducing Assetic: Asset Management for PHP 5.3

66,277

Published on

The performance of your application depends heavily on the number and size of assets on each page. Even your blazingly fastest Symfony2 application can be bogged down by bloated Javascript and CSS files. This session will give you a basic introduction to PHP's new asset management framework, Assetic, and explore how it integrates with Symfony2 for a pleasant, common sense developer experience.

Published in: Technology
1 Comment
86 Likes
Statistics
Notes
No Downloads
Views
Total Views
66,277
On Slideshare
0
From Embeds
0
Number of Embeds
40
Actions
Shares
0
Downloads
456
Comments
1
Likes
86
Embeds 0
No embeds

No notes for slide

Introducing Assetic: Asset Management for PHP 5.3

  1. 1. Introducing Assetic Asset Management for PHP 5.3 Kris Wallsmith February 9, 2011
  2. 2. @kriswallsmith• Symfony core team member• Doctrine contributor• Symfony Guru at• 10+ years experience with PHP and web development• Open source evangelist and international speaker
  3. 3. OpenSky connects you with innovators,trendsetters and tastemakers.You choose the ones you like and each week they invite you to their private online sales.
  4. 4. ShopOpenSky.com• PHP 5.3 + Symfony2• MongoDB + Doctrine MongoDB ODM• MySQL + Doctrine2 ORM• Less CSS• jQuery
  5. 5. Agenda• Strawman• The code• Twig Integration• Symfony2 Integration
  6. 6. Symfony2 is FAST
  7. 7. But you can still f*** that up
  8. 8. We build tools thatencourage best practices
  9. 9. Best practices like…• Dependency injection (DI)• Proper caching, edge side includes (ESI)• Test-driven development (TDD)• Dont repeat yourself (DRY)• Keep it simple, SVP (KISS)• Performance
  10. 10. If you haven’t optimized yourfrontend, you haven’t optimized
  11. 11. Get your assets in line.
  12. 12. A poorly optimized frontend can destroy UX
  13. 13. …and SEO!http://googlewebmastercentral.blogspot.com/2010/04/using-site-speed-in-web-search-ranking.html
  14. 14. Asset Management
  15. 15. Lots of awesome tools• CoffeeScript • LESS• Compass Framework • Packer• CSSEmbed • SASS• Google Closure Compiler • Sprockets• JSMin • YUI Compressor
  16. 16. The ones written in PHP…
  17. 17. This is a difficult problem
  18. 18. Assetic makes it easy
  19. 19. Are you ready tokick some Assetic!?!
  20. 20. # /path/to/web/js/core.php$core = new FileAsset(/path/to/jquery.js);$core->load();header(Content-Type: text/javascript);echo $core->dump();
  21. 21. # /path/to/web/js/core.php$core = new AssetCollection(array( new FileAsset(/path/to/jquery.js), new GlobAsset(/path/to/js/core/*.js),));$core->load(); many files into one: fewer HTTP requests Mergeheader(Content-Type: text/javascript);echo $core->dump();
  22. 22. # /path/to/web/js/core.php$core = new AssetCollection(array( new FileAsset(/path/to/jquery.js), new GlobAsset(/path/to/js/core/*.js),), array( new YuiCompressorJsFilter(/path/to/yui.jar),));$core->load();merged asset: less data over the wire Compress theheader(Content-Type: text/javascript);echo $core->dump();
  23. 23. <script src="js/core.php"></script>
  24. 24. Assetic isAssets & Filters
  25. 25. Inspired by Python’s webassets https://github.com/miracle2k/webassets
  26. 26. Assets have lazy, mutable content
  27. 27. Filters act on asset contents during “load” and “dump”
  28. 28. Assets can be gathered in collections
  29. 29. A collection is an asset
  30. 30. Load Filter Filter Asset Dump
  31. 31. Asset CollectionFilter FilterFilter FilterAsset Asset
  32. 32. Asset Collection Filter Filter Asset Collection AssetFilter FilterFilter FilterAsset Asset Filter Filter Asset
  33. 33. # /path/to/web/css/styles.php$styles = new AssetCollection( array(new FileAsset(/path/to/main.sass)), array(new SassFilter()));header(Content-Type: text/css);echo $styles->dump();
  34. 34. # /path/to/web/css/styles.php$styles = new AssetCollection(array( new AssetCollection( array(new FileAsset(/path/to/main.sass)), array(new SassFilter()) ), new FileAsset(/path/to/more.css),));header(Content-Type: text/css);echo $styles->dump();
  35. 35. # /path/to/web/css/styles.php$styles = new AssetCollection(array( new AssetCollection( array(new FileAsset(/path/to/main.sass)), array(new SassFilter()) ), new FileAsset(/path/to/more.css),), array( new YuiCompressorCss(/path/to/yui.jar),)); Lazy! The filesystem isnt touched until nowheader(Content-Type: text/css);echo $styles->dump();
  36. 36. Basic Asset Classes• AssetCollection• AssetReference• FileAsset• GlobAsset• StringAsset
  37. 37. Core Filter Classes• CallablesFilter • SassSassFilter• CoffeeScriptFilter • SassScssFilter• CssRewriteFilter • SprocketsFilter• GoogleClosureCompilerApiFilter • YuiCssCompressorFilter• GoogleClosureCompilerJarFilter • YuiJsCompressorFilter• LessFilter • More to come…
  38. 38. Asset Manager
  39. 39. $am = new AssetManager();$am->set(jquery, new FileAsset(/path/to/jquery.js));
  40. 40. $plugin = new AssetCollection(array( new AssetReference($am, jquery), new FileAsset(/path/to/jquery.plugin.js),));
  41. 41. jQuery will only be included once $core = new AssetCollection(array( $jquery, $plugin1, $plugin2, )); header(text/javascript); echo $core->dump();
  42. 42. Filter Manager
  43. 43. $yui = new YuiCompressorJs();$yui->setNomunge(true);$fm = new FilterManager();$fm->set(yui_js, $yui);
  44. 44. jQuery will only be compressed once $jquery = new FileAsset(/path/to/core.js); $jquery->ensureFilter($fm->get(yui_js)); $core = new AssetCollection(array( $jquery, new GlobAsset(/path/to/js/core/*.js), )); $core->ensureFilter($fm->get(yui_js));
  45. 45. Asset Factory
  46. 46. # /path/to/asset_factory.php$fm = new FilterManager();$fm->set(coffee, new CoffeeScriptFilter());$fm->set(closure, new GoogleClosureCompilerApi());$factory = new AssetFactory(/path/to/web);$factory->setAssetManager($am);$factory->setFilterManager($fm);
  47. 47. include /path/to/asset_factory.php;$asset = $factory->createAsset( array(js/src/*.coffee), array(coffee, closure));header(Content-Type: text/javascript);echo $asset->dump();
  48. 48. Debug Mode
  49. 49. Debugging compressed Javascript sucks
  50. 50. Mark filters for omission in debug mode
  51. 51. // new AssetFactory(/path/to/web, true);include /path/to/asset_factory.php;$asset = $factory->createAsset( array(js/src/*.coffee), array(coffee, closure));header(Content-Type: text/javascript);echo $asset->dump();
  52. 52. // new AssetFactory(/path/to/web, true);include /path/to/asset_factory.php;$asset = $factory->createAsset( array(js/src/*.coffee), array(coffee, ?closure));header(Content-Type: text/javascript);echo $asset->dump();
  53. 53. // new AssetFactory(/path/to/web, false);include /path/to/asset_factory.php;$asset = $factory->createAsset( array(js/src/*.coffee), array(coffee, ?closure), array(debug => true));header(Content-Type: text/javascript);echo $asset->dump();
  54. 54. Factory Workers
  55. 55. Everything passes through the workers’ hands
  56. 56. $worker = new EnsureFilterWorker( /.css$/, // the output pattern $fm->get(yui_css), // the filter false // the debug mode);$factory = new AssetFactory(/path/to/web);$factory->addWorker($worker);// compressed$factory->createAsset(css/sass/*, sass, array( output => css,));
  57. 57. $worker = new EnsureFilterWorker( /.css$/, // the output pattern $fm->get(yui_css), // the filter false // the debug mode);$factory = new AssetFactory(/path/to/web);$factory->addWorker($worker);// uncompressed$factory->createAsset(css/sass/*, sass, array( output => css, debug => true,));
  58. 58. Not Lazy Enough?
  59. 59. Asset Formulae and the Lazy Asset Manager
  60. 60. $asset = $factory->createAsset( array(js/src/*.coffee), array(coffee, ?closure), array(output => js));
  61. 61. $formula = array( array(js/src/*.coffee), array(coffee, ?closure), array(output => js));
  62. 62. $am = new LazyAssetManager($factory);$am->setFormula(core_js, $formula);header(Content-Type: text/javascript);echo $am->get(core_js)->dump();
  63. 63. Good: Basic Caching
  64. 64. # /path/to/web/css/styles.php$styles = new AssetCollection( array(new FileAsset(/path/to/main.sass)), array(new SassFilter()));echo $styles->dump();
  65. 65. # /path/to/web/css/styles.php$styles = new AssetCache(new AssetCollection( array(new FileAsset(/path/to/main.sass)), array(new SassFilter())), new FilesystemCache(/path/to/cache)); Run the filters once and cache the contentecho $styles->dump();
  66. 66. Better: HTTP Caching
  67. 67. # /path/to/web/js/core.php$mtime = gmdate($core->getLastModified());if ($mtime == $_SERVER[HTTP_IF_MODIFIED_SINCE]) { header(HTTP/1.0 304 Not Modified); exit();}header(Content-Type: text/javascript);header(Last-Modified: .$mtime);echo $core->dump();
  68. 68. Best: Static Assets
  69. 69. # /path/to/scripts/dump_assets.php$am = new AssetManager();$am->set(foo, $foo);// etc...$writer = new AssetWriter(/path/to/web);$writer->writeManagerAssets($am);
  70. 70. # /path/to/scripts/dump_assets.php$am = new AssetManager();$am->set(foo, $foo);// etc...$writer = new AssetWriter(/path/to/web);foreach (array_slice($argv, 1) as $name) { $writer->writeAsset($am->get($name));}
  71. 71. Best-est:Content Distribution Network
  72. 72. new AssetWriter(s3://my-bucket) A CloudFront S3 bucket
  73. 73. Twig Integration
  74. 74. $twig->addExtension(new AsseticExtension($factory));
  75. 75. {% assetic js/*.coffee, filter=coffee %}<script src="{{ asset_url }}"></script>{% endassetic %}
  76. 76. <script src="assets/92429d8"></script>
  77. 77. {% assetic js/*.coffee, filter=coffee %}<script src="{{ asset_url }}"></script>{% endassetic %}
  78. 78. {% assetic js/*.coffee, filter=coffee, output=js %}<script src="{{ asset_url }}"></script>{% endassetic %}
  79. 79. <script src="js/92429d8.js"></script>
  80. 80. {% assetic js/*.coffee, filter=coffee, output=js %}<script src="{{ asset_url }}"></script>{% endassetic %}
  81. 81. {% assetic js/*.coffee, filter=coffee,?closure, output=js, name=core_js %}<script src="{{ asset_url }}"></script>{% endassetic %}
  82. 82. Formula LoaderUses the Twig parser to extract asset formulae from templates
  83. 83. $loader = new FormulaLoader($twig);// loop through your templates$formulae = array();foreach ($templates as $template) { $formulae += $loader->load($template);}$am = new LazyAssetManager($factory);$am->addFormulae($formulae);
  84. 84. if (!file_exists($cache = /path/to/formulae.php)) { $loader = new FormulaLoader($twig); // loop through your templates $formulae = array(); foreach ($templates as $template) { $formulae += $loader->load($template); } file_put_contents($cache, <?php return .var_export($formulae, true));}$am = new LazyAssetManager($factory);$am->addFormulae(require $cache);
  85. 85. AsseticBundle
  86. 86. {% assetic filter=scss,?yui_css, output=css/all.css, @MainBundle/Resources/sass/main.scss, @AnotherBundle/Resources/sass/more.scss %}<link href="{{ asset_url }}" rel="stylesheet" />{% endassetic %}
  87. 87. <link href="css/all.css" rel="stylesheet" />
  88. 88. {% assetic filter=scss,?yui_css, output=css/all.css, @MainBundle/Resources/sass/main.scss, @AnotherBundle/Resources/sass/more.scss %}<link href="{{ asset_url }}" rel="stylesheet" />{% endassetic %}
  89. 89. {% assetic filter=scss,?yui_css, output=css/all.css, @MainBundle/Resources/sass/main.scss, debug=true, @AnotherBundle/Resources/sass/more.scss %}<link href="{{ asset_url }}" rel="stylesheet" />{% endassetic %}
  90. 90. Each "leaf" asset is referenced individually<link href="css/all_part1.css" rel="stylesheet" /><link href="css/all_part2.css" rel="stylesheet" />
  91. 91. Configuration
  92. 92. assetic.config: debug: %kernel.debug% use_controller: %kernel.debug% document_root: %kernel.root_dir%/../web
  93. 93. {# when use_controller=true #}<script src="{{ path(route, { name: core_js }) }}"...
  94. 94. # routing_dev.yml_assetic: resource: . type: assetic
  95. 95. {# when use_controller=false #}<script src="{{ asset(js/core.js) }}"></script> Lots for free
  96. 96. The Symfony2 Assets Helper• Multiple asset domains• Cache buster
  97. 97. app.config: templating: assets_version: 1.2.3 assets_base_urls: - http://assets1.domain.com - http://assets2.domain.com - http://assets3.domain.com - http://assets4.domain.com
  98. 98. {% assetic filter=scss,?yui_css, output=css/all.css, @MainBundle/Resources/sass/main.scss, @AnotherBundle/Resources/sass/more.scss %}<link href="{{ asset_url }}" rel="stylesheet" />{% endassetic %}
  99. 99. <link href="http://assets3.domain.com/css/all.css?1.2.3" ...
  100. 100. assetic:dump
  101. 101. $ php app/console assetic:dump web/
  102. 102. $ php app/console assetic:dump s3://my-bucket Register a stream wrapper in boot()
  103. 103. PHP templates Coming soon…
  104. 104. <?php foreach ($view[assetic]->urls( array(@MainBundle/Resources/sass/main.scss, @AnotherBundle/Resources/sass/more.scss), array(scss, ?yui_css), array(output => css/all.css)) as $url): ?> <link href="<?php echo $url ?>" rel="stylesheet" /><?php endforeach; ?>
  105. 105. Fork me!http://github.com/kriswallsmith/symfony-sandbox
  106. 106. What’s Next?• Finish Symfony2 helpers for PHP templates• Filter configuration• Image sprites, embedded image data• --watch commands• Client-aware optimizations?• Better CDN integration
  107. 107. Assetic is a killer feature of Symfony2…
  108. 108. …but is only one month old, so be nice :)
  109. 109. Questions?
  110. 110. Assetichttp://github.com/kriswallsmith/assetic
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×