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
JavaScript
;
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;