"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
try {
    require.resolve('@vue/compiler-sfc');
}
catch (e) {
    throw new Error('rollup-plugin-vue requires @vue/compiler-sfc to be present in the dependency ' +
        'tree.');
}
const compiler_sfc_1 = require("@vue/compiler-sfc");
const fs_1 = __importDefault(require("fs"));
const debug_1 = __importDefault(require("debug"));
const hash_sum_1 = __importDefault(require("hash-sum"));
const path_1 = require("path");
const querystring_1 = __importDefault(require("querystring"));
const rollup_pluginutils_1 = require("rollup-pluginutils");
const debug = debug_1.default('rollup-plugin-vue');
const defaultOptions = {
    include: /\.vue$/,
    exclude: [],
    target: 'browser',
    exposeFilename: false,
    customBlocks: [],
};
function PluginVue(userOptions = {}) {
    const options = {
        ...defaultOptions,
        ...userOptions,
    };
    const isServer = options.target === 'node';
    const isProduction = process.env.NODE_ENV === 'production' || process.env.BUILD === 'production';
    const rootContext = process.cwd();
    const filter = rollup_pluginutils_1.createFilter(options.include, options.exclude);
    const filterCustomBlock = createCustomBlockFilter(options.customBlocks);
    return {
        name: 'vue',
        async resolveId(id, importer) {
            const query = parseVuePartRequest(id);
            if (query.vue) {
                if (query.src) {
                    const resolved = await this.resolve(query.filename, importer, {
                        skipSelf: true,
                    });
                    if (resolved) {
                        cache.set(resolved.id, getDescriptor(importer));
                        const [, originalQuery] = id.split('?', 2);
                        resolved.id += `?${originalQuery}`;
                        return resolved;
                    }
                }
                else if (!filter(query.filename)) {
                    return undefined;
                }
                debug(`resolveId(${id})`);
                return id;
            }
            return undefined;
        },
        load(id) {
            const query = parseVuePartRequest(id);
            if (query.vue) {
                if (query.src) {
                    return fs_1.default.readFileSync(query.filename, 'utf-8');
                }
                const descriptor = getDescriptor(query.filename);
                if (descriptor) {
                    const block = query.type === 'template'
                        ? descriptor.template
                        : query.type === 'script'
                            ? descriptor.script
                            : query.type === 'style'
                                ? descriptor.styles[query.index]
                                : typeof query.index === 'number'
                                    ? descriptor.customBlocks[query.index]
                                    : null;
                    if (block) {
                        return {
                            code: block.content,
                            map: normalizeSourceMap(block.map, id),
                        };
                    }
                }
            }
            return undefined;
        },
        async transform(code, id) {
            const query = parseVuePartRequest(id);
            if (query.vue) {
                if (!query.src && !filter(query.filename))
                    return null;
                const descriptor = getDescriptor(query.filename);
                const hasScoped = descriptor.styles.some((s) => s.scoped);
                if (query.src) {
                    this.addWatchFile(query.filename);
                }
                if (query.type === 'template') {
                    debug(`transform(${id})`);
                    const block = descriptor.template;
                    const preprocessLang = block.lang;
                    const preprocessOptions = preprocessLang &&
                        options.templatePreprocessOptions &&
                        options.templatePreprocessOptions[preprocessLang];
                    const result = compiler_sfc_1.compileTemplate({
                        filename: query.filename,
                        source: code,
                        inMap: query.src ? undefined : block.map,
                        preprocessLang,
                        preprocessOptions,
                        preprocessCustomRequire: options.preprocessCustomRequire,
                        compiler: options.compiler,
                        ssr: isServer,
                        compilerOptions: {
                            ...options.compilerOptions,
                            scopeId: hasScoped ? `data-v-${query.id}` : undefined,
                            bindingMetadata: descriptor.script
                                ? descriptor.script.bindings
                                : undefined,
                        },
                        transformAssetUrls: options.transformAssetUrls,
                    });
                    if (result.errors.length) {
                        result.errors.forEach((error) => this.error(typeof error === 'string'
                            ? { id: query.filename, message: error }
                            : createRollupError(query.filename, error)));
                        return null;
                    }
                    if (result.tips.length) {
                        result.tips.forEach((tip) => this.warn({
                            id: query.filename,
                            message: tip,
                        }));
                    }
                    return {
                        code: result.code,
                        map: normalizeSourceMap(result.map, id),
                    };
                }
                else if (query.type === 'style') {
                    debug(`transform(${id})`);
                    const block = descriptor.styles[query.index];
                    let preprocessOptions = options.preprocessOptions || {};
                    const preprocessLang = (options.preprocessStyles
                        ? block.lang
                        : undefined);
                    if (preprocessLang) {
                        preprocessOptions =
                            preprocessOptions[preprocessLang] || preprocessOptions;
                        // include node_modules for imports by default
                        switch (preprocessLang) {
                            case 'scss':
                            case 'sass':
                                preprocessOptions = {
                                    includePaths: ['node_modules'],
                                    ...preprocessOptions,
                                };
                                break;
                            case 'less':
                            case 'stylus':
                                preprocessOptions = {
                                    paths: ['node_modules'],
                                    ...preprocessOptions,
                                };
                        }
                    }
                    else {
                        preprocessOptions = {};
                    }
                    const result = await compiler_sfc_1.compileStyleAsync({
                        filename: query.filename,
                        id: `data-v-${query.id}`,
                        source: code,
                        scoped: block.scoped,
                        vars: !!block.vars,
                        modules: !!block.module,
                        postcssOptions: options.postcssOptions,
                        postcssPlugins: options.postcssPlugins,
                        modulesOptions: options.cssModulesOptions,
                        preprocessLang,
                        preprocessCustomRequire: options.preprocessCustomRequire,
                        preprocessOptions,
                    });
                    if (result.errors.length) {
                        result.errors.forEach((error) => this.error({
                            id: query.filename,
                            message: error.message,
                        }));
                        return null;
                    }
                    if (query.module) {
                        return {
                            code: `export default ${_(result.modules)}`,
                            map: null,
                        };
                    }
                    else {
                        return {
                            code: result.code,
                            map: normalizeSourceMap(result.map, id),
                        };
                    }
                }
                return null;
            }
            else if (filter(id)) {
                debug(`transform(${id})`);
                const { descriptor, errors } = parseSFC(code, id, rootContext);
                if (errors.length) {
                    errors.forEach((error) => this.error(createRollupError(id, error)));
                    return null;
                }
                // module id for scoped CSS & hot-reload
                const output = transformVueSFC(code, id, descriptor, { rootContext, isProduction, isServer, filterCustomBlock }, options);
                debug('transient .vue file:', '\n' + output + '\n');
                return {
                    code: output,
                    map: {
                        mappings: '',
                    },
                };
            }
            else {
                return null;
            }
        },
    };
}
exports.default = PluginVue;
function createCustomBlockFilter(queries) {
    if (!queries || queries.length === 0)
        return () => false;
    const allowed = new Set(queries.filter((query) => /^[a-z]/i.test(query)));
    const disallowed = new Set(queries
        .filter((query) => /^![a-z]/i.test(query))
        .map((query) => query.substr(1)));
    const allowAll = queries.includes('*') || !queries.includes('!*');
    return (type) => {
        if (allowed.has(type))
            return true;
        if (disallowed.has(type))
            return true;
        return allowAll;
    };
}
function parseVuePartRequest(id) {
    const [filename, query] = id.split('?', 2);
    if (!query)
        return { vue: false, filename };
    const raw = querystring_1.default.parse(query);
    if ('vue' in raw) {
        return {
            ...raw,
            filename,
            vue: true,
            index: Number(raw.index),
            src: 'src' in raw,
            scoped: 'scoped' in raw,
        };
    }
    return { vue: false, filename };
}
const cache = new Map();
function getDescriptor(id) {
    if (cache.has(id)) {
        return cache.get(id);
    }
    throw new Error(`${id} is not parsed yet`);
}
function parseSFC(code, id, sourceRoot) {
    const { descriptor, errors } = compiler_sfc_1.parse(code, {
        sourceMap: true,
        filename: id,
        sourceRoot: sourceRoot,
    });
    cache.set(id, descriptor);
    return { descriptor, errors: errors };
}
function transformVueSFC(code, resourcePath, descriptor, { rootContext, isProduction, isServer, filterCustomBlock, }, options) {
    const shortFilePath = path_1.relative(rootContext, resourcePath)
        .replace(/^(\.\.[\/\\])+/, '')
        .replace(/\\/g, '/');
    const id = hash_sum_1.default(isProduction ? shortFilePath + '\n' + code : shortFilePath);
    // feature information
    const hasScoped = descriptor.styles.some((s) => s.scoped);
    const templateImport = !descriptor.template
        ? ''
        : getTemplateCode(descriptor, resourcePath, id, hasScoped, isServer);
    const renderReplace = !descriptor.template
        ? ''
        : isServer
            ? `script.ssrRender = ssrRender`
            : `script.render = render`;
    const scriptImport = getScriptCode(descriptor, resourcePath);
    const stylesCode = getStyleCode(descriptor, resourcePath, id, options.preprocessStyles);
    const customBlocksCode = getCustomBlock(descriptor, resourcePath, filterCustomBlock);
    const output = [
        scriptImport,
        templateImport,
        stylesCode,
        customBlocksCode,
        renderReplace,
    ];
    if (hasScoped) {
        output.push(`script.__scopeId = ${_(`data-v-${id}`)}`);
    }
    if (!isProduction) {
        output.push(`script.__file = ${_(shortFilePath)}`);
    }
    else if (options.exposeFilename) {
        output.push(`script.__file = ${_(path_1.basename(shortFilePath))}`);
    }
    output.push('export default script');
    return output.join('\n');
}
function getTemplateCode(descriptor, resourcePath, id, hasScoped, isServer) {
    const renderFnName = isServer ? 'ssrRender' : 'render';
    let templateImport = `const ${renderFnName} = () => {}`;
    let templateRequest;
    if (descriptor.template) {
        const src = descriptor.template.src || resourcePath;
        const idQuery = `&id=${id}`;
        const scopedQuery = hasScoped ? `&scoped=true` : ``;
        const srcQuery = descriptor.template.src ? `&src` : ``;
        const attrsQuery = attrsToQuery(descriptor.template.attrs, 'js', true);
        const query = `?vue&type=template${idQuery}${srcQuery}${scopedQuery}${attrsQuery}`;
        templateRequest = _(src + query);
        templateImport = `import { ${renderFnName} } from ${templateRequest}`;
    }
    return templateImport;
}
function getScriptCode(descriptor, resourcePath) {
    let scriptImport = `const script = {}`;
    if (descriptor.script || descriptor.scriptSetup) {
        if (compiler_sfc_1.compileScript) {
            descriptor.script = compiler_sfc_1.compileScript(descriptor);
        }
        if (descriptor.script) {
            const src = descriptor.script.src || resourcePath;
            const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js');
            const srcQuery = descriptor.script.src ? `&src` : ``;
            const query = `?vue&type=script${srcQuery}${attrsQuery}`;
            const scriptRequest = _(src + query);
            scriptImport =
                `import script from ${scriptRequest}\n` +
                    `export * from ${scriptRequest}`; // support named exports
        }
    }
    return scriptImport;
}
function getStyleCode(descriptor, resourcePath, id, preprocessStyles) {
    let stylesCode = ``;
    let hasCSSModules = false;
    if (descriptor.styles.length) {
        descriptor.styles.forEach((style, i) => {
            const src = style.src || resourcePath;
            // do not include module in default query, since we use it to indicate
            // that the module needs to export the modules json
            const attrsQuery = attrsToQuery(style.attrs, 'css', preprocessStyles);
            const attrsQueryWithoutModule = attrsQuery.replace(/&module(=true|=[^&]+)?/, '');
            // make sure to only pass id when necessary so that we don't inject
            // duplicate tags when multiple components import the same css file
            const idQuery = style.scoped ? `&id=${id}` : ``;
            const srcQuery = style.src ? `&src` : ``;
            const query = `?vue&type=style&index=${i}${srcQuery}${idQuery}`;
            const styleRequest = src + query + attrsQuery;
            const styleRequestWithoutModule = src + query + attrsQueryWithoutModule;
            if (style.module) {
                if (!hasCSSModules) {
                    stylesCode += `\nconst cssModules = script.__cssModules = {}`;
                    hasCSSModules = true;
                }
                stylesCode += genCSSModulesCode(id, i, styleRequest, styleRequestWithoutModule, style.module);
            }
            else {
                stylesCode += `\nimport ${_(styleRequest)}`;
            }
            // TODO SSR critical CSS collection
        });
    }
    return stylesCode;
}
function getCustomBlock(descriptor, resourcePath, filter) {
    let code = '';
    descriptor.customBlocks.forEach((block, index) => {
        if (filter(block.type)) {
            const src = block.src || resourcePath;
            const attrsQuery = attrsToQuery(block.attrs, block.type);
            const srcQuery = block.src ? `&src` : ``;
            const query = `?vue&type=${block.type}&index=${index}${srcQuery}${attrsQuery}`;
            const request = _(src + query);
            code += `import block${index} from ${request}\n`;
            code += `if (typeof block${index} === 'function') block${index}(script)\n`;
        }
    });
    return code;
}
function createRollupError(id, error) {
    if ('code' in error) {
        return {
            id,
            plugin: 'vue',
            pluginCode: String(error.code),
            message: error.message,
            frame: error.loc.source,
            parserError: error,
            loc: error.loc
                ? {
                    file: id,
                    line: error.loc.start.line,
                    column: error.loc.start.column,
                }
                : undefined,
        };
    }
    else {
        return {
            id,
            plugin: 'vue',
            message: error.message,
            parserError: error,
        };
    }
}
// these are built-in query parameters so should be ignored
// if the user happen to add them as attrs
const ignoreList = ['id', 'index', 'src', 'type', 'lang'];
function attrsToQuery(attrs, langFallback, forceLangFallback = false) {
    let query = ``;
    for (const name in attrs) {
        const value = attrs[name];
        if (!ignoreList.includes(name)) {
            query += `&${querystring_1.default.escape(name)}${value ? `=${querystring_1.default.escape(String(value))}` : ``}`;
        }
    }
    if (langFallback || attrs.lang) {
        query +=
            `lang` in attrs
                ? forceLangFallback
                    ? `&lang.${langFallback}`
                    : `&lang.${attrs.lang}`
                : `&lang.${langFallback}`;
    }
    return query;
}
function _(any) {
    return JSON.stringify(any);
}
function normalizeSourceMap(map, id) {
    if (!map)
        return null;
    if (!id.includes('type=script')) {
        map.file = id;
        map.sources[0] = id;
    }
    return {
        ...map,
        version: Number(map.version),
        mappings: typeof map.mappings === 'string' ? map.mappings : '',
    };
}
function genCSSModulesCode(
// @ts-ignore
id, index, request, requestWithoutModule, moduleName) {
    const styleVar = `style${index}`;
    let code = 
    // first import the CSS for extraction
    `\nimport ${_(requestWithoutModule)}` +
        // then import the json file to expose to component...
        `\nimport ${styleVar} from ${_(request + '.js')}`;
    // inject variable
    const name = typeof moduleName === 'string' ? moduleName : '$style';
    code += `\ncssModules["${name}"] = ${styleVar}`;
    return code;
}
// overwrite for cjs require('rollup-plugin-vue')() usage
module.exports = PluginVue;
