UNPKG

vite-plugin-qingkuai

Version:

384 lines (377 loc) 12.8 kB
'use strict'; var node_path = require('node:path'); var node_fs = require('node:fs'); var node_crypto = require('node:crypto'); var compiler = require('qingkuai/compiler'); var linesAndColumns = require('lines-and-columns'); var sourcemapCodec = require('@jridgewell/sourcemap-codec'); var postcss = require('postcss'); var selectorParser = require('postcss-selector-parser'); var vite = require('vite'); var sourceMapJs = require('source-map-js'); function findFilesByName(dir, targetFileName, ignoreList) { const result = []; const entries = node_fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { if (ignoreList.has(entry.name)) { continue; } const fullPath = node_path.join(dir, entry.name); if (entry.name === targetFileName) { result.push(fullPath); } else if (entry.isDirectory()) { result.push(...findFilesByName(fullPath, targetFileName, ignoreList)); } } return result; } async function attachScopeForStyleSelectors(code, hash, sourceFile, map) { let error; const processor = postcss([ { postcssPlugin: "postcss-attach-scope-qingkuai", Rule(rule) { if (rule.parent && "name" in rule?.parent && rule.parent.name === "keyframes") { return; } rule.selector = selectorParser((selectors) => { selectors.each((selector) => { const scopePseudoSelectors = selector.nodes.filter( (node) => node.type === "pseudo" && node.value === ":scope" ); if (scopePseudoSelectors.length) { if (scopePseudoSelectors.length > 1) { error = { loc: scopePseudoSelectors[1].source?.start, message: "Duplicate scope pseudo class selectors." }; } else { const fistScopePseudo = scopePseudoSelectors[0]; if (!fistScopePseudo.nodes[0]?.toString()) { error = { loc: fistScopePseudo?.source?.start, message: "Must pass paramter for the :scope pseudo class selector." }; } if (fistScopePseudo.nodes.length > 1) { error = { loc: fistScopePseudo?.source?.start, message: "The :scope pseudo class selector can accept at most one parameter." }; } fistScopePseudo.nodes[0] && (selector = fistScopePseudo.nodes[0]); } } const index = selector.nodes.findLastIndex(({ type }) => { return type === "id" || type === "tag" || type === "class" || type === "universal" || type === "attribute"; }); if (index !== -1) { const lastNode = selector.nodes[index]; lastNode.parent?.insertAfter( lastNode, selectorParser.attribute({ attribute: `qk-${hash}`, value: void 0, raws: {} }) ); scopePseudoSelectors[0]?.replaceWith(...scopePseudoSelectors[0].nodes); } }); }).processSync(rule.selector); } } ]); const ret = await processor.process(code, { from: sourceFile, map: { prev: map, annotation: false } }); return { error, code: ret.css, mappings: ret.map.toJSON().mappings }; } function offsetSourceMap(mappings, sourceIndex, preLine, preColumn) { return sourcemapCodec.decode(mappings).map((line) => { return line.map((segment) => { if (segment.length === 1 || segment[1] !== sourceIndex) { return segment; } if (segment[2] === preLine) { segment[3] += preColumn; } else { segment[2] += preLine; } return segment; }); }); } async function getOriginalPosition(map, line, column) { return new sourceMapJs.SourceMapConsumer(map).originalPositionFor({ line, column }); } function qingkuaiPlugin() { let isDev; let sourcemap; let cssSourcemap; let viteConfig; const compileResultCache = /* @__PURE__ */ new Map(); const qingkuaiConfigurations = /* @__PURE__ */ new Map(); const qingkuaiPackageServeRE = /node_modules\/\.vite\/deps\/chunk-.*$/; const confIdentifierRE = /__qk_expose_(?:dependencies|destructions)__/g; const styleIdRE = /^virtual:\[\d+\].*?\.qk.(?:css|s[ac]ss|less|stylus|postcss)\?\d{13}$/; const qingkuaiPackageBuildRE = /(?:node_modules)?\/qingkuai\/dist\/esm\/(?:chunks|runtime)\/\w+\.js$/; return { name: "qingkuai-compiler", config(_, env) { isDev = env.command === "serve"; }, configureServer(server) { createQingkuaiConfigurationWatcher(server); }, configResolved(config) { viteConfig = config; if (isDev) { sourcemap = true; cssSourcemap = !!config.css.devSourcemap; } else { cssSourcemap = true; sourcemap = config.build.sourcemap !== false; } loadAllQingkuaiConfigurations(config.root); }, resolveId(id, importer) { if (styleIdRE.test(id)) { return id; } if (importer?.endsWith(".qk") && !node_path.extname(id)) { const qingkuaiConfig = getQingkuaiConfiguration(id); return node_path.join(node_path.dirname(importer), id + (qingkuaiConfig.resolveImportExtension ? ".qk" : "")); } return id; }, async load(id) { if (styleIdRE.test(id)) { const { fileId, index } = parseStyleId(id); if (index === -1) { return ""; } let virtualFileName; const compileRes = compileResultCache.get(fileId); const style = compileRes.inputDescriptor.styles[index]; while (true) { const hash = node_crypto.randomBytes(6).toString("hex"); virtualFileName = `${fileId}.${hash}.${style.lang}`; if (!node_fs.existsSync(virtualFileName)) { break; } } const preprocessRes = await vite.preprocessCSS(style.code, virtualFileName, { ...viteConfig, css: { ...viteConfig.css, postcss: { from: virtualFileName } } }); if (!cssSourcemap) { return preprocessRes.code; } const assertedPreprocessMap = preprocessRes.map; const attachScopeResult = await attachScopeForStyleSelectors( preprocessRes.code, compileRes.hashId, virtualFileName, assertedPreprocessMap ); const offsetMappings = sourcemapCodec.encode( offsetSourceMap( attachScopeResult.mappings, preprocessRes.deps?.size || 0, style.loc.start.line - 1, style.loc.start.column - 1 ) ); if (attachScopeResult.error) { if (!attachScopeResult.error.loc) { this.error(attachScopeResult.error.message); } else { const preprocessedPosition = await getOriginalPosition( assertedPreprocessMap, attachScopeResult.error.loc.line, attachScopeResult.error.loc.column ); const preprocessedIndex = new linesAndColumns.LinesAndColumns(style.code).indexForLocation({ line: preprocessedPosition.line - 1, column: preprocessedPosition.column }) || 0; const position = compileRes.inputDescriptor.positions[style.loc.start.index + preprocessedIndex]; this.error({ message: attachScopeResult.error.message, loc: { file: fileId, line: position.line, column: position.column } }); } } return { code: attachScopeResult.code, map: { version: 3, mappings: offsetMappings, names: assertedPreprocessMap?.names || [], sources: [...assertedPreprocessMap ? assertedPreprocessMap.sources.slice(0, -1) : [], fileId] } }; } }, async transform(src, id) { const qingkuaiConfig = getQingkuaiConfiguration(id); if (!id.endsWith(".qk")) { if (qingkuaiPackageServeRE.test(id) || qingkuaiPackageBuildRE.test(id)) { const ret = src.replace(confIdentifierRE, (s) => { switch (s) { case "__qk_expose_dependencies__": return JSON.stringify(!!qingkuaiConfig.exposeDependencies); case "__qk_expose_destructions__": return JSON.stringify(!!qingkuaiConfig.exposeDestructions); default: return s; } }); return ret; } return; } try { const compileRes = compiler.compile(src, { sourcemap, debug: isDev, hashId: compileResultCache.get(id)?.hashId || void 0, reserveTemplateComment: getReserveHtmlComments(qingkuaiConfig), componentName: compiler.util.kebab2Camel(node_path.basename(id, node_path.extname(id)), true) }); compileRes.messages.forEach(({ type, value: warning }) => { if (type === "warning") { this.warn(warning.message); } }); compileResultCache.set(id, compileRes); const compiledCodeArr = [compileRes.code]; compileRes.inputDescriptor.styles.forEach((_, index) => { compiledCodeArr.push(`import "virtual:[${index}]${id}.css?${Date.now()}"`); }); const baseMap = { version: 3, sources: [id], sourcesContent: [src] }; const compiledCode = compiledCodeArr.join("\n"); if (!compileRes.inputDescriptor.script.isTS) { if (!sourcemap) { return compiledCode; } return { code: compiledCode, map: Object.assign(baseMap, { mappings: compileRes.mappings }) }; } const esBuildCompileRes = await vite.transformWithEsbuild( compiledCode, id, { sourcemap, loader: "ts", target: "esnext" }, sourcemap ? { version: 3, sources: [id], sourcesContent: [src], mappings: compileRes.mappings } : void 0 ); if (!sourcemap) { return esBuildCompileRes.code; } return { code: esBuildCompileRes.code, map: Object.assign(baseMap, { mappings: esBuildCompileRes.map.mappings }) }; } catch (err) { err.pos = err.loc.start.index, this.error(err); } } }; function getQingkuaiConfiguration(id) { let config = { insertTipComments: true, exposeDestructions: isDev, exposeDependencies: isDev, resolveImportExtension: true, reserveHtmlComments: "development" }; while (true) { const dir = node_path.dirname(id); if (dir === id) { break; } const got = qingkuaiConfigurations.get(id = dir); if (got) { config = got; break; } } return config; } function getReserveHtmlComments(config) { switch (config.reserveHtmlComments) { case "all": return true; case "never": return false; case "production": return !isDev; default: return isDev; } } function createQingkuaiConfigurationWatcher(server) { const watcher = server.watcher.add([".qingkuairc", "!**/node_modules/**"]); watcher.on("unlink", (path) => qingkuaiConfigurations.delete(node_path.dirname(path))); watcher.on("change", recordQingkuaiConfiguration); watcher.on("add", recordQingkuaiConfiguration); } function loadAllQingkuaiConfigurations(workspaceDir) { findFilesByName(workspaceDir, ".qingkuairc", /* @__PURE__ */ new Set(["node_modules"])).forEach((fileName) => { recordQingkuaiConfiguration(fileName); }); } function recordQingkuaiConfiguration(fileName) { try { qingkuaiConfigurations.set(node_path.dirname(fileName), JSON.parse(node_fs.readFileSync(fileName, "utf-8"))); } catch { } } } function parseStyleId(id) { const m1 = /^virtual:\[(\d+)\]/.exec(id); const m2 = /\.[a-z]+\?\d{13}$/.exec(id); if (!m1 || !m2) { return { index: -1, fileId: "" }; } return { index: parseInt(m1[1]), fileId: id.slice(m1.index + m1[0].length, m2.index) }; } module.exports = qingkuaiPlugin;