在react-router4中進(jìn)行代碼拆分(基于webpack)

前言

隨著前端項(xiàng)目的不斷擴(kuò)大芦缰,一個(gè)原本簡(jiǎn)單的網(wǎng)頁(yè)應(yīng)用所引用的js文件可能變得越來(lái)越龐大。尤其在近期流行的單頁(yè)面應(yīng)用中,越來(lái)越依賴一些打包工具(例如webpack)领炫,通過(guò)這些打包工具將需要處理、相互依賴的模塊直接打包成一個(gè)單獨(dú)的bundle文件张咳,在頁(yè)面第一次載入時(shí)帝洪,就會(huì)將所有的js全部載入似舵。但是,往往有許多的場(chǎng)景葱峡,我們并不需要在一次性將單頁(yè)應(yīng)用的全部依賴都載下來(lái)砚哗。例如:我們現(xiàn)在有一個(gè)帶有權(quán)限的"訂單后臺(tái)管理"單頁(yè)應(yīng)用,普通管理員只能進(jìn)入"訂單管理"部分砰奕,而超級(jí)用戶則可以進(jìn)行"系統(tǒng)管理"频祝;或者,我們有一個(gè)龐大的單頁(yè)應(yīng)用脆淹,用戶在第一次打開頁(yè)面時(shí)常空,需要等待較長(zhǎng)時(shí)間加載無(wú)關(guān)資源。這些時(shí)候盖溺,我們就可以考慮進(jìn)行一定的代碼拆分(code splitting)漓糙。

實(shí)現(xiàn)方式

簡(jiǎn)單的按需加載

代碼拆分的核心目的,就是實(shí)現(xiàn)資源的按需加載烘嘱±デ荩考慮這么一個(gè)場(chǎng)景,在我們的網(wǎng)站中蝇庭,右下角有一個(gè)類似聊天框的組件醉鳖,當(dāng)我們點(diǎn)擊圓形按鈕時(shí),頁(yè)面展示聊天組件哮内。

btn.addEventListener('click', function(e) {
    // 在這里加載chat組件相關(guān)資源 chat.js
});

從這個(gè)例子中我們可以看出盗棵,通過(guò)將加載chat.js的操作綁定在btn點(diǎn)擊事件上,可以實(shí)現(xiàn)點(diǎn)擊聊天按鈕后聊天組件的按需加載北发。而要?jiǎng)討B(tài)加載js資源的方式也非常簡(jiǎn)單(方式類似熟悉的jsonp)纹因。通過(guò)動(dòng)態(tài)在頁(yè)面中添加<scrpt>標(biāo)簽,并將src屬性指向該資源即可琳拨。

btn.addEventListener('click', function(e) {
    // 在這里加載chat組件相關(guān)資源 chat.js
    var ele = document.createElement('script');
    ele.setAttribute('src','/static/chat.js');
    document.getElementsByTagName('head')[0].appendChild(ele);
});

代碼拆分就是為了要實(shí)現(xiàn)按需加載所做的工作瞭恰。想象一下,我們使用打包工具狱庇,將所有的js全部打包到了bundle.js這個(gè)文件惊畏,這種情況下是沒有辦法做到上面所述的按需加載的,因此密任,我們需要講按需加載的代碼在打包的過(guò)程中拆分出來(lái)颜启,這就是代碼拆分。那么批什,對(duì)于這些資源农曲,我們需要手動(dòng)拆分么?當(dāng)然不是,還是要借助打包工具乳规。下面就來(lái)介紹webpack中的代碼拆分形葬。

代碼拆分

這里回到應(yīng)用場(chǎng)景,介紹如何在webpack中進(jìn)行代碼拆分暮的。在webpack有多種方式來(lái)實(shí)現(xiàn)構(gòu)建是的代碼拆分笙以。

import()

這里的import不同于模塊引入時(shí)的import,可以理解為一個(gè)動(dòng)態(tài)加載的模塊的函數(shù)(function-like)冻辩,傳入其中的參數(shù)就是相應(yīng)的模塊猖腕。例如對(duì)于原有的模塊引入import react from 'react'可以寫為import('react')。但是需要注意的是恨闪,import()會(huì)返回一個(gè)Promise對(duì)象倘感。因此,可以通過(guò)如下方式使用:

btn.addEventListener('click', e => {
    // 在這里加載chat組件相關(guān)資源 chat.js
    import('/components/chart').then(mod => {
        someOperate(mod);
    });
});

