UNPKG

vite-plugin-qingkuai

Version:

vite-plugin-qingkuai is a Vite plugin that transforms `.qk` component files into native JavaScript using the Qingkuai compiler. It enables fast and seamless development of web applications built with [Qingkuai](https://qingkuai.dev).

503 lines (492 loc) 15.9 kB
'use strict'; var vite = require('vite'); var nodePath = require('node:path'); var nodeCrypto = require('node:crypto'); var compiler = require('qingkuai/compiler'); var nodeFs = require('node:fs'); var linesAndColumns = require('lines-and-columns'); var sourcemapCodec = require('@jridgewell/sourcemap-codec'); var postcss = require('postcss'); var selectorParser = require('postcss-selector-parser'); var sourceMapJs = require('source-map-js'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var vite__namespace = /*#__PURE__*/_interopNamespaceDefault(vite); const globalStyle = "\n" + compiler.util.formatSourceCode( ` /* Injected by vite-plugin-qingkuai */ *[hidden] { display: none !important; } ` ).replace(/^/gm, " ") + "\n"; function isUndefined(v) { return void 0 === v; } function findFilesByName(dir, targetFileName, ignoreList) { const result = []; const entries = nodeFs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { if (ignoreList.has(entry.name)) { continue; } const fullPath = nodePath.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 processedRules = /* @__PURE__ */ new WeakSet(); const createHashAttribute = () => { return selectorParser.attribute({ attribute: `qk-${hash}`, value: void 0, raws: {} }); }; const processor = postcss([ { postcssPlugin: "postcss-attach-scope-qingkuai", Rule(rule) { if (processedRules.has(rule)) { return; } processedRules.add(rule); if (rule.parent && "name" in rule?.parent && rule.parent.name === "keyframes") { return; } rule.selector = selectorParser((selectors) => { selectors.each((selector) => { let usedScopeAttribute = false; for (let i = 0; i < selector.nodes.length; i++) { const item = selector.nodes[i]; if (item.type === "attribute" && item.attribute === "qk-scope") { selector.nodes[i] = createHashAttribute(); usedScopeAttribute = true; } } if (usedScopeAttribute) { return; } 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, createHashAttribute()); } }); }).processSync(rule.selector); } } ]); const ret = await processor.process(code, { from: sourceFile, map: { prev: map, annotation: false } }); const outputMap = ret.map?.toJSON(); return { error, code: ret.css, map: outputMap, mappings: outputMap?.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 qingkuai(options = {}) { let isDev; let sourcemap; let cssSourcemap; let viteConfig; const compileResultCache = /* @__PURE__ */ new Map(); const qingkuaiConfigurations = /* @__PURE__ */ new Map(); const styleIdRE = /^virtual:\[\d+\].*?\.qk.(?:css|s[ac]ss|less|stylus|postcss)\?\d{13}$/; if (isUndefined(options.maxScheduleDepth)) { options.maxScheduleDepth = 300; } return { name: "qingkuai-compiler", config(_, env) { isDev = env.command === "serve"; return { define: { __qk_max_schedule_depth: options.maxScheduleDepth } }; }, 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); }, transformIndexHtml(html) { return { html, tags: [ { tag: "style", injectTo: "head", children: globalStyle } ] }; }, resolveId(id, importer) { if (styleIdRE.test(id)) { return id; } if (importer?.endsWith(".qk") && !nodePath.extname(id)) { const qingkuaiConfig = getQingkuaiConfiguration(id); return nodePath.join( nodePath.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.styleDescriptors[index]; while (true) { const hash = nodeCrypto.randomBytes(6).toString("hex"); virtualFileName = `${fileId}.${hash}.${style.lang}`; if (!nodeFs.existsSync(virtualFileName)) { break; } } const preprocessRes = await vite__namespace.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 attachScopeMap = attachScopeResult.map ?? assertedPreprocessMap; const samePath = (left, right) => { return nodePath.normalize(left) === nodePath.normalize(right); }; const sourceIndex = attachScopeMap?.sources.findIndex((source) => samePath(source, virtualFileName)) ?? -1; const currentSourceIndex = sourceIndex === -1 ? preprocessRes.deps?.size || 0 : sourceIndex; const offsetMappings = sourcemapCodec.encode( offsetSourceMap( attachScopeResult.mappings, currentSourceIndex, 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 ); if (preprocessedPosition.source && !samePath(preprocessedPosition.source, virtualFileName)) { this.error({ message: attachScopeResult.error.message, loc: { file: preprocessedPosition.source, line: preprocessedPosition.line, column: preprocessedPosition.column } }); } const preprocessedIndex = new linesAndColumns.LinesAndColumns(style.code).indexForLocation({ line: preprocessedPosition.line - 1, column: preprocessedPosition.column }) || 0; const position = compileRes.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: attachScopeMap?.names || assertedPreprocessMap?.names || [], sources: attachScopeMap?.sources ? (() => { const sources = [...attachScopeMap.sources]; if (currentSourceIndex !== -1) { sources[currentSourceIndex] = fileId; } return sources; })() : [...assertedPreprocessMap ? assertedPreprocessMap.sources.slice(0, -1) : [], fileId], sourcesContent: attachScopeMap?.sourcesContent ? (() => { const sourcesContent = [...attachScopeMap.sourcesContent]; if (currentSourceIndex !== -1) { sourcesContent[currentSourceIndex] = nodeFs.readFileSync(fileId, "utf-8"); } return sourcesContent; })() : void 0 } }; } }, async transform(src, id) { if (!id.endsWith(".qk")) { return; } try { const compileRes = compiler.compile(src, { ...getCompileOptions(id), sourcemap, debug: isDev, hashId: compileResultCache.get(id)?.hashId }); compileRes.messages.forEach(({ type, value: warning }) => { if (type === "warning") { this.warn(warning.message); } }); compileResultCache.set(id, compileRes); const compiledCodeArr = [compileRes.code]; compileRes.styleDescriptors.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.scriptDescriptor.isTS) { if (!sourcemap) { return compiledCode; } return { code: compiledCode, map: Object.assign(baseMap, { mappings: compileRes.mappings }) }; } const transformWithOxc = vite__namespace.transformWithOxc; const transformWithEsbuild = vite__namespace.transformWithEsbuild; const tsCompileRes = await (transformWithOxc ?? transformWithEsbuild)( compiledCode, id, { sourcemap, lang: "ts", loader: "ts", target: "esnext" }, sourcemap ? { version: 3, names: [], sources: [id], sourcesContent: [src], mappings: compileRes.mappings } : void 0 ); if (!tsCompileRes) { this.error("Current Vite runtime does not provide TypeScript transform APIs."); } if (!sourcemap) { return tsCompileRes.code; } let transformedMap = tsCompileRes.map; if (typeof tsCompileRes.map === "string") { transformedMap = JSON.parse(tsCompileRes.map); } return { code: tsCompileRes.code, map: Object.assign(baseMap, { mappings: transformedMap?.mappings || compileRes.mappings }) }; } catch (err) { if (err.loc && "start" in err.loc && "index" in err.loc.start) { err.pos = err.loc.start.index, this.error(err); } else { this.error( "Qingkuai compile result is invalid. Please report this at https://github.com/qingkuai-js/qingkuai/issues and include your .qk source for reproduction." ); } } } }; function getQingkuaiConfiguration(id) { let config = { interpretiveComments: true, resolveImportExtension: true, preserveHtmlComments: "development" }; while (true) { const dir = nodePath.dirname(id); if (dir === id) { break; } const got = qingkuaiConfigurations.get(id = dir); if (got) { config = got; break; } } return config; } function createQingkuaiConfigurationWatcher(server) { const watcher = server.watcher; const isQingkuaiConfig = (filePath) => { return nodePath.basename(filePath) === ".qingkuairc" && !filePath.includes(`${nodePath.sep}node_modules${nodePath.sep}`); }; watcher.on("unlink", (filePath) => { if (isQingkuaiConfig(filePath)) { qingkuaiConfigurations.delete(nodePath.dirname(filePath)); } }); watcher.on("change", (filePath) => { if (isQingkuaiConfig(filePath)) { recordQingkuaiConfiguration(filePath); } }); watcher.on("add", (filePath) => { if (isQingkuaiConfig(filePath)) { recordQingkuaiConfiguration(filePath); } }); } function loadAllQingkuaiConfigurations(workspaceDir) { findFilesByName(workspaceDir, ".qingkuairc", /* @__PURE__ */ new Set(["node_modules"])).forEach((fileName) => { recordQingkuaiConfiguration(fileName); }); } function recordQingkuaiConfiguration(fileName) { try { qingkuaiConfigurations.set(nodePath.dirname(fileName), JSON.parse(nodeFs.readFileSync(fileName, "utf-8"))); } catch { } } function getCompileOptions(id) { const qingkuaiConfig = getQingkuaiConfiguration(id); const ret = { interpretiveComments: isUndefined(qingkuaiConfig.interpretiveComments) ? isDev : !!qingkuaiConfig.interpretiveComments, shorthandDerivedDeclaration: isUndefined(qingkuaiConfig.shorthandDerivedDeclaration) ? true : !!qingkuaiConfig.shorthandDerivedDeclaration, reactivityMode: qingkuaiConfig.reactivityMode === "shallow" ? "shallow" : "reactive" }; switch (qingkuaiConfig.whitespace) { case "trim": case "collapse": case "preserve": case "trim-collapse": { ret.whitespace = qingkuaiConfig.whitespace; break; } default: { ret.whitespace = "trim-collapse"; break; } } switch (qingkuaiConfig.preserveHtmlComments) { case "all": { ret.preserveHtmlComments = true; break; } case "never": { ret.preserveHtmlComments = false; break; } case "production": { ret.preserveHtmlComments = !isDev; break; } default: { ret.preserveHtmlComments = isDev; break; } } return ret; } } 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 = qingkuai;