All files / lib/pipeline/module/compiler deduplicateModule.ts

100% Statements 23/23
100% Branches 16/16
100% Functions 3/3
100% Lines 23/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83  26x           26x         26x   1572x       1572x   1019x   1019x 30x   989x 4x                               26x     35x     35x   19x     16x 16x                       26x   8x 7x     7x   3x 3x       4x      
import {HtmlCompilerModule, HtmlCompilerContext} from '../htmlCompiler';
import {StyleNode, TagNode} from '../../..';
 
/**
 * Node tag that indicates that a node has already been processed by DeduplicateModule.
 * Nodes with this tag set will be skipped.
 */
export const NODE_TAG_IS_DEDUPLICATED = 'DeduplicateModule.IsDeduplicated';
 
/**
 * Removes unnecessary duplicate nodes from the DOM.
 */
export class DeduplicateModule implements HtmlCompilerModule {
    enterNode(htmlContext: HtmlCompilerContext): void {
        const node = htmlContext.node;
 
        // Don't process nodes more than once.
        // This can happen if a style or link node is generated inside a nested fragment.
        if (!node.nodeTags.has(NODE_TAG_IS_DEDUPLICATED)) {
            // mark as processed
            node.nodeTags.add(NODE_TAG_IS_DEDUPLICATED);
 
            if (StyleNode.isStyleNode(node)) {
                dedupeStyle(node, htmlContext);
 
            } else if (TagNode.isTagNode(node) && node.tagName === 'link') {
                dedupeLink(node, htmlContext);
            }
        }
    }
}
 
/**
 * Removes or remembers a style tag.
 * If the contents of the style are unique, then it is preserved and the contents are remembered.
 * If the contents are not unique, then the node is removed.
 *
 * TODO keep duplicates if they have different MIME types
 *
 * @param styleNode Style node to process
 * @param htmlContext current context
 */
export function dedupeStyle(styleNode: StyleNode, htmlContext: HtmlCompilerContext): void {
    // get the style text from the node.
    // at this point in compilation the only style nodes should be raw (uncompiled) so there should only be 0 or 1 child node
    const styleTextNode = styleNode.firstChildText;
 
    // check if it contains unique styles
    if (styleTextNode?.hasContent && !htmlContext.pipelineContext.stylesInPage.has(styleTextNode.textContent)) {
        // this style is unique, so save it
        htmlContext.pipelineContext.stylesInPage.add(styleTextNode.textContent);
    } else {
        // if not unique, then cull;
        styleNode.removeSelf();
        htmlContext.setDeleted();
    }
}
 
/**
 * Removes or remembers a link tag.
 * If the href of the link is unique, then it is preserved and the href are remembered.
 * If the href is not unique, then the node is removed.
 *
 * @param linkNode link tag to process
 * @param htmlContext current context
 */
export function dedupeLink(linkNode: TagNode, htmlContext: HtmlCompilerContext): void {
    // ignore links that don't go anywhere
    if (linkNode.hasValueAttribute('href')) {
        const href = linkNode.getRequiredValueAttribute('href');
 
        // check if we have seen this link before
        if (htmlContext.pipelineContext.linksInPage.has(href)) {
            // duplicate, so cull it
            linkNode.removeSelf();
            htmlContext.setDeleted();
 
        } else {
            // unique link, so save it
            htmlContext.pipelineContext.linksInPage.add(href);
        }
    }
}