【问题标题】:Adding “defer” attribute to clientLib script tags in AEM 6.5在 AEM 6.5 中向 clientLib 脚本标签添加“defer”属性
【发布时间】:2020-12-11 09:24:19
【问题描述】:

请为此提供久经考验的解决方案,并提供需要完成的正确步骤。提前致谢

【问题讨论】:

    标签: frontend 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 && clientLibrary.allowProxy() && 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]"/>
    

    然后,您所要做的就是将 useAsyncuseDefer 布尔属性添加到您的 cq:ClientLibraryFolder 节点。

    【讨论】:

      【解决方案2】:

      为此,您需要在具有 clientlib.html 和 granite.html ClientLibUseObject.java 的应用程序中创建新的 clientlib-asyn 文件夹,然后您可以使用新的 clientlib-async 调用组件 clientlib 例如:

      <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 关注这个真的很有帮助

      【讨论】:

      • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-03
      • 1970-01-01
      • 2021-06-14
      相关资源
      最近更新 更多