Добавление атрибута “defer” к тегам скрипта clientLib в AEM 6.5

#frontend #aem

#интерфейс #aem

Вопрос:

Пожалуйста, дайте проверенное и проверенное решение для этого, с правильными шагами того, что нужно сделать. Заранее спасибо

Ответ №1:

Самым простым, вероятно, является просто написать тег включения clientlib вручную (например, просто написать <script defer src="/path/to/clientlib.js"></script> ).

Единственное другое решение, которое я нашел до сих пор, — создать фабрику перезаписи, которая делает это. Код в основном скопирован из acs-commons.

 import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Stream;

import lombok.ToString;
import lombok.experimental.Delegate;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.rewriter.ProcessingComponentConfiguration;
import org.apache.sling.rewriter.ProcessingContext;
import org.apache.sling.rewriter.Transformer;
import org.apache.sling.rewriter.TransformerFactory;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.FieldOption;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.propertytypes.ServiceDescription;
import org.osgi.service.component.propertytypes.ServiceRanking;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import com.adobe.granite.ui.clientlibs.ClientLibrary;
import com.adobe.granite.ui.clientlibs.HtmlLibrary;
import com.adobe.granite.ui.clientlibs.HtmlLibraryManager;
import com.adobe.granite.ui.clientlibs.LibraryType;

@Component(
    property = "pipeline.type=clientlib-async-include-transformer",
    service = TransformerFactory.class
)
@ServiceDescription(
    "Add defer/async to clientlib include script tags."
)
@ServiceRanking(2)
@Slf4j
@ToString
public final class ClientlibIncludeRewriterTransformerFactory implements TransformerFactory {
    private static final String PROXY_PREFIX = "/etc.clientlibs/";

    private static final String MIN_SELECTOR = "min";
    private static final String SELECTOR_EXTENSION_SEPARATOR = ".";
    private static final String MIN_SELECTOR_SEGMENT = SELECTOR_EXTENSION_SEPARATOR   MIN_SELECTOR;

    @Reference
    private HtmlLibraryManager htmlLibraryManager;

    @ToString.Exclude
    private Map<String, ClientLibrary> clientLibrariesCache;

    @Override
    public Transformer createTransformer() {
        return new ClientlibIncludeRewriterTransformer();
    }

    private Attributes rewrite(
        final String elementName,
        final String linkAttribute,
        final Attributes attrs,
        final SlingHttpServletRequest request
    ) {
        final String linkedPath = attrs.getValue(StringUtils.EMPTY, linkAttribute);
        final HtmlLibrary clientLibrary = getLibrary(linkedPath, request);
        if (clientLibrary == null) {
            log.info("{} include {} does not point to a client library", elementName, linkedPath);
            return attrs;
        }
        final AttributesImpl newAttributes = new AttributesImpl(attrs);
        if (clientLibrary.getType() != LibraryType.JS) {
            return attrs;
        }
        
        final Resource library = request.getResourceResolver().getResource(clientLibrary.getLibraryPath());
        if (library == null) {
            return attrs;
        }
        
        if (library.getValueMap().get("useAsync", Boolean.FALSE)) {
            newAttributes.addAttribute(StringUtils.EMPTY, "async", "async", "CDATA", null);
        }
        
        if (library.getValueMap().get("useDefer", Boolean.FALSE)) {
            newAttributes.addAttribute(StringUtils.EMPTY, "defer", "defer", "CDATA", null);
        }
        return newAttributes;
    }

