Assetic (Symfony Live Paris)

129
Introducing Assetic Asset Management for PHP 5.3 March 4, 2011

description

Presentation on Assetic at Symfony Live 2011 Paris.

Transcript of Assetic (Symfony Live Paris)

Page 1: Assetic (Symfony Live Paris)

Introducing AsseticAsset Management for PHP 5.3

March 4, 2011

Page 2: Assetic (Symfony Live Paris)

@kriswallsmith

• Symfony Guru at

• Symfony core team member

• Doctrine contributor

• 10+ years experience with PHP and web development

• Open source evangelist and international speaker

Page 3: Assetic (Symfony Live Paris)

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.

Page 4: Assetic (Symfony Live Paris)

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.

Page 5: Assetic (Symfony Live Paris)

ShopOpenSky.com

• PHP 5.3 + Symfony2

• MongoDB + Doctrine MongoDB ODM

• MySQL + Doctrine2 ORM

• Less CSS

• jQuery

Page 6: Assetic (Symfony Live Paris)

Symfony2 is FAST

Page 7: Assetic (Symfony Live Paris)

But you can still f*** that up

Page 8: Assetic (Symfony Live Paris)

We build tools thatencourage best practices

Page 9: Assetic (Symfony Live Paris)

Best practices like…

• Dependency injection (DI)

• Proper caching, edge side includes (ESI)

• Test-driven development (TDD)

• Don't repeat yourself (DRY)

• Keep it simple, SVP (KISS)

• Performance

Page 10: Assetic (Symfony Live Paris)

If you haven’t optimized your frontend, you haven’t optimized

Page 11: Assetic (Symfony Live Paris)

Get your assets in line.

Page 12: Assetic (Symfony Live Paris)

A poorly optimized frontendcan destroy UX

Page 13: Assetic (Symfony Live Paris)

…and SEO!

http://googlewebmastercentral.blogspot.com/2010/04/using-site-speed-in-web-search-ranking.html

Page 14: Assetic (Symfony Live Paris)

Asset Management

Page 15: Assetic (Symfony Live Paris)

Lots of awesome tools:

Page 16: Assetic (Symfony Live Paris)

Lots of awesome tools:• CoffeeScript

• Compass Framework

• CSSEmbed

• Google Closure Compiler

• JSMin

• LESS

• Packer

• SASS

• Sprockets

• Stylus

• YUI Compressor

Page 17: Assetic (Symfony Live Paris)

The ones written in PHP…

Page 18: Assetic (Symfony Live Paris)

The ones written in PHP…

Page 19: Assetic (Symfony Live Paris)

This is a difficult problem

Page 20: Assetic (Symfony Live Paris)

Assetic makes it easy

Page 21: Assetic (Symfony Live Paris)

as•cet•i•cismdescribes a lifestyle characterized by abstinence from various sorts of worldly

pleasures often with the aim of pursuing religious and spiritual goals

Page 22: Assetic (Symfony Live Paris)

No B.S.

Page 23: Assetic (Symfony Live Paris)

Enough talk

Page 24: Assetic (Symfony Live Paris)

# /path/to/web/js/core.php

$core = new FileAsset('/path/to/jquery.js');$core->load();

header('Content-Type: text/javascript');echo $core->dump();

Page 25: Assetic (Symfony Live Paris)

# /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();

Page 26: Assetic (Symfony Live Paris)

# /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();

Merge many files into one == fewer HTTP requests

Page 27: Assetic (Symfony Live Paris)

# /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();

Page 28: Assetic (Symfony Live Paris)

# /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();

Compress the merged asset == less data over the wire

Page 29: Assetic (Symfony Live Paris)

<script src="js/core.php"></script>

Page 30: Assetic (Symfony Live Paris)

Assetic isAssets & Filters

Page 31: Assetic (Symfony Live Paris)

Inspired by Python’s webassets

https://github.com/miracle2k/webassets

Page 32: Assetic (Symfony Live Paris)

Assets have lazy, mutable content

Page 33: Assetic (Symfony Live Paris)

A filter acts on an asset’s contents during “load” and “dump”

Page 34: Assetic (Symfony Live Paris)

Assets can be gathered in collections

Page 35: Assetic (Symfony Live Paris)

A collection is an asset

Page 36: Assetic (Symfony Live Paris)
Page 37: Assetic (Symfony Live Paris)

Asset

Page 38: Assetic (Symfony Live Paris)

