vite-plugin-comment-attrs 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -54,7 +54,9 @@ function parseDirectiveText(text, directives) {
|
|
|
54
54
|
const trimmed = text.trim();
|
|
55
55
|
for (const [directive, rule] of Object.entries(directives)) {
|
|
56
56
|
const escapedDirective = escapeRegExp(directive);
|
|
57
|
-
const match = trimmed.match(
|
|
57
|
+
const match = trimmed.match(
|
|
58
|
+
new RegExp(`^${escapedDirective}(?:\\s+(.+))?$`)
|
|
59
|
+
);
|
|
58
60
|
if (!match) {
|
|
59
61
|
continue;
|
|
60
62
|
}
|
|
@@ -62,7 +64,7 @@ function parseDirectiveText(text, directives) {
|
|
|
62
64
|
directive,
|
|
63
65
|
attrName: rule.attr,
|
|
64
66
|
merge: rule.merge,
|
|
65
|
-
value: match[1]
|
|
67
|
+
value: match[1]?.trim() ?? ""
|
|
66
68
|
};
|
|
67
69
|
}
|
|
68
70
|
return null;
|
|
@@ -125,6 +127,9 @@ function applyAttributes(openingElement, attrs) {
|
|
|
125
127
|
setOrMergeAttribute(openingElement, attrName, value, collected.merge);
|
|
126
128
|
}
|
|
127
129
|
}
|
|
130
|
+
function isJsxCommentOnlyExpression(node) {
|
|
131
|
+
return t.isJSXExpressionContainer(node) && t.isJSXEmptyExpression(node.expression);
|
|
132
|
+
}
|
|
128
133
|
function processJsxChildren(children, directives) {
|
|
129
134
|
for (let i = 0; i < children.length; i++) {
|
|
130
135
|
const firstDirective = getJsxDirectiveValue(children[i], directives);
|
|
@@ -148,6 +153,10 @@ function processJsxChildren(children, directives) {
|
|
|
148
153
|
j++;
|
|
149
154
|
continue;
|
|
150
155
|
}
|
|
156
|
+
if (isJsxCommentOnlyExpression(child)) {
|
|
157
|
+
j++;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
151
160
|
if (t.isJSXElement(child)) {
|
|
152
161
|
applyAttributes(child.openingElement, attrs);
|
|
153
162
|
for (let k = removeIndexes.length - 1; k >= 0; k--) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\nimport { parse } from \"@babel/parser\";\nimport traverseModule from \"@babel/traverse\";\nimport type { NodePath } from \"@babel/traverse\";\nimport generateModule from \"@babel/generator\";\nimport * as t from \"@babel/types\";\n\nfunction interopDefault<T extends object>(module: T | { default: T }): T {\n return \"default\" in module ? module.default : module;\n}\n\nconst traverse = interopDefault(traverseModule);\nconst generate = interopDefault(generateModule);\n\nexport type MergeStrategy = \"append\" | \"replace\";\n\nexport type DirectiveRule = {\n attr: string;\n merge: MergeStrategy;\n};\n\nexport type DirectiveConfig = Record<string, DirectiveRule>;\n\nexport type CommentAttrsPluginOptions = {\n directives?: DirectiveConfig;\n include?: RegExp;\n exclude?: RegExp;\n};\n\ntype ParsedDirective = {\n directive: string;\n attrName: string;\n merge: MergeStrategy;\n value: string;\n};\n\ntype CollectedAttr = {\n merge: MergeStrategy;\n values: string[];\n};\n\nconst DEFAULT_DIRECTIVES: DirectiveConfig = {\n \"@class\": { attr: \"class\", merge: \"append\" },\n};\n\nfunction isTargetFile(id: string, options: CommentAttrsPluginOptions) {\n const cleanId = id.split(\"?\")[0];\n\n if (options.exclude?.test(cleanId)) {\n return false;\n }\n\n if (options.include) {\n return options.include.test(cleanId);\n }\n\n return /\\.(jsx|tsx|js|ts)$/.test(cleanId);\n}\n\nfunction escapeRegExp(value: string) {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction codeContainsAnyDirective(code: string, directives: DirectiveConfig) {\n return Object.keys(directives).some((directive) => code.includes(directive));\n}\n\nfunction isWhitespaceJsxText(node: t.Node): boolean {\n return t.isJSXText(node) && node.value.trim() === \"\";\n}\n\nfunction getJsxAttribute(openingElement: t.JSXOpeningElement, name: string) {\n return openingElement.attributes.find((attr) => {\n return t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name, { name });\n }) as t.JSXAttribute | undefined;\n}\n\nfunction getCommentTextsFromJsxNode(node: t.Node): string[] {\n if (!t.isJSXExpressionContainer(node)) {\n return [];\n }\n\n const texts: string[] = [];\n\n if (t.isJSXEmptyExpression(node.expression)) {\n texts.push(\n ...(node.expression.innerComments ?? []).map((comment) => comment.value),\n );\n }\n\n texts.push(...(node.innerComments ?? []).map((comment) => comment.value));\n texts.push(...(node.leadingComments ?? []).map((comment) => comment.value));\n texts.push(...(node.trailingComments ?? []).map((comment) => comment.value));\n\n return texts;\n}\n\nfunction parseDirectiveText(\n text: string,\n directives: DirectiveConfig,\n): ParsedDirective | null {\n const trimmed = text.trim();\n\n for (const [directive, rule] of Object.entries(directives)) {\n const escapedDirective = escapeRegExp(directive);\n const match = trimmed.match(new RegExp(`^${escapedDirective}\\\\s+(.+)$`));\n\n if (!match) {\n continue;\n }\n\n return {\n directive,\n attrName: rule.attr,\n merge: rule.merge,\n value: match[1].trim(),\n };\n }\n\n return null;\n}\n\nfunction getJsxDirectiveValue(\n node: t.Node,\n directives: DirectiveConfig,\n): ParsedDirective | null {\n for (const text of getCommentTextsFromJsxNode(node)) {\n const parsed = parseDirectiveText(text, directives);\n\n if (parsed) {\n return parsed;\n }\n }\n\n return null;\n}\n\nfunction addCollectedAttr(\n attrs: Map<string, CollectedAttr>,\n parsed: ParsedDirective,\n) {\n const existing = attrs.get(parsed.attrName);\n\n if (!existing) {\n attrs.set(parsed.attrName, {\n merge: parsed.merge,\n values: [parsed.value],\n });\n return;\n }\n\n if (parsed.merge === \"append\") {\n existing.merge = \"append\";\n existing.values.push(parsed.value);\n return;\n }\n\n existing.merge = \"replace\";\n existing.values = [parsed.value];\n}\n\nfunction setOrMergeAttribute(\n openingElement: t.JSXOpeningElement,\n attrName: string,\n value: string,\n merge: MergeStrategy,\n) {\n const attr = getJsxAttribute(openingElement, attrName);\n\n if (!attr) {\n openingElement.attributes.push(\n t.jsxAttribute(t.jsxIdentifier(attrName), t.stringLiteral(value)),\n );\n return;\n }\n\n if (merge === \"replace\") {\n attr.value = t.stringLiteral(value);\n return;\n }\n\n if (t.isStringLiteral(attr.value)) {\n attr.value.value = `${attr.value.value} ${value}`.trim();\n return;\n }\n\n if (\n t.isJSXExpressionContainer(attr.value) &&\n t.isExpression(attr.value.expression)\n ) {\n attr.value.expression = t.templateLiteral(\n [\n t.templateElement({ raw: \"\", cooked: \"\" }),\n t.templateElement({ raw: ` ${value}`, cooked: ` ${value}` }, true),\n ],\n [attr.value.expression],\n );\n }\n}\n\nfunction applyAttributes(\n openingElement: t.JSXOpeningElement,\n attrs: Map<string, CollectedAttr>,\n) {\n for (const [attrName, collected] of attrs) {\n const value =\n collected.merge === \"append\"\n ? collected.values.join(\" \")\n : collected.values[collected.values.length - 1];\n\n setOrMergeAttribute(openingElement, attrName, value, collected.merge);\n }\n}\n\nfunction processJsxChildren(\n children: t.JSXElement[\"children\"],\n directives: DirectiveConfig,\n) {\n for (let i = 0; i < children.length; i++) {\n const firstDirective = getJsxDirectiveValue(children[i], directives);\n\n if (!firstDirective) {\n continue;\n }\n\n const attrs = new Map<string, CollectedAttr>();\n const removeIndexes: number[] = [i];\n\n addCollectedAttr(attrs, firstDirective);\n\n let j = i + 1;\n\n while (j < children.length) {\n const child = children[j];\n\n if (isWhitespaceJsxText(child)) {\n j++;\n continue;\n }\n\n const nextDirective = getJsxDirectiveValue(child, directives);\n\n if (nextDirective) {\n addCollectedAttr(attrs, nextDirective);\n removeIndexes.push(j);\n j++;\n continue;\n }\n\n if (t.isJSXElement(child)) {\n applyAttributes(child.openingElement, attrs);\n\n for (let k = removeIndexes.length - 1; k >= 0; k--) {\n children.splice(removeIndexes[k], 1);\n }\n\n i--;\n }\n\n break;\n }\n }\n}\n\nfunction processLeadingDirectiveComments(\n element: t.JSXElement,\n directives: DirectiveConfig,\n) {\n const comments = element.leadingComments;\n\n if (!comments || comments.length === 0) {\n return;\n }\n\n const attrs = new Map<string, CollectedAttr>();\n\n for (const comment of comments) {\n const parsed = parseDirectiveText(comment.value, directives);\n\n if (parsed) {\n addCollectedAttr(attrs, parsed);\n }\n }\n\n if (attrs.size === 0) {\n return;\n }\n\n applyAttributes(element.openingElement, attrs);\n\n element.leadingComments = comments.filter((comment) => {\n return parseDirectiveText(comment.value, directives) === null;\n });\n}\n\nexport function commentAttrsPlugin(\n options: CommentAttrsPluginOptions = {},\n): Plugin {\n const directives = options.directives ?? DEFAULT_DIRECTIVES;\n\n return {\n name: \"vite-plugin-comment-attrs\",\n enforce: \"pre\",\n\n transform(code, id) {\n if (!isTargetFile(id, options)) {\n return null;\n }\n\n if (!codeContainsAnyDirective(code, directives)) {\n return null;\n }\n\n const ast = parse(code, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n traverse(ast, {\n JSXElement(path: NodePath<t.JSXElement>) {\n processLeadingDirectiveComments(path.node, directives);\n processJsxChildren(path.node.children, directives);\n },\n\n JSXFragment(path: NodePath<t.JSXFragment>) {\n processJsxChildren(path.node.children, directives);\n },\n });\n\n const output = generate(\n ast,\n {\n sourceMaps: true,\n sourceFileName: id,\n },\n code,\n );\n\n return {\n code: output.code,\n map: output.map,\n };\n },\n };\n}\n\nexport default commentAttrsPlugin;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\nimport { parse } from \"@babel/parser\";\nimport traverseModule from \"@babel/traverse\";\nimport type { NodePath } from \"@babel/traverse\";\nimport generateModule from \"@babel/generator\";\nimport * as t from \"@babel/types\";\n\nfunction interopDefault<T extends object>(module: T | { default: T }): T {\n return \"default\" in module ? module.default : module;\n}\n\nconst traverse = interopDefault(traverseModule);\nconst generate = interopDefault(generateModule);\n\nexport type MergeStrategy = \"append\" | \"replace\";\n\nexport type DirectiveRule = {\n attr: string;\n merge: MergeStrategy;\n};\n\nexport type DirectiveConfig = Record<string, DirectiveRule>;\n\nexport type CommentAttrsPluginOptions = {\n directives?: DirectiveConfig;\n include?: RegExp;\n exclude?: RegExp;\n};\n\ntype ParsedDirective = {\n directive: string;\n attrName: string;\n merge: MergeStrategy;\n value: string;\n};\n\ntype CollectedAttr = {\n merge: MergeStrategy;\n values: string[];\n};\n\nconst DEFAULT_DIRECTIVES: DirectiveConfig = {\n \"@class\": { attr: \"class\", merge: \"append\" },\n};\n\nfunction isTargetFile(id: string, options: CommentAttrsPluginOptions) {\n const cleanId = id.split(\"?\")[0];\n\n if (options.exclude?.test(cleanId)) {\n return false;\n }\n\n if (options.include) {\n return options.include.test(cleanId);\n }\n\n return /\\.(jsx|tsx|js|ts)$/.test(cleanId);\n}\n\nfunction escapeRegExp(value: string) {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction codeContainsAnyDirective(code: string, directives: DirectiveConfig) {\n return Object.keys(directives).some((directive) => code.includes(directive));\n}\n\nfunction isWhitespaceJsxText(node: t.Node): boolean {\n return t.isJSXText(node) && node.value.trim() === \"\";\n}\n\nfunction getJsxAttribute(openingElement: t.JSXOpeningElement, name: string) {\n return openingElement.attributes.find((attr) => {\n return t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name, { name });\n }) as t.JSXAttribute | undefined;\n}\n\nfunction getCommentTextsFromJsxNode(node: t.Node): string[] {\n if (!t.isJSXExpressionContainer(node)) {\n return [];\n }\n\n const texts: string[] = [];\n\n if (t.isJSXEmptyExpression(node.expression)) {\n texts.push(\n ...(node.expression.innerComments ?? []).map((comment) => comment.value),\n );\n }\n\n texts.push(...(node.innerComments ?? []).map((comment) => comment.value));\n texts.push(...(node.leadingComments ?? []).map((comment) => comment.value));\n texts.push(...(node.trailingComments ?? []).map((comment) => comment.value));\n\n return texts;\n}\n\nfunction parseDirectiveText(\n text: string,\n directives: DirectiveConfig,\n): ParsedDirective | null {\n const trimmed = text.trim();\n\n for (const [directive, rule] of Object.entries(directives)) {\n const escapedDirective = escapeRegExp(directive);\n\n // const match = trimmed.match(new RegExp(`^${escapedDirective}\\\\s+(.+)$`));\n // this support directive without value\n const match = trimmed.match(\n new RegExp(`^${escapedDirective}(?:\\\\s+(.+))?$`),\n );\n\n if (!match) {\n continue;\n }\n\n return {\n directive,\n attrName: rule.attr,\n merge: rule.merge,\n value: match[1]?.trim() ?? \"\",\n };\n }\n\n return null;\n}\n\nfunction getJsxDirectiveValue(\n node: t.Node,\n directives: DirectiveConfig,\n): ParsedDirective | null {\n for (const text of getCommentTextsFromJsxNode(node)) {\n const parsed = parseDirectiveText(text, directives);\n\n if (parsed) {\n return parsed;\n }\n }\n\n return null;\n}\n\nfunction addCollectedAttr(\n attrs: Map<string, CollectedAttr>,\n parsed: ParsedDirective,\n) {\n const existing = attrs.get(parsed.attrName);\n\n if (!existing) {\n attrs.set(parsed.attrName, {\n merge: parsed.merge,\n values: [parsed.value],\n });\n return;\n }\n\n if (parsed.merge === \"append\") {\n existing.merge = \"append\";\n existing.values.push(parsed.value);\n return;\n }\n\n existing.merge = \"replace\";\n existing.values = [parsed.value];\n}\n\nfunction setOrMergeAttribute(\n openingElement: t.JSXOpeningElement,\n attrName: string,\n value: string,\n merge: MergeStrategy,\n) {\n const attr = getJsxAttribute(openingElement, attrName);\n\n if (!attr) {\n openingElement.attributes.push(\n t.jsxAttribute(t.jsxIdentifier(attrName), t.stringLiteral(value)),\n );\n return;\n }\n\n if (merge === \"replace\") {\n attr.value = t.stringLiteral(value);\n return;\n }\n\n if (t.isStringLiteral(attr.value)) {\n attr.value.value = `${attr.value.value} ${value}`.trim();\n return;\n }\n\n if (\n t.isJSXExpressionContainer(attr.value) &&\n t.isExpression(attr.value.expression)\n ) {\n attr.value.expression = t.templateLiteral(\n [\n t.templateElement({ raw: \"\", cooked: \"\" }),\n t.templateElement({ raw: ` ${value}`, cooked: ` ${value}` }, true),\n ],\n [attr.value.expression],\n );\n }\n}\n\nfunction applyAttributes(\n openingElement: t.JSXOpeningElement,\n attrs: Map<string, CollectedAttr>,\n) {\n for (const [attrName, collected] of attrs) {\n const value =\n collected.merge === \"append\"\n ? collected.values.join(\" \")\n : collected.values[collected.values.length - 1];\n\n setOrMergeAttribute(openingElement, attrName, value, collected.merge);\n }\n}\nfunction isJsxCommentOnlyExpression(node: t.Node): boolean {\n return (\n t.isJSXExpressionContainer(node) && t.isJSXEmptyExpression(node.expression)\n );\n}\nfunction processJsxChildren(\n children: t.JSXElement[\"children\"],\n directives: DirectiveConfig,\n) {\n for (let i = 0; i < children.length; i++) {\n const firstDirective = getJsxDirectiveValue(children[i], directives);\n\n if (!firstDirective) {\n continue;\n }\n\n const attrs = new Map<string, CollectedAttr>();\n const removeIndexes: number[] = [i];\n\n addCollectedAttr(attrs, firstDirective);\n\n let j = i + 1;\n\n while (j < children.length) {\n const child = children[j];\n\n if (isWhitespaceJsxText(child)) {\n j++;\n continue;\n }\n\n const nextDirective = getJsxDirectiveValue(child, directives);\n\n if (nextDirective) {\n addCollectedAttr(attrs, nextDirective);\n removeIndexes.push(j);\n j++;\n continue;\n }\n\n // Skip unrelated JSX comments between directives and the target element.\n // for example @alt and @class\n if (isJsxCommentOnlyExpression(child)) {\n j++;\n continue;\n }\n\n if (t.isJSXElement(child)) {\n applyAttributes(child.openingElement, attrs);\n\n for (let k = removeIndexes.length - 1; k >= 0; k--) {\n children.splice(removeIndexes[k], 1);\n }\n\n i--;\n }\n\n break;\n }\n }\n}\n\nfunction processLeadingDirectiveComments(\n element: t.JSXElement,\n directives: DirectiveConfig,\n) {\n const comments = element.leadingComments;\n\n if (!comments || comments.length === 0) {\n return;\n }\n\n const attrs = new Map<string, CollectedAttr>();\n\n for (const comment of comments) {\n const parsed = parseDirectiveText(comment.value, directives);\n\n if (parsed) {\n addCollectedAttr(attrs, parsed);\n }\n }\n\n if (attrs.size === 0) {\n return;\n }\n\n applyAttributes(element.openingElement, attrs);\n\n element.leadingComments = comments.filter((comment) => {\n return parseDirectiveText(comment.value, directives) === null;\n });\n}\n\nexport function commentAttrsPlugin(\n options: CommentAttrsPluginOptions = {},\n): Plugin {\n const directives = options.directives ?? DEFAULT_DIRECTIVES;\n\n return {\n name: \"vite-plugin-comment-attrs\",\n enforce: \"pre\",\n\n transform(code, id) {\n if (!isTargetFile(id, options)) {\n return null;\n }\n\n if (!codeContainsAnyDirective(code, directives)) {\n return null;\n }\n\n const ast = parse(code, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n traverse(ast, {\n JSXElement(path: NodePath<t.JSXElement>) {\n processLeadingDirectiveComments(path.node, directives);\n processJsxChildren(path.node.children, directives);\n },\n\n JSXFragment(path: NodePath<t.JSXFragment>) {\n processJsxChildren(path.node.children, directives);\n },\n });\n\n const output = generate(\n ast,\n {\n sourceMaps: true,\n sourceFileName: id,\n },\n code,\n );\n\n return {\n code: output.code,\n map: output.map,\n };\n },\n };\n}\n\nexport default commentAttrsPlugin;\n"],"mappings":"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","names":[]}
|