React配合Webpack實現代碼分割與非同步載入

来源:http://www.cnblogs.com/developerdaily/archive/2017/05/21/6886134.html
-Advertisement-
Play Games

這是Webpack+React系列配置過程記錄的第四篇。其他內容請參考: 第一篇:使用webpack、babel、react、antdesign配置單頁面應用開發環境 第二篇:使用react-router實現單頁面應用路由 第三篇:優化單頁面開發環境:webpack與react的運行時打包與熱更新 ...


這是Webpack+React系列配置過程記錄的第四篇。其他內容請參考:

自從前幾篇文章介紹如何搭建React+Webpack單頁面應用開發環境之後,我就基於這個環境對我的書籍分享網站的管理後臺進行業務代碼的實現。隨著業務代碼量的增加,我自定義的React組件也越來越多,這導致每次我刷新瀏覽器地址的時候都要等待挺久的一段時間。

解決這個問題的思路還是比較簡單,分塊載入每次需要用到什麼就載入什麼。基於這個思路進一步擴展一下,我想要針對CDN後者瀏覽器的緩存做一下優化,從而讓瀏覽器每次只載入被我修改的那部分代碼。

代碼切割

參考Webpack官方文檔,代碼分割可以從以下幾個方面進行。

CSS資源

之前我們的CSS樣式通過Webpack編譯到JS代碼中,然後由JS代碼動態插入到head標簽里。這種載入CSS樣式的方式,一方面會讓JS代碼非常大,另一方面會導致在非同步載入方式渲染頁面的時候網頁會閃爍。

這裡我們換一種載入方式,讓CSS代碼作為獨立資源導出。這樣就減少了JS代碼規模,利用瀏覽器的多個連接同時載入JS代碼和CSS代碼,提高載入速度。這需要用到一個Webpack的插件:ExtractTextPlugin。

安裝ExtractTextPlugin:

npm install --save-dev extract-text-webpack-plugin 

修改webpack.config.js文件:

// 引入ExtractTextPlugin
var ExtractTextPlugin = require('extract-text-webpack-plugin');

// 修改module.rules中關於CSS的節點的內容
//{
//  test: /\.css$/,
//  use: ['style-loader', 'css-loader']
//},
{
    test: /-m\.css$/,
    use: ExtractTextPlugin.extract({
        fallback: "style-loader",
        use: [
            {
                 loader: 'css-loader',
                 options: {
                     modules: true,
                     localIdentName: '[path][name]-[local]-[hash:base64:5]'
                  }
             }
        ]
    })
},
{
    test: /^((?!(-m)).)*\.css$/,
    use: ExtractTextPlugin.extract({
        fallback: 'style-loader',
        use: 'css-loader'
    })
}

// 在webpack的plugins節點增加下麵一行:
plugins: [  
  new ExtractTextPlugin('styles.css'), // 增加的行,樣式將輸出到styles.css
  new webpack.HotModuleReplacementPlugin(),
  new webpack.NoEmitOnErrorsPlugin()
]

上面的配置使用ExtractTextPlugin讓Webpack把結果生成到styles.css文件中。這個文件對外的訪問目錄與js一樣。我在這裡使用了兩種處理CSS文件的方式。首先是帶-m結尾的文件,我使用css-loader的啟用了模塊化處理,讓我能夠在js中以對象的方式應用css樣式。然後是非-m結尾的文件,讓webpack調用css-loader和style-loader預設處理。

下麵驗證一下效果。

在src目錄下我創建一個css文件,BasicExample-m.css,內容如下:

.red {
    color: red;
}

在BasicExample.js文件中引入css文件,然後在js中應用red樣式到一個p標簽(這也是我為什麼要讓css文件名是-m結尾的原因)。改動如下:

...
// 引入
import styles from './BasicExample-m.css';  
...
// 應用
<p className={styles.red}>Red Text</p>  
...

修改一下index.html,讓它引入styles.css即可。

<html>  
  <head>
    <link rel="stylesheet" href="/styles.css"/>
  </head>
  <body>
    <p>Hello world</p>
    <div id='main'></div>
    <script src="/out.js"></script>
  </body>
</html>  

啟動,然後在瀏覽器查看一下效果。

CSS樣式代碼分割

啟用開發者工具查看網路請求,發現確實請求了styles.css和out.js文件;而且請求到的index.html內容中,head標簽內也沒有發現嵌入了樣式代碼。

第三方依賴

第三方依賴在開發過程中屬於不常變化的部分,導出到一個獨立文件。

假設我的項目使用了第三方庫jQuery,因此我使用npm install --save jquery安裝了jQuery依賴。

首先我們在src/index.js中添加對jQuery的調用代碼,這是為了模擬實際開發中對第三方依賴的調用。如果你的代碼沒有調用依賴的代碼,Webpack找不到入口,也就沒有必要為之導出JS文件了。

index.js的內容改動如下:

...

ReactDOM.render(  
  <AppContainer>
    <BasicExample/>
  </AppContainer>,
  document.getElementById('main')
);
// 添加的代碼
import $ from 'jquery';  
$('body').append('<p>Hello vendor</p>');

if (module.hot) {  
  module.hot.accept();
}

接下來開始真正配置針對第三方依賴的代碼分割,需要用到Webpack內置的優化插件CommonsChunkPlugin。修改webpack.config.js文件中output節點和plugins節點的代碼:

...
entry: {  
  main:[
    'react-hot-loader/patch'
    'webpack-hot-middleware/client',
    './src/index.js'
  ]
},
output: {  
  filename: '[name].js',
  path: path.resolve(__dirname, 'public')
},
...
plugins: [  
    new ExtractTextPlugin('styles.css'),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: function (module) {
        // TODO 對其他第三方依賴也要在這裡進行代碼分割
        return module.context && module.context.indexOf('jquery') !== -1;
      }
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common'
    })
  ]
...

首先修改了輸出的filename,使之根據模塊名稱命名文件。並且配置了入口為main,因此將代碼將導出到main.js而不是原來我們配置的out.js了。

你可能會註意到我兩次用到了CommonsChunkPlugin插件。這樣做是有原因的。我配置了名為vendor的導出項,用於導出第三方依賴的代碼到vendor.js。但是由於Webpack在導出代碼的時候會往代碼裡面加入運行時相關的代碼。這就造成我們的main.js和vendor.js都包含同樣的Webpack運行時相關代碼。所以我配置了第二個名為common的導出項,把這部分的代碼抽離出來存放在common.js中。

代碼切割後的輸出

最後在index.html中引用common.js、vendor.js和main.js。需要註意的是這三個文件之間是有依賴關係的。vendor和main依賴了common,main依賴了vendor。都是調用關係,註意即可。

運行可以看到頁面顯示了jQuery插入的“Hello vendor”了。打開控制台也可以看到網頁請求的內容。

代碼分割後的網路資源

應用代碼

對應用裡面的代碼進行分割就不是通過配置Webpack實現的,而是使用Webpack提供的dynamic import方式實現。Webpack針對React或Vue等框架都有不同的解決方法。我盡在這裡介紹React配合react-router如何實現非同步載入React組件。

首先需要知道的是dynamic import通過返回Promise的方式實現非同步載入功能。

import('./component.js')  
    .then((m) => {
        // 處理非同步載入到的模塊m
    })
    .catch((err) => {
        // 錯誤處理
    });

要註意的是import的參數不能使用變數,簡單原則是至少要讓Webpack知曉應該預先載入哪些內容。這裡的參數除了使用常量之外,還可以使用模板字元串`componentDir/${name}.js`

其實到這裡基本完成代碼切割了,接下來做得就是結合react-router實現按模塊非同步載入。這是跟業務代碼相關的,因此每個人的做法都是不一樣的。所以以下代碼僅供參考。

非同步載入

我參考react-router的例子寫了個簡單的非同步載入組件AsyncLoader.js,內容:

import React from 'react';

export default class AsyncLoader extends React.Component {

  static propTypes = {
    path: React.PropTypes.string.isRequired,
    loading: React.PropTypes.element,
  };

  static defaultProps = {
    path: '',
    loading: <p>Loading...</p>,
    error: <p>Error</p>
  };

  constructor(props) {
    super(props);
    this.state = {
      module: null
    };
  }

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

  componentWillReceiveProps(nextProps) {
    if (nextProps.path !== this.props.path
      || nextProps.error !== this.props.error
      || nextProps.loading !== this.props.loading) {
      this.load(nextProps);
    }
  }

  load(props) {

    this.setState({module: props.loading});

    // TODO:非同步代碼的路徑希望做成可以配置的方式
    import(`./path/${props.path}`)
      .then((m) => {
        let Module = m.default ? m.default : m;
        console.log("module: ", Module);
        this.setState({module: <Module/>});
      }).catch(() => {
        this.setState({module: props.error});
      });
  }

  render() {
    return this.state.module;
  }
}

使用方法

<Route  
    exact path='/book' 
    render={()=><AsyncLoader path={'./components/Book.js'}/>} 
/>

Webpack打包的時候會根據import的參數生成相應的js文件,預設使用id(webpack生成的,從0開始)命名這個文件。

這個過程中我踩了一個坑,這裡提出來供大家參考一下。

問題是這樣的,當前路徑為http://localhost/books時發出非同步載入請求,瀏覽器請求的代碼為正常的http://localhost/0.js;但是當前路徑為http://localhost/books/detail時發出非同步載入請求,瀏覽器請求的是http://localhost/books/0.js,而/books/0.js這個文件是不存在的。

這個問題折磨了我挺長時間的。後來發現解決辦法很簡單,只需要在webpack.config.js文件的output節點中添加publicPath屬性和值就可以了。雖然沒有官方文檔可以參考,但是我測試發現,Webpack生成js的時候,如果沒有指明publicPath則生成的代碼中非同步請求是相對於當前地址開始的;否則是相對於publicPath的值。

我把BasicExample.js中的Counter.js修改成非同步載入,運行結果如下所示:

非同步載入


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 費曼是美國著名物理學家,諾貝爾獎得主,是個非常聰明、正直而且好玩的家伙,他的自傳《別鬧了,費曼先生》我一口氣看完了,精彩程度不遜於一部小說。費曼提出了一種高效學習的方法,即“費曼技巧”,其核心思想是:每當學習一個新東西的時候,想象自己正試著把它介紹給一個對此一無所知的外行人,不使用任何專業術語;如果 ...
  • 使用Vue開發SPA(單頁面應用)估計各位博友都耳熟能詳了,這裡簡單概要一下使用vue-cli快速開發單頁面應用。本博文以window系統為例(雖然用的是Mac操作的,考慮到大多數博友是用window開發),Mac系統類似,不多贅述。 環境要求node 6.0以上(不要安裝7.0,這是beta版) ...
  • 歡迎小伙伴們為 前端導航倉庫 點star https://github.com/pfan123/fr...前端導航平臺訪問 CommonJS 和 AMD 是用於 JavaScript 模塊管理的兩大規範,前者定義的是模塊的同步載入,主要用於 NodeJS ;而後者則是非同步載入,通過 RequireJ ...
  • 工具方法。對函數的統一管理。 jquery2.0.3版本$.Callback()部分的源碼如下: // String to Object options format cache var optionsCache = {}; // Convert String-formatted options i ...
  • 前言 this用法說難不難,有時候函數調用時,往往會搞不清楚this指向誰?那麼,關於this的用法,你知道多少呢? 下麵我來給大家整理一下關於this的詳細分析,希望對大家有所幫助! this指向的規律 this指向的規律往往與函數調用的方式息息相關;this指向的情況,取決於函數調用的方法有哪些 ...
  • JavaScript是一門編程語言,瀏覽器內置了JavaScript語言的解釋器,所以在瀏覽器上按照JavaScript語言的規則編寫相應代碼之,瀏覽器可以解釋並做出相應的處理。一、如何編寫二、變數三、數據類型1、數字(Number)2、字元串(String)3、布爾類型(Boolean)4、數組(... ...
  • 一.ES6簡介 引用阮一峰老師的話:ECMAScript 6.0(以下簡稱 ES6)是 JavaScript 語言的下一代標準,已經在2015年6月正式發佈了。它的目標,是使得 JavaScript 語言可以用來編寫複雜的大型應用程式,成為企業級開發語言。 二.變數 let命令: ES6新增了let ...
  • js中的this是一個頭疼的問題,尤其對於筆者這種初級的菜鳥來講,下麵梳理下this的知識,可以當做是初級進階也好入門也罷,總歸輸出的才是自己掌握的: Js中this不是由詞法作用域決定的 而是調用時動態指定,這就有點麻煩了,如果不能明確知道函數調用時的詞法作用域this的指向也就只能靠猜了,算一卦 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...