Filter

Asset

Page 39: Assetic (Symfony Live Paris)

FilterFilter

Asset

Page 40: Assetic (Symfony Live Paris)

FilterFilter

Asset

Load

Page 41: Assetic (Symfony Live Paris)

FilterFilter

Asset

Dum

p

Page 42: Assetic (Symfony Live Paris)

FilterFilter

Asset

Page 43: Assetic (Symfony Live Paris)

Asset Collection

FilterFilter

Asset

FilterFilter

Asset

Page 44: Assetic (Symfony Live Paris)

Asset Collection

Asset Collection

FilterFilter

Asset

FilterFilter

Asset

FilterFilter

Asset

FilterFilter

Asset

Page 45: Assetic (Symfony Live Paris)

# /path/to/web/css/styles.php

$styles = new FileAsset('/path/to/main.sass', array( new SassFilter(),));

header('Content-Type: text/css');echo $styles->dump();

Page 46: Assetic (Symfony Live Paris)

# /path/to/web/css/styles.php

$styles = new FileAsset('/path/to/main.sass', array( new SassFilter(),));

header('Content-Type: text/css');echo $styles->dump();

Load is implied

Page 47: Assetic (Symfony Live Paris)

# /path/to/web/css/styles.php

$styles = 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();

Page 48: Assetic (Symfony Live Paris)

# /path/to/web/css/styles.php

$styles = 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();

Page 49: Assetic (Symfony Live Paris)

# /path/to/web/css/styles.php

$styles = 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();

Lazy! The filesystem isn't touched until now

Page 50: Assetic (Symfony Live Paris)

Basic Asset Classes

• AssetCollection

• AssetReference

• FileAsset

• GlobAsset

• StringAsset

Page 51: Assetic (Symfony Live Paris)

Core Filter Classes• CallablesFilter

• CoffeeScriptFilter

• CssRewriteFilter

• GoogleClosure\CompilerApiFilter

• GoogleClosure\CompilerJarFilter

• LessFilter

• Sass\SassFilter

• Sass\ScssFilter

• SprocketsFilter

• StylusFilter

• Yui\CssCompressorFilter

• Yui\JsCompressorFilter

• More to come…

Page 52: Assetic (Symfony Live Paris)

Asset Manager

Page 53: Assetic (Symfony Live Paris)

$am = new AssetManager();$am->set('jquery', new FileAsset('/path/to/jquery.js'));

Page 54: Assetic (Symfony Live Paris)

$plugin = new AssetCollection(array( new AssetReference($am, 'jquery'), new FileAsset('/path/to/jquery.plugin.js'),));

Page 55: Assetic (Symfony Live Paris)

$core = new AssetCollection(array( $jquery, $plugin1, $plugin2,));

header('text/javascript');echo $core->dump();

Page 56: Assetic (Symfony Live Paris)

$core = new AssetCollection(array( $jquery, $plugin1, $plugin2,));

header('text/javascript');echo $core->dump();

jQuery will only be included once

Page 57: Assetic (Symfony Live Paris)

Filter Manager

Page 58: Assetic (Symfony Live Paris)

$yui = new YuiCompressorJs();$yui->setNomunge(true);

$fm = new FilterManager();$fm->set('yui_js', $yui);

Page 59: Assetic (Symfony Live Paris)

$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'));

Page 60: Assetic (Symfony Live Paris)

$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'));

jQuery will only be compressed once

Page 61: Assetic (Symfony Live Paris)

Asset Factory

Page 62: Assetic (Symfony Live Paris)

$fm = new FilterManager();$fm->set('coffee', new CoffeeScriptFilter());$fm->set('closure', new ClosureFilter());

$factory = new AssetFactory('/path/to/web');$factory->setFilterManager($fm);

Page 63: Assetic (Symfony Live Paris)

$asset = $factory->createAsset( array('js/src/*.coffee'), array('coffee', 'closure'));

header('Content-Type: text/javascript');echo $asset->dump();

Page 64: Assetic (Symfony Live Paris)

Debug Mode

Page 65: Assetic (Symfony Live Paris)

Debugging compressedJavascript sucks

Page 66: Assetic (Symfony Live Paris)

Mark filters for omissionin debug mode using a “?”

Page 67: Assetic (Symfony Live Paris)

// new AssetFactory('/path/to/web', $debug = true);

$asset = $factory->createAsset( array('js/src/*.coffee'), array('coffee', 'closure'));

