Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ matrix:
env: deps=low
- php: 7.4
env: SYMFONY_PHPUNIT_VERSION=9.4
- php: nightly
- php: 8.0
env: SYMFONY_PHPUNIT_VERSION=9.4

before_install:
Expand Down
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ webpack_encore:
output_path: '%kernel.project_dir%/public/build'
# If multiple builds are defined (as shown below), you can disable the default build:
# output_path: false

# Set attributes that will be rendered on all script and link tags
# script_attributes:
# defer: true
# referrerpolicy: origin
# link_attributes:
# referrerpolicy: origin

# if using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
# crossorigin: 'anonymous'
Expand Down Expand Up @@ -84,6 +91,13 @@ For example, to render all of the `script` and `link` tags for a specific
{{ parent() }}

{{ encore_entry_script_tags('entry1') }}

{# or render a custom attribute #}
{#
{{ encore_entry_script_tags('entry1', attributes={
defer: true
}) }}
#}
{% endblock %}

{% block stylesheets %}
Expand Down Expand Up @@ -144,3 +158,44 @@ class SomeController
If you have multiple builds, you can also autowire
`Symfony\WebpackEncoreBundle\Asset\EntrypointLookupCollectionInterface`
and use it to get the `EntrypointLookupInterface` object for any build.

## Custom Attributes on script and link Tags

Custom attributes can be added to rendered `script` or `link` in 3
different ways:

1. Via global config (`script_attributes` and `link_attributes`) - see the
config example above.

1. When rendering in Twig - see the `attributes` option in the docs above.

1. By listening to the `Symfony\WebpackEncoreBundle\Event\RenderAssetTagEvent`
event. For example:

```php
<?php

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\WebpackEncoreBundle\Event\RenderAssetTagEvent;

class ScriptNonceSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
RenderAssetTagEvent::class => 'onRenderAssetTag'
];
}

public function onRenderAssetTag(RenderAssetTagEvent $event)
{
if ($event->isScriptTag()) {
$event->setAttribute('nonce', 'lookup nonce');
}
}
}
```

Ok, have fun!
16 changes: 8 additions & 8 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@
"minimum-stability": "dev",
"require": {
"php": ">=7.1.3",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably bump to 7.2 as Symfony 4.4 requires it. This would allow you to remove a CI job too :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah indeed!

"symfony/asset": "^3.4 || ^4.0 || ^5.0",
"symfony/config": "^3.4 || ^4.0 || ^5.0",
"symfony/dependency-injection": "^3.4 || ^4.0 || ^5.0",
"symfony/http-kernel": "^3.4 || ^4.0 || ^5.0",
"symfony/asset": "^4.4 || ^5.0",
"symfony/config": "^4.4 || ^5.0",
"symfony/dependency-injection": "^4.4 || ^5.0",
"symfony/http-kernel": "^4.4 || ^5.0",
"symfony/service-contracts": "^1.0 || ^2.0"
},
"require-dev": {
"symfony/framework-bundle": "^3.4 || ^4.0 || ^5.0",
"symfony/phpunit-bridge": "^4.3.5 || ^5.0",
"symfony/twig-bundle": "^3.4 || ^4.0 || ^5.0",
"symfony/web-link": "^3.4 || ^4.0 || ^5.0"
"symfony/framework-bundle": "^4.4 || ^5.0",
"symfony/phpunit-bridge": "^4.4 || ^5.0",
"symfony/twig-bundle": "^4.4 || ^5.0",
"symfony/web-link": "^4.4 || ^5.0"
},
"extra": {
"thanks": {
Expand Down
51 changes: 44 additions & 7 deletions src/Asset/TagRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,31 @@

use Symfony\Component\Asset\Packages;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Service\ResetInterface;
use Symfony\WebpackEncoreBundle\Event\RenderAssetTagEvent;

/**
* @final
*/
class TagRenderer implements ResetInterface
{
private $entrypointLookupCollection;

private $packages;

private $defaultAttributes;
private $defaultScriptAttributes;
private $defaultLinkAttributes;
private $eventDispatcher;

private $renderedFiles = [];

public function __construct(
$entrypointLookupCollection,
Packages $packages,
array $defaultAttributes = []
array $defaultAttributes = [],
array $defaultScriptAttributes = [],
array $defaultLinkAttributes = [],
EventDispatcherInterface $eventDispatcher = null
) {
if ($entrypointLookupCollection instanceof EntrypointLookupInterface) {
@trigger_error(sprintf('The "$entrypointLookupCollection" argument in method "%s()" must be an instance of EntrypointLookupCollection.', __METHOD__), E_USER_DEPRECATED);
Expand All @@ -47,24 +53,39 @@ public function __construct(

$this->packages = $packages;
$this->defaultAttributes = $defaultAttributes;
$this->defaultScriptAttributes = $defaultScriptAttributes;
$this->defaultLinkAttributes = $defaultLinkAttributes;
$this->eventDispatcher = $eventDispatcher;

$this->reset();
}

public function renderWebpackScriptTags(string $entryName, string $packageName = null, string $entrypointName = '_default'): string
public function renderWebpackScriptTags(string $entryName, string $packageName = null, string $entrypointName = null, array $extraAttributes = []): string
{
$entrypointName = $entrypointName ?: '_default';
$scriptTags = [];
$entryPointLookup = $this->getEntrypointLookup($entrypointName);
$integrityHashes = ($entryPointLookup instanceof IntegrityDataProviderInterface) ? $entryPointLookup->getIntegrityData() : [];

foreach ($entryPointLookup->getJavaScriptFiles($entryName) as $filename) {
$attributes = $this->defaultAttributes;
$attributes = [];
$attributes['src'] = $this->getAssetPath($filename, $packageName);
$attributes = array_merge($attributes, $this->defaultAttributes, $this->defaultScriptAttributes, $extraAttributes);

if (isset($integrityHashes[$filename])) {
$attributes['integrity'] = $integrityHashes[$filename];
}

$event = new RenderAssetTagEvent(
RenderAssetTagEvent::TYPE_SCRIPT,
$attributes['src'],
$attributes
);
if (null !== $this->eventDispatcher) {
$event = $this->eventDispatcher->dispatch($event);
}
$attributes = $event->getAttributes();

$scriptTags[] = sprintf(
'<script %s></script>',
$this->convertArrayToAttributes($attributes)
Expand All @@ -76,21 +97,33 @@ public function renderWebpackScriptTags(string $entryName, string $packageName =
return implode('', $scriptTags);
}

public function renderWebpackLinkTags(string $entryName, string $packageName = null, string $entrypointName = '_default'): string
public function renderWebpackLinkTags(string $entryName, string $packageName = null, string $entrypointName = null, array $extraAttributes = []): string
{
$entrypointName = $entrypointName ?: '_default';
$scriptTags = [];
$entryPointLookup = $this->getEntrypointLookup($entrypointName);
$integrityHashes = ($entryPointLookup instanceof IntegrityDataProviderInterface) ? $entryPointLookup->getIntegrityData() : [];

foreach ($entryPointLookup->getCssFiles($entryName) as $filename) {
$attributes = $this->defaultAttributes;
$attributes = [];
$attributes['rel'] = 'stylesheet';
$attributes['href'] = $this->getAssetPath($filename, $packageName);
$attributes = array_merge($attributes, $this->defaultAttributes, $this->defaultLinkAttributes, $extraAttributes);

if (isset($integrityHashes[$filename])) {
$attributes['integrity'] = $integrityHashes[$filename];
}

$event = new RenderAssetTagEvent(
RenderAssetTagEvent::TYPE_LINK,
$attributes['href'],
$attributes
);
if (null !== $this->eventDispatcher) {
$this->eventDispatcher->dispatch($event);
}
$attributes = $event->getAttributes();

$scriptTags[] = sprintf(
'<link %s>',
$this->convertArrayToAttributes($attributes)
Expand Down Expand Up @@ -146,6 +179,10 @@ private function convertArrayToAttributes(array $attributesMap): string
{
return implode(' ', array_map(
function ($key, $value) {
// allows for things like defer: true to only render "defer"
if ($value === true) {
return $key;
}
return sprintf('%s="%s"', $key, htmlentities($value));
},
array_keys($attributesMap),
Expand Down
17 changes: 16 additions & 1 deletion src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,31 @@ public function getConfigTreeBuilder()
->useAttributeAsKey('name')
->normalizeKeys(false)
->scalarPrototype()
->validate()
->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()
->arrayNode('script_attributes')
->info('Key/value pair of attributes to render on all script tags')
->example('{ defer: true, referrerpolicy: "origin" }')
->useAttributeAsKey('name')
->normalizeKeys(false)
->scalarPrototype()->end()
->end()
->arrayNode('link_attributes')
->info('Key/value pair of attributes to render on all CSS link tags')
->example('{ referrerpolicy: "origin" }')
->useAttributeAsKey('name')
->normalizeKeys(false)
->scalarPrototype()->end()
->end()
->end()
;

Expand Down
4 changes: 3 additions & 1 deletion src/DependencyInjection/WebpackEncoreExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ public function load(array $configs, ContainerBuilder $container)
}

$container->getDefinition('webpack_encore.tag_renderer')
->replaceArgument(2, $defaultAttributes);
->replaceArgument(2, $defaultAttributes)
->replaceArgument(3, $config['script_attributes'])
->replaceArgument(4, $config['link_attributes']);

if ($config['preload']) {
if (!class_exists(AddLinkHeaderListener::class)) {
Expand Down
64 changes: 64 additions & 0 deletions src/Event/RenderAssetTagEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

/*
* This file is part of the Symfony WebpackEncoreBundle package.
* (c) Fabien Potencier <[email protected]>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\WebpackEncoreBundle\Event;

/**
* Dispatched each time a script or link tag is rendered.
*/
final class RenderAssetTagEvent
{
public const TYPE_SCRIPT = 'script';
public const TYPE_LINK = 'link';

private $type;
private $url;
private $attributes;

public function __construct(string $type, string $url, array $attributes)
{
$this->type = $type;
$this->url = $url;
$this->attributes = $attributes;
}

public function isScriptTag(): bool
{
return $this->type === self::TYPE_SCRIPT;
}

public function isLinkTag(): bool
{
return $this->type === self::TYPE_LINK;
}

public function getUrl(): string
{
return $this->url;
}

public function getAttributes(): array
{
return $this->attributes;
}

/**
* @param string $name The attribute name
* @param string|bool $value Value can be "true" to have an attribute without a value (e.g. "defer")
*/
public function setAttribute(string $name, $value): void
{
$this->attributes[$name] = $value;
}

public function removeAttribute(string $name): void
{
unset($this->attributes[$name]);
}
}
5 changes: 4 additions & 1 deletion src/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
<tag name="kernel.reset" method="reset" />
<argument type="service" id="webpack_encore.entrypoint_lookup_collection" />
<argument type="service" id="assets.packages" />
<argument type="collection" />
<argument type="collection"/> <!-- Default attributes-->
<argument type="collection"/> <!-- Default script attributes -->
<argument type="collection"/> <!-- Default link attributes -->
<argument type="service" id="event_dispatcher" />
</service>

<service id="webpack_encore.twig_entry_files_extension" class="Symfony\WebpackEncoreBundle\Twig\EntryFilesTwigExtension">
Expand Down
8 changes: 4 additions & 4 deletions src/Twig/EntryFilesTwigExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,16 @@ public function getWebpackCssFiles(string $entryName, string $entrypointName = '
->getCssFiles($entryName);
}

public function renderWebpackScriptTags(string $entryName, string $packageName = null, string $entrypointName = '_default'): string
public function renderWebpackScriptTags(string $entryName, string $packageName = null, string $entrypointName = '_default', array $attributes = []): string
{
return $this->getTagRenderer()
->renderWebpackScriptTags($entryName, $packageName, $entrypointName);
->renderWebpackScriptTags($entryName, $packageName, $entrypointName, $attributes);
}

public function renderWebpackLinkTags(string $entryName, string $packageName = null, string $entrypointName = '_default'): string
public function renderWebpackLinkTags(string $entryName, string $packageName = null, string $entrypointName = '_default', array $attributes = []): string
{
return $this->getTagRenderer()
->renderWebpackLinkTags($entryName, $packageName, $entrypointName);
->renderWebpackLinkTags($entryName, $packageName, $entrypointName, $attributes);
}

private function getEntrypointLookup(string $entrypointName): EntrypointLookupInterface
Expand Down
Loading