wepy 是騰訊開源的一款小程序框架,主要經過預編譯的手段,讓開發者採用類 Vue 風格開發。 讓咱們一塊兒看看, wepy 是如何實現預編譯的。先放上一張官網的流程圖,後面的分析能夠參考該圖。css
wepy-cli 主要負責 .wpy 文件的編譯,目錄結構以下:html
編譯的入口是 src/compile.js 中的 compile()
方法,該方法主要是根據文件類型,執行不一樣的 compiler ,好比 .wpy 文件會走 compile-wpy.js 下的 compile()
方法。git
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));
...
}
}
複製代碼
compile-wpy.js 下的 compile()
方法,核心調用了 resolveWpy()
方法。github
resolveWpy()
方法,主要是將 .wpy 拆解成 rst
對象,並對其中的 template、script 作一些預處理,而後將 template、 script、 style 三部分移交給不一樣的 compiler 處理。typescript
經過 xmldom 獲取 xml
對象,而後遍歷節點,拆解爲 rst
對象。npm
import {DOMParser} from 'xmldom';
export default {
createParser (opath) {
return new DOMParser({
...
})
},
...
resolveWpy () {
let xml = this.createParser(opath).parseFromString(content);
}
}
複製代碼
rst
對象結構以下:小程序
let rst = {
moduleId: moduleId,
style: [],
template: {
code: '',
src: '',
type: ''
},
script: {
code: '',
src: '',
type: ''
}
};
複製代碼
此外,還對 template 作了以下一些預處理:sass
pug
預編譯import
,放入 rst.template.components
中props
和 events
,放入 rst.script.code
中compile-template.js 中的 compile()
方法,根據 template 的 lang 值,執行不一樣的 compiler ,好比 wepy-compile-typescript 。編譯完成後,執行 compileXML 方法,作了以下的操做:babel
updateSlot
方法: 替換 slot 內容updateBind
方法: 在 {{}} 和 attr 上加入組件的前綴,例如:{{width}}
-> {{$ComponentName$width}}
<repeat for="xxx" index="idx" item="xxx" key="xxx"></repeat>
<!-- 轉換爲 -->
<block wx:for="xxx" wx:for-index="xxx" wx:for-item="xxx" wx:key="xxxx"></block>
複製代碼
依舊先是根據 lang 值,先執行不一樣的 compiler ,好比 wepy-compile-less 。編譯完成後,執行 src/style-compiler/scope.js 中的 scopedHandler()
方法,處理 scoped
。app
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。根據上面的代碼,打印出來的log以下:
最後,會把 requires
由絕對路徑替換爲相對路徑,並在 wxss 中引入,最終生成的 wxss 文件爲:
@import "./../components/demo.wxss";
Page{background:#F4F5F7} ...
複製代碼
依舊先是根據 lang 值,執行不一樣的 compiler。compiler 執行完以後,判斷是不是 npm 包,若是不是,依據不一樣的 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;
}
});
}
}
複製代碼
接下來會執行 resolveDeps()
方法,主要是處理 requires
。根據 require
文件的類型,拷貝至對應的目錄,再把 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 處理。先來看一下,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);
}
}
}
複製代碼
在有多個插件的時候,不斷的調用 next()
,最後執行 done()
。
wxss 與 css 相比,拓展了尺寸單位,即引入了 rpx
單位。可是設計童鞋給到的設計稿單位通常爲 px
,那如今咱們就一塊兒來編寫一個能夠將 px
轉換爲 rpx
的 wepy plugin。
從 PluginHelper 類的定義能夠看出,是調用了 plugin 中的 apply()
方法。另外,只有 .wxss 中的 rpx
才須要轉換,因此會加一層判斷,若是不是 wxss 文件,接着執行下一個 plugin。rpx
轉換爲 px
的核心是,使用了 postcss-px2units plugin。下面就是設計好的 wepy-plugin-px2units,更多源碼可參考 github 地址。
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();
});
}
}
}
複製代碼
本文分析的源碼以 wepy-cli@1.7.1 版本爲準,更多信息可參考 wepy github (即 github 1.7.x 分支)。另外,文中有任何表述不清或不當的地方,歡迎你們批評指正。
本文首發於:github.com/yingye/Blog…
歡迎各位關注個人Blog,正文以issue形式呈現,喜歡請點star,訂閱請點watch~