如何把 小程序 转换为 VUE
背景
在开发中,会遇到这样的场景,我们开发的小程序,想在 PC 上预览效果,或者,低代码平台脱出来的小程序代码,想预览效果。
这些过程,都涉及到一个核心关键步骤,将 小程序代码转换为 VUE 代码。
本文将从几个方面讲讲转换的核心思路
核心方向
要完成整个工作,我们需要从这个这四个方面分别做代码转换
具体方案
wxml -> Template
我们先分析 wxml 和 vue template 的语法区别
语法 | wxml | template |
---|---|---|
数据绑定 | <view>{{ message }}</view> |
<div>{{ message }}</div> |
列表 | 1. 默认字段<br> <br><view wx:for="{{array}}"><br> {{index}}: {{item.message}}<br></view><br>2. 自定义自定<br> <br><view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName"><br> {{idx}}: {{itemName.message}}<br></view> |
<li v-for="(item, index) in items"><br> {{ parentMessage }} - {{ index }} - {{ item.message }}<br></li> |
条件 | <view wx:if="{{length > 5}}"> 1 </view><br><view wx:elif="{{length > 2}}"> 2 </view><br><view wx:else> 3 </view> |
<div v-if="type === 'A'"> A</div><br><div v-else-if="type === 'B'">B<br></div><br><div v-else-if="type === 'C'">C</div><br><div v-else> Not A/B/C</div> |
事件绑定 | bindtap="" |
@click="" |
通过分析,我们可以将以上的差异点逐个转化,过程还是比较容易的。
转换过程,我们使用到 cheerio 这个HTML解析工具。
以if语句为例
<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>
我们的核心逻辑 是 将标签属性 wx:if -> v-if
wx:elif -> v-else-if`
wx:else -> v-else`
使用 cheerio 转换代码如下
const cheerio = require('cheerio');
/**
* 预处理HTML,转义可能干扰解析的特殊字符
* @param {string} html - 原始HTML字符串
* @returns {string} 处理后的HTML字符串
*/
function sanitizeHtml(html) {
// 转义可能导致解析问题的特殊字符组合
return html
// 处理未闭合的花括号
.replace(/{{([^}]+)$/, '{{$1}}') // 补全末尾未闭合的}}
.replace(/^([^{]+)}}/, '{{$1}}') // 补全开头未闭合的{{
// 转义HTML实体
.replace(/&(?!amp;|lt;|gt;|quot;|#39;)/g, '&')
// 处理可能的特殊字符序列
.replace(/\[\[/g, '[[')
.replace(/\]\]/g, ']]')
.replace(/\]\[/g, '][');
}
/**
* 将微信小程序的wx:if系列属性转换为Vue的v-if系列属性
* @param {string} html - 包含wx:if属性的HTML字符串
* @returns {string} 转换后的HTML字符串
*/
function convertWxIfToVueIf(html) {
try {
// 预处理HTML,清理特殊字符
const sanitizedHtml = sanitizeHtml(html);
// 加载HTML字符串,使用更兼容的配置
const $ = cheerio.load(sanitizedHtml, {
xmlMode: true,
decodeEntities: false,
lowerCaseTags: false,
recognizeSelfClosing: true,
normalizeWhitespace: false
});
// 处理wx:if属性
$('[wx\:if]').each((i, el) => {
const $el = $(el);
const ifValue = $el.attr('wx:if');
$el.attr('v-if', ifValue);
$el.removeAttr('wx:if');
});
// 处理wx:elif属性
$('[wx\:elif]').each((i, el) => {
const $el = $(el);
const elifValue = $el.attr('wx:elif');
$el.attr('v-else-if', elifValue);
$el.removeAttr('wx:elif');
});
// 处理wx:else属性
$('[wx\:else]').each((i, el) => {
const $el = $(el);
$el.attr('v-else', '');
$el.removeAttr('wx:else');
});
// 恢复转义的特殊字符
return $.html()
.replace(/[[/g, '{{')
.replace(/]]/g, '}}')
.replace(/][/g, '][');
} catch (error) {
console.error('转换过程出错:', error);
// 尝试更严格的错误恢复模式
try {
const $ = cheerio.load(html, {
xmlMode: false,
decodeEntities: false,
ignoreParseErrors: true // 忽略解析错误
});
return $.html();
} catch (fatalError) {
console.error('无法恢复的解析错误:', fatalError);
return html; // 返回原始内容,避免完全失败
}
}
}
// 示例用法
const wxHtml = `
<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2 && type[[0]] == 'test'}}"> 2 </view>
<view wx:else> 3 </view>
`;
const vueHtml = convertWxIfToVueIf(wxHtml);
console.log('转换后的Vue模板:');
console.log(vueHtml);
这里有个小优化 wx:elif
作为属性去查询,会报错 throw new Error("Attribute selector didn't terminate");
于是将 wx:elif
转化为 wx\:elif
再处理。
其余的方法类似,可以使用AI生成,就不一一列举了。
wxs -> script
还是先分析差异点
语法 | wxs | script |
---|---|---|
data | <br>{<br> data: {<br> array: [1, 2, 3, 4, 5, 1, 2, 3, 4]<br> }<br>}<br> |
<br>{<br> data() {<br> return {<br> array: [1, 2, 3, 4, 5, 1, 2, 3, 4]<br> }<br> }<br>}<br> |
数据赋值 | <br>this.setData({<br> myValue: 'leaf'<br> })<br> |
this.myValue = 'leaf' |
先列这些差异点,实际开发中差异点可能更多,我们根据实际问题处理即可
转换JS推荐使用 AST 分析的方法去处理,具体 AST 转化工具可以使用 babel。
以数据赋值为例
this.setData({
myValue: 'leaf'
})
// 转化为
this.myValue = 'leaf'
转化过程
const babel = require('@babel/core');
const t = require('@babel/types');
// Babel插件:将this.setData({...})转换为直接赋值
const setDataTransformer = {
visitor: {
// 访问调用表达式
CallExpression(path) {
const node = path.node;
// 检查是否是this.setData调用
if (
t.isMemberExpression(node.callee) &&
t.isThisExpression(node.callee.object) &&
t.isIdentifier(node.callee.property, { name: 'setData' }) &&
node.arguments.length === 1 &&
t.isObjectExpression(node.arguments[0])
) {
// 获取setData的参数对象
const dataObject = node.arguments[0];
// 创建多个赋值表达式语句
const assignments = dataObject.properties.map(prop => {
// 处理对象属性
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
const key = prop.key.name;
return t.expressionStatement(
t.assignmentExpression(
'=',
t.memberExpression(
t.thisExpression(),
t.identifier(key)
),
prop.value
)
);
}
// 对于其他情况,返回原属性(不转换)
return prop;
});
// 替换原setData调用为多个赋值语句
path.replaceWithMultiple(assignments);
}
}
}
};
// 转换函数
function transformCode(code) {
const result = babel.transform(code, {
plugins: [setDataTransformer]
});
return result.code;
}
// 示例用法
const wxCode = `
// 简单赋值
this.setData({
myValue: 'leaf',
count: 100,
isActive: true
});
// 包含表达式的赋值
this.setData({
total: this.count + 1,
message: 'Hello ' + name
});
// 不转换的其他代码
this.otherMethod({
param1: 'value1'
});
`;
// 执行转换
const transformedCode = transformCode(wxCode);
console.log('转换后的代码:');
console.log(transformedCode);
同上,其他的转换逻辑直接 AI 生成
wxss -> style
微信样式是web样式的子集 因此这部分不用特殊转换,直接复制过来就行
WX API -> Web API
由于 web 端没有 wx 提供的API,如果执行代码,会报错。
解决这个问题,可以在 web 端模拟 wx 的相关 API,比如
// 此处是A页面
wx.navigateTo({
url: 'B?id=1'
})
可以在web端实现如下
window.wx = {
navigateTo() {}
}
这里navigateTo不需要真的跳转,只需要能跑通就行
至此整个转换工作已完成
总结
本文主要讲小程序到vue组件的转化思路,提供核心方法和思考链路,很多过程没有写代码,但是可以通过AI 生成。希望整个过程有个抛砖引玉的效果。
原文地址:https://webfem.com/post/wx2vue,转载请注明出处