Compressing linked JavaScript
and CSS
files is no big deal when using Assetic
in my Symfony 2.8
project. But what about scripts that are directly embedded in the page using a script
tag. These scripts are not modified in any way.
Is it possible to compress/modify/uglify these scripts as well?
Of course I could simply move these scripts into separat files and thus apply the Assetic
filters to them as well. But in some cases it is just handy to have the scripts directly within the HTML / Twig template.
So, is there any existing solution to filter these scripts without moving them?
Since I found no solution for this, I finally managed to create my own: Add a custom tag to my (existing) Twig extension to apply Assetics uglifyJS2
filter to the embedded scripts:
Step 1: Create a Twig TokenParser
and Node
class UglifyTokenParser extends Twig_TokenParser {
private $enabled;
public function __construct($enabled = true) {
$this->enabled = (bool) $enabled;
}
public function parse(Twig_Token $token) {
$lineNumber = $token->getLine();
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(function (Twig_Token $token) {
return $token->test('enduglify');
}, true);
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
if ($this->enabled) {
$node = new UglifyNode($body, $lineNumber, $this->getTag());
return $node;
}
return $body;
}
public function getTag() {
return 'uglify';
}
}
class UglifyNode extends \Twig_Node {
public function __construct(Twig_Node $body, $lineNumber, $tag = 'uglify') {
parent::__construct(array('body' => $body), array(), $lineNumber, $tag);
}
public function compile(Twig_Compiler $compiler) {
$compiler
->addDebugInfo($this)
->write("ob_start();\n")
->subcompile($this->getNode('body'))
->write("echo \$context['_uglifier']->uglify(trim(ob_get_clean()));\n");
}
}
Step 2: Add the Uglifier
class, which is used by the Node
to pass the content to the Assetic UglifyJS2Filter
class Uglifier {
private $filter;
private $asset;
private $enabled;
public function __construct(FilterInterface $filter, $endabled) {
$this->filter = $filter;
$this->asset = new UglifierAsset(array($this->filter));
$this->enabled = $endabled;
}
public function uglify($content) {
if ($this->enabled) {
$this->asset->loadContent($content);
$uglified = $this->asset->dump();
return $uglified;
} else {
return $content;
}
}
}
class UglifierAsset extends BaseAsset {
public function __construct($filters = array()) {
parent::__construct($filters);
}
private $theContent;
public function loadContent($content) {
$this->theContent = $content;
$this->load();
}
public function load(FilterInterface $additionalFilter = null) {
$this->doLoad($this->theContent);
}
public function getLastModified() {
return 0;
}
}
Step 3: Create Uglifier
as sService, pass it to the Twig Extension
and implement custom tag within the extension
app.twig_extension:
class: AppBundle\Twig\AppExtension
public: false
arguments: [ "@app.twig_extension.uglifier" ]
tags:
- { name: twig.extension }
app.twig_extension.uglifier:
class: AppBundle\Twig\uglify\Uglifier
arguments: [ "@assetic.filter.uglifyjs2", "%twig_extension.unglifier.enabled%" ]
class AppExtension extends \Twig_Extension implements \Twig_Extension_GlobalsInterface {
private $uglifier;
public function __construct(Uglifier $uglifier) {
$this->uglifier = $uglifier;
}
public function getGlobals() {
return array(
'_uglifier' => $this->uglifier,
);
}
public function getTokenParsers() {
return array(new UglifyTokenParser());
}
}
Step 4: Wrap embedded scripts with the new `{% uglify %} tag
{# before #}
<script type="text/javascript">
// some JS
</script>
{# after #}
<script type="text/javascript">
{% uglify %}
// some JS
{% enduglify %}
</script>
DONE