可以看到咙咽,使用方式非常簡(jiǎn)單老玛,和平時(shí)我們使用的Promise并沒有區(qū)別。當(dāng)然钧敞,也可以再加入一些異常處理:

btn.addEventListener('click', e => {
    import('/components/chart').then(mod => {
        someOperate(mod);
    }).catch(err => {
        console.log('failed');
    });
});

當(dāng)然蜡豹,由于import()會(huì)返回一個(gè)Promise對(duì)象,因此要注意一些兼容性問(wèn)題溉苛。解決這個(gè)問(wèn)題也不困難镜廉,可以使用一些Promise的polyfill來(lái)實(shí)現(xiàn)兼容∮拚剑可以看到娇唯,動(dòng)態(tài)import()的方式不論在語(yǔ)意上還是語(yǔ)法使用上都是比較清晰簡(jiǎn)潔的。

require.ensure()

在webpack 2的官網(wǎng)上寫了這么一句話:

require.ensure() is specific to webpack and superseded by import().

所以凤巨,在webpack 2里面應(yīng)該是不建議使用require.ensure()這個(gè)方法的视乐。但是目前該方法仍然有效,所以可以簡(jiǎn)單介紹一下敢茁。包括在webpack 1中也是可以使用。下面是require.ensure()的語(yǔ)法:

require.ensure(dependencies: String[], callback: function(require), errorCallback: function(error), chunkName: String)

require.ensure()接受三個(gè)參數(shù):

  • 第一個(gè)參數(shù)dependencies是一個(gè)數(shù)組留美,代表了當(dāng)前require進(jìn)來(lái)的模塊的一些依賴彰檬;
  • 第二個(gè)參數(shù)callback就是一個(gè)回調(diào)函數(shù)。其中需要注意的是谎砾,這個(gè)回調(diào)函數(shù)有一個(gè)參數(shù)require逢倍,通過(guò)這個(gè)require就可以在回調(diào)函數(shù)內(nèi)動(dòng)態(tài)引入其他模塊。值得注意的是景图,雖然這個(gè)require是回調(diào)函數(shù)的參數(shù)较雕,理論上可以換其他名稱,但是實(shí)際上是不能換的,否則webpack就無(wú)法靜態(tài)分析的時(shí)候處理它亮蒋;
  • 第三個(gè)參數(shù)errorCallback比較好理解扣典,就是處理error的回調(diào);
  • 第四個(gè)參數(shù)chunkName則是指定打包的chunk名稱慎玖。

因此贮尖,require.ensure()具體的用法如下:

btn.addEventListener('click', e => {
    require.ensure([], require => {
        let chat = require('/components/chart');
        someOperate(chat);
    }, error => {
        console.log('failed');
    }, 'mychat');
});

Bundle Loader

除了使用上述兩種方法,還可以使用webpack的一些組件趁怔。例如使用Bundle Loader

npm i --save bundle-loader

使用require("bundle-loader!./file.js")來(lái)進(jìn)行相應(yīng)chunk的加載湿硝。該方法會(huì)返回一個(gè)function,這個(gè)function接受一個(gè)回調(diào)函數(shù)作為參數(shù)润努。

let chatChunk = require("bundle-loader?lazy!./components/chat");
chatChunk(function(file) {
    someOperate(file);
});

和其他loader類似关斜,Bundle Loader也需要在webpack的配置文件中進(jìn)行相應(yīng)配置。Bundle-Loader的代碼也很簡(jiǎn)短铺浇,如果閱讀一下可以發(fā)現(xiàn)痢畜,其實(shí)際上也是使用require.ensure()來(lái)實(shí)現(xiàn)的,通過(guò)給Bundle-Loader返回的函數(shù)中傳入相應(yīng)的模塊處理回調(diào)函數(shù)即可在require.ensure()的中處理随抠,代碼最后也列出了相應(yīng)的輸出格式:

/*
Output format:
    var cbs = [],
        data;
    module.exports = function(cb) {
        if(cbs) cbs.push(cb);
            else cb(data);
    }
    require.ensure([], function(require) {
        data = require("xxx");
        var callbacks = cbs;
        cbs = null;
        for(var i = 0, l = callbacks.length; i < l; i++) {
            callbacks[i](data);
        }
    });
*/

react-router v4 中的代碼拆分

