Skip to content

Commit

Permalink
Adds deep hashing to CSS imports
Browse files Browse the repository at this point in the history
  • Loading branch information
daftspunk committed Apr 3, 2024
1 parent 8d297df commit dac483d
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 34 deletions.
120 changes: 100 additions & 20 deletions src/Assetic/Filter/CssImportFilter.php
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
<?php namespace October\Rain\Assetic\Filter;

use October\Rain\Assetic\Filter\HashableInterface;
use October\Rain\Assetic\Asset\AssetInterface;
use October\Rain\Assetic\Asset\FileAsset;
use October\Rain\Assetic\Asset\HttpAsset;
use October\Rain\Assetic\Factory\AssetFactory;
use October\Rain\Assetic\Util\CssUtils;

/**
* CssImportFilter inlines imported stylesheets.
* CssImportFilter converts imported stylesheets to inline.
*
* @author Kris Wallsmith <[email protected]>
*/
class CssImportFilter extends BaseCssFilter implements DependencyExtractorInterface
class CssImportFilter extends BaseCssFilter implements HashableInterface, DependencyExtractorInterface
{
/**
* @var mixed importFilter
*/
protected $importFilter;

/**
* @var string lastHash
*/
protected $lastHash;

/**
* __construct
*
Expand All @@ -37,29 +44,29 @@ public function filterLoad(AssetInterface $asset)
$sourcePath = $asset->getSourcePath();

$callback = function ($matches) use ($importFilter, $sourceRoot, $sourcePath) {
if (!$matches['url'] || null === $sourceRoot) {
if (!$matches['url'] || $sourceRoot === null) {
return $matches[0];
}

$importRoot = $sourceRoot;

if (false !== strpos($matches['url'], '://')) {
// absolute
// Absolute
if (strpos($matches['url'], '://') !== false) {
list($importScheme, $tmp) = explode('://', $matches['url'], 2);
list($importHost, $importPath) = explode('/', $tmp, 2);
$importRoot = $importScheme.'://'.$importHost;
}
elseif (0 === strpos($matches['url'], '//')) {
// protocol-relative
// Protocol-relative
elseif (strpos($matches['url'], '//') === 0) {
list($importHost, $importPath) = explode('/', substr($matches['url'], 2), 2);
$importRoot = '//'.$importHost;
}
elseif ('/' == $matches['url'][0]) {
// root-relative
// Root-relative
elseif ($matches['url'][0] == '/') {
$importPath = substr($matches['url'], 1);
}
elseif (null !== $sourcePath) {
// document-relative
// Document-relative
elseif ($sourcePath !== null) {
$importPath = $matches['url'];
if ('.' != $sourceDir = dirname($sourcePath)) {
$importPath = $sourceDir.'/'.$importPath;
Expand All @@ -70,15 +77,15 @@ public function filterLoad(AssetInterface $asset)
}

$importSource = $importRoot.'/'.$importPath;
if (false !== strpos($importSource, '://') || 0 === strpos($importSource, '//')) {
$import = new HttpAsset($importSource, array($importFilter), true);
if (strpos($importSource, '://') !== false || strpos($importSource, '//') === 0) {
$import = new HttpAsset($importSource, [$importFilter], true);
}
elseif ('css' != pathinfo($importPath, PATHINFO_EXTENSION) || !file_exists($importSource)) {
// ignore non-css and non-existant imports
// Ignore non-css and non-existent imports
elseif (pathinfo($importPath, PATHINFO_EXTENSION) != 'css' || !file_exists($importSource)) {
return $matches[0];
}
else {
$import = new FileAsset($importSource, array($importFilter), $importRoot, $importPath);
$import = new FileAsset($importSource, [$importFilter], $importRoot, $importPath);
}

$import->setTargetPath($sourcePath);
Expand All @@ -92,7 +99,7 @@ public function filterLoad(AssetInterface $asset)
do {
$content = $this->filterImports($content, $callback);
$hash = md5($content);
} while ($lastHash != $hash && $lastHash = $hash);
} while ($lastHash != $hash && ($lastHash = $hash));

$asset->setContent($content);
}
Expand All @@ -105,11 +112,84 @@ public function filterDump(AssetInterface $asset)
}

/**
* getChildren
* hashAsset
*/
public function hashAsset($asset, $localPath)
{
$factory = new AssetFactory($localPath);
$children = $this->getAllChildren($factory, file_get_contents($asset), dirname($asset));

$allFiles = [];
foreach ($children as $child) {
$allFiles[] = $child;
}

$modified = [];
foreach ($allFiles as $file) {
$modified[] = $file->getLastModified();
}

return md5(implode('|', $modified));
}

/**
* setHash
*/
public function setHash($hash)
{
$this->lastHash = $hash;
}

/**
* hash generated for the object
* @return string
*/
public function hash()
{
return $this->lastHash ?: serialize($this);
}

/**
* getAllChildren loads all children recursively
*/
public function getAllChildren(AssetFactory $factory, $content, $loadPath = null)
{
$children = (new static)->getChildren($factory, $content, $loadPath);

foreach ($children as $child) {
$childContent = file_get_contents($child->getSourceRoot().'/'.$child->getSourcePath());
$children = array_merge($children, (new static)->getChildren($factory, $childContent, $loadPath.'/'.dirname($child->getSourcePath())));
}

return $children;
}

/**
* getChildren only returns one level of children
*/
public function getChildren(AssetFactory $factory, $content, $loadPath = null)
{
// todo
return [];
if (!$loadPath) {
return [];
}

$children = [];
foreach (CssUtils::extractImports($content) as $reference) {
// Strict check, only allow .css imports
if (substr($reference, -4) !== '.css') {
continue;
}

if (file_exists($file = $loadPath.'/'.$reference)) {
$coll = $factory->createAsset($file, [], ['root' => $loadPath]);
foreach ($coll as $leaf) {
$leaf->ensureFilter($this);
$children[] = $leaf;
break;
}
}
}

return $children;
}
}
31 changes: 26 additions & 5 deletions src/Assetic/Filter/LessCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,27 @@
*/
class LessCompiler implements FilterInterface, HashableInterface, DependencyExtractorInterface
{
/**
* @var array presets
*/
protected $presets = [];

/**
* @var string lastHash
*/
protected $lastHash;

/**
* setPresets
*/
public function setPresets(array $presets)
{
$this->presets = $presets;
}

/**
* filterLoad
*/
public function filterLoad(AssetInterface $asset)
{
$parser = new Less_Parser();
Expand All @@ -41,10 +53,16 @@ public function filterLoad(AssetInterface $asset)
$asset->setContent($parser->getCss());
}

/**
* filterDump
*/
public function filterDump(AssetInterface $asset)
{
}

/**
* hashAsset
*/
public function hashAsset($asset, $localPath)
{
$factory = new AssetFactory($localPath);
Expand All @@ -55,21 +73,24 @@ public function hashAsset($asset, $localPath)
$allFiles[] = $child;
}

$modifieds = [];
$modified = [];
foreach ($allFiles as $file) {
$modifieds[] = $file->getLastModified();
$modified[] = $file->getLastModified();
}

return md5(implode('|', $modifieds));
return md5(implode('|', $modified));
}

