A Modular Asset Toolkit for PHP Applications
HTML PHP JavaScript CoffeeScript Other

README.md

AssetKit

AssetKit is designed for PHP's performance, all configuration files are compiled into PHP source code, this makes AssetKit loads these asset configuration files very quickly.

AssetKit is a powerful asset manager, provides a simple command-line interface and a simple PHP library with simple API, there are many built-in filters and compressors in it.

Build Status Coverage Status

Concept

  • To improvement the asset loading performance, we register these wanted asset manifest files into an assetkit configuration file, which contains the asset source directory and other manifest file information. the config file is converted into PHP source.

  • When one asset is required from a web page, the asset can be quickly loaded through the AssetLoader, then the asset will be filtered through the filters and compiled/squashed to the front-end output. If the environment is production, the AssetRenderer will cache the url manifest for you, so you don't have to compile these assets everytime.

  • In production mode, the asset compiler squashes the loaded asset files to the minified files.

  • In development mode, the asset compiler simply render the include paths.

  • You may define different required assets in each different page with a page id (target).

    The page id (target) is also used for caching results.

    So that in your product page, you may include jquery, product assets together with a page id "yourapp-products". And in your main page, you may include jquery, mainpage assets with a page id "youapp-mainpage"

  • One asset can have multiple file collections, the file collection can be css, scss, sass, coffee-script, live-script or javascript collection.

  • Each file collection may have its own filters and compressors. so that a CSS file collection can use "cssmin" and "yuicss" compressor, and a SASS file collection can use "sass" filter and "cssmin" compressor to generate the minified output.

  • A "target" is consist of many assets, which is something like "page", we usually includes many assets in one single page, so called "target".

Why do we separately loading the different assets and define the asset manifest ? Because in the modern web application, most compononents are modularized, so in one application, there are many different plugins, modules, libraries, some plugins might provide its own assets, but some don't. some assets need to be compiled with some specific filters, but some don't. some assets need to be compressed through compressors like 'CSSMin' or 'JSMin', but some don't.

When developing front-end files, we usaually need to re-compile these asset files again and again, and at the end, we still need to re-compile them into one single squashed file to improve the front-end performance. And to re-compile these files, some people use Makefile, some people use Grunt.js, but it's still hard to configure, manage and distribute.

To give PHP applications a better flexibility, we designed a better archtecture to organize these asset files. that is, AssetKit.

Features

  • Centralized asset configuration.
  • Automatically fetch & update your asset files.
  • AssetCompiler: Compile multiple assets into one squashed file.
  • AssetRender: Render compiled assets to HTML fragments, stylesheet tag or script tag.
  • Command-line tool for installing, register, precompile assets.
  • CSSMin compressor, YUI compressor, JSMin compressor, CoffeeScript, SASS, SCSS filters.
  • APC cache support, which caches the compiled manifest, so you don't need to recompile them everytime.
  • Twig Extension support. (see below)

Requirement

  • yaml extension.

Installation

Install the requirements:

$ gem install compass sass   # for sass/compass filter
$ npm install coffee-script  # for coffee-script filter

Install library from composer:

{
    "require": {
        "corneltek/assetkit": "~3"
    }
}

Get the command-line assetkit:

$ curl -O https://raw.github.com/c9s/AssetKit/master/assetkit
$ chmod +x assetkit
$ sudo mv assetkit /usr/bin

Demo

git clone https://github.com/c9s/AssetKit
cd demo
php -S localhost:3000
// open your browser and go to http://localhost:3000 to see the sample app in production mode.

// http://localhost:3000/index.php?force=1 to force the asset compilation

The Asset Manifest File

To define file collections, you need to create a manifest.yml file in your asset directory, for example, the backbonejs manifest.yml file:

---
resource:
  url: http://backbonejs.org/backbone.js
collections:
  - js:
    - backbone.js
  - css:
    - app.css
  - sass:
    - home.sass

You can also define the resource, assetkit would fetch it for you. currently assetkit supports svn, git, hg resource types.

Basic Usage

Once you got assetkit, you can initialize it with your public path (web root):

$ assetkit init --baseDir "public/assets" --baseUrl "/assets" assetkit.yml

