关于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

欢迎纠错

猜你喜欢

转载自blog.csdn.net/u013012391/article/details/83215664
今日推荐