Optimization
This commit is contained in:
parent
1f5e7fa049
commit
08cd7940e2
13
package.json
13
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@aet/tailwind",
|
"name": "@aet/tailwind",
|
||||||
"version": "1.0.23",
|
"version": "1.0.24",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -33,12 +33,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aet/eslint-rules": "2.0.35",
|
"@aet/eslint-rules": "2.0.36",
|
||||||
"@types/babel__core": "^7.20.5",
|
"@types/babel__core": "^7.20.5",
|
||||||
"@types/bun": "^1.2.0",
|
"@types/babel__traverse": "^7.20.6",
|
||||||
|
"@types/bun": "^1.2.2",
|
||||||
"@types/dedent": "^0.7.2",
|
"@types/dedent": "^0.7.2",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^22.13.0",
|
"@types/node": "^22.13.1",
|
||||||
"@types/postcss-safe-parser": "^5.0.4",
|
"@types/postcss-safe-parser": "^5.0.4",
|
||||||
"@types/react": "^19.0.8",
|
"@types/react": "^19.0.8",
|
||||||
"@types/stylis": "^4.2.7",
|
"@types/stylis": "^4.2.7",
|
||||||
@ -56,8 +57,8 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"tsup": "^8.3.6",
|
"tsup": "^8.3.6",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"vite": "^6.0.11",
|
"vite": "^6.1.0",
|
||||||
"vitest": "^3.0.4"
|
"vitest": "^3.0.5"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"tailwindcss": "^3.4.17"
|
"tailwindcss": "^3.4.17"
|
||||||
|
838
pnpm-lock.yaml
generated
838
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -4,32 +4,59 @@ import fs from "node:fs";
|
|||||||
const supportedTags = [
|
const supportedTags = [
|
||||||
"a",
|
"a",
|
||||||
"abbr",
|
"abbr",
|
||||||
|
"address",
|
||||||
"article",
|
"article",
|
||||||
|
"b",
|
||||||
|
"bdi",
|
||||||
"blockquote",
|
"blockquote",
|
||||||
"button",
|
"button",
|
||||||
|
"caption",
|
||||||
"cite",
|
"cite",
|
||||||
"code",
|
"code",
|
||||||
|
"col",
|
||||||
|
"colgroup",
|
||||||
|
"dd",
|
||||||
|
"del",
|
||||||
"details",
|
"details",
|
||||||
|
"dialog",
|
||||||
"div",
|
"div",
|
||||||
|
"dl",
|
||||||
|
"dt",
|
||||||
|
"em",
|
||||||
|
"fieldset",
|
||||||
|
"figcaption",
|
||||||
"figure",
|
"figure",
|
||||||
"footer",
|
"footer",
|
||||||
|
"form",
|
||||||
"h1",
|
"h1",
|
||||||
"h2",
|
"h2",
|
||||||
"h3",
|
"h3",
|
||||||
"h4",
|
"h4",
|
||||||
|
"h5",
|
||||||
|
"h6",
|
||||||
|
"header",
|
||||||
"hr",
|
"hr",
|
||||||
|
"i",
|
||||||
"iframe",
|
"iframe",
|
||||||
"img",
|
"img",
|
||||||
"input",
|
"input",
|
||||||
"ins",
|
"ins",
|
||||||
|
"kbd",
|
||||||
"label",
|
"label",
|
||||||
|
"legend",
|
||||||
"li",
|
"li",
|
||||||
"main",
|
"main",
|
||||||
|
"mark",
|
||||||
|
"menu",
|
||||||
|
"meter",
|
||||||
"nav",
|
"nav",
|
||||||
"ol",
|
"ol",
|
||||||
"output",
|
"output",
|
||||||
"p",
|
"p",
|
||||||
|
"picture",
|
||||||
"pre",
|
"pre",
|
||||||
|
"progress",
|
||||||
|
"q",
|
||||||
"rt",
|
"rt",
|
||||||
"ruby",
|
"ruby",
|
||||||
"section",
|
"section",
|
||||||
@ -39,14 +66,21 @@ const supportedTags = [
|
|||||||
"sub",
|
"sub",
|
||||||
"summary",
|
"summary",
|
||||||
"sup",
|
"sup",
|
||||||
|
"svg",
|
||||||
"table",
|
"table",
|
||||||
"tbody",
|
"tbody",
|
||||||
"td",
|
"td",
|
||||||
|
"textarea",
|
||||||
|
"tfoot",
|
||||||
|
"th",
|
||||||
"thead",
|
"thead",
|
||||||
|
"time",
|
||||||
"tr",
|
"tr",
|
||||||
|
"u",
|
||||||
"ul",
|
"ul",
|
||||||
"var",
|
"var",
|
||||||
"video",
|
"video",
|
||||||
|
"wbr",
|
||||||
].sort();
|
].sort();
|
||||||
|
|
||||||
function replaceFile(file: string, search: string | RegExp, replace: string) {
|
function replaceFile(file: string, search: string | RegExp, replace: string) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`emit > supports emitting as CSS module 1`] = `
|
exports[`emit > supports emitting as CSS module 1`] = `
|
||||||
"// babel-tailwind:/Users/aet/Documents/Git/babel-tailwind/src/.temp-attr/index.module.css
|
"// babel-tailwind:/Users/aet/Documents/Git/babel-tailwind/src/.temp-emit/index.module.css
|
||||||
var index_default = {
|
var index_default = {
|
||||||
"tw-gqn2k6": "index_tw-gqn2k6",
|
"tw-gqn2k6": "index_tw-gqn2k6",
|
||||||
"tw-1qtvvjy": "index_tw-1qtvvjy"
|
"tw-1qtvvjy": "index_tw-1qtvvjy"
|
||||||
|
@ -3,7 +3,7 @@ import { describe, it } from "vitest";
|
|||||||
import { getBuild, matchSnapshot } from "./utils";
|
import { getBuild, matchSnapshot } from "./utils";
|
||||||
|
|
||||||
describe("emit", () => {
|
describe("emit", () => {
|
||||||
const compileESBuild = getBuild("attr");
|
const compileESBuild = getBuild("emit");
|
||||||
|
|
||||||
it("supports emitting as CSS module", async () => {
|
it("supports emitting as CSS module", async () => {
|
||||||
const { files } = await compileESBuild({
|
const { files } = await compileESBuild({
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import { getBuild } from "./utils";
|
import { getBuild } from "./utils";
|
||||||
|
|
||||||
describe("babel-tailwind", () => {
|
describe("babel-tailwind", () => {
|
||||||
|
@ -45,8 +45,50 @@ describe("merges with existing className attribute", () => {
|
|||||||
|
|
||||||
const clsName = getClassName("text-center");
|
const clsName = getClassName("text-center");
|
||||||
expect(files.js.text).toContain(
|
expect(files.js.text).toContain(
|
||||||
`className: ({\n isEntering\n }) => _cx(isEntering ? "enter" : "exit", "${clsName}")`
|
`className: ({\n isEntering\n }) => _cx("${clsName}", isEntering ? "enter" : "exit")`
|
||||||
);
|
);
|
||||||
expect(files.css.text).toMatch(`.${clsName} {\n text-align: center;\n}`);
|
expect(files.css.text).toMatch(`.${clsName} {\n text-align: center;\n}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("reuses clsx in scope", async () => {
|
||||||
|
const { files } = await compileESBuild({
|
||||||
|
clsx: "clsx",
|
||||||
|
expectFiles: 2,
|
||||||
|
javascript: /* tsx */ `
|
||||||
|
import { clsx } from "clsx";
|
||||||
|
|
||||||
|
export function Hello(className) {
|
||||||
|
return (
|
||||||
|
<div className={clsx("font-semibold", className)} css="text-center" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect([...files.js.text.match(/from "clsx"/g)!]).toHaveLength(1);
|
||||||
|
expect(files.js.text).toContain(
|
||||||
|
'{ className: clsx("tw-gqn2k6", "font-semibold", className)'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not reuse invalid clsx", async () => {
|
||||||
|
const { files } = await compileESBuild({
|
||||||
|
clsx: "clsx",
|
||||||
|
expectFiles: 2,
|
||||||
|
javascript: /* tsx */ `
|
||||||
|
import { clsx } from "clsx";
|
||||||
|
|
||||||
|
export function Hello(className) {
|
||||||
|
let clsx = () => {};
|
||||||
|
return (
|
||||||
|
<div className={clsx("font-semibold", className)} css="text-center" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(files.js.text).toContain(
|
||||||
|
'className: _cx("tw-gqn2k6", clsx("font-semibold", className))'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,6 +2,7 @@ import { basename, dirname, extname, join } from "node:path";
|
|||||||
|
|
||||||
import type b from "@babel/core";
|
import type b from "@babel/core";
|
||||||
import { type NodePath, type types as t } from "@babel/core";
|
import { type NodePath, type types as t } from "@babel/core";
|
||||||
|
import type { Scope } from "@babel/traverse";
|
||||||
import hash from "@emotion/hash";
|
import hash from "@emotion/hash";
|
||||||
import invariant from "tiny-invariant";
|
import invariant from "tiny-invariant";
|
||||||
|
|
||||||
@ -17,6 +18,14 @@ type Type = "css" | "js";
|
|||||||
|
|
||||||
export type BabelPluginUtils = ReturnType<typeof getUtils>;
|
export type BabelPluginUtils = ReturnType<typeof getUtils>;
|
||||||
|
|
||||||
|
interface Import {
|
||||||
|
source: string;
|
||||||
|
specifiers: {
|
||||||
|
local: string;
|
||||||
|
imported: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
function getUtils({
|
function getUtils({
|
||||||
path,
|
path,
|
||||||
state,
|
state,
|
||||||
@ -47,6 +56,36 @@ function getUtils({
|
|||||||
const cssMap = new Map<string, StyleMapEntry>();
|
const cssMap = new Map<string, StyleMapEntry>();
|
||||||
const jsMap = new Map<string, StyleMapEntry>();
|
const jsMap = new Map<string, StyleMapEntry>();
|
||||||
|
|
||||||
|
const imports: Import[] = path.node.body
|
||||||
|
.filter(node => t.isImportDeclaration(node))
|
||||||
|
.map(i => ({
|
||||||
|
source: i.source.value,
|
||||||
|
specifiers: i.specifiers
|
||||||
|
.filter(x => t.isImportSpecifier(x))
|
||||||
|
.filter(x => x.importKind === "value")
|
||||||
|
.map(x => ({
|
||||||
|
local: x.local.name,
|
||||||
|
imported: t.isStringLiteral(x.imported) ? x.imported.value : x.imported.name,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
let existingCx: string | undefined;
|
||||||
|
switch (clsx) {
|
||||||
|
case "emotion":
|
||||||
|
existingCx = imports
|
||||||
|
.find(i => i.source === "@emotion/css")
|
||||||
|
?.specifiers.find(s => s.imported === "cx")?.local;
|
||||||
|
break;
|
||||||
|
case "clsx":
|
||||||
|
existingCx = imports
|
||||||
|
.find(i => i.source === "clsx")
|
||||||
|
?.specifiers.find(s => s.imported === "clsx")?.local;
|
||||||
|
break;
|
||||||
|
case "classnames":
|
||||||
|
existingCx = imports.find(i => i.source === "classnames")?.specifiers[0]?.local;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
function getStyleImport() {
|
function getStyleImport() {
|
||||||
styleImport ??= path.scope.generateUidIdentifier("styles");
|
styleImport ??= path.scope.generateUidIdentifier("styles");
|
||||||
return t.cloneNode(styleImport);
|
return t.cloneNode(styleImport);
|
||||||
@ -59,7 +98,16 @@ function getUtils({
|
|||||||
return t.cloneNode(cssModuleImport);
|
return t.cloneNode(cssModuleImport);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const reuseImport = (scope: Scope, id?: string) => {
|
||||||
|
if (id && scope.getBinding(id) === path.scope.getBinding(id)) {
|
||||||
|
return t.identifier(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
program: path,
|
||||||
|
existingCx,
|
||||||
|
|
||||||
getClass(type: Type, value: string) {
|
getClass(type: Type, value: string) {
|
||||||
return type === "css" ? getClass(value) : "tw_" + hash(value);
|
return type === "css" ? getClass(value) : "tw_" + hash(value);
|
||||||
},
|
},
|
||||||
@ -74,7 +122,7 @@ function getUtils({
|
|||||||
.join("\n"),
|
.join("\n"),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
recordIfAbsent(type: "css", entry: StyleMapEntry) {
|
recordIfAbsent(type: Type, entry: StyleMapEntry) {
|
||||||
const map = type === "css" ? cssMap : jsMap;
|
const map = type === "css" ? cssMap : jsMap;
|
||||||
if (!map.has(entry.key)) {
|
if (!map.has(entry.key)) {
|
||||||
map.set(entry.key, entry);
|
map.set(entry.key, entry);
|
||||||
@ -100,8 +148,11 @@ function getUtils({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getCx: () => {
|
getCx: (localScope: Scope) => {
|
||||||
if (cx == null) {
|
if (cx == null) {
|
||||||
|
const reuse = reuseImport(localScope, existingCx);
|
||||||
|
if (reuse) return reuse;
|
||||||
|
|
||||||
cx = path.scope.generateUidIdentifier("cx");
|
cx = path.scope.generateUidIdentifier("cx");
|
||||||
path.node.body.unshift(getClsxImport(t, cx, clsx));
|
path.node.body.unshift(getClsxImport(t, cx, clsx));
|
||||||
}
|
}
|
||||||
@ -295,8 +346,8 @@ export function babelTailwind(
|
|||||||
|
|
||||||
if (classNameAttribute) {
|
if (classNameAttribute) {
|
||||||
const attrValue = classNameAttribute.value!;
|
const attrValue = classNameAttribute.value!;
|
||||||
const wrap = (originalValue: b.types.Expression) =>
|
const wrap = (...originalValue: (b.types.Expression | b.types.SpreadElement)[]) =>
|
||||||
t.callExpression(_.getCx(), [originalValue, valuePathNode]);
|
t.callExpression(_.getCx(path.scope), [valuePathNode, ...originalValue]);
|
||||||
|
|
||||||
// If both are string literals, we can merge them directly here
|
// If both are string literals, we can merge them directly here
|
||||||
if (t.isStringLiteral(attrValue) && t.isStringLiteral(valuePathNode)) {
|
if (t.isStringLiteral(attrValue) && t.isStringLiteral(valuePathNode)) {
|
||||||
@ -309,13 +360,32 @@ export function babelTailwind(
|
|||||||
!t.isBlockStatement(internalAttrValue.body)
|
!t.isBlockStatement(internalAttrValue.body)
|
||||||
) {
|
) {
|
||||||
internalAttrValue.body = wrap(internalAttrValue.body);
|
internalAttrValue.body = wrap(internalAttrValue.body);
|
||||||
|
} else if (
|
||||||
|
// if the existing className is already wrapped with cx, we unwrap it
|
||||||
|
// to avoid double calling: cx(cx())
|
||||||
|
t.isCallExpression(internalAttrValue) &&
|
||||||
|
t.isIdentifier(internalAttrValue.callee) &&
|
||||||
|
_.existingCx &&
|
||||||
|
_.program.scope
|
||||||
|
.getBinding(_.existingCx)
|
||||||
|
?.referencePaths.map(p => p.node)
|
||||||
|
.includes(internalAttrValue.callee)
|
||||||
|
) {
|
||||||
|
classNameAttribute.value = t.jsxExpressionContainer(
|
||||||
|
wrap(
|
||||||
|
...(internalAttrValue.arguments as (
|
||||||
|
| b.types.Expression
|
||||||
|
| b.types.SpreadElement
|
||||||
|
)[])
|
||||||
|
)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
classNameAttribute.value = t.jsxExpressionContainer(wrap(internalAttrValue));
|
classNameAttribute.value = t.jsxExpressionContainer(wrap(internalAttrValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const wrap = (originalValue: b.types.Expression) =>
|
const wrap = (originalValue: b.types.Expression) =>
|
||||||
t.callExpression(_.getCx(), [valuePathNode, originalValue]);
|
t.callExpression(_.getCx(path.scope), [valuePathNode, originalValue]);
|
||||||
|
|
||||||
const rest = parent.attributes.filter(attr => t.isJSXSpreadAttribute(attr));
|
const rest = parent.attributes.filter(attr => t.isJSXSpreadAttribute(attr));
|
||||||
let arg;
|
let arg;
|
||||||
@ -368,7 +438,7 @@ export function babelTailwind(
|
|||||||
} else {
|
} else {
|
||||||
const containerValue = t.isStringLiteral(valuePathNode)
|
const containerValue = t.isStringLiteral(valuePathNode)
|
||||||
? valuePathNode
|
? valuePathNode
|
||||||
: t.callExpression(_.getCx(), [valuePathNode]);
|
: t.callExpression(_.getCx(path.scope), [valuePathNode]);
|
||||||
|
|
||||||
parent.attributes.push(
|
parent.attributes.push(
|
||||||
t.jsxAttribute(
|
t.jsxAttribute(
|
||||||
@ -400,7 +470,10 @@ function getClsxImport(t: BabelTypes, cx: t.Identifier, clsx: string) {
|
|||||||
t.stringLiteral("@emotion/css")
|
t.stringLiteral("@emotion/css")
|
||||||
);
|
);
|
||||||
case "clsx":
|
case "clsx":
|
||||||
return t.importDeclaration([t.importDefaultSpecifier(cx)], t.stringLiteral("clsx"));
|
return t.importDeclaration(
|
||||||
|
[t.importSpecifier(cx, t.identifier("clsx"))],
|
||||||
|
t.stringLiteral("clsx")
|
||||||
|
);
|
||||||
case "classnames":
|
case "classnames":
|
||||||
return t.importDeclaration(
|
return t.importDeclaration(
|
||||||
[t.importDefaultSpecifier(cx)],
|
[t.importDefaultSpecifier(cx)],
|
||||||
|
@ -164,32 +164,59 @@ const isNodePath = <T extends t.Node>(
|
|||||||
const supportedTags = new Set<SupportedTag>([
|
const supportedTags = new Set<SupportedTag>([
|
||||||
"a",
|
"a",
|
||||||
"abbr",
|
"abbr",
|
||||||
|
"address",
|
||||||
"article",
|
"article",
|
||||||
|
"b",
|
||||||
|
"bdi",
|
||||||
"blockquote",
|
"blockquote",
|
||||||
"button",
|
"button",
|
||||||
|
"caption",
|
||||||
"cite",
|
"cite",
|
||||||
"code",
|
"code",
|
||||||
|
"col",
|
||||||
|
"colgroup",
|
||||||
|
"dd",
|
||||||
|
"del",
|
||||||
"details",
|
"details",
|
||||||
|
"dialog",
|
||||||
"div",
|
"div",
|
||||||
|
"dl",
|
||||||
|
"dt",
|
||||||
|
"em",
|
||||||
|
"fieldset",
|
||||||
|
"figcaption",
|
||||||
"figure",
|
"figure",
|
||||||
"footer",
|
"footer",
|
||||||
|
"form",
|
||||||
"h1",
|
"h1",
|
||||||
"h2",
|
"h2",
|
||||||
"h3",
|
"h3",
|
||||||
"h4",
|
"h4",
|
||||||
|
"h5",
|
||||||
|
"h6",
|
||||||
|
"header",
|
||||||
"hr",
|
"hr",
|
||||||
|
"i",
|
||||||
"iframe",
|
"iframe",
|
||||||
"img",
|
"img",
|
||||||
"input",
|
"input",
|
||||||
"ins",
|
"ins",
|
||||||
|
"kbd",
|
||||||
"label",
|
"label",
|
||||||
|
"legend",
|
||||||
"li",
|
"li",
|
||||||
"main",
|
"main",
|
||||||
|
"mark",
|
||||||
|
"menu",
|
||||||
|
"meter",
|
||||||
"nav",
|
"nav",
|
||||||
"ol",
|
"ol",
|
||||||
"output",
|
"output",
|
||||||
"p",
|
"p",
|
||||||
|
"picture",
|
||||||
"pre",
|
"pre",
|
||||||
|
"progress",
|
||||||
|
"q",
|
||||||
"rt",
|
"rt",
|
||||||
"ruby",
|
"ruby",
|
||||||
"section",
|
"section",
|
||||||
@ -199,12 +226,19 @@ const supportedTags = new Set<SupportedTag>([
|
|||||||
"sub",
|
"sub",
|
||||||
"summary",
|
"summary",
|
||||||
"sup",
|
"sup",
|
||||||
|
"svg",
|
||||||
"table",
|
"table",
|
||||||
"tbody",
|
"tbody",
|
||||||
"td",
|
"td",
|
||||||
|
"textarea",
|
||||||
|
"tfoot",
|
||||||
|
"th",
|
||||||
"thead",
|
"thead",
|
||||||
|
"time",
|
||||||
"tr",
|
"tr",
|
||||||
|
"u",
|
||||||
"ul",
|
"ul",
|
||||||
"var",
|
"var",
|
||||||
"video",
|
"video",
|
||||||
|
"wbr",
|
||||||
]);
|
]);
|
||||||
|
36
src/macro.d.ts
vendored
36
src/macro.d.ts
vendored
@ -13,32 +13,59 @@ type CSSAttributeValue = string | (string | RecursiveStringObject)[];
|
|||||||
export type SupportedTag =
|
export type SupportedTag =
|
||||||
| "a"
|
| "a"
|
||||||
| "abbr"
|
| "abbr"
|
||||||
|
| "address"
|
||||||
| "article"
|
| "article"
|
||||||
|
| "b"
|
||||||
|
| "bdi"
|
||||||
| "blockquote"
|
| "blockquote"
|
||||||
| "button"
|
| "button"
|
||||||
|
| "caption"
|
||||||
| "cite"
|
| "cite"
|
||||||
| "code"
|
| "code"
|
||||||
|
| "col"
|
||||||
|
| "colgroup"
|
||||||
|
| "dd"
|
||||||
|
| "del"
|
||||||
| "details"
|
| "details"
|
||||||
|
| "dialog"
|
||||||
| "div"
|
| "div"
|
||||||
|
| "dl"
|
||||||
|
| "dt"
|
||||||
|
| "em"
|
||||||
|
| "fieldset"
|
||||||
|
| "figcaption"
|
||||||
| "figure"
|
| "figure"
|
||||||
| "footer"
|
| "footer"
|
||||||
|
| "form"
|
||||||
| "h1"
|
| "h1"
|
||||||
| "h2"
|
| "h2"
|
||||||
| "h3"
|
| "h3"
|
||||||
| "h4"
|
| "h4"
|
||||||
|
| "h5"
|
||||||
|
| "h6"
|
||||||
|
| "header"
|
||||||
| "hr"
|
| "hr"
|
||||||
|
| "i"
|
||||||
| "iframe"
|
| "iframe"
|
||||||
| "img"
|
| "img"
|
||||||
| "input"
|
| "input"
|
||||||
| "ins"
|
| "ins"
|
||||||
|
| "kbd"
|
||||||
| "label"
|
| "label"
|
||||||
|
| "legend"
|
||||||
| "li"
|
| "li"
|
||||||
| "main"
|
| "main"
|
||||||
|
| "mark"
|
||||||
|
| "menu"
|
||||||
|
| "meter"
|
||||||
| "nav"
|
| "nav"
|
||||||
| "ol"
|
| "ol"
|
||||||
| "output"
|
| "output"
|
||||||
| "p"
|
| "p"
|
||||||
|
| "picture"
|
||||||
| "pre"
|
| "pre"
|
||||||
|
| "progress"
|
||||||
|
| "q"
|
||||||
| "rt"
|
| "rt"
|
||||||
| "ruby"
|
| "ruby"
|
||||||
| "section"
|
| "section"
|
||||||
@ -48,14 +75,21 @@ export type SupportedTag =
|
|||||||
| "sub"
|
| "sub"
|
||||||
| "summary"
|
| "summary"
|
||||||
| "sup"
|
| "sup"
|
||||||
|
| "svg"
|
||||||
| "table"
|
| "table"
|
||||||
| "tbody"
|
| "tbody"
|
||||||
| "td"
|
| "td"
|
||||||
|
| "textarea"
|
||||||
|
| "tfoot"
|
||||||
|
| "th"
|
||||||
| "thead"
|
| "thead"
|
||||||
|
| "time"
|
||||||
| "tr"
|
| "tr"
|
||||||
|
| "u"
|
||||||
| "ul"
|
| "ul"
|
||||||
| "var"
|
| "var"
|
||||||
| "video";
|
| "video"
|
||||||
|
| "wbr";
|
||||||
|
|
||||||
type Modifier =
|
type Modifier =
|
||||||
| "2xl"
|
| "2xl"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user