I ran into some issues yesterday so I figured out how deal with it.
Because I'm lazy and I don't want to import manually each file, I wrote a little Kohana 3.3.1Assets helper.
What does it do?
Compatible with the current version without any change.
Default options (i.e: preprocessor) for both css and js.
Respect priority (like the current script, the load order is respected.
Load all css/js files recursively.
Avoid to import twice the same file using the recursive import. (Check if the file is not already loaded by the Assets instance.
Possible to avoid to auto import folder/file using a pattern. (Basically _ at the filename first char)
Check extension while using the recursive import, for both js and css methods.
It's not a kohana module/add-on, it's strongly linked to the way my app works but it should be easy to use in your project or add it directly to the asset-merger module.
Example of use:
echo Assets::factory(App::get_theme())
// Load files that must be required in first.
->js("jquery/jquery.min.js")
->js("jquery/jquery-ui-1.10.4.custom.min.js")
->js("plupload/plupload.js")
->js("_ie/excanvas.min.js", array('condition' => 'lte IE 8'))
->load_assets()// Load assets recursively
->load_theme_assets()// Load theme assets recursively. (my app specific)
->render();
How to use it?
Assuming asset-merger is already configured
Include classes/Asset.php (See below, dependency)
Include classes/File.php (See below, dependency)
Include classes/Assets.php (See below)
Replace the variables such as App::$assets_directory, App::$styles_directory, App::$js_directory
Potentially remove the methods load_theme_assets and load_views_styles that are really strongly linked to my app, but are good example "how to", if you use something close in your app, anyway you can chose to not use them.
Configure the allowed extensions and other options. (I don't know if .coffee should be used)
Use the ->load_assets() method should import all assets that are not already loaded. Don't forget that some lb should be still required "by hand" before, such as jquery/jquery-ui.
This is the config file I used, I use themes, (app specific) but you just have to change the config file to cache the file without use this stuff.
config/asset-merger.php
Of course, because this script is strongly linked to my app, there are some dependencies.
App.php
class App extends Kohana {
/**
* Theme in use.
* Updated by Controller_Template->_initialize_template() on each HTTP request.
* @var string
*/
public static $current_theme = 'default';
/**
* Assets directory name.
* @var string
*/
public static $assets_directory = 'assets';
/**
* Upload directory name. Directory used for upload all images.
* MUST be in assets directory.
* @var string
*/
public static $uploads_directory = 'uploads';
/**
* Theme directory name.
* @var string
*/
public static $themes_directory = 'themes';
/**
* Image directory name.
* @var string
*/
public static $img_directory = 'images';
/**
* Javascript directory name.
* @var string
*/
public static $js_directory = 'js';
/**
* Styles directory name.
* @var string
*/
public static $styles_directory = 'styles';
/**
* Less directory name.
* @var string
*/
public static $less_directory = 'less';
// More but not useful here!
}
I had to make visible the protected $_file from the Kohana_Asset class, so I overloaded it.
classes/Asset.php
<?php defined('SYSPATH') OR die('No direct script access.');
class Asset extends Kohana_Asset {
/**
* Return the filename.
*
* @return string
*/
public function file(){
return $this->_file;
}
}
?>
I also use a custom function to read all files recursively, to do so I overloaded the Kohana_File class.
classes/File.php
<?php defined('SYSPATH') OR die('No direct script access.');
class File extends Kohana_File {
/**
* Read all files in all subdirectories for a directory.
*
* @param $dir
*
* @return array
* @see http://www.php.net/manual/fr/function.scandir.php
*/
public static function scandir_recursive($dir)
{
$root = scandir($dir);
foreach($root as $value)
{
if($value === '.' || $value === '..') {continue;}
if(is_file("$dir/$value")) {$result[]="$dir/$value";continue;}
foreach(File::scandir_recursive("$dir/$value") as $value)
{
$result[]=$value;
}
}
return $result;
}
}
Finally, the big script:
classes/Assets.php
<?php defined('SYSPATH') OR die('No direct script access.');
class Assets extends Kohana_Assets {
/**
* Array of default options to use when load css files.
* @var array
*/
public static $default_css_options = array('processor' => 'cssmin');
/**
* Array of default options to use when load javascript files.
* @var array
*/
public static $default_js_options = array('processor' => 'jsmin');
/**
* All file or dir that start by this pattern won't be auto loaded.
* One character only.
* @var string
*/
public static $pattern_autoload_disable = '_';
/**
* Allowed file extensions.
* @var array
*/
public static $allowed_ext = array(
Assets::JAVASCRIPT => array('js', 'coffee'),
Assets::STYLESHEET => array('css', 'less', 'scss', 'sass'),
);
/**
* Directory that contains the style by views.
* @var string
*/
public static $views_dir = '_views';
/**
* Path to use to go from styles directory to the theme directory.
* @var string
*/
public static $path_to_themes_from_styles = '../';
/**
* *************************************************************
* ********************* Override ******************************
* *************************************************************
*/
/**
* Add stylesheet
*
* @param string $file
* @param array $options
* @return Assets
*/
public function css($file, array $options = array())
{
return parent::css($file, array_merge(Assets::$default_css_options, $options));
}
/**
* Add javascript
*
* @param string $file
* @param array $options
* @return Assets
*/
public function js($file, array $options = array())
{
return parent::js($file, array_merge(Assets::$default_js_options, $options));
}
/**
* *************************************************************
* ********************* App specific helpers ******************
* *************************************************************
*/
/**
* Load all the theme assets for the current theme used.
*
* @return Assets
*/
public function load_theme_assets(){
$style_paths = DOCROOT . App::$assets_directory . DIRECTORY_SEPARATOR . App::$themes_directory . '/' .App::get_theme() . '/' . App::$styles_directory;
$js_paths = DOCROOT . App::$assets_directory . DIRECTORY_SEPARATOR . App::$themes_directory . '/' .App::get_theme() . '/' . App::$js_directory;
if(is_dir($style_paths)){
Assets::load_styles($this, Assets::_clean_file_names(File::scandir_recursive($style_paths), DOCROOT . App::$assets_directory . DIRECTORY_SEPARATOR, Assets::STYLESHEET), Assets::$path_to_themes_from_styles);
}
if(is_dir($js_paths)){
Assets::load_javascripts($this, Assets::_clean_file_names(File::scandir_recursive($js_paths), DOCROOT . App::$assets_directory . DIRECTORY_SEPARATOR, Assets::JAVASCRIPT), Assets::$path_to_themes_from_styles);
}
return $this;
}
/**
* Load the view style with the same name than the current controller used.
*
* @param string $controller - Controller name. First letter should be lower case.
*
* @return Assets
*/
public function load_views_styles($controller){
$style_paths = DOCROOT . App::$assets_directory . DIRECTORY_SEPARATOR . App::$themes_directory . '/' .App::get_theme() . '/' . App::$styles_directory . '/' . Assets::$views_dir;
if(is_dir($style_paths)){
$files = Assets::_clean_file_names(File::scandir_recursive($style_paths), DOCROOT . App::$assets_directory . DIRECTORY_SEPARATOR, Assets::STYLESHEET, false);
// Could be null.
if($files){
foreach($files as $file){
// Try to load the file that has the controller name.
if(is_file(DOCROOT . App::$assets_directory . DIRECTORY_SEPARATOR . $file)){
// Remove the path until the view dir to keep only what's left.
$filename = explode(Assets::$views_dir . '/', $file)[1];
if(File::remove_all_ext($filename) == $controller){
// A file that
Assets::load_style($this, Assets::$path_to_themes_from_styles . $file);
}
}
}
}
}
return $this;
}
/**
* *************************************************************
* ********************* Helpers *******************************
* *************************************************************
*/
/**
* Load all the js/style assets.
* They are basically inside the assets folder, don't load assets in sub folders. (Such as themes)
*
* @return Assets
*/
public function load_assets(){
$style_paths = DOCROOT . App::$assets_directory . DIRECTORY_SEPARATOR . App::$styles_directory;
$js_paths = DOCROOT . App::$assets_directory . DIRECTORY_SEPARATOR . App::$js_directory;
if(is_dir($style_paths)){
Assets::load_styles($this, Assets::_clean_file_names(File::scandir_recursive($style_paths), $style_paths . '/', Assets::STYLESHEET));
}
if(is_dir($js_paths)){
Assets::load_javascripts($this, Assets::_clean_file_names(File::scandir_recursive($js_paths), $js_paths . '/', Assets::JAVASCRIPT));
}
return $this;
}
/**
* Load an array of style files.
*
* @param Assets $assets
* @param array $styles
* @param string $before
*
* @return Assets
*/
public static function load_styles(Assets $assets, array $styles, $before = ''){
foreach($styles as $style){
Assets::load_style($assets, $before . $style);
}
return $assets;
}
/**
* Load a style file.
*
* @param Assets $assets
* @param $file
*
* @return Assets
*/
public static function load_style(Assets $assets, $file){
if(Assets::is_unique($assets, $file, Assets::STYLESHEET)){
return $assets->css($file, Assets::$default_css_options);
}
return $assets;
}
/**
* Load an array of javascript files.
*
* @param Assets $assets
* @param array $javascripts
* @param string $before
*
* @return Assets
*/
public static function load_javascripts(Assets $assets, array $javascripts, $before = ''){
foreach($javascripts as $js){
Assets::load_js($assets, $before . $js);
}
return $assets;
}
/**
* Load a js file.
*
* @param Assets $assets
* @param $file
*
* @return Assets
*/
public static function load_js(Assets $assets, $file){
if(Assets::is_unique($assets, $file, Assets::JAVASCRIPT)){
return $assets->js($file, Assets::$default_css_options);
}
return $assets;
}
/**
* Check that the file isn't already loaded.
*
* @param Assets $assets
* @param $file
* @param $group
*
* @return bool
*/
private static function is_unique(Assets $assets, $file, $group){
$collection = $assets->_groups[$group];
$loaded_assets = $collection->assets();
foreach($loaded_assets as $loaded_asset){
if($loaded_asset->file() === $file){
return false;
}
}
return true;
}
/**
* Clean file names to be usable by the asset-merger module.
* Check that the file should be loaded or not. (extension, etc.)
* Clean by creating a new array and add entries only if they pass the validation.
*
* @param $files
* @param $path_to_clean
* @param $group
* @param $check_autoload
* @param $check_extension
*
* @return array
*/
private static function _clean_file_names(array $files, $path_to_clean, $group, $check_autoload = true, $check_extension = true){
$files_cleaned = array();
foreach($files as $file){
if($file_cleaned = Assets::_clean_file_name($file, $path_to_clean, $group, $check_autoload, $check_extension)){
$files_cleaned[] = $file_cleaned;
}
}
return $files_cleaned;
}
/**
* Clean a file name and returns the cleaned name.
* Returns null if the file doesn't pass the validation.
*
* @param $file
* @param $path_to_clean
* @param $group
* @param bool $check_autoload
* @param bool $check_extension
*
* @return mixed
*/
private static function _clean_file_name($file, $path_to_clean, $group, $check_autoload = true, $check_extension = true){
// Filter results by deleting all entries that do not match our allowed extensions.
if(Assets::_check_file_name($file, $group, $check_autoload, $check_extension)){
return str_replace($path_to_clean, '', $file);
}
}
/**
* Check that an asset file should be loaded.
* Check pattern to don't auto load.
* Check file extension.
*
* @param $file
* @param $group
* @param $check_autoload
* @param $check_extension
*
* @return bool
*/
private static function _check_file_name($file, $group, $check_autoload = true, $check_extension = true){
$dir_and_files = explode("/", $file);
foreach($dir_and_files as $name){
// Should be auto loadable.
if($check_autoload && $name[0] === Assets::$pattern_autoload_disable){
return false;
}
// Should contain an allowed extension.
if($check_extension && !in_array(pathinfo($file, PATHINFO_EXTENSION), Assets::$allowed_ext[$group])){
return false;
}
}
return true;
}
}
?>
What next?
I don't know, I never built any Kohana module, I think that if what I did is cool, then it should be included in the asset-merger module, but in accordance with the kohana way. (I.e: Write configuration inside the config/asset-merger.php, etc.)
Hi.
I ran into some issues yesterday so I figured out how deal with it. Because I'm lazy and I don't want to import manually each file, I wrote a little
Kohana 3.3.1
Assets
helper.What does it do?
css
andjs
.Assets
instance._
at the filename first char)js
andcss
methods.asset-merger
module.Example of use:
How to use it?
Assuming asset-merger is already configured
App::$assets_directory
,App::$styles_directory
,App::$js_directory
load_theme_assets
andload_views_styles
that are really strongly linked to my app, but are good example "how to", if you use something close in your app, anyway you can chose to not use them.->load_assets()
method should import all assets that are not already loaded. Don't forget that some lb should be still required "by hand" before, such asjquery/jquery-ui
.Rendered HTML
Asset-merger config
This is the config file I used, I use themes, (app specific) but you just have to change the config file to cache the file without use this stuff. config/asset-merger.php
Source code - Dependencies/helpers
Of course, because this script is strongly linked to my app, there are some dependencies.
App.php
I had to make visible the
protected $_file
from the Kohana_Asset class, so I overloaded it.classes/Asset.php
I also use a custom function to read all files recursively, to do so I overloaded the Kohana_File class.
classes/File.php
Finally, the big script:
classes/Assets.php
What next?
I don't know, I never built any Kohana module, I think that if what I did is cool, then it should be included in the
asset-merger
module, but in accordance with the kohana way. (I.e: Write configuration inside the config/asset-merger.php, etc.)Let me know!