"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.cssPostPlugin = exports.cssPlugin = exports.chunkToEmittedCssFileMap = exports.isDirectCSSRequest = exports.isCSSRequest = void 0;
const utils_1 = require("../utils");
const path_1 = __importDefault(require("path"));
const postcss_load_config_1 = __importDefault(require("postcss-load-config"));
const merge_source_map_1 = __importDefault(require("merge-source-map"));
const pluginutils_1 = require("@rollup/pluginutils");
const chalk_1 = __importDefault(require("chalk"));
const constants_1 = require("../constants");
const asset_1 = require("./asset");
const cssLangs = `\\.(css|less|sass|scss|styl|stylus|postcss)($|\\?)`;
const cssLangRE = new RegExp(cssLangs);
const cssModuleRE = new RegExp(`\\.module${cssLangs}`);
const directRequestRE = /(\?|&)direct\b/;
exports.isCSSRequest = (request) => cssLangRE.test(request) && !directRequestRE.test(request);
exports.isDirectCSSRequest = (request) => cssLangRE.test(request) && directRequestRE.test(request);
const cssModulesCache = new WeakMap();
exports.chunkToEmittedCssFileMap = new WeakMap();
/**
 * Plugin applied before user plugins
 */
function cssPlugin(config) {
    let server;
    const moduleCache = new Map();
    cssModulesCache.set(config, moduleCache);
    return {
        name: 'vite:css',
        configureServer(_server) {
            server = _server;
        },
        async transform(raw, id) {
            if (!cssLangRE.test(id)) {
                return;
            }
            let { code: css, modules, deps } = await compileCSS(id, raw, config);
            if (modules) {
                moduleCache.set(id, modules);
            }
            if (server) {
                // server only logic for handling CSS @import dependency hmr
                const { moduleGraph } = server;
                const thisModule = moduleGraph.getModuleById(id);
                // CSS modules cannot self-accept since it exports values
                const isSelfAccepting = !modules;
                if (deps) {
                    // record deps in the module graph so edits to @import css can trigger
                    // main import to hot update
                    const depModules = new Set([...deps].map((file) => moduleGraph.createFileOnlyEntry(file)));
                    moduleGraph.updateModuleInfo(thisModule, depModules, 
                    // The root CSS proxy module is self-accepting and should not
                    // have an explicit accept list
                    new Set(), isSelfAccepting);
                    for (const file of deps) {
                        this.addWatchFile(file);
                    }
                }
                else {
                    thisModule.isSelfAccepting = isSelfAccepting;
                }
                // rewrite urls based on BASE_URL
                css = await rewriteCssUrls(css, thisModule.url);
            }
            else {
                // if build, analyze url() asset reference
                // account for comments https://github.com/vitejs/vite/issues/426
                css = css.replace(/\/\*[\s\S]*?\*\//gm, '');
                if (cssUrlRE.test(css)) {
                    css = await rewriteCssUrls(css, (url) => asset_1.urlToBuiltUrl(url, id, config, this));
                }
            }
            return css;
        }
    };
}
exports.cssPlugin = cssPlugin;
/**
 * Plugin applied after user plugins
 */
function cssPostPlugin(config) {
    const styles = new Map();
    const emptyChunks = new Set();
    const moduleCache = cssModulesCache.get(config);
    let extractedCss = '';
    return {
        name: 'vite:css-post',
        transform(css, id) {
            if (!cssLangRE.test(id)) {
                return;
            }
            const modules = moduleCache.get(id);
            const modulesCode = modules && pluginutils_1.dataToEsm(modules, { namedExports: true, preferConst: true });
            if (config.command === 'serve') {
                if (exports.isDirectCSSRequest(id)) {
                    return css;
                }
                else {
                    // server only
                    return [
                        `import { updateStyle, removeStyle } from ${JSON.stringify(constants_1.CLIENT_PUBLIC_PATH)}`,
                        `const id = ${JSON.stringify(id)}`,
                        `const css = ${JSON.stringify(css)}`,
                        `updateStyle(id, css)`,
                        // css modules exports change on edit so it can't self accept
                        `${modulesCode || `import.meta.hot.accept()\nexport default css`}`,
                        `import.meta.hot.prune(() => removeStyle(id))`
                    ].join('\n');
                }
            }
            // build CSS handling ----------------------------------------------------
            // record css
            styles.set(id, css);
            return {
                code: modulesCode || `export default ${JSON.stringify(css)}`,
                map: null,
                // avoid the css module from being tree-shaken so that we can retrieve
                // it in renderChunk()
                moduleSideEffects: 'no-treeshake'
            };
        },
        async renderChunk(code, chunk) {
            let chunkCSS = '';
            const ids = Object.keys(chunk.modules);
            for (const id of ids) {
                if (styles.has(id)) {
                    chunkCSS += styles.get(id);
                }
            }
            if (!chunkCSS) {
                return null;
            }
            // replace asset url references with resolved url
            chunkCSS = chunkCSS.replace(asset_1.assetUrlRE, (_, fileId, postfix = '') => {
                return config.build.base + this.getFileName(fileId) + postfix;
            });
            if (config.build.cssCodeSplit) {
                if (!code.trim()) {
                    // this is a shared CSS-only chunk that is empty.
                    emptyChunks.add(chunk.fileName);
                }
                // minify
                if (config.build.minify) {
                    chunkCSS = await minifyCSS(chunkCSS, config);
                }
                // for non-entry chunks, collect its css and inline it as JS strings.
                if (!chunk.isEntry) {
                    const placeholder = `__VITE_CSS__`;
                    code =
                        `let ${placeholder} = document.createElement('style');` +
                            `${placeholder}.innerHTML = ${JSON.stringify(chunkCSS)};` +
                            `document.head.appendChild(${placeholder});` +
                            code;
                }
                else {
                    // for entry chunks, emit corresponding css file
                    const fileHandle = this.emitFile({
                        name: chunk.name + '.css',
                        type: 'asset',
                        source: chunkCSS
                    });
                    exports.chunkToEmittedCssFileMap.set(chunk, fileHandle);
                }
                return {
                    code,
                    map: null
                };
            }
            else {
                extractedCss += chunkCSS;
                return null;
            }
        },
        async generateBundle(_options, bundle) {
            // remove empty css chunks and their imports
            if (emptyChunks.size) {
                emptyChunks.forEach((fileName) => {
                    delete bundle[fileName];
                });
                const emptyChunkFiles = [...emptyChunks].join('|').replace(/\./g, '\\.');
                const emptyChunkRE = new RegExp(`\\bimport\\s*"[^"]*(?:${emptyChunkFiles})";\n?`, 'g');
                for (const file in bundle) {
                    const chunk = bundle[file];
                    if (chunk.type === 'chunk') {
                        chunk.code = chunk.code.replace(emptyChunkRE, '');
                    }
                }
            }
            if (extractedCss) {
                // minify css
                if (config.build.minify) {
                    extractedCss = await minifyCSS(extractedCss, config);
                }
                this.emitFile({
                    name: 'style.css',
                    type: 'asset',
                    source: extractedCss
                });
            }
        }
    };
}
exports.cssPostPlugin = cssPostPlugin;
async function compileCSS(id, code, config) {
    var _a;
    const { modules: modulesOptions, preprocessorOptions } = config.css || {};
    const isModule = modulesOptions !== false && cssModuleRE.test(id);
    // although at serve time it can work without processing, we do need to
    // crawl them in order to register watch dependencies.
    const needInlineImport = code.includes('@import');
    const postcssConfig = await loadPostcssConfig(config.root);
    const lang = (_a = id.match(cssLangRE)) === null || _a === void 0 ? void 0 : _a[1];
    // 1. plain css that needs no processing
    if (lang === 'css' && !postcssConfig && !isModule && !needInlineImport) {
        return { code };
    }
    let map;
    let modules;
    const deps = new Set();
    // 2. pre-processors: sass etc.
    if (lang && lang in preProcessors) {
        const preProcessor = preProcessors[lang];
        let opts = (preprocessorOptions && preprocessorOptions[lang]) || {};
        // support @import from node dependencies by default
        switch (lang) {
            case 'scss':
            case 'sass':
                opts = {
                    includePaths: ['node_modules'],
                    ...opts
                };
                break;
            case 'less':
            case 'styl':
            case 'stylus':
                opts = {
                    paths: ['node_modules'],
                    ...opts
                };
        }
        // important: set this for relative import resolving
        opts.filename = utils_1.cleanUrl(id);
        const preprocessResult = await preProcessor(code, undefined, opts);
        if (preprocessResult.errors.length) {
            throw preprocessResult.errors[0];
        }
        code = preprocessResult.code;
        map = preprocessResult.map;
        if (preprocessResult.deps) {
            preprocessResult.deps.forEach((dep) => {
                // sometimes sass registers the file itself as a dep
                if (dep !== opts.filename) {
                    deps.add(dep);
                }
            });
        }
    }
    // 3. postcss
    const postcssOptions = (postcssConfig && postcssConfig.options) || {};
    const postcssPlugins = postcssConfig && postcssConfig.plugins ? postcssConfig.plugins.slice() : [];
    if (needInlineImport) {
        postcssPlugins.unshift((await Promise.resolve().then(() => __importStar(require('postcss-import')))).default());
    }
    if (isModule) {
        postcssPlugins.unshift((await Promise.resolve().then(() => __importStar(require('postcss-modules')))).default({
            localsConvention: 'camelCaseOnly',
            ...modulesOptions,
            getJSON(_, _modules) {
                modules = _modules;
            }
        }));
    }
    if (!postcssPlugins.length) {
        return {
            code,
            map
        };
    }
    // postcss is an unbundled dep and should be lazy imported
    const postcssResult = await (await Promise.resolve().then(() => __importStar(require('postcss'))))
        .default(postcssPlugins)
        .process(code, {
        ...postcssOptions,
        to: id,
        from: id,
        map: {
            inline: false,
            annotation: false,
            prev: map
        }
    });
    // record CSS dependencies from @imports
    for (const message of postcssResult.messages) {
        if (message.type === 'dependency') {
            deps.add(message.file);
        }
        else if (message.type === 'warning') {
            let msg = `[vite:css] ${message.text}`;
            if (message.line && message.column) {
                msg += `\n${utils_1.generateCodeFrame(code, {
                    line: message.line,
                    column: message.column
                })}`;
            }
            config.logger.warn(chalk_1.default.yellow(msg));
        }
    }
    return {
        ast: postcssResult,
        code: postcssResult.css,
        map: postcssResult.map,
        modules,
        deps
    };
}
let cachedPostcssConfig;
async function loadPostcssConfig(root) {
    if (cachedPostcssConfig !== undefined) {
        return cachedPostcssConfig;
    }
    try {
        return (cachedPostcssConfig = await postcss_load_config_1.default({}, root));
    }
    catch (e) {
        if (!/No PostCSS Config found/.test(e.message)) {
            throw e;
        }
        return (cachedPostcssConfig = null);
    }
}
function loadPreprocessor(lang) {
    try {
        return require(lang);
    }
    catch (e) {
        throw new Error(`Preprocessor dependency "${lang}" not found. Did you install it?`);
    }
}
// .scss/.sass processor
const scss = async (source, map, options) => {
    const nodeSass = loadPreprocessor('sass');
    const finalOptions = {
        ...options,
        data: getSource(source, options.filename, options.additionalData),
        file: options.filename,
        outFile: options.filename,
        sourceMap: !!map
    };
    try {
        const result = await new Promise((resolve, reject) => {
            nodeSass.render(finalOptions, (err, res) => {
                if (err) {
                    reject(err);
                }
                else {
                    resolve(res);
                }
            });
        });
        const deps = result.stats.includedFiles;
        if (map) {
            return {
                code: result.css.toString(),
                map: merge_source_map_1.default(map, JSON.parse(result.map.toString())),
                errors: [],
                deps
            };
        }
        return { code: result.css.toString(), errors: [], deps };
    }
    catch (e) {
        return { code: '', errors: [e], deps: [] };
    }
};
const sass = (source, map, options) => scss(source, map, {
    ...options,
    indentedSyntax: true
});
const less = (source, map, options) => {
    const nodeLess = loadPreprocessor('less');
    let result;
    let error = null;
    nodeLess.render(getSource(source, options.filename, options.additionalData), { ...options, syncImport: true }, (err, output) => {
        error = err;
        result = output;
    });
    if (error) {
        // normalize error info
        const normalizedError = new Error(error.message);
        normalizedError.loc = {
            file: options.filename,
            line: error.line,
            column: error.column
        };
        return { code: '', errors: [normalizedError], deps: [] };
    }
    const deps = result.imports;
    if (map) {
        return {
            code: result.css.toString(),
            map: merge_source_map_1.default(map, result.map),
            errors: [],
            deps
        };
    }
    return {
        code: result.css.toString(),
        errors: [],
        deps
    };
};
// .styl
const styl = (source, map, options) => {
    const nodeStylus = loadPreprocessor('stylus');
    try {
        const ref = nodeStylus(source);
        Object.keys(options).forEach((key) => ref.set(key, options[key]));
        if (map)
            ref.set('sourcemap', { inline: false, comment: false });
        const result = ref.render();
        const deps = ref.deps();
        if (map) {
            return {
                code: result,
                map: merge_source_map_1.default(map, ref.sourcemap),
                errors: [],
                deps
            };
        }
        return { code: result, errors: [], deps };
    }
    catch (e) {
        return { code: '', errors: [e], deps: [] };
    }
};
function getSource(source, filename, additionalData) {
    if (!additionalData)
        return source;
    if (typeof additionalData === 'function') {
        return additionalData(source, filename);
    }
    return additionalData + source;
}
const preProcessors = {
    less,
    sass,
    scss,
    styl,
    stylus: styl
};
const cssUrlRE = /url\(\s*('[^']+'|"[^"]+"|[^'")]+)\s*\)/;
function rewriteCssUrls(css, replacerOrBase) {
    let replacer;
    if (typeof replacerOrBase === 'string') {
        replacer = (rawUrl) => {
            return path_1.default.posix.resolve(path_1.default.posix.dirname(replacerOrBase), rawUrl);
        };
    }
    else {
        replacer = replacerOrBase;
    }
    return utils_1.asyncReplace(css, cssUrlRE, async (match) => {
        let [matched, rawUrl] = match;
        let wrap = '';
        const first = rawUrl[0];
        if (first === `"` || first === `'`) {
            wrap = first;
            rawUrl = rawUrl.slice(1, -1);
        }
        if (utils_1.isExternalUrl(rawUrl) || utils_1.isDataUrl(rawUrl) || rawUrl.startsWith('#')) {
            return matched;
        }
        rawUrl = await replacer(rawUrl);
        return `url(${wrap}${rawUrl}${wrap})`;
    });
}
let CleanCSS;
async function minifyCSS(css, config) {
    CleanCSS = CleanCSS || (await Promise.resolve().then(() => __importStar(require('clean-css')))).default;
    const res = new CleanCSS({
        rebase: false,
        ...config.build.cleanCssOptions
    }).minify(css);
    if (res.errors && res.errors.length) {
        config.logger.error(chalk_1.default.red(`error when minifying css:\n${res.errors}`));
        // TODO format this
        throw res.errors[0];
    }
    if (res.warnings && res.warnings.length) {
        config.logger.warn(chalk_1.default.yellow(`warnings when minifying css:\n${res.warnings}`));
    }
    return res.styles;
}
//# sourceMappingURL=css.js.map