Minify / Uglify embedded JavaScript in Symfony/Twig response

1.2k views Asked by At

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?

1

There are 1 answers

0
Andrei Herford On BEST ANSWER

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