小議webpack下的AOP式無侵入注入
說起來, 面向切面編程(AOP)自從誕生之日起,一直都是計算機科學領域十分熱門的話題,但是很奇怪的是,在前端圈子裡,探討AOP的文章似乎並不是多,而且多數拘泥在給出理論,然後實現個片段的定式)難免陷入了形而上學的尷尬境地,本文列舉了兩個生產環境的實際例子論述webpack和AOP預編譯處理的結合,意在拋磚引玉。當然,筆者能力有限,如果有覺得不妥之處,還請大家積極的反饋出來, 共同進步哈。
重要的概念
AOP: 面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。
Joint point:表示在程序中明確定義的點,典型的包括方法調用,對類成員的訪問以及異常處理程序塊的執行等等,它自身還可以嵌套其它 joint point。Advice:Advice 定義了在 pointcut 裡面定義的程序點具體要做的操作,它通過 before、after 和 around 來區別是在每個 joint point 之前、之後還是代替執行的代碼。
通過前面的定義,我們可以提煉出一句更簡單的定義,利用靜/動態的方式使代碼塊在何時/何地運行。
性能統計
項目的背景是一個利用vue+webpack打造的多頁面應用 (多入口點),她的結構大概是這個樣子的
var baseConf = {n // code heren entry: {n index: src/index,n list: src/list,n detail: src/detail,n // and so on ...n },n // code heren}n
然後以index入口點舉例,大概代碼為src/index/index.js
import Vue from vuenimport App from ./appnnew Vue({n el: #app,n render: h => h(App)n})n
期望引入一個vue插件,能夠自動的監控當前頁面的性能,於是,代碼看起來像是這個樣子
import Vue from vuenVue.use(performance) //性能統計nimport App from ./appnnew Vue({n el: #app,n render: h => h(App)n})n
由於這種方式意味著每個入口點均需要進行修改,(實際上這個項目的入口點超過30個,而且隨時可能繼續增加下去)簡直就是一個體力活。所以,讓我們用AOP的思想來考慮一下如何處理這個問題
首先觀察入口點邏輯
原:引入vue -> 引入app組件 -> 實例化vue組件
新:引入vue -> 應用性能統計組件 -> 引入app組件 -> 實例化vue組件
套用到我們的定義上,可以輕鬆的得到
- Joint point(何處) 引入vue
- advice(何時) 之後
這樣理論上的東西似乎閉著眼睛都可以推論出來,但是如何將這樣的步驟替換到每一個入口點就是一個大問題了orz。幸運的是這是一個import,而翻閱webpack的文檔恰好有著這樣一個神奇的屬性--alias
resolve: {n alias: {n vue$: resolve(src/vueHook.js)n }n
src/vueHook.js
import vue from vue/dist/vue.commonnnvue.use(performance)nnexport default vuen
這樣,我們就完成了一個vue的全局鉤子模塊,我們按照步驟歸納,並且找到注入的位置 ,最後利用替換的方式成功的完成了無侵入式的組件應用
code spliting
可能上面的例子有點小打小鬧的感覺,那麼我們換一個案例,再來體驗一下這種靜態替換式的注入的威力,我們採用官方支持較差的react作為參考(vue在code spliting方面做得真心是超級棒~)
import SingleImage from ../../component-modules/magic-single-image/src/index;nimport DoubleImage from ../../component-modules/magic-double-image/src/index;nimport ThreeImage from ../../component-modules/magic-three-image/src/index;n// many component herennnswitch (componentName) {n case SingleImage:n PreviewingComponent = SingleImage;n break;n case DoubleImage:n PreviewingComponent = DoubleImage;n break;n case ThreeImage:n PreviewingComponent = ThreeImage;n break;n // many component heren}nnreturn(<PreviewingComponent></PreviewingComponent>)n
一段中規中矩的代碼,對吧?相信大家已經發現了,在上述的代碼裡面似乎並不是每個組件都是必須的,那麼,基於以上的思考,可以對上面組件進行按需載入處理。 Bundle.jsx
import React, { Component, PropTypes } from react;nnclass Bundle extends Component {n static propTypes = {n load: PropTypes.func,n children: PropTypes.func,n }nn state = {n mod: null,n }nn componentWillMount() {n this.load(this.props);n }nn componentWillReceiveProps(nextProps) {n if (nextProps.load !== this.props.load) {n this.load(nextProps);n }n }nnn load(props) {n this.setState({n mod: null,n });n props.load().then((mod) => {n this.setState({n // handle both es imports and cjsn mod: mod.default ? mod.default : mod,n });n });n }nn render() {n return this.state.mod ? this.props.children(this.state.mod) : null;n }n}nnexport default Bundle;n
以及相應的alias hook
export default (n <Bundlen load={() => import(/* webpackChunkName: "widget" */n `../../component-modules/magic-single-image/src/index`n )}n >n {Widget => <Widget {...props} />}n </Bundle>n )n
思考,當組件多的時候每一個模塊都需要一個人口點嗎,可以從webpack.context角度簡化這個問題嗎?
以上兩個例子均是模塊引用作為join point來進行注入操作的,而且完成了無侵入式的功能增強,這得益於webpack將js模塊作為一等公民。我們擁有著超多的權利完成靜態式的注入工作。 本文並沒有在技術上涉及太多,還是那句話,拋磚引玉哈~~~
推薦閱讀: