@borela-tech/vite-plugin-multiline-tailwindcss
Version:
Allows tailwindcss classes to be broken into multiple lines.
442 lines (418 loc) • 11.1 kB
JavaScript
// src/compileCssPlugin.ts
import { Scanner } from "@tailwindcss/oxide";
import {
compile,
toSourceMap
} from "@tailwindcss/node";
function compileCssPlugin(state) {
return {
name: "@borela-tech/vite-plugin-multiline-tailwindcss:compile-css",
enforce: "pre",
async transform(code, id) {
const {
candidatesFromTransforms: {
className: classNameCandidatesPerId,
tagged: taggedCandidatesPerId
},
projectRootPath,
rootCssPath,
srcDirPath
} = state;
if (id !== rootCssPath)
return code;
const compiler = await compile(code, {
base: srcDirPath,
from: id,
onDependency: (path) => {
this.addWatchFile(path);
},
shouldRewriteUrls: true
});
const scanner = new Scanner({
sources: [
{
base: projectRootPath,
pattern: "**/*",
negated: false
},
...compiler.sources
]
});
const ALL_CANDIDATES = scanner.scan();
for (const [, candidates] of classNameCandidatesPerId)
ALL_CANDIDATES.push(...candidates);
for (const [, candidates] of taggedCandidatesPerId)
ALL_CANDIDATES.push(...candidates);
const MAP = compiler.buildSourceMap();
const GENERATED_CODE = compiler.build(ALL_CANDIDATES);
const GENERATED_MAP = toSourceMap(MAP).raw;
return {
code: GENERATED_CODE,
map: GENERATED_MAP
};
}
};
}
// src/initialize.ts
import { join } from "node:path";
function initialize(state) {
return {
name: "@borela-tech/vite-plugin-multiline-tailwindcss:initialize",
enforce: "pre",
configResolved(config) {
if (!config.root)
throw new Error("root is not defined");
state.rootCssPath = join(config.root, "src", "index.css");
state.projectRootPath = config.root;
state.srcDirPath = join(config.root, "src");
},
configureServer(server) {
state.devServer = server;
}
};
}
// ../../lib/transformJsxCssClasses.ts
import * as t from "@babel/types";
// ../../lib/babel/generate.ts
import generatorModuleOrFunction from "@babel/generator";
var generate = generatorModuleOrFunction;
if (typeof generatorModuleOrFunction != "function")
generate = generatorModuleOrFunction.default;
// ../../lib/transformJsxCssClasses.ts
import { parse as parse2 } from "@babel/parser";
// ../../lib/generator/generateCodeForArray.ts
function generateCodeForArray(node) {
const indexItems = node.indexItems.map(generateCodeForNode).join(",");
return `${node.name}[${indexItems}]${node.suffix}`;
}
// ../../lib/generator/generateCodeForFunction.ts
function generateCodeForFunction(node) {
const args = node.args.map(generateCodeForNode).join(",");
return `${node.name}(${args})${node.suffix}`;
}
// ../../lib/generator/generateCodeForNode.ts
function generateCodeForNode(node) {
if (node.type === "Function")
return generateCodeForFunction(node);
if (node.type === "Array")
return generateCodeForArray(node);
return node.value;
}
// ../../lib/generator/generateCodeForNodes.ts
function generateCodeForNodes(nodes) {
return nodes.map((node) => generateCodeForNode(node)).join(" ");
}
// ../../lib/parser/next.ts
function next(state) {
return state.input[state.pos++];
}
// ../../lib/parser/peek.ts
function peek(state) {
return state.input[state.pos];
}
// ../../lib/parser/parseIdentifier.ts
function parseIdentifier(state) {
let value = "";
while (state.pos < state.input.length) {
if (/[\s,()[\]]/.test(peek(state)))
break;
value += next(state);
}
return value;
}
// ../../lib/parser/skipWhitespace.ts
function skipWhitespace(state) {
while (/\s/.test(peek(state)))
next(state);
}
// ../../lib/parser/parseFunction.ts
function parseFunction(state, name) {
const args = [];
next(state);
while (state.pos < state.input.length) {
skipWhitespace(state);
if (peek(state) === ")")
break;
args.push(parseExpression(state));
skipWhitespace(state);
if (peek(state) === ",")
next(state);
}
next(state);
const suffix = parseIdentifier(state);
return {
args,
name,
suffix,
type: "Function"
};
}
// ../../lib/parser/parseExpression.ts
function parseExpression(state) {
skipWhitespace(state);
const identifier = parseIdentifier(state);
skipWhitespace(state);
if (peek(state) === "(")
return parseFunction(state, identifier);
return {
type: "Identifier",
value: identifier
};
}
// ../../lib/parser/parseArrayIndex.ts
function parseArrayIndex(state) {
const items = [];
next(state);
while (state.pos < state.input.length) {
skipWhitespace(state);
if (peek(state) === "]")
break;
items.push(parseExpression(state));
skipWhitespace(state);
if (peek(state) === ",")
next(state);
}
next(state);
return items;
}
// ../../lib/parser/parse.ts
function parse(input) {
const ast = [];
const state = {
input,
pos: 0
};
while (state.pos < state.input.length) {
skipWhitespace(state);
const identifier = parseIdentifier(state);
skipWhitespace(state);
if (peek(state) === "[") {
const indexItems = parseArrayIndex(state);
const suffix = parseIdentifier(state);
const arrayNode = {
indexItems,
name: identifier,
suffix,
type: "Array"
};
ast.push(arrayNode);
continue;
}
if (peek(state) === "(") {
const functionNode = parseFunction(state, identifier);
ast.push(functionNode);
continue;
}
const identifierNode = {
type: "Identifier",
value: identifier
};
ast.push(identifierNode);
}
return ast;
}
// ../../lib/transformTailwindClasses.ts
function transformTailwindClasses(tailwindClasses) {
const nodes = parse(tailwindClasses);
return generateCodeForNodes(nodes);
}
// ../../lib/babel/traverse.ts
import traverseModuleOrFunction from "@babel/traverse";
var traverse = traverseModuleOrFunction;
if (typeof traverseModuleOrFunction != "function")
traverse = traverseModuleOrFunction.default;
// ../../lib/transformJsxCssClasses.ts
function transformJsxCssClasses(code) {
const candidatesFound = [];
const ast = parse2(code, {
sourceType: "module",
plugins: [
"jsx",
"typescript"
]
});
traverse(ast, {
JSXAttribute(path) {
const {
node: {
name,
value
}
} = path;
if (!t.isJSXIdentifier(name))
return;
if (name.name != "className")
return;
if (!t.isStringLiteral(value))
return;
const transformed = transformTailwindClasses(value.value);
const filtered = transformed.split(" ").filter(Boolean);
candidatesFound.push(...filtered);
value.value = filtered.join(" ");
}
});
const {
code: transformedCode,
map: transformedCodeMap
} = generate(ast, {}, code);
return {
candidatesFound,
transformedCode: {
code: transformedCode,
map: transformedCodeMap
}
};
}
// src/updateModule.ts
var intervals = /* @__PURE__ */ new Map();
function updateModule(devServer, id) {
const module = devServer.moduleGraph.getModuleById(id);
if (!module)
return;
let interval = intervals.get(id);
if (interval)
clearInterval(interval);
devServer.moduleGraph.invalidateModule(module);
interval = setInterval(() => {
if (module.invalidationState != "HARD_INVALIDATED") {
clearInterval(interval);
intervals.delete(id);
return;
}
devServer.moduleGraph.invalidateModule(module);
void devServer.reloadModule(module);
}, 10);
intervals.set(id, interval);
}
// src/transformJsxCssClassesPlugin.ts
function transformJsxCssClassesPlugin(state) {
return {
name: "@borela-tech/vite-plugin-multiline-tailwindcss:transform-jsx-css-classes",
enforce: "pre",
transform(code, id) {
if (!/\.[jt]sx$/.test(id))
return code;
const {
candidatesFromTransforms: {
className: candidatesPerId
},
devServer,
rootCssPath
} = state;
const {
candidatesFound,
transformedCode: {
code: transformedCode,
map: transformedCodeMap
}
} = transformJsxCssClasses(code);
candidatesPerId.set(id, candidatesFound);
if (candidatesFound.length > 0) {
if (devServer)
updateModule(devServer, rootCssPath);
}
return {
code: transformedCode,
map: transformedCodeMap
};
}
};
}
// ../../lib/transformTaggedStrings.ts
import * as t2 from "@babel/types";
import { parse as parse3 } from "@babel/parser";
function transformTaggedStrings(code) {
const candidatesFound = [];
const ast = parse3(code, {
sourceType: "module",
plugins: [
"jsx",
"typescript"
]
});
traverse(ast, {
TaggedTemplateExpression(path) {
const {
tag,
quasi: { quasis }
} = path.node;
if (!t2.isIdentifier(tag))
return;
if (tag.name !== "tailwindcss")
return;
if (quasis.length !== 1)
throw new Error("Tailwind tagged template should have exactly one argument.");
const rawString = quasis[0].value.raw;
const transformed = transformTailwindClasses(rawString);
const filtered = transformed.split(" ").filter(Boolean);
candidatesFound.push(...filtered);
path.replaceWith(
t2.stringLiteral(transformed)
);
}
});
const {
code: transformedCode,
map: transformedCodeMap
} = generate(ast, {}, code);
return {
candidatesFound,
transformedCode: {
code: transformedCode,
map: transformedCodeMap
}
};
}
// src/transformTaggedStringsPlugin.ts
function transformTaggedStringsPlugin(state) {
return {
name: "@borela-tech/vite-plugin-multiline-tailwindcss:transform-tagged-strings",
enforce: "pre",
transform(code, id) {
if (!/\.[jt]sx?$/.test(id))
return code;
const {
candidatesFromTransforms: {
tagged: candidatesPerId
},
devServer,
rootCssPath
} = state;
const {
candidatesFound,
transformedCode: {
code: transformedCode,
map: transformedCodeMap
}
} = transformTaggedStrings(code);
candidatesPerId.set(id, candidatesFound);
if (candidatesFound.length > 0) {
if (devServer)
updateModule(devServer, rootCssPath);
}
return {
code: transformedCode,
map: transformedCodeMap
};
}
};
}
// src/index.ts
function multilineTailwindCss() {
const state = {
candidatesFromTransforms: {
className: /* @__PURE__ */ new Map(),
tagged: /* @__PURE__ */ new Map()
}
};
return [
initialize(state),
transformTaggedStringsPlugin(state),
transformJsxCssClassesPlugin(state),
compileCssPlugin(state)
];
}
export {
multilineTailwindCss
};
//# sourceMappingURL=index.js.map