diff --git a/src/Asset/EntrypointLookupCollection.php b/src/Asset/EntrypointLookupCollection.php
new file mode 100644
index 00000000..add000d8
--- /dev/null
+++ b/src/Asset/EntrypointLookupCollection.php
@@ -0,0 +1,39 @@
+
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\WebpackEncoreBundle\Asset;
+
+use Symfony\WebpackEncoreBundle\Exception\UndefinedBuildException;
+use Psr\Container\ContainerInterface;
+
+/**
+ * Aggregate the different entry points configured in the container.
+ *
+ * Retrieve the EntrypointLookup instance from the given key.
+ *
+ * @final
+ */
+class EntrypointLookupCollection
+{
+ private $buildEntrypoints;
+
+ public function __construct(ContainerInterface $buildEntrypoints)
+ {
+ $this->buildEntrypoints = $buildEntrypoints;
+ }
+
+ public function getEntrypointLookup(string $buildName): EntrypointLookupInterface
+ {
+ if (!$this->buildEntrypoints->has($buildName)) {
+ throw new UndefinedBuildException(sprintf('Given entry point "%s" is not configured', $buildName));
+ }
+
+ return $this->buildEntrypoints->get($buildName);
+ }
+}
diff --git a/src/Asset/TagRenderer.php b/src/Asset/TagRenderer.php
index d1fbc652..a7661b00 100644
--- a/src/Asset/TagRenderer.php
+++ b/src/Asset/TagRenderer.php
@@ -10,23 +10,39 @@
namespace Symfony\WebpackEncoreBundle\Asset;
use Symfony\Component\Asset\Packages;
+use Symfony\Component\DependencyInjection\ServiceLocator;
final class TagRenderer
{
- private $entrypointLookup;
+ private $entrypointLookupCollection;
private $packages;
- public function __construct(EntrypointLookupInterface $entrypointLookup, Packages $packages)
- {
- $this->entrypointLookup = $entrypointLookup;
+ public function __construct(
+ $entrypointLookupCollection,
+ Packages $packages
+ ) {
+ if ($entrypointLookupCollection instanceof EntrypointLookupInterface) {
+ @trigger_error(sprintf('The "$entrypointLookupCollection" argument in method "%s()" must be an instance of EntrypointLookupCollection.', __METHOD__), E_USER_DEPRECATED);
+
+ $this->entrypointLookupCollection = new EntrypointLookupCollection(
+ new ServiceLocator(['_default' => function() use ($entrypointLookupCollection) {
+ return $entrypointLookupCollection;
+ }])
+ );
+ } elseif ($entrypointLookupCollection instanceof EntrypointLookupCollection) {
+ $this->entrypointLookupCollection = $entrypointLookupCollection;
+ } else {
+ throw new \TypeError('The "$entrypointLookupCollection" argument must be an instance of EntrypointLookupCollection.');
+ }
+
$this->packages = $packages;
}
- public function renderWebpackScriptTags(string $entryName, string $packageName = null): string
+ public function renderWebpackScriptTags(string $entryName, string $packageName = null, string $entrypointName = '_default'): string
{
$scriptTags = [];
- foreach ($this->entrypointLookup->getJavaScriptFiles($entryName) as $filename) {
+ foreach ($this->getEntrypointLookup($entrypointName)->getJavaScriptFiles($entryName) as $filename) {
$scriptTags[] = sprintf(
'',
htmlentities($this->getAssetPath($filename, $packageName))
@@ -36,10 +52,10 @@ public function renderWebpackScriptTags(string $entryName, string $packageName =
return implode('', $scriptTags);
}
- public function renderWebpackLinkTags(string $entryName, string $packageName = null): string
+ public function renderWebpackLinkTags(string $entryName, string $packageName = null, string $entrypointName = '_default'): string
{
$scriptTags = [];
- foreach ($this->entrypointLookup->getCssFiles($entryName) as $filename) {
+ foreach ($this->getEntrypointLookup($entrypointName)->getCssFiles($entryName) as $filename) {
$scriptTags[] = sprintf(
'',
htmlentities($this->getAssetPath($filename, $packageName))
@@ -60,4 +76,9 @@ private function getAssetPath(string $assetPath, string $packageName = null): st
$packageName
);
}
+
+ private function getEntrypointLookup(string $buildName): EntrypointLookupInterface
+ {
+ return $this->entrypointLookupCollection->getEntrypointLookup($buildName);
+ }
}
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
index 829272ff..1e762541 100644
--- a/src/DependencyInjection/Configuration.php
+++ b/src/DependencyInjection/Configuration.php
@@ -12,6 +12,7 @@
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
+use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
final class Configuration implements ConfigurationInterface
{
@@ -27,6 +28,19 @@ public function getConfigTreeBuilder()
->isRequired()
->info('The path where Encore is building the assets - i.e. Encore.setOutputPath()')
->end()
+ ->arrayNode('builds')
+ ->useAttributeAsKey('name')
+ ->scalarPrototype()
+ ->validate()
+ ->always(function ($values) {
+ if (isset($values['_default'])) {
+ throw new InvalidDefinitionException("Key '_default' can't be used as build name.");
+ }
+
+ return $values;
+ })
+ ->end()
+ ->end()
->end()
;
diff --git a/src/DependencyInjection/WebpackEncoreExtension.php b/src/DependencyInjection/WebpackEncoreExtension.php
index 2d4eba98..035cd658 100644
--- a/src/DependencyInjection/WebpackEncoreExtension.php
+++ b/src/DependencyInjection/WebpackEncoreExtension.php
@@ -13,6 +13,10 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
+use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\WebpackEncoreBundle\Asset\EntrypointLookup;
final class WebpackEncoreExtension extends Extension
{
@@ -24,7 +28,23 @@ public function load(array $configs, ContainerBuilder $container)
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
+ $factories = [
+ '_default' => new Reference($this->entrypointFactory($container, '_default', $config['output_path']))
+ ];
+ foreach ($config['builds'] as $name => $path) {
+ $factories[$name] = new Reference($this->entrypointFactory($container, $name, $path));
+ };
+
$container->getDefinition('webpack_encore.entrypoint_lookup')
- ->replaceArgument(0, $config['output_path'].'/entrypoints.json');
+ ->replaceArgument(0, $factories['_default']);
+ $container->getDefinition('webpack_encore.entrypoint_lookup_collection')
+ ->replaceArgument(0, ServiceLocatorTagPass::register($container, $factories));
+ }
+
+ private function entrypointFactory(ContainerBuilder $container, string $name, string $path): string
+ {
+ $id = sprintf('webpack_encore.entrypoint_lookup[%s]', $name);
+ $container->setDefinition($id, new Definition(EntrypointLookup::class, [$path.'/entrypoints.json']));
+ return $id;
}
}
diff --git a/src/Exception/UndefinedBuildException.php b/src/Exception/UndefinedBuildException.php
new file mode 100644
index 00000000..13ebfb2e
--- /dev/null
+++ b/src/Exception/UndefinedBuildException.php
@@ -0,0 +1,14 @@
+
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\WebpackEncoreBundle\Exception;
+
+class UndefinedBuildException extends \InvalidArgumentException
+{
+}
diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml
index 9b0abce3..2c88c213 100644
--- a/src/Resources/config/services.xml
+++ b/src/Resources/config/services.xml
@@ -10,9 +10,12 @@
+
+
+
-
+
@@ -23,6 +26,7 @@
+
diff --git a/src/Twig/EntryFilesTwigExtension.php b/src/Twig/EntryFilesTwigExtension.php
index 2ea2c249..30254cb3 100644
--- a/src/Twig/EntryFilesTwigExtension.php
+++ b/src/Twig/EntryFilesTwigExtension.php
@@ -34,33 +34,34 @@ public function getFunctions()
];
}
- public function getWebpackJsFiles(string $entryName): array
+ public function getWebpackJsFiles(string $entryName, string $entrypointName = '_default'): array
{
- return $this->getEntrypointLookup()
+ return $this->getEntrypointLookup($entrypointName)
->getJavaScriptFiles($entryName);
}
- public function getWebpackCssFiles(string $entryName): array
+ public function getWebpackCssFiles(string $entryName, string $entrypointName = '_default'): array
{
- return $this->getEntrypointLookup()
+ return $this->getEntrypointLookup($entrypointName)
->getCssFiles($entryName);
}
- public function renderWebpackScriptTags(string $entryName, string $packageName = null): string
+ public function renderWebpackScriptTags(string $entryName, string $packageName = null, string $entrypointName = '_default'): string
{
return $this->getTagRenderer()
- ->renderWebpackScriptTags($entryName, $packageName);
+ ->renderWebpackScriptTags($entryName, $packageName, $entrypointName);
}
- public function renderWebpackLinkTags(string $entryName, string $packageName = null): string
+ public function renderWebpackLinkTags(string $entryName, string $packageName = null, string $entrypointName = '_default'): string
{
return $this->getTagRenderer()
- ->renderWebpackLinkTags($entryName, $packageName);
+ ->renderWebpackLinkTags($entryName, $packageName, $entrypointName);
}
- private function getEntrypointLookup(): EntrypointLookupInterface
+ private function getEntrypointLookup(string $entrypointName): EntrypointLookupInterface
{
- return $this->container->get('webpack_encore.entrypoint_lookup');
+ return $this->container->get('webpack_encore.entrypoint_lookup_collection')
+ ->getEntrypointLookup($entrypointName);
}
private function getTagRenderer(): TagRenderer
diff --git a/tests/Asset/EntrypointLookupCollectionTest.php b/tests/Asset/EntrypointLookupCollectionTest.php
new file mode 100644
index 00000000..ce4506c0
--- /dev/null
+++ b/tests/Asset/EntrypointLookupCollectionTest.php
@@ -0,0 +1,20 @@
+getEntrypointLookup('something');
+ }
+}
diff --git a/tests/Asset/EntrypointLookupTest.php b/tests/Asset/EntrypointLookupTest.php
index 2c47d819..ce39b128 100644
--- a/tests/Asset/EntrypointLookupTest.php
+++ b/tests/Asset/EntrypointLookupTest.php
@@ -48,6 +48,18 @@ public function testGetJavaScriptFiles()
['file1.js', 'file2.js'],
$this->entrypointLookup->getJavaScriptFiles('my_entry')
);
+
+ $this->assertEquals(
+ [],
+ $this->entrypointLookup->getJavaScriptFiles('my_entry')
+ );
+
+ $this->entrypointLookup->reset();
+
+ $this->assertEquals(
+ ['file1.js', 'file2.js'],
+ $this->entrypointLookup->getJavaScriptFiles('my_entry')
+ );
}
public function testGetJavaScriptFilesReturnsUniqueFilesOnly()
@@ -79,6 +91,32 @@ public function testEmptyReturnOnValidEntryNoJsOrCssFile()
);
}
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessageContains There was a problem JSON decoding the
+ */
+ public function testExceptionOnInvalidJson()
+ {
+ $filename = tempnam(sys_get_temp_dir(), 'WebpackEncoreBundle');
+ file_put_contents($filename, "abcd");
+
+ $this->entrypointLookup = new EntrypointLookup($filename);
+ $this->entrypointLookup->getJavaScriptFiles('an_entry');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessageContains Could not find an "entrypoints" key in the
+ */
+ public function testExceptionOnMissingEntrypointsKeyInJson()
+ {
+ $filename = tempnam(sys_get_temp_dir(), 'WebpackEncoreBundle');
+ file_put_contents($filename, "{}");
+
+ $this->entrypointLookup = new EntrypointLookup($filename);
+ $this->entrypointLookup->getJavaScriptFiles('an_entry');
+ }
+
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Could not find the entrypoints file
diff --git a/tests/Asset/TagRendererTest.php b/tests/Asset/TagRendererTest.php
index b00bc716..59feda16 100644
--- a/tests/Asset/TagRendererTest.php
+++ b/tests/Asset/TagRendererTest.php
@@ -5,6 +5,7 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Asset\Packages;
use Symfony\WebpackEncoreBundle\Asset\EntrypointLookupInterface;
+use Symfony\WebpackEncoreBundle\Asset\EntrypointLookupCollection;
use Symfony\WebpackEncoreBundle\Asset\TagRenderer;
class TagRendererTest extends TestCase
@@ -15,6 +16,11 @@ public function testRenderScriptTags()
$entrypointLookup->expects($this->once())
->method('getJavaScriptFiles')
->willReturn(['/build/file1.js', '/build/file2.js']);
+ $entrypointCollection = $this->createMock(EntrypointLookupCollection::class);
+ $entrypointCollection->expects($this->once())
+ ->method('getEntrypointLookup')
+ ->withConsecutive(['_default'])
+ ->will($this->onConsecutiveCalls($entrypointLookup));
$packages = $this->createMock(Packages::class);
$packages->expects($this->exactly(2))
@@ -26,7 +32,7 @@ public function testRenderScriptTags()
->willReturnCallback(function($path) {
return 'http://localhost:8080'.$path;
});
- $renderer = new TagRenderer($entrypointLookup, $packages);
+ $renderer = new TagRenderer($entrypointCollection, $packages);
$output = $renderer->renderWebpackScriptTags('my_entry', 'custom_package');
$this->assertContains(
@@ -45,6 +51,11 @@ public function testRenderScriptTagsWithBadFilename()
$entrypointLookup->expects($this->once())
->method('getJavaScriptFiles')
->willReturn(['/build/file<"bad_chars.js']);
+ $entrypointCollection = $this->createMock(EntrypointLookupCollection::class);
+ $entrypointCollection->expects($this->once())
+ ->method('getEntrypointLookup')
+ ->withConsecutive(['_default'])
+ ->will($this->onConsecutiveCalls($entrypointLookup));
$packages = $this->createMock(Packages::class);
$packages->expects($this->once())
@@ -52,7 +63,7 @@ public function testRenderScriptTagsWithBadFilename()
->willReturnCallback(function($path) {
return 'http://localhost:8080'.$path;
});
- $renderer = new TagRenderer($entrypointLookup, $packages);
+ $renderer = new TagRenderer($entrypointCollection, $packages);
$output = $renderer->renderWebpackScriptTags('my_entry', 'custom_package');
$this->assertContains(
@@ -60,4 +71,61 @@ public function testRenderScriptTagsWithBadFilename()
$output
);
}
+
+ public function testRenderScriptTagsWithinAnEntryPointCollection()
+ {
+ $entrypointLookup = $this->createMock(EntrypointLookupInterface::class);
+ $entrypointLookup->expects($this->once())
+ ->method('getJavaScriptFiles')
+ ->willReturn(['/build/file1.js']);
+
+ $secondEntrypointLookup = $this->createMock(EntrypointLookupInterface::class);
+ $secondEntrypointLookup->expects($this->once())
+ ->method('getJavaScriptFiles')
+ ->willReturn(['/build/file2.js']);
+ $thirdEntrypointLookup = $this->createMock(EntrypointLookupInterface::class);
+ $thirdEntrypointLookup->expects($this->once())
+ ->method('getJavaScriptFiles')
+ ->willReturn(['/build/file3.js']);
+
+ $entrypointCollection = $this->createMock(EntrypointLookupCollection::class);
+ $entrypointCollection->expects($this->exactly(3))
+ ->method('getEntrypointLookup')
+ ->withConsecutive(['_default'], ['second'], ['third'])
+ ->will($this->onConsecutiveCalls(
+ $entrypointLookup,
+ $secondEntrypointLookup,
+ $thirdEntrypointLookup
+ ));
+
+ $packages = $this->createMock(Packages::class);
+ $packages->expects($this->exactly(3))
+ ->method('getUrl')
+ ->withConsecutive(
+ ['/build/file1.js', 'custom_package'],
+ ['/build/file2.js', null],
+ ['/build/file3.js', 'specific_package']
+ )
+ ->willReturnCallback(function($path) {
+ return 'http://localhost:8080'.$path;
+ });
+ $renderer = new TagRenderer($entrypointCollection, $packages);
+
+ $output = $renderer->renderWebpackScriptTags('my_entry', 'custom_package');
+ $this->assertContains(
+ '',
+ $output
+ );
+ $output = $renderer->renderWebpackScriptTags('my_entry', null, 'second');
+ $this->assertContains(
+ '',
+ $output
+ );
+ $output = $renderer->renderWebpackScriptTags('my_entry', 'specific_package', 'third');
+ $this->assertContains(
+ '',
+ $output
+ );
+ }
+
}
diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php
index 5ca75443..361cb83b 100644
--- a/tests/IntegrationTest.php
+++ b/tests/IntegrationTest.php
@@ -18,12 +18,44 @@ public function testTwigIntegration()
$kernel->boot();
$container = $kernel->getContainer();
- $html2 = $container->get('twig')->render('@integration_test/template.twig');
+ $html1 = $container->get('twig')->render('@integration_test/template.twig');
$this->assertContains(
'',
+ $html1
+ );
+ $this->assertContains(
+ ''.
+ '',
+ $html1
+ );
+ $this->assertContains(
+ '',
+ $html1
+ );
+ $this->assertContains(
+ ''.
+ '',
+ $html1
+ );
+
+ $html2 = $container->get('twig')->render('@integration_test/manual_template.twig');
+ $this->assertContains(
+ '',
+ $html2
+ );
+ $this->assertContains(
+ '',
$html2
);
+ }
+ public function testEntriesAreNotRepeteadWhenAlreadyOutputIntegration()
+ {
+ $kernel = new WebpackEncoreIntegrationTestKernel(true);
+ $kernel->boot();
+ $container = $kernel->getContainer();
+
+ $html1 = $container->get('twig')->render('@integration_test/template.twig');
$html2 = $container->get('twig')->render('@integration_test/manual_template.twig');
$this->assertContains(
'',
@@ -34,6 +66,16 @@ public function testTwigIntegration()
'',
$html2
);
+ // styles3.css is not repeated
+ $this->assertNotContains(
+ '',
+ $html2
+ );
+ // styles4.css is not repeated
+ $this->assertNotContains(
+ '',
+ $html2
+ );
}
}
@@ -75,6 +117,9 @@ public function registerContainerConfiguration(LoaderInterface $loader)
$container->loadFromExtension('webpack_encore', [
'output_path' => __DIR__.'/fixtures/build',
+ 'builds' => [
+ 'different_build' => __DIR__.'/fixtures/different_build'
+ ]
]);
});
}
@@ -88,4 +133,4 @@ public function getLogDir()
{
return sys_get_temp_dir().'/logs'.spl_object_hash($this);
}
-}
\ No newline at end of file
+}
diff --git a/tests/fixtures/different_build/entrypoints.json b/tests/fixtures/different_build/entrypoints.json
new file mode 100644
index 00000000..1128194b
--- /dev/null
+++ b/tests/fixtures/different_build/entrypoints.json
@@ -0,0 +1,22 @@
+{
+ "entrypoints": {
+ "third_entry": {
+ "js": [
+ "build/other3.js"
+ ],
+ "css": [
+ "build/styles3.css",
+ "build/styles4.css"
+ ]
+ },
+ "next_entry": {
+ "js": [
+ "build/other4.js"
+ ],
+ "css": [
+ "build/styles3.css",
+ "build/styles4.css"
+ ]
+ }
+ }
+}
diff --git a/tests/fixtures/manual_template.twig b/tests/fixtures/manual_template.twig
index 4cc1e129..c4111810 100644
--- a/tests/fixtures/manual_template.twig
+++ b/tests/fixtures/manual_template.twig
@@ -5,3 +5,11 @@
{% for cssFile in encore_entry_css_files('other_entry') %}
{% endfor %}
+
+{% for jsFile in encore_entry_js_files('next_entry', 'different_build') %}
+
+{% endfor %}
+
+{% for cssFile in encore_entry_css_files('next_entry', 'different_build') %}
+
+{% endfor %}
diff --git a/tests/fixtures/template.twig b/tests/fixtures/template.twig
index 0a50c3b3..11b66b92 100644
--- a/tests/fixtures/template.twig
+++ b/tests/fixtures/template.twig
@@ -1,2 +1,4 @@
{{ encore_entry_script_tags('my_entry') }}
{{ encore_entry_link_tags('my_entry') }}
+{{ encore_entry_script_tags('third_entry', null, 'different_build') }}
+{{ encore_entry_link_tags('third_entry', null, 'different_build') }}