Introducing Assetic (NYPHP)

7,329 views
7,270 views

Published on

Published in: Technology

Introducing Assetic (NYPHP)

  1. 1. Introducing Assetic Asset Management for PHP 5.3 March 1, 2011
  2. 2. @kriswallsmith• Symfony Guru at• Symfony core team member• Doctrine contributor• 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. 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.
  5. 5. ShopOpenSky.com• PHP 5.3 + Symfony2• MongoDB + Doctrine MongoDB ODM• MySQL + Doctrine2 ORM• Less CSS• jQuery
  6. 6. Agenda• !(straw man)• Assetic• Twig, Symfony2 integration
  7. 7. If you haven’t optimized yourfrontend, you haven’t optimized
  8. 8. A poorly optimized frontend can destroy UX
  9. 9. …and SEOhttp://googlewebmastercentral.blogspot.com/2010/04/using-site-speed-in-web-search-ranking.html
  10. 10. Get your assets in line.
  11. 11. Asset Management
  12. 12. Lots of awesome tools:
  13. 13. Lots of awesome tools:• CoffeeScript • Packer• Compass Framework • SASS• CSSEmbed • Sprockets• Google Closure Compiler • Stylus• JSMin • YUI Compressor• LESS
  14. 14. The ones written in PHP…
  15. 15. The ones written in PHP…
  16. 16. This is a difficult problem
  17. 17. Assetic makes it easy
  18. 18. Enough talk
  19. 19. # /path/to/web/js/core.php$core = new FileAsset(/path/to/jquery.js);$core->load();header(Content-Type: text/javascript);echo $core->dump();
  20. 20. # /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();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();header(Content-Type: text/javascript);echo $core->dump();
  23. 23. # /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(); Compress the merged asset == less data over the wireheader(Content-Type: text/javascript);echo $core->dump();
  24. 24. <script src="js/core.php"></script>
  25. 25. Assetic isAssets & Filters
  26. 26. Inspired by Python’s webassets https://github.com/miracle2k/webassets
  27. 27. Assets have lazy, mutable content
  28. 28. Filters act on asset contents during “load” and “dump”
  29. 29. Assets can be gathered in collections
  30. 30. A collection is an asset
  31. 31. Asset
  32. 32. FilterAsset
  33. 33. FilterFilterAsset
  34. 34. Load Filter Filter Asset
  35. 35. FilterFilterAsset Dump
  36. 36. FilterFilterAsset
  37. 37. Asset CollectionFilter FilterFilter FilterAsset Asset
  38. 38. Asset Collection Filter Filter Asset Collection AssetFilter FilterFilter FilterAsset Asset Filter Filter Asset
  39. 39. # /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();
  40. 40. # /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();
  41. 41. # /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),));header(Content-Type: text/css);echo $styles->dump();
  42. 42. # /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();
  43. 43. Basic Asset Classes• AssetCollection• AssetReference• FileAsset• GlobAsset• StringAsset
  44. 44. Core Filter Classes• CallablesFilter • SassScssFilter• CoffeeScriptFilter • SprocketsFilter• CssRewriteFilter • StylusFilter• GoogleClosureCompilerApiFilter • YuiCssCompressorFilter• GoogleClosureCompilerJarFilter • YuiJsCompressorFilter• LessFilter • More to come…• SassSassFilter
  45. 45. Asset Manager
  46. 46. $am = new AssetManager();$am->set(jquery, new FileAsset(/path/to/jquery.js));
  47. 47. $plugin = new AssetCollection(array( new AssetReference($am, jquery), new FileAsset(/path/to/jquery.plugin.js),));
  48. 48. $core = new AssetCollection(array( $jquery, $plugin1, $plugin2,));header(text/javascript);echo $core->dump();
  49. 49. jQuery will only be included once $core = new AssetCollection(array( $jquery, $plugin1, $plugin2, )); header(text/javascript); echo $core->dump();
  50. 50. Filter Manager
  51. 51. $yui = new YuiCompressorJs();$yui->setNomunge(true);$fm = new FilterManager();$fm->set(yui_js, $yui);
  52. 52. $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));
  53. 53. 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));
  54. 54. Asset Factory
  55. 55. # /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->setFilterManager($fm);
  56. 56. include /path/to/asset_factory.php;$asset = $factory->createAsset( array(js/src/*.coffee), array(coffee, closure));header(Content-Type: text/javascript);echo $asset->dump();
  57. 57. Debug Mode
  58. 58. Debugging compressed Javascript sucks
  59. 59. Mark filters for omissionin debug mode using a “?”
  60. 60. // 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();
  61. 61. // 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();
  62. 62. // 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();
  63. 63. Factory Workers
  64. 64. Everything passes through the workers’ hands
  65. 65. $worker = new EnsureFilterWorker( /.css$/, // the output pattern $fm->get(yui_css) // the filter);$factory = new AssetFactory(/path/to/web);$factory->addWorker($worker);// compressed$factory->createAsset(css/sass/*, sass, array( output => css/*.css,));
  66. 66. Good: Basic Caching
  67. 67. # /path/to/web/css/styles.php$styles = new AssetCollection( array(new FileAsset(/path/to/main.sass)), array(new SassFilter()));echo $styles->dump();
  68. 68. # /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));echo $styles->dump();
  69. 69. # /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();
  70. 70. Better: HTTP Caching
  71. 71. // $core = new AssetCache(...$mtime = gmdate(D, d M y H:i:s, $core->getLastModified()). GMT;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();
  72. 72. Best: Static Assets
  73. 73. # /path/to/scripts/dump_assets.php$am = new AssetManager();$am->set(foo, $foo);// etc...$writer = new AssetWriter(/path/to/web);$writer->writeManagerAssets($am);
  74. 74. Best-est:Content Distribution Network
  75. 75. new AssetWriter(s3://my-bucket)
  76. 76. new AssetWriter(s3://my-bucket) A CloudFront S3 bucket
  77. 77. Not Lazy Enough?
  78. 78. Asset Formulae and the Lazy Asset Manager
  79. 79. $asset = $factory->createAsset( array(js/src/*.coffee), array(coffee, ?closure), array(output => js/*.js));
  80. 80. $formula = array( array(js/src/*.coffee), array(coffee, ?closure), array(output => js/*.js));
  81. 81. $am = new LazyAssetManager($factory);$am->setFormula(core_js, $formula);header(Content-Type: text/javascript);echo $am->get(core_js)->dump();
  82. 82. This will make more sense in a few minutes…
  83. 83. Thought…Assets are a part of the view layer and should be defined there.
  84. 84. <!-- header.php --><?php foreach (assetic_javascripts( array(js/core.js, js/more.js), array(?yui_js)) as $url): ?><script src="<?php echo $url ?>"></script><?php endforeach; ?>
  85. 85. # config/assetic.phprequire_once /path/to/assetic/functions.php;assetic_init($factory);
  86. 86. Issue…Assets defined in the view layermust actually exist somewhere
  87. 87. Option Bad...Lazily dump assets to the web directory
  88. 88. Option Good…Eagerly dump assets to the web directory
  89. 89. Formula Loaders
  90. 90. $loader = new FunctionCallsFormulaLoader();$resource = new DirectoryResource( /path/to/templates, /.php$/);$formulae = $loader->load($resource);
  91. 91. $am = new LazyAssetManager($factory);$am->setLoader(php, $loader);$am->addResource($resource, php);$writer = new AssetWriter(/path/to/web);$writer->writeManagerAssets($am);
  92. 92. $am = new LazyAssetManager($factory);$am->setLoader(php, $loader);$am->addResource($resource, php); Expensive every time$writer = new AssetWriter(/path/to/web);$writer->writeManagerAssets($am);
  93. 93. $cache = new ConfigCache(/path/to/cache);$loader = new CachedFormulaLoader( $loader, $cache, $debug);
  94. 94. Twig Integration
  95. 95. $twig->addExtension(new AsseticExtension($factory));
  96. 96. {% assetic js/*.coffee, filter=coffee %}<script src="{{ asset_url }}"></script>{% endassetic %}
  97. 97. <script src="assets/92429d8"></script>
  98. 98. {% assetic js/*.coffee, filter=coffee %}<script src="{{ asset_url }}"></script>{% endassetic %}
  99. 99. {% assetic js/*.coffee, filter=coffee, output=js/*.js %}<script src="{{ asset_url }}"></script>{% endassetic %}
  100. 100. <script src="js/92429d8.js"></script>
  101. 101. {% assetic js/*.coffee, filter=coffee, output=js/*.js %}<script src="{{ asset_url }}"></script>{% endassetic %}
  102. 102. {% assetic js/*.coffee, filter=coffee,?closure, output=js/*.js, name=core_js %}<script src="{{ asset_url }}"></script>{% endassetic %}
  103. 103. AsseticBundle Symfony2 integration
  104. 104. {% 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 %}
  105. 105. <link href="css/all.css" rel="stylesheet" />
  106. 106. {% 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 %}
  107. 107. {% 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 %}
  108. 108. <link href="css/all_part1.css" rel="stylesheet" /><link href="css/all_part2.css" rel="stylesheet" />
  109. 109. Each "leaf" asset is referenced individually<link href="css/all_part1.css" rel="stylesheet" /><link href="css/all_part2.css" rel="stylesheet" />
  110. 110. Configuration
  111. 111. assetic: debug: %kernel.debug% use_controller: %kernel.debug% read_from: %kernel.root_dir%/../web write_to: s3://mybucket
  112. 112. {# when use_controller=true #}<script src="{{ path(assetic_foo) }}"...
  113. 113. # routing_dev.yml_assetic: resource: . type: assetic
  114. 114. {# when use_controller=false #}<script src="{{ asset(js/core.js) }}"></script>
  115. 115. {# when use_controller=false #}<script src="{{ asset(js/core.js) }}"></script> Lots for free
  116. 116. The Symfony2 Assets Helper• Multiple asset domains• Cache buster
  117. 117. framework: 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
  118. 118. {% 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 %}
  119. 119. <link href="http://assets3.domain.com/css/all.css?1.2.3" ...
  120. 120. assetic:dump
  121. 121. $ php app/console assetic:dump web/
  122. 122. $ php app/console assetic:dump s3://my-bucket
  123. 123. assetic:dump --watch Dump static assets in the background as you develop
  124. 124. Questions?http://github.com/kriswallsmith/assetic

×