UNPKG

rollup-plugin-styles

Version:

Universal Rollup plugin for styles: PostCSS, Sass, Less, Stylus and more

1,635 lines (1,357 loc) 54.2 kB
import path from 'path'; import { makeLegalIdentifier, createFilter } from '@rollup/pluginutils'; import cssnano from 'cssnano'; import PQueue from 'p-queue'; import fs from 'fs-extra'; import postcss from 'postcss'; import { SourceMapConsumer, SourceMapGenerator } from 'source-map-js'; import resolver, { sync } from 'resolve'; import { createHash } from 'crypto'; import { cosmiconfig } from 'cosmiconfig'; import valueParser from 'postcss-value-parser'; import { parseUrl, stringifyUrl } from 'query-string'; import { lookup } from 'mime-types'; import modulesValues from 'postcss-modules-values'; import localByDefault from 'postcss-modules-local-by-default'; import extractImports from 'postcss-modules-extract-imports'; import modulesScope from 'postcss-modules-scope'; import { extractICSS, replaceSymbols, replaceValueSymbols } from 'icss-utils'; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } const isAbsolutePath = path => /^(?:\/|(?:[A-Za-z]:)?[/\\|])/.test(path); const isRelativePath = path => /^\.?\.[/\\]/.test(path); function normalizePath(...paths) { const f = path.join(...paths).replace(/\\/g, "/"); if (/^\.[/\\]/.test(paths[0])) return `./${f}`; return f; } const resolvePath = (...paths) => normalizePath(path.resolve(...paths)); const relativePath = (from, to) => normalizePath(path.relative(from, to)); const humanlizePath = file => relativePath(process.cwd(), file); const hashRe = /\[hash(?::(\d+))?]/; const firstExtRe = /(?<!^|[/\\])(\.[^\s.]+)/i; const dataURIRe = /data:[^\n\r;]+?(?:;charset=[^\n\r;]+?)?;base64,([\d+/A-Za-z]+={0,2})/; const mapBlockRe = /(?:\n|\r\n)?\/\*[#*@]+?\s*?sourceMappingURL\s*?=\s*?(\S+)\s*?\*+?\//gm; const mapLineRe = /(?:\n|\r\n)?\/\/[#@]+?\s*?sourceMappingURL\s*?=\s*?(\S+)\s*?$/gm; async function getMap(code, id) { var _ref, _mapBlockRe$exec, _dataURIRe$exec; const [, data] = (_ref = (_mapBlockRe$exec = mapBlockRe.exec(code)) !== null && _mapBlockRe$exec !== void 0 ? _mapBlockRe$exec : mapLineRe.exec(code)) !== null && _ref !== void 0 ? _ref : []; if (!data) return; const [, uriMap] = (_dataURIRe$exec = dataURIRe.exec(data)) !== null && _dataURIRe$exec !== void 0 ? _dataURIRe$exec : []; if (uriMap) return Buffer.from(uriMap, "base64").toString(); if (!id) throw new Error("Extracted map detected, but no ID is provided"); const mapFileName = path.resolve(path.dirname(id), data); const exists = await fs.pathExists(mapFileName); if (!exists) return; return fs.readFile(mapFileName, "utf8"); } const stripMap = code => code.replace(mapBlockRe, "").replace(mapLineRe, ""); class MapModifier { constructor(map) { _defineProperty(this, "map", void 0); if (typeof map === "string") try { this.map = JSON.parse(map); } catch { /* noop */ } else this.map = map; } modify(f) { if (!this.map) return this; f(this.map); return this; } modifySources(op) { if (!this.map) return this; if (this.map.sources) this.map.sources = this.map.sources.map(s => op(s)); return this; } resolve(dir = process.cwd()) { return this.modifySources(source => { if (source === "<no source>") return source; return resolvePath(dir, source); }); } relative(dir = process.cwd()) { return this.modifySources(source => { if (source === "<no source>") return source; if (isAbsolutePath(source)) return relativePath(dir, source); return normalizePath(source); }); } toObject() { return this.map; } toString() { if (!this.map) return this.map; return JSON.stringify(this.map); } toConsumer() { if (!this.map) return this.map; return new SourceMapConsumer(this.map); } toCommentData() { const map = this.toString(); if (!map) return ""; const sourceMapData = Buffer.from(map).toString("base64"); return `\n/*# sourceMappingURL=data:application/json;base64,${sourceMapData} */`; } toCommentFile(fileName) { if (!this.map) return ""; return `\n/*# sourceMappingURL=${fileName} */`; } } const mm = map => new MapModifier(map); var arrayFmt = (arr => arr.map((id, i, arr) => { const fmt = `\`${id}\``; switch (i) { case arr.length - 1: return `or ${fmt}`; case arr.length - 2: return fmt; default: return `${fmt},`; } }).join(" ")); const defaultOpts = { caller: "Resolver", basedirs: [__dirname], extensions: [".mjs", ".js", ".json"], preserveSymlinks: true, packageFilter(pkg) { if (pkg.module) pkg.main = pkg.module; if (pkg.style) pkg.main = pkg.style; return pkg; } }; const resolverAsync = async (id, options = {}) => new Promise(resolve => resolver(id, options, (_, res) => resolve(res))); async function resolveAsync(ids, userOpts) { const options = { ...defaultOpts, ...userOpts }; for await (const basedir of options.basedirs) { const opts = { ...options, basedir, basedirs: undefined, caller: undefined }; for await (const id of ids) { const resolved = await resolverAsync(id, opts); if (resolved) return resolved; } } throw new Error(`${options.caller} could not resolve ${arrayFmt(ids)}`); } const resolverSync = (id, options = {}) => { try { return sync(id, options); } catch { return; } }; function resolveSync(ids, userOpts) { const options = { ...defaultOpts, ...userOpts }; for (const basedir of options.basedirs) { const opts = { ...options, basedir, basedirs: undefined, caller: undefined }; for (const id of ids) { const resolved = resolverSync(id, opts); if (resolved) return resolved; } } throw new Error(`${options.caller} could not resolve ${arrayFmt(ids)}`); } var hasher = (data => createHash("sha256").update(data).digest("hex")); var safeId = ((id, ...salt) => { const hash = hasher([id, "0iOXBLSx", ...salt].join(":")).slice(0, 8); return makeLegalIdentifier(`${id}_${hash}`); }); const loaded = {}; const options = { caller: "Module loader", basedirs: [process.cwd()], extensions: [".js", ".mjs", ".json"], preserveSymlinks: false, packageFilter: pkg => pkg }; function loadModule (moduleId) { if (loaded[moduleId]) return loaded[moduleId]; if (loaded[moduleId] === null) return; try { loaded[moduleId] = require(resolveSync([moduleId, `./${moduleId}`], options)); } catch { loaded[moduleId] = null; return; } return loaded[moduleId]; } function inferOption(option, defaultValue) { if (typeof option === "boolean") return option && {}; if (typeof option === "object") return option; return defaultValue; } const modes = ["inject", "extract", "emit"]; const modesFmt = arrayFmt(modes); function inferModeOption(mode) { var _m$, _m$2; const m = Array.isArray(mode) ? mode : [mode]; if (m[0] && !modes.includes(m[0])) throw new Error(`Incorrect mode provided, allowed modes are ${modesFmt}`); return { inject: (!m[0] || m[0] === "inject") && ((_m$ = m[1]) !== null && _m$ !== void 0 ? _m$ : true), extract: m[0] === "extract" && ((_m$2 = m[1]) !== null && _m$2 !== void 0 ? _m$2 : true), emit: m[0] === "emit" }; } function inferSourceMapOption(sourceMap) { const sm = Array.isArray(sourceMap) ? sourceMap : [sourceMap]; if (!sm[0]) return false; return { content: true, ...sm[1], inline: sm[0] === "inline" }; } function inferHandlerOption(option, alias) { const opt = inferOption(option, {}); if (alias && typeof opt === "object" && !opt.alias) opt.alias = alias; return opt; } function ensureUseOption(opts) { var _opts$sass, _opts$less, _opts$stylus; const all = { sass: ["sass", (_opts$sass = opts.sass) !== null && _opts$sass !== void 0 ? _opts$sass : {}], less: ["less", (_opts$less = opts.less) !== null && _opts$less !== void 0 ? _opts$less : {}], stylus: ["stylus", (_opts$stylus = opts.stylus) !== null && _opts$stylus !== void 0 ? _opts$stylus : {}] }; if (typeof opts.use === "undefined") return Object.values(all);else if (!Array.isArray(opts.use)) throw new TypeError("`use` option must be an array!"); return opts.use.map(loader => { if (typeof loader !== "string") throw new TypeError("`use` option must be an array of strings!"); return all[loader] || [loader, {}]; }); } function ensurePCSSOption(option, type) { if (typeof option !== "string") return option; const module = loadModule(option); if (!module) throw new Error(`Unable to load PostCSS ${type} \`${option}\``); return module; } function ensurePCSSPlugins(plugins) { if (typeof plugins === "undefined") return [];else if (typeof plugins !== "object") throw new TypeError("`plugins` option must be an array or an object!"); const ps = []; for (const p of !Array.isArray(plugins) ? Object.entries(plugins) : plugins) { if (!p) continue; if (!Array.isArray(p)) { ps.push(ensurePCSSOption(p, "plugin")); continue; } const [plug, opts] = p; if (!opts) ps.push(ensurePCSSOption(plug, "plugin"));else ps.push(ensurePCSSOption(plug, "plugin")(opts)); } return ps; } async function loadConfig (id, config) { var _process$env$NODE_ENV, _config$ctx; if (!config) return { plugins: [], options: {} }; const { ext, dir, base } = path.parse(id); const searchPath = config.path ? path.resolve(config.path) : dir; const found = await cosmiconfig("postcss").search(searchPath); if (!found || found.isEmpty) return { plugins: [], options: {} }; const { plugins, parser, syntax, stringifier } = typeof found.config === "function" ? found.config({ cwd: process.cwd(), env: (_process$env$NODE_ENV = process.env["NODE_ENV"]) !== null && _process$env$NODE_ENV !== void 0 ? _process$env$NODE_ENV : "development", file: { extname: ext, dirname: dir, basename: base }, options: (_config$ctx = config.ctx) !== null && _config$ctx !== void 0 ? _config$ctx : {} }) : found.config; const result = { plugins: ensurePCSSPlugins(plugins), options: {} }; if (parser) result.options.parser = ensurePCSSOption(parser, "parser"); if (syntax) result.options.syntax = ensurePCSSOption(syntax, "syntax"); if (stringifier) result.options.stringifier = ensurePCSSOption(stringifier, "stringifier"); return result; } const resolve$2 = async (inputUrl, basedir, extensions) => { const options = { caller: "@import resolver", basedirs: [basedir], extensions }; const parseOptions = { parseFragmentIdentifier: true, sort: false, decode: false }; const { url } = parseUrl(inputUrl, parseOptions); const from = await resolveAsync([url, `./${url}`], options); return { from, source: await fs.readFile(from) }; }; const name$3 = "styles-import"; const extensionsDefault$1 = [".css", ".pcss", ".postcss", ".sss"]; const plugin$3 = (options = {}) => { var _options$resolve, _options$alias, _options$extensions; const resolve = (_options$resolve = options.resolve) !== null && _options$resolve !== void 0 ? _options$resolve : resolve$2; const alias = (_options$alias = options.alias) !== null && _options$alias !== void 0 ? _options$alias : {}; const extensions = (_options$extensions = options.extensions) !== null && _options$extensions !== void 0 ? _options$extensions : extensionsDefault$1; return { postcssPlugin: name$3, // eslint-disable-next-line @typescript-eslint/naming-convention async Once(css, { result: res }) { var _css$source; if (!((_css$source = css.source) !== null && _css$source !== void 0 && _css$source.input.file)) return; const opts = { ...res.opts }; delete opts.map; const { file } = css.source.input; const importList = []; const basedir = path.dirname(file); css.walkAtRules(/^import$/i, rule => { // Top level only if (rule.parent && rule.parent.type !== "root") { rule.warn(res, "`@import` should be top level"); return; } // Child nodes should not exist if (rule.nodes) { rule.warn(res, "`@import` was not terminated correctly"); return; } const [urlNode] = valueParser(rule.params).nodes; // No URL detected if (!urlNode || urlNode.type !== "string" && urlNode.type !== "function") { rule.warn(res, `No URL in \`${rule.toString()}\``); return; } let url = ""; if (urlNode.type === "string") { url = urlNode.value; } else if (urlNode.type === "function") { var _urlNode$nodes$; // Invalid function if (!/^url$/i.test(urlNode.value)) { rule.warn(res, `Invalid \`url\` function in \`${rule.toString()}\``); return; } const isString = ((_urlNode$nodes$ = urlNode.nodes[0]) === null || _urlNode$nodes$ === void 0 ? void 0 : _urlNode$nodes$.type) === "string"; url = isString ? urlNode.nodes[0].value : valueParser.stringify(urlNode.nodes); } url = url.replace(/^\s+|\s+$/g, ""); // Resolve aliases for (const [from, to] of Object.entries(alias)) { if (url !== from && !url.startsWith(`${from}/`)) continue; url = normalizePath(to) + url.slice(from.length); } // Empty URL if (url.length === 0) { rule.warn(res, `Empty URL in \`${rule.toString()}\``); return; } // Skip Web URLs if (!isAbsolutePath(url)) { try { new URL(url); return; } catch {// Is not a Web URL, continuing } } importList.push({ rule, url }); }); for await (const { rule, url } of importList) { try { const { source, from } = await resolve(url, basedir, extensions); if (!(source instanceof Uint8Array) || typeof from !== "string") { rule.warn(res, `Incorrectly resolved \`@import\` in \`${rule.toString()}\``); continue; } if (normalizePath(from) === normalizePath(file)) { rule.warn(res, `\`@import\` loop in \`${rule.toString()}\``); continue; } const imported = await postcss(plugin$3(options)).process(source, { ...opts, from }); res.messages.push(...imported.messages, { plugin: name$3, type: "dependency", file: from }); if (!imported.root) rule.remove();else rule.replaceWith(imported.root); } catch { rule.warn(res, `Unresolved \`@import\` in \`${rule.toString()}\``); } } } }; }; plugin$3.postcss = true; const resolve$1 = async (inputUrl, basedir) => { const options = { caller: "URL resolver", basedirs: [basedir] }; const parseOptions = { parseFragmentIdentifier: true, sort: false, decode: false }; const { url, query, fragmentIdentifier } = parseUrl(inputUrl, parseOptions); const from = await resolveAsync([url, `./${url}`], options); const urlQuery = stringifyUrl({ url: "", query, fragmentIdentifier }, parseOptions); return { from, source: await fs.readFile(from), urlQuery }; }; var generateName = ((placeholder, file, source) => { const { dir, name, ext, base } = path.parse(file); const hash = hasher(`${base}:${Buffer.from(source).toString()}`); const match = hashRe.exec(placeholder); const hashLen = match && Number.parseInt(match[1]); return placeholder.replace("[dir]", path.basename(dir)).replace("[name]", name).replace("[extname]", ext).replace(".[ext]", ext).replace("[ext]", ext.slice(1)).replace(hashRe, hashLen ? hash.slice(0, hashLen) : hash.slice(0, 8)); }); const urlFuncRe = /^url$/i; const imageSetFuncRe = /^(?:-webkit-)?image-set$/i; const isDeclWithUrl = decl => /(?:url|(?:-webkit-)?image-set)\(/i.test(decl.value); const walkUrls = (parsed, callback) => { parsed.walk(node => { if (node.type !== "function") return; if (urlFuncRe.test(node.value)) { const { nodes } = node; const [urlNode] = nodes; const url = (urlNode === null || urlNode === void 0 ? void 0 : urlNode.type) === "string" ? urlNode.value : valueParser.stringify(nodes); callback(url.replace(/^\s+|\s+$/g, ""), urlNode); return; } if (imageSetFuncRe.test(node.value)) { for (const nNode of node.nodes) { if (nNode.type === "string") { callback(nNode.value.replace(/^\s+|\s+$/g, ""), nNode); continue; } if (nNode.type === "function" && urlFuncRe.test(nNode.value)) { const { nodes } = nNode; const [urlNode] = nodes; const url = (urlNode === null || urlNode === void 0 ? void 0 : urlNode.type) === "string" ? urlNode.value : valueParser.stringify(nodes); callback(url.replace(/^\s+|\s+$/g, ""), urlNode); continue; } } } }); }; var inlineFile = ((file, source) => { const mime = lookup(file) || "application/octet-stream"; const data = Buffer.from(source).toString("base64"); return `data:${mime};base64,${data}`; }); const name$2 = "styles-url"; const placeholderHashDefault = "assets/[name]-[hash][extname]"; const placeholderNoHashDefault = "assets/[name][extname]"; const plugin$2 = (options = {}) => { var _options$inline, _options$publicPath, _options$assetDir, _options$resolve, _options$alias, _options$hash; const inline = (_options$inline = options.inline) !== null && _options$inline !== void 0 ? _options$inline : false; const publicPath = (_options$publicPath = options.publicPath) !== null && _options$publicPath !== void 0 ? _options$publicPath : "./"; const assetDir = (_options$assetDir = options.assetDir) !== null && _options$assetDir !== void 0 ? _options$assetDir : "."; const resolve = (_options$resolve = options.resolve) !== null && _options$resolve !== void 0 ? _options$resolve : resolve$1; const alias = (_options$alias = options.alias) !== null && _options$alias !== void 0 ? _options$alias : {}; const placeholder = ((_options$hash = options.hash) !== null && _options$hash !== void 0 ? _options$hash : true) ? typeof options.hash === "string" ? options.hash : placeholderHashDefault : placeholderNoHashDefault; return { postcssPlugin: name$2, // eslint-disable-next-line @typescript-eslint/naming-convention async Once(css, { result: res }) { var _css$source, _css$source$input$map; if (!((_css$source = css.source) !== null && _css$source !== void 0 && _css$source.input.file)) return; const { file } = css.source.input; const map = mm((_css$source$input$map = css.source.input.map) === null || _css$source$input$map === void 0 ? void 0 : _css$source$input$map.text).resolve(path.dirname(file)).toConsumer(); const urlList = []; const imported = res.messages.filter(msg => msg.type === "dependency").map(msg => msg.file); css.walkDecls(decl => { if (!isDeclWithUrl(decl)) return; const parsed = valueParser(decl.value); walkUrls(parsed, (url, node) => { var _decl$source, _decl$source2; // Resolve aliases for (const [from, to] of Object.entries(alias)) { if (url !== from && !url.startsWith(`${from}/`)) continue; url = normalizePath(to) + url.slice(from.length); } // Empty URL if (!node || url.length === 0) { decl.warn(res, `Empty URL in \`${decl.toString()}\``); return; } // Skip Data URI if (dataURIRe.test(url)) return; // Skip Web URLs if (!isAbsolutePath(url)) { try { new URL(url); return; } catch {// Is not a Web URL, continuing } } const basedirs = new Set(); // Use PostCSS imports if ((_decl$source = decl.source) !== null && _decl$source !== void 0 && _decl$source.input.file && imported.includes(decl.source.input.file)) basedirs.add(path.dirname(decl.source.input.file)); // Use SourceMap if ((_decl$source2 = decl.source) !== null && _decl$source2 !== void 0 && _decl$source2.start) { const pos = decl.source.start; const realPos = map === null || map === void 0 ? void 0 : map.originalPositionFor(pos); const basedir = (realPos === null || realPos === void 0 ? void 0 : realPos.source) && path.dirname(realPos.source); if (basedir) basedirs.add(path.normalize(basedir)); } // Use current file basedirs.add(path.dirname(file)); urlList.push({ node, url, decl, parsed, basedirs }); }); }); const usedNames = new Map(); for await (const { node, url, decl, parsed, basedirs } of urlList) { let resolved; for await (const basedir of basedirs) { try { if (!resolved) resolved = await resolve(url, basedir); } catch { /* noop */ } } if (!resolved) { decl.warn(res, `Unresolved URL \`${url}\` in \`${decl.toString()}\``); continue; } const { source, from, urlQuery } = resolved; if (!(source instanceof Uint8Array) || typeof from !== "string") { decl.warn(res, `Incorrectly resolved URL \`${url}\` in \`${decl.toString()}\``); continue; } res.messages.push({ plugin: name$2, type: "dependency", file: from }); if (inline) { node.type = "string"; node.value = inlineFile(from, source); } else { const unsafeTo = normalizePath(generateName(placeholder, from, source)); let to = unsafeTo; // Avoid file overrides const hasExt = firstExtRe.test(unsafeTo); for (let i = 1; usedNames.has(to) && usedNames.get(to) !== from; i++) { to = hasExt ? unsafeTo.replace(firstExtRe, `${i}$1`) : `${unsafeTo}${i}`; } usedNames.set(to, from); node.type = "string"; node.value = publicPath + (/[/\\]$/.test(publicPath) ? "" : "/") + path.basename(to); if (urlQuery) node.value += urlQuery; to = normalizePath(assetDir, to); res.messages.push({ plugin: name$2, type: "asset", to, source }); } decl.value = parsed.toString(); } } }; }; plugin$2.postcss = true; var generateScopedNameDefault = ((placeholder = "[name]_[local]__[hash:8]") => (local, file, css) => { const { dir, name, base } = path.parse(file); const hash = hasher(`${base}:${css}`); const match = hashRe.exec(placeholder); const hashLen = match && Number.parseInt(match[1]); return makeLegalIdentifier(placeholder.replace("[dir]", path.basename(dir)).replace("[name]", name).replace("[local]", local).replace(hashRe, hashLen ? hash.slice(0, hashLen) : hash)); }); var postcssModules = (options => { const opts = { mode: "local", ...options, generateScopedName: typeof options.generateScopedName === "function" ? options.generateScopedName : generateScopedNameDefault(options.generateScopedName) }; return [modulesValues(), localByDefault({ mode: opts.mode }), extractImports({ failOnWrongOrder: opts.failOnWrongOrder }), modulesScope({ exportGlobals: opts.exportGlobals, generateScopedName: opts.generateScopedName })]; }); const load = async (url, file, extensions, processor, opts) => { const options = { caller: "ICSS loader", basedirs: [path.dirname(file)], extensions }; const from = await resolveAsync([url, `./${url}`], options); const source = await fs.readFile(from); const { messages } = await processor.process(source, { ...opts, from }); const exports = {}; for (const msg of messages) { if (msg.type !== "icss") continue; Object.assign(exports, msg.export); } return exports; }; async function resolve (icssImports, load, file, extensions, processor, opts) { const imports = {}; for await (const [url, values] of Object.entries(icssImports)) { const exports = await load(url, file, extensions, processor, opts); for (const [k, v] of Object.entries(values)) { imports[k] = exports[v]; } } return imports; } const name$1 = "styles-icss"; const extensionsDefault = [".css", ".pcss", ".postcss", ".sss"]; const plugin$1 = (options = {}) => { var _options$load, _options$extensions; const load$1 = (_options$load = options.load) !== null && _options$load !== void 0 ? _options$load : load; const extensions = (_options$extensions = options.extensions) !== null && _options$extensions !== void 0 ? _options$extensions : extensionsDefault; return { postcssPlugin: name$1, // eslint-disable-next-line @typescript-eslint/naming-convention async OnceExit(css, { result: res }) { var _css$source; if (!((_css$source = css.source) !== null && _css$source !== void 0 && _css$source.input.file)) return; const opts = { ...res.opts }; delete opts.map; const { icssImports, icssExports } = extractICSS(css); const imports = await resolve(icssImports, load$1, css.source.input.file, extensions, res.processor, opts); replaceSymbols(css, imports); for (const [k, v] of Object.entries(icssExports)) { res.messages.push({ plugin: name$1, type: "icss", export: { [k]: replaceValueSymbols(v, imports) } }); } } }; }; plugin$1.postcss = true; const name = "styles-noop"; const plugin = () => ({ postcssPlugin: name }); plugin.postcss = true; let injectorId; const cssVarName = "css"; const reservedWords = [cssVarName]; function getClassNameDefault(name) { const id = makeLegalIdentifier(name); if (reservedWords.includes(id)) return `_${id}`; return id; } function ensureAutoModules(am, id) { if (typeof am === "function") return am(id); if (am instanceof RegExp) return am.test(id); return am && /\.module\.[A-Za-z]+$/.test(id); } const loader$4 = { name: "postcss", alwaysProcess: true, async process({ code, map, extracted }) { var _options$to, _res$map; const options = { ...this.options }; const config = await loadConfig(this.id, options.config); const plugins = []; const autoModules = ensureAutoModules(options.autoModules, this.id); const supportModules = Boolean(options.modules || autoModules); const modulesExports = {}; const postcssOpts = { ...config.options, ...options.postcss, from: this.id, to: (_options$to = options.to) !== null && _options$to !== void 0 ? _options$to : this.id, map: { inline: false, annotation: false, sourcesContent: this.sourceMap ? this.sourceMap.content : true, prev: mm(map).relative(path.dirname(this.id)).toObject() } }; delete postcssOpts.plugins; if (options.import) plugins.push(plugin$3({ extensions: options.extensions, ...options.import })); if (options.url) plugins.push(plugin$2({ inline: Boolean(options.inject), ...options.url })); if (options.postcss.plugins) plugins.push(...options.postcss.plugins); if (config.plugins) plugins.push(...config.plugins); if (supportModules) { const modulesOptions = typeof options.modules === "object" ? options.modules : {}; plugins.push(...postcssModules({ generateScopedName: undefined, failOnWrongOrder: true, ...modulesOptions }), plugin$1({ extensions: options.extensions })); } if (options.minimize) { const cssnanoOpts = typeof options.minimize === "object" ? options.minimize : {}; plugins.push(cssnano(cssnanoOpts)); } // Avoid PostCSS warning if (plugins.length === 0) plugins.push(plugin); const res = await postcss(plugins).process(code, postcssOpts); for (const msg of res.messages) switch (msg.type) { case "warning": this.warn({ name: msg.plugin, message: msg.text }); break; case "icss": Object.assign(modulesExports, msg.export); break; case "dependency": this.deps.add(normalizePath(msg.file)); break; case "asset": this.assets.set(msg.to, msg.source); break; } map = mm((_res$map = res.map) === null || _res$map === void 0 ? void 0 : _res$map.toJSON()).resolve(path.dirname(postcssOpts.to)).toString(); if (!options.extract && this.sourceMap) { const m = mm(map).modify(map => void delete map.file).relative(); if (this.sourceMap.transform) m.modify(this.sourceMap.transform); map = m.toString(); res.css += m.toCommentData(); } if (options.emit) return { code: res.css, map }; const saferId = id => safeId(id, path.basename(this.id)); const modulesVarName = saferId("modules"); const output = [`export var ${cssVarName} = ${JSON.stringify(res.css)};`]; const dts = [`export var ${cssVarName}: string;`]; if (options.namedExports) { const getClassName = typeof options.namedExports === "function" ? options.namedExports : getClassNameDefault; for (const name in modulesExports) { const newName = getClassName(name); if (name !== newName) this.warn(`Exported \`${name}\` as \`${newName}\` in ${humanlizePath(this.id)}`); const fmt = JSON.stringify(modulesExports[name]); output.push(`export var ${newName} = ${fmt};`); if (options.dts) dts.push(`export var ${newName}: ${fmt};`); } } if (options.extract) extracted = { id: this.id, css: res.css, map }; if (options.inject) { if (typeof options.inject === "function") { output.push(options.inject(cssVarName, this.id), `var ${modulesVarName} = ${JSON.stringify(modulesExports)};`); } else { const { treeshakeable, ...injectorOptions } = typeof options.inject === "object" ? options.inject : {}; const injectorName = saferId("injector"); const injectorCall = `${injectorName}(${cssVarName},${JSON.stringify(injectorOptions)});`; if (!injectorId) { const opts = { basedirs: [path.join(__dirname, "runtime")] }; injectorId = await resolveAsync(["./inject-css"], opts); injectorId = `"${normalizePath(injectorId)}"`; } output.unshift(`import ${injectorName} from ${injectorId};`); if (!treeshakeable) output.push(`var ${modulesVarName} = ${JSON.stringify(modulesExports)};`, injectorCall); if (treeshakeable) { output.push("var injected = false;"); const injectorCallOnce = `if (!injected) { injected = true; ${injectorCall} }`; if (modulesExports.inject) { throw new Error("`inject` keyword is reserved when using `inject.treeshakeable` option"); } let getters = ""; for (const [k, v] of Object.entries(modulesExports)) { const name = JSON.stringify(k); const value = JSON.stringify(v); getters += `get ${name}() { ${injectorCallOnce} return ${value}; },\n`; } getters += `inject() { ${injectorCallOnce} },`; output.push(`var ${modulesVarName} = {${getters}};`); } } } if (!options.inject) output.push(`var ${modulesVarName} = ${JSON.stringify(modulesExports)};`); const defaultExport = `export default ${supportModules ? modulesVarName : cssVarName};`; output.push(defaultExport); if (options.dts && (await fs.pathExists(this.id))) { if (supportModules) dts.push(`interface ModulesExports ${JSON.stringify(modulesExports)}`, typeof options.inject === "object" && options.inject.treeshakeable ? `interface ModulesExports {inject:()=>void}` : "", `declare const ${modulesVarName}: ModulesExports;`); dts.push(defaultExport); await fs.writeFile(`${this.id}.d.ts`, dts.filter(Boolean).join("\n")); } return { code: output.filter(Boolean).join("\n"), map, extracted }; } }; const loader$3 = { name: "sourcemap", alwaysProcess: true, async process({ code, map }) { var _await$getMap; map = (_await$getMap = await getMap(code, this.id)) !== null && _await$getMap !== void 0 ? _await$getMap : map; return { code: stripMap(code), map }; } }; const ids = ["node-sass", "sass"]; const idsFmt = arrayFmt(ids); function loadSass (impl) { // Loading provided implementation if (impl) { const provided = loadModule(impl); if (provided) return [provided, impl]; throw new Error(`Could not load \`${impl}\` Sass implementation`); } // Loading one of the supported modules for (const id of ids) { const sass = loadModule(id); if (sass) return [sass, id]; } throw new Error(`You need to install ${idsFmt} package in order to process Sass files`); } const isModule = url => /^~[\d@A-Za-z]/.test(url); function getUrlOfPartial(url) { const { dir, base } = path.parse(url); return dir ? `${normalizePath(dir)}/_${base}` : `_${base}`; } function normalizeUrl(url) { if (isModule(url)) return normalizePath(url.slice(1)); if (isAbsolutePath(url) || isRelativePath(url)) return normalizePath(url); return `./${normalizePath(url)}`; } const extensions$1 = [".scss", ".sass", ".css"]; const importer$1 = (url, importer, done) => { const finalize = id => done({ file: id.replace(/\.css$/i, "") }); const next = () => done(null); if (!isModule(url)) return next(); const moduleUrl = normalizeUrl(url); const partialUrl = getUrlOfPartial(moduleUrl); const options = { caller: "Sass importer", basedirs: [path.dirname(importer)], extensions: extensions$1 }; // Give precedence to importing a partial resolveAsync([partialUrl, moduleUrl], options).then(finalize).catch(next); }; const finalize = id => ({ file: id.replace(/\.css$/i, "") }); const importerSync = (url, importer) => { if (!isModule(url)) return null; const moduleUrl = normalizeUrl(url); const partialUrl = getUrlOfPartial(moduleUrl); const options = { caller: "Sass importer", basedirs: [path.dirname(importer)], extensions: extensions$1 }; // Give precedence to importing a partial try { return finalize(resolveSync([partialUrl, moduleUrl], options)); } catch { return null; } }; const loader$2 = { name: "sass", test: /\.(sass|scss)$/i, async process({ code, map }) { var _options$sync; const options = { ...this.options }; const [sass, type] = loadSass(options.impl); const sync = (_options$sync = options.sync) !== null && _options$sync !== void 0 ? _options$sync : type !== "node-sass"; const importers = [sync ? importerSync : importer$1]; if (options.data) code = options.data + code; if (options.importer) Array.isArray(options.importer) ? importers.push(...options.importer) : importers.push(options.importer); const render = async options => new Promise((resolve, reject) => { if (sync) resolve(sass.renderSync(options));else sass.render(options, (err, css) => err ? reject(err) : resolve(css)); }); // Remove non-Sass options delete options.impl; delete options.sync; // node-sass won't produce sourcemaps if the `data` // option is used and `sourceMap` option is not a string. // // In case it is a string, `sourceMap` option // should be a path where the sourcemap is written. // // But since we're using the `data` option, // the sourcemap will not actually be written, but // all paths in sourcemap's sources will be relative to that path. const res = await render({ ...options, file: this.id, data: code, indentedSyntax: /\.sass$/i.test(this.id), sourceMap: this.id, omitSourceMapUrl: true, sourceMapContents: true, importer: importers }); const deps = res.stats.includedFiles; for (const dep of deps) this.deps.add(normalizePath(dep)); return { code: Buffer.from(res.css).toString(), map: res.map ? Buffer.from(res.map).toString() : map }; } }; const loader$1 = { name: "stylus", test: /\.(styl|stylus)$/i, async process({ code, map }) { var _style$sourcemap, _mm$toString; const options = { ...this.options }; const stylus = loadModule("stylus"); if (!stylus) throw new Error("You need to install `stylus` package in order to process Stylus files"); const basePath = normalizePath(path.dirname(this.id)); const paths = [`${basePath}/node_modules`, basePath]; if (options.paths) paths.push(...options.paths); const style = stylus(code, options).set("filename", this.id).set("paths", paths).set("sourcemap", { comment: false, basePath }); const render = async () => new Promise((resolve, reject) => { style.render((err, css) => err ? reject(err) : resolve(css)); }); code = await render(); const deps = style.deps(); for (const dep of deps) this.deps.add(normalizePath(dep)); // We have to manually modify the `sourcesContent` field // since stylus compiler doesn't support it yet if ((_style$sourcemap = style.sourcemap) !== null && _style$sourcemap !== void 0 && _style$sourcemap.sources && !style.sourcemap.sourcesContent) { style.sourcemap.sourcesContent = await Promise.all(style.sourcemap.sources.map(async source => { const file = normalizePath(basePath, source); const exists = await fs.pathExists(file); if (!exists) return null; return fs.readFile(file, "utf8"); })); } map = (_mm$toString = mm(style.sourcemap).toString()) !== null && _mm$toString !== void 0 ? _mm$toString : map; return { code, map }; } }; const extensions = [".less", ".css"]; const getStylesFileManager = less => new class extends less.AbstractFileManager { supports() { return true; } async loadFile(filename, filedir, opts) { const url = normalizeUrl(filename); const partialUrl = getUrlOfPartial(url); const options = { caller: "Less importer", basedirs: [filedir], extensions }; if (opts.paths) options.basedirs.push(...opts.paths); // Give precedence to importing a partial const id = await resolveAsync([partialUrl, url], options); return { filename: id, contents: await fs.readFile(id, "utf8") }; } }(); const importer = { install(less, pluginManager) { pluginManager.addFileManager(getStylesFileManager(less)); } }; const loader = { name: "less", test: /\.less$/i, async process({ code, map }) { var _res$map; const options = { ...this.options }; const less = loadModule("less"); if (!less) throw new Error("You need to install `less` package in order to process Less files"); const plugins = [importer]; if (options.plugins) plugins.push(...options.plugins); const res = await less.render(code, { ...options, plugins, filename: this.id, sourceMap: { outputSourceFiles: true, sourceMapBasepath: path.dirname(this.id) } }); const deps = res.imports; for (const dep of deps) this.deps.add(normalizePath(dep)); return { code: res.css, map: (_res$map = res.map) !== null && _res$map !== void 0 ? _res$map : map }; } }; function matchFile(file, condition) { if (!condition) return false; if (typeof condition === "function") return condition(file); return condition.test(file); } // This queue makes sure one thread is always available, // which is necessary for some cases // ex.: https://github.com/sass/node-sass/issues/857 const threadPoolSize = process.env.UV_THREADPOOL_SIZE ? Number.parseInt(process.env.UV_THREADPOOL_SIZE) : 4; // default `libuv` threadpool size const workQueue = new PQueue({ concurrency: threadPoolSize - 1 }); class Loaders { constructor(options) { _defineProperty(this, "use", void 0); _defineProperty(this, "test", void 0); _defineProperty(this, "loaders", new Map()); this.use = new Map(options.use.reverse()); this.test = file => options.extensions.some(ext => file.toLowerCase().endsWith(ext)); this.add(loader$4, loader$3, loader$2, loader, loader$1); if (options.loaders) this.add(...options.loaders); } add(...loaders) { for (const loader of loaders) { if (!this.use.has(loader.name)) continue; this.loaders.set(loader.name, loader); } } isSupported(file) { if (this.test(file)) return true; for (const [, loader] of this.loaders) { if (matchFile(file, loader.test)) return true; } return false; } async process(payload, context) { for await (const [name, options] of this.use) { const loader = this.loaders.get(name); if (!loader) continue; const ctx = { ...context, options }; if (loader.alwaysProcess || matchFile(ctx.id, loader.test)) { payload = await workQueue.add(loader.process.bind(ctx, payload)); } } return payload; } } async function concat (extracted) { const sm = new SourceMapGenerator({ file: "" }); const content = []; let offset = 0; for await (const { css, map } of extracted) { content.push(css); const _map = mm(map); const data = _map.toObject(); if (!data) continue; const consumer = _map.toConsumer(); if (!consumer) continue; consumer.eachMapping(m => sm.addMapping({ generated: { line: offset + m.generatedLine, column: m.generatedColumn }, original: { line: m.originalLine, column: m.originalColumn }, source: m.source, name: m.name })); if (data.sourcesContent) { for (const source of data.sources) { const content = consumer.sourceContentFor(source, true); sm.setSourceContent(source, content); } } offset += css.split("\n").length; } return { css: content.join("\n"), map: sm }; } var index = ((options = {}) => { var _options$dts, _options$namedExports, _options$autoModules, _options$extensions; const isIncluded = createFilter(options.include, options.exclude); const sourceMap = inferSourceMapOption(options.sourceMap); const loaderOpts = { ...inferModeOption(options.mode), minimize: inferOption(options.minimize, false), config: inferOption(options.config, {}), import: inferHandlerOption(options.import, options.alias), url: inferHandlerOption(options.url, options.alias), modules: inferOption(options.modules, false), to: options.to, dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts : false, namedExports: (_options$namedExports = options.namedExports) !== null && _options$namedExports !== void 0 ? _options$namedExports : false, autoModules: (_options$autoModules = options.autoModules) !== null && _options$autoModules !== void 0 ? _options$autoModules : false, extensions: (_options$extensions = options.extensions) !== null && _options$extensions !== void 0 ? _options$extensions : [".css", ".pcss", ".postcss", ".sss"], postcss: {} }; if (typeof loaderOpts.inject === "object" && loaderOpts.inject.treeshakeable && loaderOpts.namedExports) throw new Error("`inject.treeshakeable` option is incompatible with `namedExports` option"); if (options.parser) loaderOpts.postcss.parser = ensurePCSSOption(options.parser, "parser"); if (options.syntax) loaderOpts.postcss.syntax = ensurePCSSOption(options.syntax, "syntax"); if (options.stringifier) loaderOpts.postcss.stringifier = ensurePCSSOption(options.stringifier, "stringifier"); if (options.plugins) loaderOpts.postcss.plugins = ensurePCSSPlugins(options.plugins); const loaders = new Loaders({ use: [["postcss", loaderOpts], ...ensureUseOption(options), ["sourcemap", {}]], loaders: options.loaders, extensions: loaderOpts.extensions }); let extracted = []; const plugin = { name: "styles", async transform(code, id) { if (!isIncluded(id) || !loaders.isSupported(id)) return null; // Skip empty files if (code.replace(/\s/g, "") === "") return null; // Check if file was already processed into JS // by other instance(s) of this or other plugin(s) try { this.parse(code, {}); // If it doesn't throw... this.warn(`Skipping processed file ${humanlizePath(id)}`); return null; } catch {// Was not already processed, continuing } if (typeof options.onImport === "function") options.onImport(code, id); const ctx = { id, sourceMap, deps: new Set(), assets: new Map(), warn: this.warn.bind(this), plugin: this, options: {} }; const res = await loaders.process({ code }, ctx); for (const dep of ctx.deps) this.addWatchFile(dep); for (const [fileName, source] of ctx.assets) this.emitFile({ type: "asset", fileName, source }); if (res.extracted) { const { id } = res.extracted; extracted = extracted.filter(e => e.id !== id); extracted.push(res.extracted); } return { code: res.code, map: sourceMap && res.map ? res.map : { mappings: "" }, moduleSideEffects: res.extracted ? true : null }; }, augmentChunkHash(chunk) { if (extracted.length === 0) return; const ids = []; for (const module of Object.keys(chunk.modules)) { const traversed = new Set(); let current = [module]; do { const imports = []; for (const id of current) { if (traversed.has(id)) continue; if (loaders.isSupported(id)) { if (isIncluded(id)) imports.push(id); continue; } traversed.add(id); const i = this.getModuleInfo(id); i && imports.push(...i.importedIds); } current = imports; } while (current.some(id => !loaders.isSupported(id))); ids.push(...current); } const hashable = extracted.filter(e => ids.includes(e.id)).sort((a, b) => ids.lastIndexOf(a.id) - ids.lastIndexOf(b.id)).map(e => `${path.basename(e.id)}:${e.css}`); if (hashable.length === 0) return; return hashable.join(":"); }, async generateBundle(opts, bundle) { var _opts$dir; if (extracted.length === 0 || !(opts.dir || opts.file)) return; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- either `file` or `dir` are always present const dir = (_opts$dir = opts.dir) !== null && _opts$dir !== void 0 ? _opts$dir : path.dirname(opts.file); const chunks = Object.values(bundle).filter(c => c.type === "chunk"); const manual = chunks.filter(c => !c.facadeModuleId); const emitted = opts.preserveModules ? chunks : chunks.filter(c => c.isEntry || c.isDynamicEntry); const emittedList = []; const getExtractedData = async (name, ids) => { const fileName = typeof loaderOpts.extract === "string" ? normalizePath(loaderOpts.extract).replace(/^\.[/\\]/, "") : normalizePath(`${name}.css`); if (isAbsolutePath(fileName)) this.error(["Extraction path must be relative to the output directory,", `which is ${humanlizePath(dir)}`].join("\n")); if (isRelativePath(fileName)) this.error(["Extraction path must be nested inside output directory,", `which is ${humanlizePath(dir)}`].join("\n")); const entries = extracted.filter(e => ids.includes(e.id)).sort((a, b) => ids.lastIndexOf(a.id) - ids.lastIndexOf(b.id)); const res = await concat(entries); return { name: fileName, css: res.css, map: mm(res.map.toString()).relative(path.dirname(path.resolve(dir, fileName))).toString() }; }; const getName = chunk => { if (opts.file) return path.parse(opts.file).name; if (opts.preserveModules) { const { dir, name } = path.parse(chunk.fileName); return dir ? `${dir}/${name}` : name; } return chunk.name; }; const getImports = chunk => { const ids = []; for (const module of Object.keys(chunk.modules)) { const traversed = new Set(); let current = [module]; do { const imports = []; for (const id of current) { if (traversed.has(id)) continue; if (loaders.isSupported(id)) { if (isIncluded(id)) imports.push(id); continue; } traversed.add(id); const i = this.getModuleInfo(id); i && imports.push(...i.importedIds); } current = imports;