/**
* setHash
*/
public function setHash($hash)
{
$this->lastHash = $hash;
}

/**
* Generates a hash for the object
* hash generated for the object
* @return string
*/
public function hash()
Expand All @@ -78,7 +99,7 @@ public function hash()
}

/**
* Load children recusive
* getChildren loads children recursively
*/
public function getChildren(AssetFactory $factory, $content, $loadPath = null)
{
Expand Down
12 changes: 5 additions & 7 deletions src/Assetic/Filter/LessphpFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public function filterDump(AssetInterface $asset)
public function getChildren(AssetFactory $factory, $content, $loadPath = null)
{
$loadPaths = $this->loadPaths;
if (null !== $loadPath) {
if ($loadPath !== null) {
$loadPaths[] = $loadPath;
}

Expand All @@ -128,28 +128,26 @@ public function getChildren(AssetFactory $factory, $content, $loadPath = null)

$children = [];
foreach (LessUtils::extractImports($content) as $reference) {
if ('.css' === substr($reference, -4)) {
if (substr($reference, -4) === '.css') {
// skip normal css imports
// todo: skip imports with media queries
continue;
}

if ('.less' !== substr($reference, -5)) {
if (substr($reference, -5) !== '.less') {
$reference .= '.less';
}

foreach ($loadPaths as $loadPath) {
if (file_exists($file = $loadPath.'/'.$reference)) {
$coll = $factory->createAsset($file, array(), array('root' => $loadPath));
$coll = $factory->createAsset($file, [], ['root' => $loadPath]);
foreach ($coll as $leaf) {
$leaf->ensureFilter($this);
$children[] = $leaf;
goto next_reference;
break 2;
}
}
}

next_reference:
}

return $children;
Expand Down
3 changes: 1 addition & 2 deletions src/Assetic/Traits/HasDeepHasher.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use October\Rain\Assetic\Factory\AssetFactory;

/**
* Combiner helper class
* HasDeepHasher checks if imports have changed their content and busts the cache
*
* @package october/assetic
* @author Alexey Bobkov, Samuel Georges
Expand Down Expand Up @@ -48,7 +48,6 @@ public function getDeepHashFromAssets($assets)

/**
* setHashOnCombinerFilters busts the cache based on a different cache key.
* @return void
*/
protected function setDeepHashKeyOnFilters($hash)
{
Expand Down

0 comments on commit dac483d

Please sign in to comment.