The config is stored at assetkit.yml file.

Then fetch anything you want:

$ assetkit add assets/jquery
Submodule 'src/sizzle' () registered for path 'src/sizzle'
Submodule 'test/qunit' () registered for path 'test/qunit'
Submodule 'src/sizzle' () registered for path 'src/sizzle'
Submodule 'test/qunit' () registered for path 'test/qunit'
Checking jQuery against JSHint...
JSHint check passed.
jQuery Size - compared to last make
  252787      (-) jquery.js
   94771      (-) jquery.min.js
   33635      (-) jquery.min.js.gz
jQuery build complete.
Saving config...
Done

And your assetkit.yml file will be updated, these asset files will be installed into public/assets.

NOTE: To install asset files with symbol link, use --link option, Which is convenient for asset development.

If someone wants to clone your project, you can add assetkit.yml file to the repository, then B can do update command to update assets:

$ assetkit update

To use assetkit in your application, just few lines to write:

// load the autoload.php from composer
require 'vendor/autoload.php';

// load your asset config file, this contains asset manifest and types
$config = new AssetKit\AssetConfig( '../assetkit.yml', array( 
    'root' => APP_ROOT // the absolute path where you run "assetkit" command.
));

// initialize an asset loader
$loader = new AssetKit\AssetLoader( $config );

// load the required assets (of your page, application or controller)
$assets = $loader->loadAssets(array( 'jquery', 'jquery-ui' ));

// Use AssetRender to compile and render the HTML tag
$render = new AssetKit\AssetRender($config, $loader);

$targetName = 'demo-page';
$render->renderAssets($assets, $targetName); // pipe html tags to output buffer, the targetName is optional.

Now just load the script from your browser, it should work.

You may simply check example script in the example folder.

Registering Assets

Assets can be registered offline, this is to reduce the online overhead. Once you have an AssetLoader object, you can call the register method to register an asset from the asset manifest file path:

$loader = new AssetKit\AssetLoader($config);
$asset = $loader->register("tests/assets/jquery-ui");

The registered assets will be added to the asset entry storage and will be cached.

Loading Assets

When you want to render an asset, or compile a collection of asset, you need to load the asset from the entry storage:

$asset = $loader->load("jquery-ui");

Sometimes you just want to load a part of the asset, you can append a filetype string to filter out the collections you want:

$asset = $loader->load("jquery-ui:stylesheet"); // Asset with stylesheet collections only
$asset = $loader->load("jquery-ui:javascript"); // Asset with javascript collections only

To find a specific collection in the asset, you can predefine an ID to the collection, and use the ID to look up the collection in the runtime:

$asset = $loader->load("jquery-ui#darkness"); // Asset with darkness theme stylesheet collections only

Advanced Usage

This creates and initializes the assetkit.yml file:

$ assetkit init --baseUrl=/assets --baseDir=public/assets --dir=private/assets assetkit.yml

Where the --baseDir option is the assets will be installed to.

Where the --baseUrl option is the assets can be loaded from front-end browser.

Where the --dir option is the location that you store your private asset files.

assetkit.yml is your config file, it's in YAML format, you can also modify it directly.

Register the assets you need:

$ assetkit add app/assets/jquery
$ assetkit add plugins/foo/assets/jquery-ui
$ assetkit add plugins/bar/assets/bootstrap

Then install asset resources into the --baseDir that you've setup:

$ assetkit install

There are two modes for installation, link and copy, to simply copy assets files into the baseDir, we use default asset installer.

To symbol link assets to the baseDir, you may pass the --link flag.

Then integrate the AssetKit API into your PHP web application, there are just few lines to write (you may check the public/index.php sample):

use AssetKit\AssetConfig;
// Please install php-fileutil extension for beter performance, 

// To use AssetCompiler, AssetLoader or AssetRender, we need to initialize AssetConfig object.
$config = new AssetKit\AssetConfig( 'config/assetkit.yml',array(
    // the application root, contains the assetkit.yml file.
    'root' => APPLICATION_ROOT,
    'cache' => new UniversalCache\ApcCache(array( 'namespace' => 'myapp_' ));
));

