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 分支)。另外,文中有任何表述不清或不當的地方,歡迎你們批評指正。
本文首發於:https://github.com/yingye/Blo...
歡迎各位關注個人Blog,正文以issue形式呈現,喜歡請點star,訂閱請點watch~