知識
不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們在追求其視覺表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營銷的便利,運(yùn)營的高效,讓網(wǎng)站成為營銷工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏壧峁┍憬莸闹С郑?
您當(dāng)前位置>首頁 » 新聞資訊 » 小程序相關(guān) >
微信小程序轉(zhuǎn)換器(一)—— 轉(zhuǎn)換器的實(shí)現(xiàn)
發(fā)表時(shí)間:2021-1-5
發(fā)布人:葵宇科技
瀏覽次數(shù):72
準(zhǔn)備
在開始前得先準(zhǔn)備點(diǎn)東西:
- 1、隨便學(xué)點(diǎn)node的api知識,用來操作文件
- 2、AST Explorer一個(gè)可以看到各種插件轉(zhuǎn)出的AST樹結(jié)構(gòu)的網(wǎng)站
- 3、JS的轉(zhuǎn)換器:
@babel/parser(code -> AST)
、@babel/traverse(用來遍歷得到的AST)
、@babel/generator(AST -> code)
- 4、HTML的轉(zhuǎn)換器
htmlparser2(HTML -> AST)
, AST -> HTML 的則需要自己手寫 - 5、CSS的轉(zhuǎn)換器
css-tree
這一個(gè)干完所有
配置文件
需要個(gè)配置文件來標(biāo)明編譯入口
// analyze.config.js
const config = {
entry: './',
output: {
name: 'dist',
src: './'
}
}
module.exports = config
復(fù)制代碼
封裝工具方法
// common.js
const path = require("path");
const fs = require("fs");
const config = require(path.resolve('./analyze.config.js'))
// 讀文件
function readFile(url) {
return fs.readFileSync(url, 'utf8')
}
// 寫文件
function writeFile(filename, data) {
return fs.writeFileSync(filename, data, 'utf8')
}
// 遞歸刪除文件夾
function deleteall(path) {
var files = [];
if(fs.existsSync(path)) {
files = fs.readdirSync(path);
files.forEach(function(file, index) {
var curPath = path + "/" + file;
if(fs.statSync(curPath).isDirectory()) { // recurse
deleteall(curPath);
} else { // delete file
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
}
}
// 復(fù)制文件
function copyFile(src, dist) {
fs.writeFileSync(dist, fs.readFileSync(src));
}
// 替換屬性
function replaceAtrr(option, key, aimsKey) {
const value = http://www.wxapp-union.com/option[key]
option[aimsKey] = value
delete option[key]
}
// 獲得輸入路徑
function inputAppPath(url) {
return url ? path.resolve(config.entry, url) : path.resolve(config.entry)
}
// 獲得輸出路徑
function outputAppPath(url) {
return url ? path.resolve(config.output.src, config.output.name, url) : path.resolve(config.output.src, config.output.name)
}
復(fù)制代碼
主要是app.json中的window屬性、tabbar屬性需要替換。window屬性在頁面的json里也需要用到。
// compares.js
const WINDOWCONVERTERCONFIG = {
'navigationBarTitleText':{ target: 'defaultTitle' },
'enablePullDownRefresh':{ target: 'pullRefresh',
handler: (config) => {
const enablePullDownRefresh = config['enablePullDownRefresh']
if (enablePullDownRefresh) config['allowsBounceVertical'] = 'YES'
}
},
'navigationBarTitleText':{ target: 'defaultTitle' },
'navigationStyle': {
handler: (config) => {
if (config['navigationStyle'] == 'custom') {
config['transparentTitle'] == 'always'
delete config['navigationStyle']
}
}
},
'navigationBarBackgroundColor':{ target: 'titleBarColor' },
'onReachBottomDistance':{ target: 'onReachBottomDistance' },
}
const TABBARCONVERTERCONFIG = [
{ originalKey: 'color', key: 'textColor' },
{ originalKey: 'list', key: 'items' , list: [
{ originalKey: 'text', key: 'name' },
{ originalKey: 'iconPath', key: 'icon' },
{ originalKey: 'selectedIconPath', key: 'activeIcon' },
]},
]
module.exports = {
WINDOWCONVERTERCONFIG,
TABBARCONVERTERCONFIG
}
復(fù)制代碼
編譯文件
編譯入口
根據(jù)不同類型文件,選擇不同類型入口,將入口和源碼處理分開來,方便之后類似loader處理的拓展。之后編譯部分只用關(guān)心輸入的源碼和輸出的代碼就可以了。
// analyze.js
// Js編譯入口
function buildJs(inputPath, outputPath) {
let source = readFile(inputPath)
source = parseJS(source)
writeFile(outputAppPath(outputPath), source)
return source
}
// Json編譯入口
function buildJson(inputPath, outputPath) {
let source = readFile(inputPath)
const jonParser = new JsonParser()
source = jonParser.parser(JSON.parse(source))
source = JSON.stringify(source)
writeFile(outputAppPath(outputPath), source)
return source
}
// Html編譯入口
function buildXml(inputPath, outputPath) {
let source = readFile(inputPath)
parseXML(source).then(code => {
writeFile(outputAppPath(outputPath), code)
})
}
// Wxss編譯入口
function buildWxss(inputPath, outputPath) {
let source = readFile(inputPath)
const code = parseCSS(source)
writeFile(outputAppPath(outputPath), code)
}
復(fù)制代碼
使用轉(zhuǎn)換器
function parseJS(source) {
const jsParser = new JsParser()
let ast = jsParser.parse(source)
ast = jsParser.astConverter(ast)
return jsParser.astToCode(ast)
}
async function parseXML(source) {
const templateParser = new TemplateParser()
let ast = await templateParser.parse(source)
ast = templateParser.templateConverter(ast)
return templateParser.astToString(ast)
}
function parseCSS(source) {
const cssParser = new CssParser()
let ast = cssParser.parse(source)
ast = cssParser.astConverter(ast)
return cssParser.astToCss(ast)
}
復(fù)制代碼
實(shí)現(xiàn)轉(zhuǎn)換器
js轉(zhuǎn)換器封裝
// JsParser.js js轉(zhuǎn)換器
const parser = require('@babel/parser')
const generate = require('@babel/generator').default
const traverse = require('@babel/traverse').default
class JsParser{
constructor() {}
// code -> ast
parse(source) {
let ast = parser.parse(source, {
sourceType: 'module'
})
return ast
}
// ast 語法樹編輯
astConverter(ast) {
traverse(ast, {
MemberExpression(p) {
let node = p.node
// 遍歷wx方法調(diào)用的節(jié)點(diǎn),并將其替換成my調(diào)用
if (node.object.name == 'wx') {
node.object.name = 'my'
}
}
})
return ast
}
// ast -> code
astToCode(ast) {
return generate(ast).code
}
}
復(fù)制代碼
css轉(zhuǎn)換器封裝
// CssParser.js css轉(zhuǎn)換器
const csstree = require('css-tree')
class CssParser {
constructor() {}
// code -> ast
parse(source) {
const ast = csstree.parse(source)
return ast
}
// ast 語法樹編輯
astConverter(ast) {
csstree.walk(ast, function(node) {
if (node.type == 'Atrule' && node.name == 'import') {
node.prelude.children.forEach(item => {
const value = http://www.wxapp-union.com/item.value
item.value = value.replace('.wxss','.acss')
});
}
})
return ast
}
// ast -> code
astToCss(ast) {
return csstree.generate(ast)
}
}
復(fù)制代碼
json轉(zhuǎn)換器封裝
// JsonParser.js json轉(zhuǎn)換器
const { WINDOWCONVERTERCONFIG } = require('./compares')
class JsonParser{
constructor() {}
// 替換屬性key
parser(source) {
function replaceAtrr(orginKey, key) {
const value = http://www.wxapp-union.com/source[orginKey]
source[key] = value
delete source[orginKey]
}
Object.keys(source).forEach(key => {
const item = WINDOWCONVERTERCONFIG[key]
if (item) {
if (item.target) replaceAtrr(key, item.target)
item.handler && item.handler(source)
}
})
return source
}
}
復(fù)制代碼
html轉(zhuǎn)換器封裝
html編譯器比較復(fù)雜,因?yàn)樗霓D(zhuǎn)換庫沒有提供AST轉(zhuǎn)換HTML的功能,需要自己去實(shí)現(xiàn)一下。需要替換的參照表也比較復(fù)雜。使用方法參考了這篇
// HtmlTemplateParser.js html轉(zhuǎn)換器
const htmlparser = require('htmlparser2') //html的AST類庫
const ATTRCONVERTERCONFIG = {
'wx:for':{ target:'a:for', },
'wx:if':{ target: 'a:if' },
'wx:elif':{ target: 'a:elif' },
'else':{ target: 'a:else' },
'wx:else':{ target: 'a:else' },
'wx:for-index':{ target: 'a:for-index' },
'wx:for-item':{ target: 'a:for-item' },
'wx:key':{ target: 'a:key' },
'bindtap':{ target: 'onTap' },
'bindtouchstart':{ target: 'onTouchstart' },
'bindtouchmove':{ target: 'onTouchMove' },
'bindtouchend':{ target: 'onTouchEnd' },
'bindtouchcancel':{ target: 'onTouchCancel' },
'bindlongtap':{ target: 'onLongTap' },
'bindlongpress':{ target: 'onLongTap' },
'catchtap':{ target: 'catchTap' },
'catchtouchstart':{ target: 'catchTouchstart' },
'catchtouchmove':{ target: 'catchTouchMove' },
'catchtouchend':{ target: 'catchTouchEnd' },
'catchtouchcancel':{ target: 'catchTouchCancel' },
'catchlongtap':{ target: 'catchLongTap' },
'catchlongpress':{ target: 'catchLongTap' },
}
function comparesAtrr(attr, key) {
function replaceAtrr(orginKey, key) {
const value = http://www.wxapp-union.com/attr[orginKey]
attr[key] = value
delete attr[orginKey]
}
if (ATTRCONVERTERCONFIG[key]) replaceAtrr(key, ATTRCONVERTERCONFIG[key].target)
}
class TemplateParser{
constructor() {}
// code -> ast
parse(source){
return new Promise((resolve, reject) => {
const handler = new htmlparser.DomHandler((error, dom)=>{
if (error) reject(error);
else resolve(dom);
});
let parser = new htmlparser.Parser(handler)
parser.write(source)
parser.end()
})
}
// ast -> code
astToString (ast) {
let str = '';
ast.forEach(item => {
if (item.type === 'text') {
str += item.data;
} else if (item.type === 'tag') {
str += '<' + item.name;
if (item.attribs) {
Object.keys(item.attribs).forEach(attr => {
str += ` ${attr}="${item.attribs[attr]}"`;
});
}
str += '>';
if (item.children && item.children.length) {
str += this.astToString(item.children);
}
str += `${item.name}>`;
}
});
return str;
}
// ast 語法樹編輯
templateConverter(ast){
for(let i = 0;i){
let node = ast[i]
//檢測到是html節(jié)點(diǎn)
if(node.type === 'tag'){
// 遍歷節(jié)點(diǎn)屬性,對比參照表有沒有需要替換的部分
Object.keys(node.attribs).forEach(key => {
comparesAtrr(node.attribs, key)
})
}
//因?yàn)槭菢錉罱Y(jié)構(gòu),所以需要進(jìn)行遞歸
if(node.children) this.templateConverter(node.children)
}
return ast
}
}
復(fù)制代碼
我對跨小程序想說的話和看法
先說一下我對這個(gè)事的看法,市面上有antmove已經(jīng)做到這個(gè)事了,但總的來說差異都不可能完全磨平,只能說是同樣小程序平臺轉(zhuǎn)換上需要更改的成本會比較低。然后我覺得運(yùn)行時(shí)每套方法寫一遍來區(qū)別不同平臺的方案,可能需要同時(shí)適應(yīng)n套混雜規(guī)則,當(dāng)有問題了,不知道該去遵守哪一套規(guī)則,開發(fā)體驗(yàn)可能不是特別好。因此我更偏向于靠編譯的方式來大概磨平差異,后續(xù)迭代也可以選擇只編譯更新了的部分內(nèi)容。 以上純屬本人愚見。請不要太在意。
相關(guān)案例查看更多
相關(guān)閱讀
- php網(wǎng)站
- 小程序的開發(fā)公司
- web教程
- 汽車報(bào)廢回收管理軟件
- 網(wǎng)站小程序
- 報(bào)廢車管理系統(tǒng)
- 小程序商城
- 網(wǎng)絡(luò)公司
- 云南小程序開發(fā)首選品牌
- 云南網(wǎng)站建設(shè)百度官方
- 云南網(wǎng)站建設(shè)費(fèi)用
- 做小程序被騙
- 小程序用戶登錄
- 制作一個(gè)小程序
- 曲靖小程序開發(fā)
- 云南小程序制作
- 前端開發(fā)
- 小程序生成海報(bào)
- 云南建設(shè)廳官方網(wǎng)站
- 專業(yè)網(wǎng)站建設(shè)公司
- 云南網(wǎng)絡(luò)推廣
- 云南網(wǎng)絡(luò)公司
- 全國前十名小程序開發(fā)公司
- 云南小程序哪家好
- 云南省住房建設(shè)廳網(wǎng)站
- 報(bào)廢車管理
- 云南微信小程序開發(fā)
- 楚雄小程序開發(fā)
- 昆明網(wǎng)站建設(shè)公司
- 南通小程序制作公司