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

57.14% Statements 20/35
50% Branches 5/10
60% Functions 3/5
57.14% Lines 20/35

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  26x                   26x   26x   1576x   1576x   2x 2x   1574x   3x 3x 3x           5x   5x 5x                     5x   5x     5x 5x     5x 5x                                                      
import {HtmlCompilerContext, HtmlCompilerModule} from '../htmlCompiler';
import {
    CompiledStyleNode,
    ExternalStyleNode,
    InternalStyleNode,
    MimeType,
    StyleNodeBind,
    TagNode,
    TextNode,
    UncompiledStyleNode
} from '../../..';
import {resolveResPath} from '../../../fs/pathUtils';
 
export class StyleModule implements HtmlCompilerModule {
    async enterNode(htmlContext: HtmlCompilerContext): Promise<void> {
        const pipelineContext = htmlContext.pipelineContext;
 
        if (InternalStyleNode.isInternalStyleNode(htmlContext.node)) {
            // internal (inline) CSS
            const src = pipelineContext.fragment.path;
            await compileStyle(htmlContext.node, htmlContext.node.styleContent, src, htmlContext);
 
        } else if (ExternalStyleNode.isExternalStyleNode(htmlContext.node)) {
            // external CSS
            const resPath = resolveResPath(htmlContext.node.src, pipelineContext.fragment.path);
            const styleContent = await pipelineContext.pipeline.getRawText(resPath, MimeType.CSS);
            await compileStyle(htmlContext.node, styleContent, htmlContext.node.src, htmlContext);
        }
    }
}
 
async function compileStyle(node: CompiledStyleNode, styleContent: string, src: string, htmlContext: HtmlCompilerContext): Promise<void> {
    switch (node.bind) {
        case StyleNodeBind.HEAD:
            compileStyleHead(node, styleContent, htmlContext);
            break;
        case StyleNodeBind.LINK:
            await compileStyleLink(node, src, styleContent, htmlContext);
            break;
        default:
            throw new Error(`Unknown StyleNodeBind value: '${ node.bind }'`);
    }
}
 
function compileStyleHead(currentNode: CompiledStyleNode, styleContent: string, htmlContext: HtmlCompilerContext): void {
    // create text to hold CSS
    const styleText = new TextNode(styleContent);
    // if this formatting should be skipped, then set whitespace sensitivity
    styleText.isWhitespaceSensitive = currentNode.skipFormat;
 
    // create style node
    const styleNode = new UncompiledStyleNode(currentNode.lang);
    styleNode.appendChild(styleText);
 
    // replace compile node
    currentNode.replaceSelf([ styleNode ]);
    htmlContext.setDeleted();
}
 
function getMimeTypeForLang(lang: string | undefined): MimeType {
    switch (lang) {
        case 'sass': return MimeType.SASS;
        case 'scss': return MimeType.SCSS;
        default: return MimeType.CSS;
    }
}
 
async function compileStyleLink(currentNode: CompiledStyleNode, src: string, styleContent: string, htmlContext: HtmlCompilerContext): Promise<void> {
    const rootResPath = htmlContext.pipelineContext.fragmentContext.rootResPath;
 
    // write external CSS
    const mimeType = getMimeTypeForLang(currentNode.lang);
    const styleResPath = await htmlContext.pipelineContext.pipeline.linkResource(mimeType, styleContent, rootResPath);
 
    // create link
    const link = new TagNode('link');
    link.setAttribute('rel', 'stylesheet');
    link.setAttribute('href', styleResPath);
 
    // replace
    currentNode.replaceSelf([ link ]);
    htmlContext.setDeleted();
}