Search code examples
javascriptsymfonytwigasseticuglifyjs

Minify / Uglify embedded JavaScript in Symfony/Twig response


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?


Solution

  • 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