zoukankan      html  css  js  c++  java
  • 借助Code Splitting 提升单页面应用性能

    近日的工作集中于一个单页面应用(Single-page application),在项目中尝试了闻名已久的Code splitting,收获极大,特此分享。

    Why we need code splitting

    SPA的客户端路由极大的减少了Server 与 Client端之间的Round trip,在此基础上,我们还可以借助Server Side Rendering 砍掉客户端的初次页面渲染时间(这里是SSR实现的参考链接:ReactAngular2).
    仍然有一个问题普遍存在着:随着应用复杂度/规模的增加,应用初始所加载的文件大小也随之增加。我们可以通过将文件分割成按需加载的chunks来解决这一问题,对于初始页面,只请求他所用到的模块的相关文件,等我们进入新的路由,或者使用到一些复杂的功能模块时,才加载与之相关的chunk。
    借助于webpackreact-router(目前我的应用是基于React开发的),我们可以快速实现这些按需加载的chunks。

    webpack

    Webpack是非常火的一个module bundler,这里是一个很好的入门参考链接。
    我们可以借助代码中定义split point以创建按需加载的chunk。
    使用require.ensure(dependencies, callback)可以加载 CommonJs modules, 使用require(dependencies, callback)加载 AMD modules。webpack会在build过程中检测到这些split points,创建chunks。

    React router

    React router 是一个基于React且非常流行的客户端路由库。
    我们能以plain JavaScript object或者declaratively的形式定义客户端路由。
    Plain JavaScript way:

    let myRoute = {
      path: `${some path}`,
      childRoutes: [
        RouteA,
        RouteB,
        RouteC,
      ]
    }
    

    declaratively way:

    const routes = (
      <Route component={Component}>
        <Route path="pathA" component={ComponentA}/>
        <Route path="pathB" component={ComponentB}/>
      </Route>
    )
    

    React router 可以实现代码的lazy load, 而我们正好可以把split points 定义在这些lazy load code中(参考链接)。

    Code Splitting implement

    below is a demo of create two on demand loaded chunks, chunk A will load once when enter rootUrl/A, chunk B will load once when enter rootUrl/B.
    接下来的代码就是创建按需加载的chunks的例子,chunk A 只有当进入rootUrl/A才会加载,chunk B 只有当进入rootUrl/B才会加载。
    routes

    /* ---            RootRoute            --- */
    ...
    import RouteA from './RouteA'
    import RouteB from './RouteB'
    
    export default {
      path: '/',
      component: App,
      childRoutes: [
        RouteA,
        RouteB,
      ],
      indexRoute: {
        component: Index
      }
    }
    
    /* ---              RouteA              --- */
    ...
    export default {
      path: 'A',
      getComponent(location, cb) {
        require.ensure([], (require) => {
          cb(null, require(`${PathOfRelatedComponent}`))
        }, 'chunkA')
      }
    }
    
    /* ---              RouteB              --- */
    ...
    export default {
      path: 'B',
      getComponent(location, cb) {
        require.ensure([], (require) => {
          cb(null, require(`${PathOfRelatedComponent}`))
        }, 'chunkB')
      }
    }
    

    client side code for client side render

    ...
    import { match, Router } from 'react-router'
    
    const { pathname, search, hash } = window.location
    const location = `${pathname}${search}${hash}`
    
    //use match to trigger the split code to load before rendering.
    match({ routes, location }, () => {
      render(
        <Router routes={routes} history={createHistory()} />,
          document.getElementById('app')
      )
    })
    

    server code for server side rendering

    ...
    app.createServer((req, res) => {
      match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
        if (error)
          writeError('ERROR!', res)
        else if (redirectLocation)
          redirect(redirectLocation, res)
        else if (renderProps)
          renderApp(renderProps, res)
        else
          writeNotFound(res)
    }).listen(PORT)
    
    function renderApp(props, res) {
      const markup = renderToString(<RoutingContext {...props}/>)
      const html = createPage(markup)
      write(html, 'text/html', res)
    }
    
    export function createPage(html) {
      return `
      <!doctype html>
      <html>
        <head>
          <meta charset="utf-8"/>
          <title>My Universal App</title>
        </head>
        <body>
          <div id="app">${html}</div>
          <script src="/__build__/main.js"></script>
        </body>
      </html>
      `
    }
    

    实现中可能会遇到的坑

    取决于你是如何写自己的模块的,你可能会遇到这个错误:React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components). Check the render method of RoutingContext.require()之后加一个.default即可。
    如果你收到了这样的错误提示:require.ensure is not function, 增加一个polyfill即可: if (typeof require.ensure !== 'function') require.ensure = (d, c) => c(require),在Server端使用require来代替require.ensure.

    谢谢,希望能指正我的错误!

    最后附一张目前项目的chunks图:

  • 相关阅读:
    进程,线程,协程,异步IO知识点
    Socket网络编程知识点
    面向对象编程知识点
    Zabbix系列之七——添加磁盘IO监测
    WARNING: 'aclocal-1.14' is missing on your system.
    tomcat的catalina.out日志按自定义时间日式进行分割
    Plugin with id 'com.novoda.bintray-release' not found.的解决方案
    MaterialCalendarDialog【Material样式的日历对话框】
    导入项目报错【Minimum supported Gradle version is 3.3. Current version is 2.14.1】
    通过Calendar简单解析Date日期,获取年、月、日、星期的数值
  • 原文地址:https://www.cnblogs.com/E-WALKER/p/5166623.html
Copyright © 2011-2022 走看看