$loader = new AssetKit\AssetLoader( $config );

$compiler = new AssetKit\AssetCompiler($config, $loader);
$compiler->enableFstatCheck();
$compiler->defaultJsCompressor = 'uglifyjs';
$compiler->defaultCssCompressor = 'cssmin';

$assets = $loader->loadAssets(array( 'jquery', 'jquery-ui') );
$render = new AssetKit\AssetRender($config,$loader, $compiler);
$render->renderAssets($assets,'page-id');

The rendered result:

<script type="text/javascript"  src="assets/demo/d95da0fbdccc220ccb5e4949a41ec796.min.js" ></script>
<link rel="stylesheet" type="text/css"  href="assets/demo/3fffd7e7bf5d2a459cad396bd3c375b4.min.css"/>

To update asset resource from remote (eg: git, github, hg or svn) if needed.

$ assetkit update

Once you've done, you can precompile the asset files to a squashed javascript/stylesheet files:

$ assetkit compile --target demo-page jquery jquery-ui
Notice: You may enable apc.enable_cli option to precompile production files from command-line.
Compiling assets to target 'demo-page'...
Stylesheet:
  MD5:  9399a997d354919cba9f84517eb7604a
  URL:  assets/demo-page/9399a997d354919cba9f84517eb7604a.min.css
  File: /Users/c9s/git/Work/AssetKit/public/assets/demo-page/9399a997d354919cba9f84517eb7604a.min.css
Javascript:
  MD5:   4a09100517e2d98c3f462376fd69d887
  URL:   assets/demo-page/4a09100517e2d98c3f462376fd69d887.min.js
  File:  /Users/c9s/git/Work/AssetKit/public/assets/demo-page/4a09100517e2d98c3f462376fd69d887.min.js
Done

You can also do:

$ assetkit compile --target main --html-output head.php jquery

So that in your application, you can simple drop a line:

<?php require "head.php"; ?>

You can also use the Twig Extension in your template:

<html>
    <head>
    {% assets "jquery", "jquery-ui" as "my-home-page" %}
    </head>
</html>

To check all compiled target, you may simply run:

$ assetkit target

To add assets to a target, you can run:

$ assetkit target add demo-page jquery jquery-ui bootstrap

To remove a target, you can run:

$ assetkit target remove demo-page

Setting Up Your Preferred Default Compressor

Note that we've built-in uglifyjs compressor.

$compiler->defaultJsCompressor = 'uglifyjs';
$compiler->defaultCssCompressor = 'cssmin';
$compiler->registerCompressor('uglifyjs', function() {
    return YourCompressor;
});

To use YUI Compressor

YUI_COMPRESSOR_BIN=utils/yuicompressor-2.4.7/build/yuicompressor-2.4.7.jar \
    assetkit add assets/test/manifest.yml

Filters

CoffeeScriptFilter

$filter = new AssetKit\Filter\CoffeeScriptFilter;

SassFilter

ScssFilter

CssImportFilter

CssRewriteFilter

API

AssetConfig API

$config = new AssetKit\AssetConfig('assetkit.yml',array(  
    'cache' => true,
    'cache_id' => 'your_app_id',
    'cache_expiry' => 3600
));
$config->setBaseUrl('/assets');
$config->setBaseDir('tests/assets');
$config->setEnvironment('production');

$baseDir = $config->getBaseDir(true); // absolute path
$baseUrl = $config->getBaseUrl();
$root = $config->getRoot();
$compiledDir = $config->getCompiledDir();
$compiledUrl = $config->getCompiledUrl();

$config->addAssetDirectory('vendor/assets');

$assetStashes = $config->all();

$config->save();

AssetLoader API

$loader = new AssetKit\AssetLoader($config);

// load asset from a directory that might contains a manifest file,
// Note: Since you're going to put the assetkit.yml file 
//       In your VCS, you should use relative path instead of 
//       absolute path.
$asset = $loader->register("tests/assets/jquery");

// load asset from a manifest file directly, 
$asset = $loader->loadManifestFile("tests/assets/jquery/manifest.yml");