header('Content-Type: text/javascript');echo $asset->dump();

Page 68: Assetic (Symfony Live Paris)

// new AssetFactory('/path/to/web', true);

$asset = $factory->createAsset( array('js/src/*.coffee'), array('coffee', '?closure'));

header('Content-Type: text/javascript');echo $asset->dump();

Page 69: Assetic (Symfony Live Paris)

// new AssetFactory('/path/to/web', false);

$asset = $factory->createAsset( array('js/src/*.coffee'), array('coffee', '?closure'), array('debug' => true));

header('Content-Type: text/javascript');echo $asset->dump();

Page 70: Assetic (Symfony Live Paris)

Good: Basic Caching

Page 71: Assetic (Symfony Live Paris)

# /path/to/web/css/styles.php

$styles = new AssetCollection( array(new FileAsset('/path/to/main.sass')), array(new SassFilter()));

echo $styles->dump();

Page 72: Assetic (Symfony Live Paris)

# /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();

Page 73: Assetic (Symfony Live Paris)

# /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();

Run the filters once and cache the content

Page 74: Assetic (Symfony Live Paris)

Better: HTTP Caching

Page 75: Assetic (Symfony Live Paris)

// $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();

Page 76: Assetic (Symfony Live Paris)

Best: Static Assets

Page 77: Assetic (Symfony Live Paris)

# /path/to/scripts/dump_assets.php

$am = new AssetManager();$am->set('foo', $foo);// etc...

$writer = new AssetWriter('/path/to/web');$writer->writeManagerAssets($am);

Page 78: Assetic (Symfony Live Paris)

Best-est:Content Distribution Network

Page 79: Assetic (Symfony Live Paris)

new AssetWriter('s3://my-bucket')

Page 80: Assetic (Symfony Live Paris)

new AssetWriter('s3://my-bucket')

A CloudFront S3 bucket

Page 81: Assetic (Symfony Live Paris)

Custom Stream Wrappers

$s3 = new Zend_Service_Amazon_S3($key, $secret);$s3->registerStreamWrapper();

Page 82: Assetic (Symfony Live Paris)

Not Lazy Enough?

Page 83: Assetic (Symfony Live Paris)

Asset Formulae and theLazy Asset Manager

Page 84: Assetic (Symfony Live Paris)

$asset = $factory->createAsset( array('js/src/*.coffee'), array('coffee', '?closure'), array('output' => 'js/*.js'));

Page 85: Assetic (Symfony Live Paris)

$formula = array( array('js/src/*.coffee'), array('coffee', '?closure'), array('output' => 'js/*.js'));

Page 86: Assetic (Symfony Live Paris)

$am = new LazyAssetManager($factory);$am->setFormula('core_js', $formula);

header('Content-Type: text/javascript');echo $am->get('core_js')->dump();

Page 87: Assetic (Symfony Live Paris)

A ThoughtAssets are a part of the view layer

and should be defined there.

Page 88: Assetic (Symfony Live Paris)

<!-- 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; ?>

Page 89: Assetic (Symfony Live Paris)

An IssueAssets defined in the view layer must actually exist somewhere

Page 90: Assetic (Symfony Live Paris)

Option Number BadLazily dump assets to the

web directory

Page 91: Assetic (Symfony Live Paris)

Option Number GoodEagerly dump assets to the

web directory

Page 92: Assetic (Symfony Live Paris)

A template is a configuration file

Page 93: Assetic (Symfony Live Paris)

Formula Loadersextract asset formulae from templates

Page 94: Assetic (Symfony Live Paris)

$loader = new FunctionCallsFormulaLoader();$resource = new DirectoryResource( '/path/to/templates', '/\.php$/');

$formulae = $loader->load($resource);

Page 95: Assetic (Symfony Live Paris)

$am = new LazyAssetManager($factory);$am->setLoader('php', $loader);$am->addResource($resource, 'php');

$writer = new AssetWriter('/path/to/web');$writer->writeManagerAssets($am);

Page 96: Assetic (Symfony Live Paris)

$am = new LazyAssetManager($factory);$am->setLoader('php', $loader);$am->addResource($resource, 'php');

$writer = new AssetWriter('/path/to/web');$writer->writeManagerAssets($am);

Expensive every time

Page 97: Assetic (Symfony Live Paris)

$cache = new ConfigCache('/path/to/cache');