最后裁着,回到實(shí)際的工作中,基于webpack拱她,在react-router4中實(shí)現(xiàn)代碼拆分二驰。react-router 4相較于react-router 3有了較大的變動(dòng)。其中秉沼,在代碼拆分方面桶雀,react-router 4的使用方式也與react-router 3有了較大的差別。
在react-router 3中唬复,可以使用Route組件中getComponent這個(gè)API來(lái)進(jìn)行代碼拆分矗积。getComponent是異步的,只有在路由匹配時(shí)才會(huì)調(diào)用敞咧。但是棘捣,在react-router 4中并沒有找到這個(gè)API,那么如何來(lái)進(jìn)行代碼拆分呢休建?
react-router 4官網(wǎng)上有一個(gè)代碼拆分的例子乍恐。其中,應(yīng)用了Bundle Loader來(lái)進(jìn)行按需加載與動(dòng)態(tài)引入

import loadSomething from 'bundle-loader?lazy!./Something'

然而测砂,在項(xiàng)目中使用類似的方式后茵烈,出現(xiàn)了這樣的警告:

Unexpected '!' in 'bundle-loader?lazy!./component/chat'. Do not use import syntax to configure webpack loaders import/no-webpack-loader-syntax
Search for the keywords to learn more about each error.

在webpack 2中已經(jīng)不能使用import這樣的方式來(lái)引入loader了(no-webpack-loader-syntax

Webpack allows specifying the loaders to use in the import source string using a special syntax like this:

var moduleWithOneLoader = require("my-loader!./my-awesome-module");

This syntax is non-standard, so it couples the code to Webpack. The recommended way to specify Webpack loader configuration is in a Webpack configuration file.

我的應(yīng)用使用了create-react-app作為腳手架,屏蔽了webpack的一些配置砌些。當(dāng)然呜投,也可以通過(guò)運(yùn)行npm run eject使其暴露webpack等配置文件。然而,是否可以用其他方法呢仑荐?當(dāng)然雕拼。
這里就可以使用之前說(shuō)到的兩種方式來(lái)處理:import()require.ensure()
和官方實(shí)例類似释漆,我們首先需要一個(gè)異步加載的包裝組件Bundle悲没。Bundle的主要功能就是接收一個(gè)組件異步加載的方法,并返回相應(yīng)的react組件:

export default class Bundle extends Component {
    constructor(props) {
        super(props);
        this.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((mod) => {
            this.setState({
                mod: mod.default ? mod.default : mod
            });
        });
    }

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

在原有的例子中男图,通過(guò)Bundle Loader來(lái)引入模塊:

import loadSomething from 'bundle-loader?lazy!./About'

const About = (props) => (
    <Bundle load={loadAbout}>
        {(About) => <About {...props}/>}
    </Bundle>
)

由于不再使用Bundle Loader示姿,我們可以使用import()對(duì)該段代碼進(jìn)行改寫:

const Chat = (props) => (
    <Bundle load={() => import('./component/chat')}>
        {(Chat) => <Chat {...props}/>}
    </Bundle>
);

需要注意的是,由于import()會(huì)返回一個(gè)Promise對(duì)象逊笆,因此Bundle組件中的代碼也需要相應(yīng)進(jìn)行調(diào)整

export default class Bundle extends Component {
    constructor(props) {
        super(props);
        this.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
        });
        //注意這里栈戳,使用Promise對(duì)象; mod.default導(dǎo)出默認(rèn)
        props.load().then((mod) => {
            this.setState({
                mod: mod.default ? mod.default : mod
            });
        });
    }

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

路由部分沒有變化

<Route path="/chat" component={Chat}/>

這時(shí)候,執(zhí)行npm run start难裆,可以看到在載入最初的頁(yè)面時(shí)加載的資源如下

這里寫圖片描述

而當(dāng)點(diǎn)擊觸發(fā)到/chat路徑時(shí)子檀,可以看到

這里寫圖片描述

動(dòng)態(tài)加載了2.chunk.js這個(gè)js文件,如果打開這個(gè)文件查看乃戈,就可以發(fā)現(xiàn)這個(gè)就是我們剛才動(dòng)態(tài)import()進(jìn)來(lái)的模塊褂痰。
當(dāng)然,除了使用import()仍然可以使用require.ensure()來(lái)進(jìn)行模塊的異步加載症虑。相關(guān)示例代碼如下:

const Chat = (props) => (
    <Bundle load={(cb) => {
        require.ensure([], require => {
            cb(require('./component/chat'));
        });
    }}>
    {(Chat) => <Chat {...props}/>}
  </Bundle>
);
export default class Bundle extends Component {
    constructor(props) {
        super(props);
        this.state = {
            mod: null
        };
    }

    load = props => {
        this.setState({
            mod: null
        });
        props.load(mod => {
            this.setState({
                mod: mod ? mod : null
            });
        });
    }

    componentWillMount() {
        this.load(this.props);
    }

    render() {
        return this.state.mod ? this.props.children(this.state.mod) : null
    }
}

此外缩歪,如果是直接使用webpack config的話,也可以進(jìn)行如下配置

output: {
    // The build folder.
    path: paths.appBuild,
    // There will be one main bundle, and one file per asynchronous chunk.
    filename: 'static/js/[name].[chunkhash:8].js',
    chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
  },

結(jié)束

代碼拆分在單頁(yè)應(yīng)用中非常常見谍憔,對(duì)于提高單頁(yè)應(yīng)用的性能與體驗(yàn)具有一定的幫助匪蝙。我們通過(guò)將第一次訪問(wèn)應(yīng)用時(shí),并不需要的模塊拆分出來(lái)习贫,通過(guò)scipt標(biāo)簽動(dòng)態(tài)加載的原理逛球,可以實(shí)現(xiàn)有效的代碼拆分。在實(shí)際項(xiàng)目中苫昌,使用webpack中的import()颤绕、require.ensure()或者一些loader(例如Bundle Loader)來(lái)做代碼拆分與組件按需加載。

http://blog.csdn.net/foralienzhou/article/details/73437057

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末祟身,一起剝皮案震驚了整個(gè)濱河市屋厘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌月而,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件议纯,死亡現(xiàn)場(chǎng)離奇詭異父款,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門憨攒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)世杀,“玉大人,你說(shuō)我怎么就攤上這事肝集≌鞍樱” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵杏瞻,是天一觀的道長(zhǎng)所刀。 經(jīng)常有香客問(wèn)我,道長(zhǎng)捞挥,這世上最難降的妖魔是什么浮创? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮砌函,結(jié)果婚禮上斩披,老公的妹妹穿的比我還像新娘。我一直安慰自己讹俊,他們只是感情好垦沉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著仍劈,像睡著了一般厕倍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耳奕,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天绑青,我揣著相機(jī)與錄音,去河邊找鬼屋群。 笑死闸婴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芍躏。 我是一名探鬼主播邪乍,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼对竣!你這毒婦竟也來(lái)了庇楞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤否纬,失蹤者是張志新(化名)和其女友劉穎吕晌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體临燃,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡睛驳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年烙心,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乏沸。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡淫茵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蹬跃,到底是詐尸還是另有隱情匙瘪,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布蝶缀,位于F島的核電站丹喻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏扼劈。R本人自食惡果不足惜驻啤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荐吵。 院中可真熱鬧骑冗,春花似錦、人聲如沸先煎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)薯蝎。三九已至遥倦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間占锯,已是汗流浹背袒哥。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留消略,地道東北人堡称。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像艺演,于是被迫代替她去往敵國(guó)和親却紧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • GitChat技術(shù)雜談 前言 本文較長(zhǎng)胎撤,為了節(jié)省你的閱讀時(shí)間晓殊,在文前列寫作思路如下: 什么是 webpack,它要...
    蕭玄辭閱讀 12,697評(píng)論 7 110
  • 無(wú)意中看到zhangwnag大佬分享的webpack教程感覺受益匪淺伤提,特此分享以備自己日后查看巫俺,也希望更多的人看到...
    小小字符閱讀 8,171評(píng)論 7 35
  • 作者:小 boy (滬江前端開發(fā)工程師)本文原創(chuàng)识藤,轉(zhuǎn)載請(qǐng)注明作者及出處砚著。原文地址:https://www.smas...
    iKcamp閱讀 2,759評(píng)論 0 18
  • 學(xué)習(xí)流程 參考文檔:入門Webpack,看這篇就夠了Webpack for React 一. 簡(jiǎn)單使用webpac...
    Jason_Zeng閱讀 3,138評(píng)論 2 16
  • 幸福雙翼《家學(xué)》是一門關(guān)于如何獲得個(gè)人及家庭幸福能力的課程痴昧,學(xué)期兩年,我剛學(xué)習(xí)了幾節(jié)課冠王,很是受益赶撰,感謝黃志猛老師,...
    舜間永恒閱讀 994評(píng)論 0 2