1. 程式人生 > >關於React首屏白屏問題

關於React首屏白屏問題

主要從下面幾個地方優化:

  1. 在HTML內實現Loading狀態或者骨架屏
  2. 去掉外聯 css
  3. 快取基礎框架
  4. 使用動態 polyfill
  5. 使用 SplitChunksPlugin 拆分公共程式碼
  6. 正確地使用 Webpack 4.0 的 Tree Shaking
  7. 使用動態 import,切分頁面程式碼,減小首屏 JS 體積
  8. 編譯到 ES2015+,提高程式碼執行效率,減小體積
  9. 使用 lazyload 和 placeholder 提升載入體驗
------------------------------在HTML內實現Loading狀態或者骨架屏------------------------
1. 普通的處理方式,自定義Loading介面  (可以載入SVG檔案)

2. 使用html-webpack-plugin自動插入loading
var HtmlWebpackPlugin = require('html-webpack-plugin');
var path = require('path');

// 讀取寫好的 loading 態的 html 和 css
var loading = {
    html: fs.readFileSync(path.join(__dirname, './loading.html')),
    css: '<style>' + fs.readFileSync(path.join(__dirname, './loading.css')) + '</style>'
}

var webpackConfig = {
  entry: 'index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'index_bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'xxxx.html',
      template: 'template.html',
      loading: loading
    })
  ]
};
然後在模板中引用即可:
<!DOCTYPE html>
<html lang="en">
    <head>
        <%= htmlWebpackPlugin.options.loading.css %>
    </head>

    <body>
        <div id="root">
            <%= htmlWebpackPlugin.options.loading.html %>
        </div>
    </body>
</html>

3.使用prerender-spa-plugin渲染首屏(參考: https://zhuanlan.zhihu.com/p/31954853)
  在一些比較大型的專案中,Loading 可能本身就是一個 React/Vue 元件,在不做伺服器端渲染的情況下,想把一個已經元件化的 Loading 直接寫入 html 檔案中會很複雜,不過依然有解決辦法。

  prerender-spa-plugin 是一個可以幫你在構建時就生成頁面首屏 html 的一個 webpack 外掛,原理大致如下:
  1).指定 dist 目錄和要渲染的路徑
  2).外掛在 dist 目錄中開啟一個靜態伺服器,並且使用無頭瀏覽器(puppeteer)訪問對應的路徑,執行JS,抓取對應路徑的 html。
  3).把抓到的內容寫入 html,這樣即使沒有做伺服器端渲染,也能達到跟伺服器端渲染幾乎相同的作用(不考慮動態資料的話)
  plugins: [
    new PrerenderSpaPlugin(
      path.join(__dirname, 'dist'),
      [ '/', '/products/1', '/products/2', '/products/3']
    )
  ]
------------------------------去掉外聯CSS------------------------
實際上,webpack 預設就是沒有外鏈 css 的,你什麼都不需要做就可以了。當然如果你的專案之前配置了 extract-text-webpack-plugin 或者 mini-css-extract-plugin 來生成獨立的 css 檔案,直接去掉即可。
------------------------------快取基礎框架--------------------------------------------------
HTTP為我們提供了很好幾種快取的解決方案,不妨總結一下:
1. expires
expires: Thu, 16 May 2019 03:05:59 GMT
在 http 頭中設定一個過期時間,在這個過期時間之前,瀏覽器的請求都不會發出,而是自動從快取中讀取檔案,除非快取被清空,或者強制重新整理。缺陷在於,伺服器時間和使用者端時間可能存在不一致,所以 HTTP/1.1 加入了 cache-control 頭來改進這個問題。

2. cache-control
cache-control: max-age=31536000
設定過期的時間長度(秒),在這個時間範圍內,瀏覽器請求都會直接讀快取。當 expires 和 cache-control 都存在時,expires 的優先順序更高。

3. last-modified / if-modified-since
這是一組請求/相應頭

響應頭:

last-modified: Wed, 16 May 2018 02:57:16 GMT
請求頭:

if-modified-since: Wed, 16 May 2018 05:55:38 GMT
伺服器端返回資源時,如果頭部帶上了 last-modified,那麼資源下次請求時就會把值加入到請求頭 if-modified-since中,伺服器可以對比這個值,確定資源是否發生變化,如果沒有發生變化,則返回 304。

