diff --git a/README.md b/README.md index aa5c28d..a673bd5 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,12 @@ a much more elegant fashion. The syntax of this tag works like this: {{ asset }} {% endwebpack %} ``` +Or if you want to have inline javascript code without including a file: +```twig +{% webpack %} + +{% endwebpack %} +``` If you want to include javascript files, simply do this: ```twig @@ -130,6 +136,14 @@ If you want to include javascript files, simply do this: {% endwebpack %} ``` +Or the inline variant: +```twig +{% webpack inline %} + +{% endwebpack %} +``` The same method can be applied for CSS files. ```twig diff --git a/src/Bundle/Resources/config/webpack.yml b/src/Bundle/Resources/config/webpack.yml index 400a1c3..4ba8dfd 100644 --- a/src/Bundle/Resources/config/webpack.yml +++ b/src/Bundle/Resources/config/webpack.yml @@ -28,6 +28,7 @@ services: - '%kernel.root_dir%' - '' # asset_path - [] # bundles + hostnet_webpack.bridge.asset_dumper: class: Hostnet\Component\Webpack\Asset\Dumper arguments: @@ -47,6 +48,7 @@ services: arguments: - '@hostnet_webpack.bridge.asset_tracker' - '@twig' + - '%kernel.cache_dir%' hostnet_webpack.bridge.asset_compiler: class: Hostnet\Component\Webpack\Asset\Compiler diff --git a/src/Bundle/Twig/Node/WebpackInlineNode.php b/src/Bundle/Twig/Node/WebpackInlineNode.php new file mode 100644 index 0000000..25d03dd --- /dev/null +++ b/src/Bundle/Twig/Node/WebpackInlineNode.php @@ -0,0 +1,35 @@ + + */ +class WebpackInlineNode extends \Twig_Node +{ + /** + * @param array $attributes An array of attributes (should not be nodes) + * @param int $lineno The line number + * @param string $tag The tag name associated with the Node + */ + public function __construct(array $attributes = array(), $lineno = 0, $tag = null) + { + parent::__construct([], $attributes, $lineno, $tag); + } + + /** {@inheritdoc} */ + public function compile(\Twig_Compiler $compiler) + { + if (false !== ($file = $this->getAttribute('js_file'))) { + $compiler + ->write('echo ') + ->string('') + ->raw(";\n"); + } + if (false !== ($file = $this->getAttribute('css_file'))) { + $compiler + ->write('echo ') + ->string('') + ->raw(";\n"); + } + } +} diff --git a/src/Bundle/Twig/Token/WebpackTokenParser.php b/src/Bundle/Twig/Token/WebpackTokenParser.php index 91a1bab..71664e8 100644 --- a/src/Bundle/Twig/Token/WebpackTokenParser.php +++ b/src/Bundle/Twig/Token/WebpackTokenParser.php @@ -1,6 +1,7 @@ parser->getStream(); - $files = []; $lineno = $stream->getCurrent()->getLine(); // Export type: "js" or "css" $export_type = $stream->expect(\Twig_Token::NAME_TYPE)->getValue(); - if (! in_array($export_type, ['js', 'css'])) { + if (! in_array($export_type, ['js', 'css', 'inline'])) { // This exception will include the template filename by itself. throw new \Twig_Error_Syntax(sprintf( - 'Expected export type "js" or "css", got "%s" at line %d.', + 'Expected export type "inline", "js" or "css", got "%s" at line %d.', $export_type, $lineno )); } + if ($export_type === "inline") { + return $this->parseInline($stream, $lineno); + } + return $this->parseType($stream, $lineno, $export_type); + } + + private function parseType(\Twig_TokenStream $stream, $lineno, $export_type) + { + $files = []; while (! $stream->isEOF() && ! $stream->getCurrent()->test(\Twig_Token::BLOCK_END_TYPE)) { $asset = $stream->expect(\Twig_Token::STRING_TYPE)->getValue(); + if (false === ($file = $this->extension->webpackAsset($asset)[$export_type])) { continue; } $files[] = $file; } + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(function ($token) { return $token->test(['end' . $this->getTag()]); }, true); + $stream->expect(\Twig_Token::BLOCK_END_TYPE); return new WebpackNode([$body], ['files' => $files], $lineno, $this->getTag()); } + + private function parseInline(\Twig_TokenStream $stream, $lineno) + { + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + + $this->parser->subparse(function ($token) { + return $token->test(['end' . $this->getTag()]); + }, true); + + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + + $file = $this->parser->getEnvironment()->getLoader()->getCacheKey($stream->getFilename()); + + if (!isset($this->inline_blocks[$file])) { + $this->inline_blocks[$file] = 0; + } + + $file_name = md5($file . $this->inline_blocks[$file]). ".js"; + $assets = $this->extension->webpackAsset('cache.' . $file_name); + + $this->inline_blocks[$file]++; + + return new WebpackInlineNode( + ['js_file' => $assets['js'], 'css_file' => $assets['css']], + $lineno, + $this->getTag() + ); + } } diff --git a/src/Component/Asset/TwigParser.php b/src/Component/Asset/TwigParser.php index 9249fb3..efce530 100644 --- a/src/Component/Asset/TwigParser.php +++ b/src/Component/Asset/TwigParser.php @@ -1,8 +1,6 @@ tracker = $tracker; - $this->twig = $twig; + $this->tracker = $tracker; + $this->twig = $twig; + $this->cache_dir = $cache_dir; } /** @@ -33,8 +33,9 @@ public function __construct(Tracker $tracker, \Twig_Environment $twig) */ public function findSplitPoints($template_file) { - $stream = $this->twig->tokenize(file_get_contents($template_file)); - $points = []; + $inline_blocks = 0; + $stream = $this->twig->tokenize(file_get_contents($template_file)); + $points = []; while (! $stream->isEOF() && $token = $stream->next()) { // {{ webpack_asset(...) }} @@ -47,10 +48,27 @@ public function findSplitPoints($template_file) // {% webpack_javascripts %} and {% webpack_stylesheets %} if ($token->test(\Twig_Token::BLOCK_START_TYPE) && $stream->getCurrent()->test(WebpackTokenParser::TAG_NAME)) { $stream->next(); - $stream->next(); - while (! $stream->isEOF() && ! $stream->getCurrent()->test(\Twig_Token::BLOCK_END_TYPE)) { - $asset = $stream->expect(\Twig_Token::STRING_TYPE)->getValue(); - $points[$asset] = $this->resolveAssetPath($asset, $template_file, $token); + + if ($stream->getCurrent()->getValue() === 'inline') { + $stream->next(); + $stream->next(); + + $file_name = md5($template_file . $inline_blocks). ".js"; + + file_put_contents( + $this->cache_dir . '/' . $file_name, + $this->stripScript($stream->getCurrent()->getValue()) + ); + + $asset = $file_name; + $points['cache/'.$asset] = $this->resolveAssetPath($this->cache_dir . '/' . $asset, $template_file, $token); + $inline_blocks++; + } else { + $stream->next(); + while (! $stream->isEOF() && ! $stream->getCurrent()->test(\Twig_Token::BLOCK_END_TYPE)) { + $asset = $stream->expect(\Twig_Token::STRING_TYPE)->getValue(); + $points[$asset] = $this->resolveAssetPath($asset, $template_file, $token); + } } } } @@ -105,4 +123,14 @@ private function expect($filename, \Twig_Token $token, $type, $value = null) )); } } + + private function stripScript($str) + { + $matches = []; + if (preg_match('/^\s*(.*)<\/script>\s*$/s', $str, $matches)) { + return $matches[2]; + } + + return $str; + } } diff --git a/test/Component/Asset/TwigParserTest.php b/test/Component/Asset/TwigParserTest.php index 6fab7e0..24a9578 100644 --- a/test/Component/Asset/TwigParserTest.php +++ b/test/Component/Asset/TwigParserTest.php @@ -22,12 +22,18 @@ class TwigParserTest extends \PHPUnit_Framework_TestCase */ private $path; + /** + * @var string + */ + private $cache_dir; + /** {@inheritdoc} */ protected function setUp() { - $this->tracker = $this->getMockBuilder(Tracker::class)->disableOriginalConstructor()->getMock(); - $this->twig = new \Twig_Environment(); - $this->path = realpath(__DIR__ . '/../../Fixture'); + $this->tracker = $this->getMockBuilder(Tracker::class)->disableOriginalConstructor()->getMock(); + $this->twig = new \Twig_Environment(); + $this->path = realpath(__DIR__ . '/../../Fixture'); + $this->cache_dir = '/tmp'; } public function testParseValid() @@ -35,19 +41,24 @@ public function testParseValid() // Call count expectations: // 1: webpack_asset.js // 2: webpack_asset.css - // 3: {% webpack_javascripts %} - // 4: {% webpack_javascripts %} - // 5: {% webpack_stylesheets %} - $this->tracker->expects($this->exactly(5))->method('resolveResourcePath')->willReturn('foobar'); + // 3: {% webpack js %} + // 4: {% webpack js %} + // 5: {% webpack css %} + // 6: {% webpack inline %} + // 7: {% webpack inline %} + $this->tracker->expects($this->exactly(7))->method('resolveResourcePath')->willReturn('foobar'); - $parser = new TwigParser($this->tracker, $this->twig); - $points = ($parser->findSplitPoints($this->path . '/Resources/template.html.twig')); + $parser = new TwigParser($this->tracker, $this->twig, $this->cache_dir); + $file = $this->path . '/Resources/template.html.twig'; + $points = ($parser->findSplitPoints($file)); - $this->assertCount(4, $points); + $this->assertCount(6, $points); $this->assertArrayHasKey('@BarBundle/app.js', $points); $this->assertArrayHasKey('@BarBundle/app2.js', $points); $this->assertArrayHasKey('@BarBundle/app3.js', $points); $this->assertArrayHasKey('@BarBundle/app4.js', $points); + $this->assertArrayHasKey('cache/' . md5($file . 0) . '.js', $points); + $this->assertArrayHasKey('cache/' . md5($file . 1) . '.js', $points); $this->assertContains('foobar', $points); } @@ -60,7 +71,7 @@ public function testParseError() { $this->tracker->expects($this->never())->method('resolveResourcePath'); - $parser = new TwigParser($this->tracker, $this->twig); + $parser = new TwigParser($this->tracker, $this->twig, $this->cache_dir); $parser->findSplitPoints($this->path . '/Resources/template_parse_error.html.twig'); } @@ -72,7 +83,7 @@ public function testResolveError() { $this->tracker->expects($this->once())->method('resolveResourcePath')->willReturn(false); - $parser = new TwigParser($this->tracker, $this->twig); + $parser = new TwigParser($this->tracker, $this->twig, $this->cache_dir); $parser->findSplitPoints($this->path . '/Resources/template.html.twig'); } } diff --git a/test/Fixture/Resources/template.html.twig b/test/Fixture/Resources/template.html.twig index 0af524d..acd865a 100644 --- a/test/Fixture/Resources/template.html.twig +++ b/test/Fixture/Resources/template.html.twig @@ -12,3 +12,13 @@ {% webpack css '@BarBundle/app4.js' %} {% endwebpack %} + +{# and webpack inline #} +{% webpack inline %} + +{% endwebpack %} +{% webpack inline %} + console.log("HENK"); +{% endwebpack %}