天天看點

小議webpack下的AOP式無侵入注入

​    說起來, 面向切面程式設計(AOP)自從誕生之日起,一直都是計算機科學領域十分熱門的話題,但是很奇怪的是,在前端圈子裡,探讨AOP的文章似乎并不是多,而且多數拘泥在給出理論,然後實作個片段的定式)難免陷入了形而上學的尴尬境地。​

    本文列舉了兩個生産環境的實際例子論述webpack和AOP預編譯處理的結合,意在抛磚引玉。當然,筆者能力有限,如果有覺得不妥之處,還請大家積極的回報出來, 共同進步哈。

重要的概念

AOP: 面向切面程式設計,通過預編譯方式和運作期動态代理實作程式功能的統一維護的一種技術。

Joint point:表示在程式中明确定義的點,典型的包括方法調用,對類成員的通路以及異常處理程式塊的執行等等,它自身還可以嵌套其它 joint point。

Advice:Advice 定義了在 pointcut 裡面定義的程式點具體要做的操作,它通過 before、after 和 around 來差別是在每個 joint point 之前、之後還是代替執行的代碼。

通過前面的定義,我們可以提煉出一句更簡單的定義,利用靜/動态的方式使代碼塊在何時/何地運作。

性能統計

項目的背景是一個利用vue+webpack打造的多頁面應用 (多入口點),她的結構大概是這個樣子的

var baseConf = {

  // code here

  entry: {

    index: 'src/index',

    list: 'src/list',

    detail: 'src/detail',

    // and so on ...

  },

  // code here

}      

然後以index入口點舉例,大概代碼為src/index/index.js

import Vue from 'vue'

import App from './app'

new Vue({

  el: '#app',

  render: h => h(App)

})      

期望引入一個vue插件,能夠自動的監控目前頁面的性能,于是,代碼看起來像是這個樣子

import Vue from 'vue'

Vue.use(performance) //性能統計

import App from './app'

new Vue({

  el: '#app',

  render: h => h(App)

})      

由于這種方式意味着每個入口點均需要進行修改,(實際上這個項目的入口點超過30個,而且随時可能繼續增加下去)簡直就是一個體力活。是以,讓我們用AOP的思想來考慮一下如何處理這個問題

首先觀察入口點邏輯

原:引入vue -> 引入app元件 -> 執行個體化vue元件

新:引入vue -> 應用性能統計元件 -> 引入app元件 -> 執行個體化vue元件

套用到我們的定義上,可以輕松的得到

  • ​Joint point(何處) 引入vue​
  • ​advice(何時) 之後​

這樣理論上的東西似乎閉着眼睛都可以推論出來,但是如何将這樣的步驟替換到每一個入口點就是一個大問題了orz。幸運的是這是一個import,而翻閱webpack的文檔恰好有着這樣一個神奇的屬性--alias

resolve: {

    alias: {

      'vue$': resolve('src/vueHook.js')

    }      

src/vueHook.js

import vue from 'vue/dist/vue.common'


vue.use(performance)


export default vue      

這樣,我們就完成了一個vue的全局鈎子子產品,我們按照步驟歸納,并且找到注入的位置 ,最後利用替換的方式成功的完成了無侵入式的元件應用

code spliting

可能上面的例子有點小打小鬧的感覺,那麼我們換一個案例,再來體驗一下這種靜态替換式的注入的威力,我們采用官方支援較差的react作為參考(vue在code spliting方面做得真心是超級棒~)

import SingleImage from '../../component-modules/magic-single-image/src/index';

import DoubleImage from '../../component-modules/magic-double-image/src/index';

import ThreeImage from '../../component-modules/magic-three-image/src/index';

// many component here



switch (componentName) {

  case 'SingleImage':

    PreviewingComponent = SingleImage;

    break;

  case 'DoubleImage':

    PreviewingComponent = DoubleImage;

    break;

  case 'ThreeImage':

    PreviewingComponent = ThreeImage;

    break;

  // many component here

}


return(<PreviewingComponent></PreviewingComponent>)      

一段中規中矩的代碼,對吧?相信大家已經發現了,在上述的代碼裡面似乎并不是每個元件都是必須的,那麼,基于以上的思考,可以對上面元件進行按需加載處理。 Bundle.jsx

import React, { Component, PropTypes } from 'react';


class Bundle extends Component {

  static propTypes = {

    load: PropTypes.func,

    children: PropTypes.func,

  }


  state = {

    mod: null,

  }


  componentWillMount() {

    this.load(this.props);

  }


  componentWillReceiveProps(nextProps) {

    if (nextProps.load !== this.props.load) {

      this.load(nextProps);

    }

  }



  load(props) {

    this.setState({

      mod: null,

    });

    props.load().then((mod) => {

      this.setState({

        // handle both es imports and cjs

        mod: mod.default ? mod.default : mod,

      });

    });

  }


  render() {

    return this.state.mod ? this.props.children(this.state.mod) : null;

  }

}


export default Bundle;      

以及相應的alias hook

export default (

    <Bundle

      load={() => import(/* webpackChunkName: "widget" */

        `../../component-modules/magic-single-image/src/index`

      )}

    >

      {Widget => <Widget {...props} />}

    </Bundle>

  )      
思考,當元件多的時候每一個子產品都需要一個人口點嗎,可以從webpack.context角度簡化這個問題嗎?

    以上兩個例子均是子產品引用作為join point來進行注入操作的,而且完成了無侵入式的功能增強,這得益于webpack将js子產品作為一等公民。我們擁有着超多的權利完成靜态式的注入工作。 本文并沒有在技術上涉及太多,還是那句話,抛磚引玉哈~~~

​———​————————————​———​