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

100% Statements 43/43
100% Branches 8/8
100% Functions 10/10
100% Lines 43/43

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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115  26x                 26x 26x         26x   1525x 44x         26x 44x     44x     44x 44x               44x     44x     44x 44x     26x 47x     47x   87x     87x     87x     87x     47x     26x 87x   87x 25x 25x     87x     26x   89x 11x   78x       26x 89x 11x   78x       26x 48x   30x     30x         30x   30x    
import {HtmlCompilerModule, HtmlCompilerContext} from '../htmlCompiler';
import {
    MFragmentNode,
    DocumentNode,
    FragmentContext,
    Node,
    MContentNode,
    ScopeData,
    SlotReferenceNode
} from '../../..';
import {resolveResPath} from '../../../fs/pathUtils';
import {convertSnakeCaseToCamelCase} from '../../../util/caseUtils';
 
/**
 * Resolve <m-fragment> and replace with compiled HTML 
 */
export class FragmentModule implements HtmlCompilerModule {
    async exitNode(htmlContext: HtmlCompilerContext): Promise<void> {
        if (MFragmentNode.isMFragmentNode(htmlContext.node) ) {
            await replaceFragment(htmlContext.node, htmlContext);
        }
    }
}
 
export async function replaceFragment(mFragment: MFragmentNode, htmlContext: HtmlCompilerContext): Promise<void> {
    const pipelineContext = htmlContext.pipelineContext;
 
    // get slot contents
    const slotContents: Map<string, DocumentNode> = extractSlotContents(mFragment);
 
    // create usage context
    const parentFragmentContext = pipelineContext.fragmentContext;
    const fragmentContext: FragmentContext = {
        slotContents: slotContents,
        scope: createFragmentScope(mFragment),
        fragmentResPath: mFragment.src,
        rootResPath: parentFragmentContext.rootResPath
    };
 
    // compute path to fragment
    const fragmentPath = resolveResPath(mFragment.src, pipelineContext.fragment.path);
 
    // call pipeline to load reference
    const fragment = await pipelineContext.pipeline.compileFragment(fragmentPath, fragmentContext);
 
    // replace with compiled fragment
    mFragment.replaceSelf(fragment.dom.childNodes);
    htmlContext.setDeleted();
}
 
export function extractSlotContents(mFragment: MFragmentNode): Map<string, DocumentNode> {
    const slotMap = new Map<string, DocumentNode>();
 
    // loop through all direct children of fragment reference
    for (const node of Array.from(mFragment.childNodes)) {
        // get content for this slot
        const slotContents: Node[] = convertNodeToContent(node);
 
        // check if it specified a slot
        const slotName: string = getContentTargetName(node);
 
        // get dom for this slot
        const slotDom: DocumentNode = getContentDom(slotMap, slotName);
 
        // add slot contents to new DOM (will automatically detach them)
        slotDom.appendChildren(slotContents);
    }
 
    return slotMap;
}
 
export function getContentDom(slotMap: Map<string, DocumentNode>, slotName: string): DocumentNode {
    let slotDom: DocumentNode | undefined = slotMap.get(slotName);
 
    if (slotDom === undefined) {
        slotDom = new DocumentNode();
        slotMap.set(slotName, slotDom);
    }
 
    return slotDom;
}
 
export function convertNodeToContent(node: Node): Node[] {
    // check if this element is an m-content
    if (MContentNode.isMContentNode(node)) {
        return node.childNodes;
    } else {
        return [ node ];
    }
}
 
export function getContentTargetName(node: Node): string {
    if (MContentNode.isMContentNode(node)) {
        return node.slot;
    } else {
        return SlotReferenceNode.DefaultSlotName;
    }
}
 
export function createFragmentScope(fragmentNode: MFragmentNode): ScopeData {
    return fragmentNode.parameters
        // get parameter names
        .map(param => param[0])
 
        // convert parameter / attribute names to scope names
        .map(parameterName => convertSnakeCaseToCamelCase(parameterName))
 
        // copy all data from MFragmentNode scope to new fragment scope
        .reduce<ScopeData>((fragmentScope, scopeName) => {
            // copy current parameter from scope prototype chain into an isolated ScopeData
            fragmentScope[scopeName] = fragmentNode.nodeData[scopeName];
 
            return fragmentScope;
        }, {});
}