vite-plugin-qingkuai
Version:
384 lines (377 loc) • 12.8 kB
JavaScript
;
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;