$loader = new CachedFormulaLoader( $loader, $cache, $debug);

Page 98: Assetic (Symfony Live Paris)

$cache = new ConfigCache('/path/to/cache');

$loader = new CachedFormulaLoader( $loader, $cache, $debug);

Whether to stat each file for changes

Page 99: Assetic (Symfony Live Paris)

Twig Integration

Page 100: Assetic (Symfony Live Paris)

$twig->addExtension(new AsseticExtension($factory));

Page 101: Assetic (Symfony Live Paris)

{% assetic 'js/*.coffee' filter='coffee' %}<script src="{{ asset_url }}"></script>{% endassetic %}

Page 102: Assetic (Symfony Live Paris)

<script src="assets/92429d8"></script>

Page 103: Assetic (Symfony Live Paris)

{% assetic 'js/*.coffee' filter='coffee' %}<script src="{{ asset_url }}"></script>{% endassetic %}

Page 104: Assetic (Symfony Live Paris)

{% assetic 'js/*.coffee' filter='coffee' output='js/*.js' %}<script src="{{ asset_url }}"></script>{% endassetic %}

Page 105: Assetic (Symfony Live Paris)

<script src="js/92429d8.js"></script>

Page 106: Assetic (Symfony Live Paris)

{% assetic 'js/*.coffee' filter='coffee' output='js/*.js' %}<script src="{{ asset_url }}"></script>{% endassetic %}

Page 107: Assetic (Symfony Live Paris)

{% javascripts 'js/*.coffee' filter='coffee,?closure' %}<script src="{{ asset_url }}"></script>{% endjavascripts %}

Page 108: Assetic (Symfony Live Paris)

{% javascripts 'js/*.coffee' filter='coffee,?closure' %}<script src="{{ asset_url }}"></script>{% endjavascripts %}

Adds a default output string

Page 109: Assetic (Symfony Live Paris)

{% javascripts 'js/*.coffee' filter='coffee,?closure' debug=true %}<script src="{{ asset_url }}"></script>{% endjavascripts %}

Page 110: Assetic (Symfony Live Paris)

<script src="js/92429d8_1.js"></script><script src="js/92429d8_2.js"></script><script src="js/92429d8_3.js"></script>

Page 111: Assetic (Symfony Live Paris)

<script src="js/92429d8_1.js"></script><script src="js/92429d8_2.js"></script><script src="js/92429d8_3.js"></script>

Each "leaf" asset is referenced individually

Page 112: Assetic (Symfony Live Paris)

AsseticBundleSymfony2 integration

Page 113: Assetic (Symfony Live Paris)

{% 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 %}

Page 114: Assetic (Symfony Live Paris)

<link href="css/all.css" rel="stylesheet" />

Page 115: Assetic (Symfony Live Paris)

Configuration

Page 116: Assetic (Symfony Live Paris)

assetic: debug: %kernel.debug% use_controller: %kernel.debug% read_from: %kernel.root_dir%/../web write_to: s3://mybucket

Page 117: Assetic (Symfony Live Paris)

{# when use_controller=true #}

<script src="{{ path('assetic_foo') }}"...

Page 118: Assetic (Symfony Live Paris)

# routing_dev.yml_assetic: resource: . type: assetic

Page 119: Assetic (Symfony Live Paris)

{# when use_controller=false #}

<script src="{{ asset('js/core.js') }}"></script>

Page 120: Assetic (Symfony Live Paris)

{# when use_controller=false #}

<script src="{{ asset('js/core.js') }}"></script>

Lots for free

Page 121: Assetic (Symfony Live Paris)

The Symfony2 Assets Helper

• Multiple asset domains

• Cache buster

Page 122: Assetic (Symfony Live Paris)

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

Page 123: Assetic (Symfony Live Paris)

{% 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 %}

Page 124: Assetic (Symfony Live Paris)

<link href="http://assets3.domain.com/css/all.css?1.2.3" ...

Page 125: Assetic (Symfony Live Paris)

assetic:dump

Page 126: Assetic (Symfony Live Paris)

$ php app/console assetic:dump web/

Page 127: Assetic (Symfony Live Paris)

$ php app/console assetic:dump s3://my-bucket

Page 128: Assetic (Symfony Live Paris)

assetic:dump --watchDump static assets in the background as you develop

Page 129: Assetic (Symfony Live Paris)

Questions?

http://github.com/kriswallsmith/assetic