Webpack按需載入秒開應用

DBCdouble發表於2019-03-06
作者 DBCdouble

一、前言

本文將基於上一篇文章《Webpack4+Babel7優化70%速度》所搭建的環境去做動態路由載入,同時完成 React16 和 React-Router4 的升級工作,使整個專案的技術棧以及效能體驗儘可能達到最佳狀態。

二、背景

我們知道,Webpack主要從兩個方面進行優化,一個是提升構建速度,另一個則是減小檔案體積,而在上一篇文章《Webpack4+Babel7優化70%速度》中,我們已經完成了提升構建速度的部分,這一章我們將通過實現動態載入路由的方式來將最終生成的打包檔案拆分成多個子檔案來減小bundle.js的體積,這樣就能極大的減小首屏載入過慢的痛點,至此之後,也就再也不用擔心隨著應用越來越大,bundle.js的體積越來越大導致首屏載入的速度越來越慢的問題了。

三、升級模組

這裡一個個模組安裝升級是為了更準確地來把控更新之後有可能引起的報錯

1、React16

npm install react@16.8.4 --save複製程式碼

這裡安裝目前react的最新版本v16.8.4,安裝完成之後開啟專案,發現頁面報錯,如圖:

Webpack按需載入秒開應用

Webpack按需載入秒開應用

出現上面的報錯的原因是React v15.5及以上版本已經將PropTypes模組剔除,然後執行 

npm install prop-types --save 
將程式碼中
 import { PropTypes } from 'react' 
程式碼修改為
import PropTypes from 'prop-types'
注意:除了入口檔案下的程式碼需要替換PropTypes,你專案中使用到的第三方庫內部也有可能使用到了 
import { PropTypes } from 'react'
,遇到這種情況,需要將該第三方庫升級到最新版本,包括react-router之前的老版本就是依賴於react庫中的PropTypes作資料型別判斷,所以接下來升級react-router

2、react-router-dom(這裡使用react-router-dom,它基於react-router,加入了在瀏覽器執行環境下的一些功能)

npm uninstall react-router && npm install react-router-dom --save複製程式碼

react-router-dom依賴react-router,所以我們使用npm安裝依賴的時候,只需要安裝相應環境下的庫即可,不用再顯式安裝react-router

  • 將程式碼中的
     import { Link } from 'react-router' 
    修改為
     import { Link } from 'react-router-dom'
  • 因為react-router4.x版本較之前版本改動較大,基本需要重寫專案中的路由層

四、路由配置

1、入口檔案

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';import App from './app';

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('app')
);複製程式碼

BrowserRouter是一個高階元件,內建history的api來保持 UI 和 URL 的同步

2、路由配置

// routes.js
import Home from './home';
import About from './about';
import Help from './help';

export default [{
  path: '/',
  exact: true,
  component: Home
}, {
  path: '/about',
  component: About
}, {
  path: '/help',
  component: Help
}];複製程式碼

// app.js
import React from 'react';
import { Switch, Route } from 'react-router';
import routes from './routes';

class App extends React.Component {
  render() {
    return (
      <Switch>
        {routes.map((route, i) => <Route key={i} exact={!!route.exact} path={route.path} component={route.component} />)}
      </Switch>
    );
  }
}

export default App;複製程式碼

Switch用於渲染與路徑匹配的第一個子 <Route><Redirect>

五、非同步動態載入路由和Code Splitting

非同步動態載入路由從狹義上理解就是頁面上沒有出現的頁面不載入對應的js和css,只載入當前頁面展示出來頁面的js和css,通過動態匯入(dynamic imports)檔案的方式實現程式碼拆分

1、封裝一個高階函式來非同步載入元件

//async_load.js
import React, { Component } from 'react'
export default (loadComponent, placeholder = '拼命載入中...') => {
  return class AsyncComponent extends Component {
    unmount = false
    constructor () {
      super()
      this.state = {
        Child: null
      }
    }
    componentWillUnmount () {
      this.unmount = true
    }
    async componentDidMount () {
      const { default: Child } = await loadComponent()
      if (this.unmount) return
      this.setState({
        Child
      })
    }
    render () {
      const { Child } = this.state
      return (
        Child ? <Child {...this.props} /> : placeholder
      )
    }
  }
}複製程式碼

2、修改路由配置檔案

// routes.js
import React from 'react';
import Load from './async_load';

export default [{
  path: '/',
  exact: true,
  component(props) {
    // 這裡的 component 函式也是一個高階元件
    return <Load {...props} load={() => import('./home')} />;
  }
}, {
  path: '/about',
  component(props) {
    return <Load {...props} load={() => import('./about')} />;
  }
}, {
  path: '/help',
  component(props) {
    return <Load {...props} load={() => import('./help')} />;
  }
}];複製程式碼

當涉及到動態程式碼拆分時,webpack 提供了兩個類似的技術。對於動態匯入,第一種,也是優先選擇的方式是,使用符合 ECMAScript 提案import() 語法。第二種,則是使用 webpack 特定的 require.ensure

完成以上配置之後開啟打包,出現一下錯誤

Webpack按需載入秒開應用

因為import語法還處於提案階段,所以需要通過安裝Babel的外掛@babel/plugin-syntax-dynamic-import才能使用,安裝完成之後需要在配置babel-loader的options:

{
    plugins: ['@babel/plugin-syntax-dynamic-import']
}複製程式碼

完成以上的步驟之後,想必已經沒問題了吧,於是啟動專案,又報錯了:

Webpack按需載入秒開應用

在這個報錯上我停留了太久,於是把問題拋給了一個朋友(大佬),在webpack在github上的Issue找到了答案,原來是webpack的4.29.x版本有bug


Webpack按需載入秒開應用

於是我回退了版本webpack@4.28.2,最終啟動成功,可以看到bundle.js被拆分成多個子js檔案

Webpack按需載入秒開應用

在瀏覽器開啟開發者工具點選network檢視請求的js檔案,可以看到每進入一個新的頁面,會動態載入一個新的js

進入首屏路由頁面

Webpack按需載入秒開應用

進入另外一個路由頁面

Webpack按需載入秒開應用


六、總結

不得不說升級老專案過程很痛苦,坑也很多。但是把坑一個個填完,最終完美升級也是一件很有意思,很有成就感的事。希望這篇文章能對你有所幫助。

Webpack按需載入秒開應用

文章有任何不清楚或者不準確的地方,麻煩在評論區指出


相關文章