    private HtmlLibrary getLibrary(final String linkedPath, final SlingHttpServletRequest request) {
        final String contextPath = request.getContextPath();
        String libraryPath = linkedPath;
        if (StringUtils.isNotBlank(contextPath)) {
            libraryPath = libraryPath.substring(contextPath.length());
        }
        final String extension = StringUtils.substringAfterLast(libraryPath, SELECTOR_EXTENSION_SEPARATOR);
        final LibraryType libraryType = getLibraryTypeFromExtension(extension);
        if (libraryType == null) {
            return null;
        }
        libraryPath = StringUtils.substringBeforeLast(libraryPath, SELECTOR_EXTENSION_SEPARATOR);
        if (libraryPath.endsWith(MIN_SELECTOR_SEGMENT)) {
            libraryPath = StringUtils.removeEnd(libraryPath, MIN_SELECTOR_SEGMENT);
        }
        final ResourceResolver resourceResolver = request.getResourceResolver();
        final String resolvedLibraryPath = resolvePathIfProxied(libraryType, libraryPath, resourceResolver);
        if (resolvedLibraryPath == null) {
            return null;
        }
        return htmlLibraryManager.getLibrary(libraryType, resolvedLibraryPath);
    }

    private LibraryType getLibraryTypeFromExtension(final String extension) {
        for (LibraryType libraryType : LibraryType.values()) {
            if (StringUtils.equals(libraryType.extension, SELECTOR_EXTENSION_SEPARATOR   extension)) {
                return libraryType;
            }
        }
        return null;
    }

    private String resolvePathIfProxied(
        final LibraryType libraryType,
        final String libraryPath,
        final ResourceResolver resourceResolver
    ) {
        if (!libraryPath.startsWith(PROXY_PREFIX)) {
            return libraryPath;
        }
        return resolveProxiedClientLibrary(libraryType, libraryPath, resourceResolver, true);
    }

    private String resolveProxiedClientLibrary(
        final LibraryType libraryType,
        final String proxiedPath,
        final ResourceResolver resourceResolver,
        final boolean refreshCacheIfNotFound
    ) {
        final String relativePath = proxiedPath.substring(PROXY_PREFIX.length());
        for (final String prefix : resourceResolver.getSearchPath()) {
            final String absolutePath = prefix   relativePath;
            // check whether the ClientLibrary exists before calling HtmlLibraryManager#getLibrary in order
            // to avoid WARN log messages that are written when an unknown HtmlLibrary is requested
            if (hasProxyClientLibrary(libraryType, absolutePath)) {
                return absolutePath;
            }
        }

        if (refreshCacheIfNotFound) {
            // maybe the library has appeared and our copy of the cache is stale
            log.info("Refreshing client libraries cache, because {} could not be found", proxiedPath);
            clientLibrariesCache = null;
            return resolveProxiedClientLibrary(libraryType, proxiedPath, resourceResolver, false);
        }
        return null;
    }

    private boolean hasProxyClientLibrary(final LibraryType type, final String path) {
        final ClientLibrary clientLibrary = getClientLibrary(path);
        return clientLibrary != null amp;amp; clientLibrary.allowProxy() amp;amp; clientLibrary.getTypes().contains(type);
    }

    private ClientLibrary getClientLibrary(String path) {
        if (clientLibrariesCache == null) {
            clientLibrariesCache = Collections.unmodifiableMap(htmlLibraryManager.getLibraries());
        }
        return clientLibrariesCache.get(path);
    }

    private interface StartElement {
        void startElement(
            String namespaceURI,
            String localName,
            String qName,
            Attributes attrs
        ) throws SAXException;
    }

    private class ClientlibIncludeRewriterTransformer implements Transformer, StartElement {
        public static final String STYLESHEET_LINK_ATTR = "href";
        public static final String SCRIPT_LINK_ATTR = "src";

        private SlingHttpServletRequest request;

        @Delegate(types = ContentHandler.class, excludes = StartElement.class)
        private ContentHandler contentHandler;

        @Override
        public void init(ProcessingContext context, ProcessingComponentConfiguration config) {
            request = context.getRequest();
        }

        @Override
        public void dispose() {
            contentHandler = null;
        }

