Skip to content

Commit 441f8e7

Browse files
committed
chore: commit vue case
1 parent 3a02441 commit 441f8e7

File tree

6 files changed

+280
-2
lines changed

6 files changed

+280
-2
lines changed

packages/core/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"@babel/types": "^7.22.17",
5252
"@tailwindcss-mangle/config": "workspace:^",
5353
"@tailwindcss-mangle/shared": "workspace:^",
54+
"@vue/compiler-sfc": "^3.3.4",
5455
"fast-sort": "^3.4.0",
5556
"magic-string": "^0.30.3",
5657
"micromatch": "^4.0.5",
@@ -63,7 +64,8 @@
6364
"@parse5/tools": "^0.3.0",
6465
"@types/babel__core": "^7.20.1",
6566
"@types/babel__traverse": "^7.20.1",
66-
"@types/micromatch": "^4.0.2"
67+
"@types/micromatch": "^4.0.2",
68+
"@vue/compiler-core": "^3.3.4"
6769
},
6870
"homepage": "https://github.com/sonofmagic/tailwindcss-mangle",
6971
"repository": {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`vue > module mode preamble 1`] = `
4+
"import { createVNode as _createVNode, resolveDirective as _resolveDirective } from \\"vue\\"
5+
6+
export function render(_ctx, _cache) {
7+
return null
8+
}"
9+
`;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
/* eslint-disable camelcase */
3+
import MagicString from 'magic-string'
4+
5+
/** @see https://github.com/sveltejs/svelte/blob/d3297e2a2595db08c85356d65fd5f953b04a681f/packages/svelte/src/compiler/preprocess/index.js#L255C1-L255C85 */
6+
const regex_style_tags = /<!--[^]*?-->|<style(\s[^]*?)?(?:>([^]*?)<\/style>|\/>)/gi
7+
8+
/** @see https://github.com/sveltejs/svelte/blob/d3297e2a2595db08c85356d65fd5f953b04a681f/packages/svelte/src/compiler/preprocess/index.js#L256C1-L256C88 */
9+
const regex_script_tags = /<!--[^]*?-->|<script(\s[^]*?)?(?:>([^]*?)<\/script>|\/>)/gi
10+
11+
export const svelteToTsx = (code: string) => {
12+
try {
13+
const scripts = []
14+
const original = new MagicString(code)
15+
16+
// Remove script tags & extract script content
17+
let match: RegExpExecArray | null
18+
while ((match = regex_script_tags.exec(code)) != null) {
19+
const [fullMatch, _attributesStr, scriptContent] = match
20+
if (scriptContent) {
21+
scripts.push(scriptContent)
22+
original.remove(match.index, match.index + fullMatch.length)
23+
}
24+
}
25+
26+
const templateContent = original.toString().trimStart().replaceAll(regex_style_tags, '').replaceAll(regex_style_tags, '')
27+
const transformed = `${scripts.join('')}\nconst render = <div>${templateContent}</div>`
28+
29+
return transformed.toString().trim()
30+
} catch {
31+
return ''
32+
}
33+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/* eslint-disable unicorn/no-array-for-each */
2+
import { parse } from '@vue/compiler-sfc'
3+
import MagicString from 'magic-string'
4+
import type { BaseElementNode } from '@vue/compiler-core'
5+
// https://github.com/chakra-ui/panda/blob/09ed20f919113d310c870b136c5a1c55ecc1b1e1/packages/parser/src/vue-to-tsx.ts
6+
/**
7+
* @see https://github.com/vuejs/core/blob/d2c3d8b70b2df6e16f053a7ac58e6b04e7b2078f/packages/compiler-core/src/ast.ts#L28-L60
8+
* import { NodeTypes } from '@vue/compiler-core' isn't working for some reason (?)
9+
* Cannot read properties of undefined (reading 'ELEMENT')
10+
*/
11+
const NodeTypes = {
12+
ROOT: 0,
13+
ELEMENT: 1,
14+
TEXT: 2,
15+
COMMENT: 3,
16+
SIMPLE_EXPRESSION: 4,
17+
INTERPOLATION: 5,
18+
ATTRIBUTE: 6,
19+
DIRECTIVE: 7,
20+
COMPOUND_EXPRESSION: 8,
21+
IF: 9,
22+
IF_BRANCH: 10,
23+
FOR: 11,
24+
TEXT_CALL: 12,
25+
VNODE_CALL: 13,
26+
JS_CALL_EXPRESSION: 14,
27+
JS_OBJECT_EXPRESSION: 15,
28+
JS_PROPERTY: 16,
29+
JS_ARRAY_EXPRESSION: 17,
30+
JS_FUNCTION_EXPRESSION: 18,
31+
JS_CONDITIONAL_EXPRESSION: 19,
32+
JS_CACHE_EXPRESSION: 20,
33+
JS_BLOCK_STATEMENT: 21,
34+
JS_TEMPLATE_LITERAL: 22,
35+
JS_IF_STATEMENT: 23,
36+
JS_ASSIGNMENT_EXPRESSION: 24,
37+
JS_SEQUENCE_EXPRESSION: 25,
38+
JS_RETURN_STATEMENT: 26
39+
} as const
40+
41+
export const vueToTsx = (code: string) => {
42+
try {
43+
const parsed = parse(code)
44+
const fileStr = new MagicString(`<template>${parsed.descriptor.template?.content}</template>` ?? '')
45+
46+
const rewriteProp = (prop: BaseElementNode['props'][number]) => {
47+
if (prop.type === NodeTypes.DIRECTIVE && prop.exp?.type === NodeTypes.SIMPLE_EXPRESSION && prop.arg?.type === NodeTypes.SIMPLE_EXPRESSION) {
48+
fileStr.replace(prop.loc.source, `${prop.arg.content}={${prop.exp.content}}`)
49+
}
50+
}
51+
52+
const stack = [...parsed.descriptor.template!.ast.children]
53+
// recursion-free traversal
54+
while (stack.length > 0) {
55+
const node = stack.pop()
56+
if (!node) continue
57+
58+
if (node.type === NodeTypes.ELEMENT) {
59+
node.props.forEach((element) => {
60+
rewriteProp(element)
61+
})
62+
for (const child of node.children) stack.push(child)
63+
}
64+
}
65+
66+
const scriptContent = (parsed.descriptor.scriptSetup ?? parsed.descriptor.script)?.content + '\n'
67+
68+
const transformed = new MagicString(`${scriptContent}\nconst render = ${fileStr.toString()}`)
69+
70+
return transformed.toString()
71+
} catch {
72+
return ''
73+
}
74+
}

packages/core/test/vue.test.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { parse, walk } from '@vue/compiler-sfc'
2+
import {
3+
generate,
4+
baseParse,
5+
transform,
6+
TransformOptions,
7+
RootNode,
8+
locStub,
9+
createSimpleExpression,
10+
CREATE_VNODE,
11+
RESOLVE_DIRECTIVE,
12+
helperNameMap,
13+
NodeTransform
14+
} from '@vue/compiler-core'
15+
import { getTestCase } from './utils'
16+
17+
const NodeTypes = {
18+
ROOT: 0,
19+
ELEMENT: 1,
20+
TEXT: 2,
21+
COMMENT: 3,
22+
SIMPLE_EXPRESSION: 4,
23+
INTERPOLATION: 5,
24+
ATTRIBUTE: 6,
25+
DIRECTIVE: 7,
26+
COMPOUND_EXPRESSION: 8,
27+
IF: 9,
28+
IF_BRANCH: 10,
29+
FOR: 11,
30+
TEXT_CALL: 12,
31+
VNODE_CALL: 13,
32+
JS_CALL_EXPRESSION: 14,
33+
JS_OBJECT_EXPRESSION: 15,
34+
JS_PROPERTY: 16,
35+
JS_ARRAY_EXPRESSION: 17,
36+
JS_FUNCTION_EXPRESSION: 18,
37+
JS_CONDITIONAL_EXPRESSION: 19,
38+
JS_CACHE_EXPRESSION: 20,
39+
JS_BLOCK_STATEMENT: 21,
40+
JS_TEMPLATE_LITERAL: 22,
41+
JS_IF_STATEMENT: 23,
42+
JS_ASSIGNMENT_EXPRESSION: 24,
43+
JS_SEQUENCE_EXPRESSION: 25,
44+
JS_RETURN_STATEMENT: 26
45+
} as const
46+
47+
function createRoot(options: Partial<RootNode> = {}): RootNode {
48+
return {
49+
type: 0, // NodeTypes.ROOT,
50+
children: [],
51+
helpers: new Set(),
52+
components: [],
53+
directives: [],
54+
imports: [],
55+
hoists: [],
56+
cached: 0,
57+
temps: 0,
58+
codegenNode: createSimpleExpression(`null`, false),
59+
loc: locStub,
60+
...options
61+
}
62+
}
63+
// https://github.com/vuejs/core/blob/main/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts
64+
describe('vue', () => {
65+
test('module mode preamble', () => {
66+
const root = createRoot({
67+
helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE])
68+
})
69+
const { code } = generate(root, { mode: 'module' })
70+
expect(code).toMatch(
71+
`import { ${helperNameMap[CREATE_VNODE]} as _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]} as _${helperNameMap[RESOLVE_DIRECTIVE]} } from "vue"`
72+
)
73+
expect(code).toMatchSnapshot()
74+
})
75+
it('test for vue', () => {
76+
const testCase = getTestCase('preserve-fn-case1.vue')
77+
const result = parse(testCase)
78+
79+
console.log(result.descriptor.template, result.descriptor.script, result.descriptor.scriptSetup)
80+
const content = result.descriptor.template?.content ?? ''
81+
const ast = baseParse(content)
82+
const plugin: NodeTransform = (node, context) => {
83+
if (node.type === NodeTypes.ELEMENT && node.tag === 'div') {
84+
// change the node to <p>
85+
context.replaceNode(
86+
Object.assign({}, node, {
87+
tag: 'p',
88+
children: [
89+
{
90+
type: NodeTypes.TEXT,
91+
content: 'hello',
92+
isEmpty: false
93+
}
94+
]
95+
})
96+
)
97+
}
98+
}
99+
transform(ast, {
100+
nodeTransforms: [plugin]
101+
})
102+
const t = generate(ast, { mode: 'module' })
103+
expect(t.code).toBe(content)
104+
})
105+
})

pnpm-lock.yaml

Lines changed: 56 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)