4. etag / if-none-match
這也是一組請求/相應頭

響應頭:

etag: "D5FC8B85A045FF720547BC36FC872550"
請求頭:

if-none-match: "D5FC8B85A045FF720547BC36FC872550"
原理類似,伺服器端返回資源時,如果頭部帶上了 etag,那麼資源下次請求時就會把值加入到請求頭 if-none-match 中,伺服器可以對比這個值,確定資源是否發生變化,如果沒有發生變化,則返回 304。

上面四種快取的優先順序:cache-control > expires > etag > last-modified
------------------------------使用動態Polyfill----------------------------------------------
------------------------------使用SplitChunksPlugin拆分公共程式碼------------------------
Webpack 4 拋棄了原有的 CommonChunksPlugin,換成了更為先進的 SplitChunksPlugin,用於提取公用程式碼。

它們的區別就在於,CommonChunksPlugin 會找到多數模組中都共有的東西,並且把它提取出來(common.js),也就意味著如果你載入了 common.js,那麼裡面可能會存在一些當前模組不需要的東西。

而 SplitChunksPlugin 採用了完全不同的 heuristics 方法,它會根據模組之間的依賴關係,自動打包出很多很多(而不是單個)通用模組,可以保證載入進來的程式碼一定是會被依賴到的。

下面的配置物件代表了splitChunksPlugin的預設行為:
splitChunks: {
    chunks: "async",
    minSize: 30000,
    minChunks: 1,
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    automaticNameDelimiter: '~',
    name: true,
    cacheGroups: {
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
        },
    default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
        }
    }
}

參考:https://segmentfault.com/a/1190000015938570
------------------------------正確地使用Webpack 4.0的Tree Shaking-------------------------
去掉用不到的CSS, 使用purifycss-webpack
const path = require('path');
const glob = require('glob');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const PurifyCSSPlugin = require('purifycss-webpack');
 
module.exports = {
  entry: {...},
  output: {...},
  module: {
    rules: [
      {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract({
          fallbackLoader: 'style-loader',
          loader: 'css-loader'
        })
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin('[name].[contenthash].css'),
    // Make sure this is after ExtractTextPlugin!
    new PurifyCSSPlugin({
      // Give paths to parse for rules. These should be absolute!
      paths: glob.sync(path.join(__dirname, 'app/*.html')),
    })
  ]
};

參考: https://www.npmjs.com/package/purifycss-webpack
----------------------------使用動態import切分頁面程式碼,減小首屏JS體積------------------------
import: 遵循元件化思想, 減少當前JS程式碼的體積,好處就是程式碼易讀,減少渲染,按需載入
--------------------編譯到ES2015+,提高程式碼執行效率,減小體積---------------------------------
遵循ES6語法
------------------------------使用Lazyload和placeholder提升載入體驗------------------------
1. placeholder: 簡單的處理方式, 給標籤設定預設值, 或者說是佔位符

2. Lazyload:懶載入
import React from 'react';
import ReactDOM from 'react-dom';
import LazyLoad from 'react-lazyload';
import MyComponent from './MyComponent';
 
const App = () => {
  return (
    <div className="list">
      <LazyLoad height={200}>
        <img src="tiger.jpg" /> /*
                                  Lazy loading images is supported out of box,
                                  no extra config needed, set `height` for better
                                  experience
                                 */
      </LazyLoad>
      <LazyLoad height={200} once >
                                /* Once this component is loaded, LazyLoad will
                                 not care about it anymore, set this to `true`
                                 if you're concerned about improving performance */
        <MyComponent />
      </LazyLoad>
      <LazyLoad height={200} offset={100}>
                              /* This component will be loaded when it's top
                                 edge is 100px from viewport. It's useful to
                                 make user ignorant about lazy load effect. */
        <MyComponent />
      </LazyLoad>
      <LazyLoad>
        <MyComponent />
      </LazyLoad>
    </div>
  );
};
 
ReactDOM.render(<App />, document.body);

參考: https://www.npmjs.com/package/react-lazyload

歡迎糾錯