近日的工做集中于一个单页面应用(Single-page application),在项目中尝试了闻名已久的Code splitting,收获极大,特此分享。javascript
SPA的客户端路由极大的减小了Server 与 Client端之间的Round trip,在此基础上,咱们还能够借助Server Side Rendering 砍掉客户端的初次页面渲染时间(这里是SSR实现的参考连接:React,Angular2).
仍然有一个问题广泛存在着:随着应用复杂度/规模的增长,应用初始所加载的文件大小也随之增长。咱们能够经过将文件分割成按需加载的chunks来解决这一问题,对于初始页面,只请求他所用到的模块的相关文件,等咱们进入新的路由,或者使用到一些复杂的功能模块时,才加载与之相关的chunk。
借助于webpack与react-router(目前个人应用是基于React开发的),咱们能够快速实现这些按需加载的chunks。html
Webpack是很是火的一个module bundler,这里是一个很好的入门参考连接。
咱们能够借助代码中定义split point
以建立按需加载的chunk。
使用require.ensure(dependencies, callback)
能够加载 CommonJs modules, 使用require(dependencies, callback)
加载 AMD modules。webpack会在build过程当中检测到这些split points,建立chunks。java
React router 是一个基于React且很是流行的客户端路由库。
咱们能以plain JavaScript object或者declaratively的形式定义客户端路由。
Plain JavaScript way:react
let myRoute = { path: `${some path}`, childRoutes: [ RouteA, RouteB, RouteC, ] }
declaratively way:webpack
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中(参考连接)。git
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才会加载。
routesgithub
/* --- 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 renderweb
... 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 renderingreact-router
... 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.app
谢谢,但愿能指正个人错误!
最后附一张目前项目的chunks图: