"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createBuildHtmlPlugin = void 0;
const path_1 = __importDefault(require("path"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const magic_string_1 = __importDefault(require("magic-string"));
const utils_1 = require("../utils");
const buildPluginAsset_1 = require("./buildPluginAsset");
exports.createBuildHtmlPlugin = async (root, indexPath, publicBasePath, assetsDir, inlineLimit, resolver, shouldPreload, config) => {
    if (!fs_extra_1.default.existsSync(indexPath)) {
        return {
            renderIndex: () => '',
            htmlPlugin: null
        };
    }
    const rawHtml = await fs_extra_1.default.readFile(indexPath, 'utf-8');
    const preprocessedHtml = await utils_1.transformIndexHtml(rawHtml, config.indexHtmlTransforms, 'pre', true);
    const assets = new Map();
    let { html: processedHtml, js } = await compileHtml(root, preprocessedHtml, publicBasePath, assetsDir, inlineLimit, resolver, assets);
    const htmlPlugin = {
        name: 'vite:html',
        async load(id) {
            if (id === indexPath) {
                return js;
            }
        },
        generateBundle(_options, bundle) {
            buildPluginAsset_1.registerAssets(assets, bundle);
        }
    };
    const injectCSS = (html, filename) => {
        const tag = `<link rel="stylesheet" href="${publicBasePath}${path_1.default.posix.join(assetsDir, filename)}">`;
        if (/<\/head>/.test(html)) {
            return html.replace(/<\/head>/, `${tag}\n</head>`);
        }
        else {
            return tag + '\n' + html;
        }
    };
    const injectScript = (html, filename) => {
        filename = utils_1.isExternalUrl(filename)
            ? filename
            : `${publicBasePath}${path_1.default.posix.join(assetsDir, filename)}`;
        const tag = `<script type="module" src="${filename}"></script>`;
        if (/<\/head>/.test(html)) {
            return html.replace(/<\/head>/, `${tag}\n</head>`);
        }
        else {
            return html + '\n' + tag;
        }
    };
    const injectPreload = (html, filename) => {
        filename = utils_1.isExternalUrl(filename)
            ? filename
            : `${publicBasePath}${path_1.default.posix.join(assetsDir, filename)}`;
        const tag = `<link rel="modulepreload" href="${filename}" />`;
        if (/<\/head>/.test(html)) {
            return html.replace(/<\/head>/, `${tag}\n</head>`);
        }
        else {
            return tag + '\n' + html;
        }
    };
    const renderIndex = async (bundleOutput) => {
        let result = processedHtml;
        for (const chunk of bundleOutput) {
            if (chunk.type === 'chunk') {
                if (chunk.isEntry) {
                    // js entry chunk
                    result = injectScript(result, chunk.fileName);
                }
                else if (shouldPreload && shouldPreload(chunk)) {
                    // async preloaded chunk
                    result = injectPreload(result, chunk.fileName);
                }
            }
            else {
                // imported css chunks
                if (chunk.fileName.endsWith('.css') &&
                    chunk.source &&
                    !assets.has(chunk.fileName)) {
                    result = injectCSS(result, chunk.fileName);
                }
            }
        }
        return await utils_1.transformIndexHtml(result, config.indexHtmlTransforms, 'post', true);
    };
    return {
        renderIndex,
        htmlPlugin
    };
};
// this extends the config in @vue/compiler-sfc with <link href>
const assetAttrsConfig = {
    link: ['href'],
    video: ['src', 'poster'],
    source: ['src'],
    img: ['src'],
    image: ['xlink:href', 'href'],
    use: ['xlink:href', 'href']
};
// compile index.html to a JS module, importing referenced assets
// and scripts
const compileHtml = async (root, html, publicBasePath, assetsDir, inlineLimit, resolver, assets) => {
    const { parse, transform } = require('@vue/compiler-dom');
    // @vue/compiler-core doesn't like lowercase doctypes
    html = html.replace(/<!doctype\s/i, '<!DOCTYPE ');
    const ast = parse(html);
    let js = '';
    const s = new magic_string_1.default(html);
    const assetUrls = [];
    const viteHtmlTransform = (node) => {
        if (node.type === 1 /* ELEMENT */) {
            if (node.tag === 'script') {
                let shouldRemove = false;
                const srcAttr = node.props.find((p) => p.type === 6 /* ATTRIBUTE */ && p.name === 'src');
                const typeAttr = node.props.find((p) => p.type === 6 /* ATTRIBUTE */ && p.name === 'type');
                const isJsModule = typeAttr && typeAttr.value && typeAttr.value.content === 'module';
                if (isJsModule) {
                    if (srcAttr && srcAttr.value) {
                        if (!utils_1.isExternalUrl(srcAttr.value.content)) {
                            // <script type="module" src="..."/>
                            // add it as an import
                            js += `\nimport ${JSON.stringify(srcAttr.value.content)}`;
                            shouldRemove = true;
                        }
                    }
                    else if (node.children.length) {
                        // <script type="module">...</script>
                        // add its content
                        // TODO: if there are multiple inline module scripts on the page,
                        // they should technically be turned into separate modules, but
                        // it's hard to imagine any reason for anyone to do that.
                        js += `\n` + node.children[0].content.trim() + `\n`;
                        shouldRemove = true;
                    }
                }
                if (shouldRemove) {
                    // remove the script tag from the html. we are going to inject new
                    // ones in the end.
                    s.remove(node.loc.start.offset, node.loc.end.offset);
                }
            }
            // For asset references in index.html, also generate an import
            // statement for each - this will be handled by the asset plugin
            const assetAttrs = assetAttrsConfig[node.tag];
            if (assetAttrs) {
                for (const p of node.props) {
                    if (p.type === 6 /* ATTRIBUTE */ &&
                        p.value &&
                        assetAttrs.includes(p.name) &&
                        !utils_1.isExternalUrl(p.value.content) &&
                        !utils_1.isDataUrl(p.value.content)) {
                        assetUrls.push(p);
                    }
                }
            }
        }
    };
    transform(ast, {
        nodeTransforms: [viteHtmlTransform]
    });
    // for each encountered asset url, rewrite original html so that it
    // references the post-build location.
    for (const attr of assetUrls) {
        const value = attr.value;
        const { fileName, content, url } = await buildPluginAsset_1.resolveAsset(resolver.requestToFile(value.content), root, publicBasePath, assetsDir, utils_1.cleanUrl(value.content).endsWith('.css') ? 0 : inlineLimit);
        s.overwrite(value.loc.start.offset, value.loc.end.offset, `"${url}"`);
        if (fileName && content) {
            assets.set(fileName, content);
        }
    }
    return {
        html: s.toString(),
        js
    };
};
//# sourceMappingURL=buildPluginHtml.js.map