// load multiple asset at one time
$assets = $loader->loadAssets(array('jquery','jquery-ui'));

$jquery = $loader->load('jquery');
$jqueryui = $loader->load('jquery-ui');

// get all loaded asset objects
$assets = $loader->all();

// get all loaded asset objects by pairs.
//   array( 'name' => [asset object], ... )
$assetMap = $loader->pairs();

// check if we've loaded the asset by asset name
if( $loader->has('jquery') ) {
    // do something here.
}

$updater = new ResourceUpdater;
$updater->update($asset);

AssetInstaller API

$installer = new AssetKit\Installer;
$installer->install( $asset );
$installer->uninstall( $asset );
$installer = new AssetKit\LinkInstaller;
$installer->install( $asset );
$installer->uninstall( $asset );

AssetCompiler API

$asset = $loader->registerAssetFromPath("tests/assets/jquery-ui");
$compiler = new AssetKit\AssetCompiler($config,$loader);
$files = $compiler->compile($asset);

echo $files['js_url'];  //  outputs /assets/compiled/jquery-ui.min.js
echo $files['css_url']; //  outputs /assets/compiled/jquery-ui.min.css

When in production mode, the compiled manifest is cached in APC, to make AssetCompiler recompile your assets, you need to restart your HTTP server to clean up these cache.

We don't scan file modification time by default, because too many IO operations might slow down your application.

To auto-recompile these assets when you modified them, you can setup an option to make your PHP application scan the modification time of asset files to recompile assets:

$render = new AssetKit\AssetRender($config,$loader);
$compiler = $render->getCompiler();
$compiler->enableFstatCheck();

To enable builtin filters, compressors:

$compiler->registerDefaultCompressors();
$compiler->registerDefaultFilters();

To register filters, compressors:

$compiler->registerCompressor('jsmin', '\AssetKit\Compressor\JsMinCompressor');
$compiler->registerCompressor('cssmin', '\AssetKit\Compressor\CssMinCompressor');
$compiler->registerFilter( 'coffeescript','\AssetKit\Filter\CoffeeScriptFilter');
$compiler->registerFilter( 'css_import', '\AssetKit\Filter\CssImportFilter');
$compiler->registerFilter( 'sass', '\AssetKit\Filter\SassFilter');
$compiler->registerFilter( 'scss', '\AssetKit\Filter\ScssFilter');

AssetRender API

This is the top level API to compile/render asset HTML tags, which operates AssetCompiler to compile loaded assets.

$render = new AssetKit\AssetRender($config,$loader);
$render->renderAssets($assets,'demo');

Asset Twig Extension

Include stylesheets and javascripts in front-end page

Include specified asset:

{% assets 'jquery' %}

Include multiple assets:

{% assets "jquery", "jquery-ui" %}

Include multiple assets to the target:

{% assets "jquery", "jquery-ui" as "jquery-all" %}

Hacking

Install deps:

$ git clone git://github.com/c9s/AssetKit.git
$ cd AssetKit
$ composer install

Install some extensions to boost the speed:

phpbrew ext install github:sqmk/pecl-jsmin
phpbrew ext install github:c9s/cssmin

Make sure all staff work:

phpunit

... Hack Hack Hack ...

Run tests again:

$ phpunit

Make sure command runs fine:

$ php bin/assetkit init --base-dir .... --base-url ....
$ php bin/assetkit compile --target mainpage jquery jquery-ui 

Re-Compile the command-line binary:

$ scripts/compile

Test the compiled binary, simply type:

$ ./assetkit

Todos

  • watch command (fork filter process to watch asset files or directories)

Setup XHProf

You should install the xhprof extension from http://github.com/facebook/xhprof

Then setup hostname xhprof.dev to your xhprof_html directory.

Then run:

pear install -f phpunit/PHPUnit_TestListener_XHProf
phpunit -c phpunit-xhprof.xml

The asset port manifest

The manifest.yml file:

---
resource:
  git: git@github.com:blah/blah.git
asset:
  - filters: [ "css_import" ]
    css:
    - css/*.sass
  - coffee:
    - js/*.coffee
  - js:
    - js/*
    - js/javascript.js