Assetic (OSCON)

Post on 15-Jan-2015

2.068 views 2 download

Tags:

description

 

Transcript of Assetic (OSCON)

Introducing AsseticAsset Management for PHP 5.3

Kris Wallsmith (OpenSky)July 29, 2011

@kriswallsmith

• Symfony Guru at

• Symfony core team member

• Doctrine contributor

• Author of Assetic

• 10+ years experience with PHP and web development

• Open source evangelist and international speaker

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.

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.

opensky.com

• PHP 5.3 + Symfony2

• MongoDB + Doctrine MongoDB ODM

• MySQL + Doctrine2 ORM

• Less CSS

• jQuery

We all want to be FAST

We use good open source tools that encourage best practices

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

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

Get your assets in line.

A poorly optimized frontendcan destroy UX

…and SEO!

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

Asset Management

Lots of awesome tools:

Lots of awesome tools:• CoffeeScript

• Compass Framework

• CSSEmbed

• Google Closure Compiler

• jpegoptim

• JSMin

• LESS

• OptiPNG

• Packager

• SASS & SCSS

• Sprockets

• Stylus

• YUI Compressor

Integrating these tools cleanlyis a difficult problem

Assetic makes it easy

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

No B.S.

Enough talk

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

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

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

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

# /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

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

# /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

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

Assetic isAssets & Filters

Inspired by Python’s webassets

https://github.com/miracle2k/webassets

Assets have lazy, mutable content

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

Assets can be gathered in collections

A collection is an asset

Asset

Filter

Asset

FilterFilter

Asset

FilterFilter

Asset

Load

FilterFilter

Asset

Dum

p

FilterFilter

Asset

Asset Collection

FilterFilter

Asset

FilterFilter

Asset

Asset Collection

Asset Collection

FilterFilter

Asset

FilterFilter

Asset

FilterFilter

Asset

FilterFilter

Asset

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

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

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

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

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

# /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

Basic Asset Classes

• AssetCollection

• AssetReference

• FileAsset

• GlobAsset

• HttpAsset

• StringAsset

Core Filter Classes• CoffeeScriptFilter

• CompassFilter

• CssEmbedFilter

• CssImportFilter

• CssMinFilter

• CssRewriteFilter

• GoogleClosure\CompilerApiFilter

• GoogleClosure\CompilerJarFilter

• JpegoptimFilter

• JpegtranFilter

• LessFilter

• LessphpFilter

• OptiPngFilter

• PackagerFilter

Core Filter Classes• PngoutFilter

• Sass\SassFilter

• Sass\ScssFilter

• SprocketsFilter

• StylusFilter

• Yui\CssCompressorFilter

• Yui\JsCompressorFilter

• More to come…

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

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

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

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

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

jQuery will only be included once

Filter Manager

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

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

$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 = 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

Asset Factory

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

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

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

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

Debug Mode

Debugging compressedJavascript sucks

Mark filters for omissionin debug mode using a “?”

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

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

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

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

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

Good: Basic Caching

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

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

echo $styles->dump();

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

# /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

Better: HTTP Caching

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

Best: Static Assets

# /path/to/deploy/scripts/dump_assets.php

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

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

Best-est:Content Distribution Network

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

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

A CloudFront S3 bucket

Custom Stream Wrappers

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

Not Lazy Enough?

Asset Formulae and theLazy Asset Manager

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

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

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

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

A ThoughtAssets are a part of the view layer

and should be defined there.

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

An IssueAssets defined in the view layer must actually exist somewhere

Option Number BadLazily dump assets to the

web directory

Option Number GoodEagerly dump assets to the

web directory

A template is a configuration file

Formula Loadersextract asset formulae from templates

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

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

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

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

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

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

Expensive every time

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

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

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

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

Whether to stat each file for changes

Twig Integration

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

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

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

{% javascripts 'js/*.coffee' filter='coffee' output='js/all.js' %}<script src="{{ asset_url }}"></script>{% endjavascripts %}

<script src="js/all.js"></script>

{% javascripts 'js/*.coffee' filter='coffee' output='js/all.js' %}<script src="{{ asset_url }}"></script>{% endjavascripts %}

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

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

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

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

<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

AsseticBundleSymfony2 integration

Configuration

# config.yml

assetic: debug: %kernel.debug% use_controller: false filters: coffee: ~ yui_js: jar: /path/to/yuicompressor.jar

{# when use_controller=true (config_dev.yml) #}

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

# routing_dev.yml_assetic: resource: . type: assetic

{# when use_controller=false (config_prod.yml) #}

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

{# when use_controller=false (config_prod.yml) #}

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

Lots for free

The Symfony2 Assets Helper

• Multiple asset domains

• Cache buster

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

{% stylesheets filter='scss,?yui_css' output='css/all.css' 'css/src/main.scss' 'css/src/more.scss' %}<link href="{{ asset_url }}" rel="stylesheet">{% endstylesheets %}

<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

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

How can you help?

How can you help?

• Join the team!

How can you help?

• Join the team!

• Documentation

How can you help?

• Join the team!

• Documentation

• Assetic needs a website

Questions?http://github.com/kriswallsmith/assetic