本文摘要 來自摩拜前端團隊 yingye 本文從源碼入手分析了 wpy 文件的編譯過程,,在文末還介紹了如何編寫wepy plugin,。 歡迎關(guān)注本系列,留言并分享 小程序開發(fā) 中的體會,。
wepy 是騰訊開源的一款小程序框架,,主要通過預(yù)編譯的手段,讓開發(fā)者采用類 Vue 風(fēng)格開發(fā),。 讓我們一起看看,, wepy 是如何實現(xiàn)預(yù)編譯的。先放上一張官網(wǎng)的流程圖,,后面的分析可以參考該圖,。
wepy-cli 主要負(fù)責(zé) .wpy 文件的編譯,目錄結(jié)構(gòu)如下: 編譯的入口是 src/compile.js 中的 compile() 方法,,該方法主要是根據(jù)文件類型,,執(zhí)行不同的 compiler ,,比如 .wpy 文件會執(zhí)行 compile-wpy.js 下的 compile() 方法,。 compile(opath) {
...
switch(opath.ext) {
case ext:
cWpy.compile(opath);
break;
case '.less':
cStyle.compile('less', opath);
break;
case '.sass':
cStyle.compile('sass', opath);
break;
case '.scss':
cStyle.compile('scss', opath);
break;
case '.js':
cScript.compile('babel', null, 'js', opath);
break;
case '.ts':
cScript.compile('typescript', null, 'ts', opath);
break;
default:
util.output('拷貝', path.join(opath.dir, opath.base));
...
}
}
.wpy文件拆解compile-wpy.js 下的 compile() 方法,,核心調(diào)用了 resolveWpy() 方法。 resolveWpy() 方法,,主要是將 .wpy 拆解成 rst 對象,,并對其中的 template、script 做一些預(yù)處理,,然后將 template,、 script、 style 三部分移交給不同的 compiler 處理,。
生成rst對象通過 xmldom 獲取 xml 對象,,然后遍歷節(jié)點,拆解為 rst 對象,。 import {DOMParser} from 'xmldom';
export default {
createParser (opath) {
return new DOMParser({
...
})
},
...
resolveWpy () {
let xml = this.createParser(opath).parseFromString(content);
}
}
rst 對象結(jié)構(gòu)如下:
let rst = {
moduleId: moduleId,
style: [],
template: {
code: '',
src: '',
type: ''
},
script: {
code: '',
src: '',
type: ''
}
};
此外,,還對 template 做了如下一些預(yù)處理: compile-templatecompile-template.js 中的 compile() 方法,,根據(jù) template 的 lang 值,執(zhí)行不同的 compiler ,,比如 wepy-compile-typescript ,。編譯完成后,執(zhí)行 compileXML 方法,,做了如下的操作: updateSlot 方法: 替換 slot 內(nèi)容
updateBind 方法: 在 {{}} 和 attr 上加入組件的前綴,,例如: {{width}} -> {{$ComponentName$width}}
把自定義的標(biāo)簽、指令轉(zhuǎn)換為 wxml 語法,,例如:
<> for='xxx' index='idx' item='xxx' key='xxx'>
<> wx:for='xxx' wx:for-index='xxx' wx:for-item='xxx' wx:key='xxxx'>
compile-style依舊先是根據(jù) lang 值,,先執(zhí)行不同的 compiler ,比如 wepy-compile-less ,。編譯完成后,,執(zhí)行 src/style-compiler/scope.js 中的 scopedHandler() 方法,處理 scoped ,。 import postcss from 'postcss';
import scopeId from './scope-id';
export default function scopedHandler (id, content) {
console.log('id is: ', id)
console.log('css content is: ', content)
return postcss([scopeId(id)])
.process(content)
.then(function (result) {
console.log('css result is: ', result.css)
return result.css
}).catch((e) => {
return Promise.reject(e)
})
}
這里主要是利用 add-id 的 postcss 插件,,插件源碼可參考 src/style-compiler/scope-id.js。根據(jù)上面的代碼,,打印出來的log如下: 最后,,會把 requires 由絕對路徑替換為相對路徑,并在 wxss 中引入,,最終生成的 wxss 文件為: @import './../components/demo.wxss';
Page{background:#F4F5F7} ...
compile-script依舊先是根據(jù) lang 值,,執(zhí)行不同的 compiler,。compiler 執(zhí)行完之后,判斷是否是 npm 包,,如果不是,,依據(jù)不同的 type 類型,加入 wepy 初始化的代碼,。 if (type !== 'npm') {
if (type === 'page' || type === 'app') {
code = code.replace(/exports\.default\s*=\s*(\w+);/ig, function (m, defaultExport) {
if (defaultExport === 'undefined') {
return '';
}
if (type === 'page') {
let pagePath = path.join(path.relative(appPath.dir, opath.dir), opath.name).replace(/\\/ig, '/');
return `\nPage(require('wepy').default.$createPage(${defaultExport} , '${pagePath}'));\n`;
} else {
appPath = opath;
let appConfig = JSON.stringify(config.appConfig || {});
let appCode = `\nApp(require('wepy').default.$createApp(${defaultExport}, ${appConfig}));\n`;
if (config.cliLogs) {
appCode += 'require(\'./_wepylogs.js\')\n';
}
return appCode;
}
});
}
}
接下來會執(zhí)行 resolveDeps() 方法,,主要是處理 requires 。根據(jù) require 文件的類型,,拷貝至對應(yīng)的目錄,,再把 code 中的 require 代碼替換為 相對路徑。 處理好的 code 最終會寫入 js 文件中,,文件存儲路徑會判斷類型是否為 npm,。 let target;
if (type !== 'npm') {
target = util.getDistPath(opath, 'js');
} else {
code = this.npmHack(opath, code);
target = path.join(npmPath, path.relative(opath.npm.modulePath, path.join(opath.dir, opath.base)));
}
plugin根據(jù)上面的流程圖,可以看出所有的文件生成之前都會經(jīng)過 Plugin 處理,。先來看一下,,compiler 中是如何載入 Plugin 的。 let plg = new loader.PluginHelper(config.plugins, {
type: 'css',
code: allContent,
file: target,
output (p) {
util.output(p.action, p.file);
},
done (rst) {
util.output('寫入', rst.file);
util.writeFile(target, rst.code);
}
});
其中,,config.plugins 就是在 wepy.config.js 中定義的 plugins,。讓我們來看一下 PluginHelper 類是如何定義的。 class PluginHelper {
constructor (plugins, op) {
this.applyPlugin(0, op);
return true;
}
applyPlugin (index, op) {
let plg = loadedPlugins[index];
if (!plg) {
op.done && op.done(op);
} else {
op.next = () => {
this.applyPlugin(index + 1, op);
};
op.catch = () => {
op.error && op.error(op);
};
if (plg)
plg.apply(op);
}
}
}
在有多個插件的時候,,不斷的調(diào)用 next() ,,最后執(zhí)行 done() 。 編寫pluginwxss 與 css 相比,,拓展了尺寸單位,,即引入了 rpx 單位。但是設(shè)計童鞋給到的設(shè)計稿單位一般為 px ,,那現(xiàn)在我們就一起來編寫一個可以將 px 轉(zhuǎn)換為 rpx 的 wepy plugin,。 從 PluginHelper 類的定義可以看出,是調(diào)用了 plugin 中的 apply() 方法,。另外,,只有 .wxss 中的 rpx 才需要轉(zhuǎn)換,所以會加一層判斷,,如果不是 wxss 文件,,接著執(zhí)行下一個 plugin。 rpx 轉(zhuǎn)換為 px 的核心是,,使用了 postcss-px2units plugin,。下面就是設(shè)計好的 wepy-plugin-px2units,更多源碼可參考 github 地址( https://github.com/yingye/wepy-plugin-px2units ),。 import postcss from 'postcss';
import px2units from 'postcss-px2units';
export default class {
constructor(c = {}) {
const def = {
filter: new RegExp('\.(wxss)$'),
config: {}
};
this.setting = Object.assign({}, def, c);
}
apply (op) {
let setting = this.setting;
if (!setting.filter.test(op.file)) {
op.next();
} else {
op.output && op.output({
action: '變更',
file: op.file
});
let prefixer = postcss([ px2units(this.setting.config) ]);
prefixer.process(op.code, { from: op.file }).then((result) => {
op.code = result.css;
op.next();
}).catch(e => {
op.err = e;
op.catch();
});
}
}
}
最后本文分析的源碼以 [email protected] 版本為準(zhǔn),,更多信息可參考 wepy github (即 github 1.7.x 分支,,https://github.com/Tencent/wepy/tree/1.7.x )。另外,,文中有任何表述不清或不當(dāng)?shù)牡胤?,歡迎大家批評指正。
|