        @Override
        public void startElement(
            final String namespaceURI,
            final String localName,
            final String qName,
            final Attributes attrs
        ) throws SAXException {
            Attributes nextAttributes = attrs;
            final String linkAttribute = getApplicableAttribute(localName, attrs);
            if (linkAttribute != null) {
                nextAttributes = rewrite(localName, linkAttribute, attrs, request);
            }
            contentHandler.startElement(namespaceURI, localName, qName, nextAttributes);
        }

        /**
         * Determines if the element given is applicable to be transformed and
         * returns the name of the attribute that contains the clientlib reference.
         * <p>
         * The following elements are deemed applicable
         * <p>
         * – Script elements with a src attribute (src is returned)
         * – Link elements with a rel set to stylesheet and a href attribute (href is returned)
         *
         * @param localName the element name with namespaces removed
         * @param attrs     the attributes currently on the element
         * @return the name of the attribute to check or null if not an applicable element
         */
        private String getApplicableAttribute(
            final String localName,
            final Attributes attrs
        ) {
            if (isValidScriptLink(localName, attrs)) {
                return SCRIPT_LINK_ATTR;
            }
            if (isValidStylesheetLink(localName, attrs)) {
                return STYLESHEET_LINK_ATTR;
            }
            return null;
        }

        private boolean isValidScriptLink(final String localName, final Attributes attrs) {
            if (!StringUtils.equalsIgnoreCase(localName, "script")) {
                return false;
            }
            // Element is a valid script include if it has a src attribute
            return StringUtils.isNotBlank(attrs.getValue(StringUtils.EMPTY, SCRIPT_LINK_ATTR));

        }

        private boolean isValidStylesheetLink(final String localName, final Attributes attrs) {
            if (!StringUtils.equalsIgnoreCase(localName, "link")) {
                return false;
            }
            // According to specs, the rel attribute is a whitespace-separated list of rel tokens
            final String[] rels = StringUtils.defaultIfBlank(
                attrs.getValue(StringUtils.EMPTY, "rel"),
                StringUtils.EMPTY
            ).split("\s ");
            if (Stream.of(rels).noneMatch("stylesheet"::equals)) {
                // Not a link to a stylesheet
                return false;
            }
            // Element is a valid stylesheet include if it has an href attribute
            return StringUtils.isNotBlank(attrs.getValue(StringUtils.EMPTY, STYLESHEET_LINK_ATTR));
        }

        @Override
        public void setContentHandler(ContentHandler handler) {
            contentHandler = handler;
        }
    }
}
 

Для этого требуется конфигурация перезаписи, аналогичная конфигурации, описанной для acs-commons: https://adobe-consulting-services.github.io/acs-aem-commons/features/versioned-clientlibs/index.html#rewriter-configuration-node где-нибудь в разделе /apps/*/config/rewriter:

 <?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="sling:Folder"
    contentTypes="[text/html]"
    enabled="{Boolean}true"
    generatorType="htmlparser"
    order="{Long}1"
    serializerType="htmlwriter"
    transformerTypes="[linkchecker,clientlib-async-include-transformer]"/>
 

Затем все, что вам нужно сделать, это добавить useAsync или useDefer логические свойства к вашему cq:ClientLibraryFolder узлу.

Ответ №2:

Для этого вам необходимо создать новую папку clientlib-asyn внутри приложений, которые имеют clientlib.html и granite.html ClientLibUseObject.java затем вы можете вызвать свой компонент clientlib с новым clientlib-async, например:

 <sly data-sly-use.clientLibAsync="/apps/clientlib-async/sightly/templates/clientlib.html" data-sly-call="${clientLibAsync.js @ categories='clientlib-async-sample.async-sample', loading='async', onload='sayHello()'}" data-sly-unwrap/>
 

https://github.com/nateyolles/aem-clientlib-async
следуйте этому, это действительно полезно

Комментарии:

1. Хотя эта ссылка может дать ответ на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы только для ссылок могут стать недействительными, если связанная страница изменится.