• 路由和代码分割
    • 使用 vue-router 的路由
    • 代码分割

    路由和代码分割

    使用 vue-router 的路由

    你可能已经注意到,我们的服务器代码使用了一个 * 处理程序,它接受任意 URL。这允许我们将访问的 URL 传递到我们的 Vue 应用程序中,然后对客户端和服务器复用相同的路由配置!

    为此,建议使用官方提供的 vue-router。我们首先创建一个文件,在其中创建 router。注意,类似于 createApp,我们也需要给每个请求一个新的 router 实例,所以文件导出一个 createRouter 函数:

    1. // router.js
    2. import Vue from 'vue'
    3. import Router from 'vue-router'
    4. Vue.use(Router)
    5. export function createRouter () {
    6. return new Router({
    7. mode: 'history',
    8. routes: [
    9. // ...
    10. ]
    11. })
    12. }

    然后更新 app.js:

    1. // app.js
    2. import Vue from 'vue'
    3. import App from './App.vue'
    4. import { createRouter } from './router'
    5. export function createApp () {
    6. // 创建 router 实例
    7. const router = createRouter()
    8. const app = new Vue({
    9. // 注入 router 到根 Vue 实例
    10. router,
    11. render: h => h(App)
    12. })
    13. // 返回 app 和 router
    14. return { app, router }
    15. }

    现在我们需要在 entry-server.js 中实现服务器端路由逻辑 (server-side routing logic):

    1. // entry-server.js
    2. import { createApp } from './app'
    3. export default context => {
    4. // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
    5. // 以便服务器能够等待所有的内容在渲染前,
    6. // 就已经准备就绪。
    7. return new Promise((resolve, reject) => {
    8. const { app, router } = createApp()
    9. // 设置服务器端 router 的位置
    10. router.push(context.url)
    11. // 等到 router 将可能的异步组件和钩子函数解析完
    12. router.onReady(() => {
    13. const matchedComponents = router.getMatchedComponents()
    14. // 匹配不到的路由,执行 reject 函数,并返回 404
    15. if (!matchedComponents.length) {
    16. return reject({ code: 404 })
    17. }
    18. // Promise 应该 resolve 应用程序实例,以便它可以渲染
    19. resolve(app)
    20. }, reject)
    21. })
    22. }

    假设服务器 bundle 已经完成构建(请再次忽略现在的构建设置),服务器用法看起来如下:

    1. // server.js
    2. const createApp = require('/path/to/built-server-bundle.js')
    3. server.get('*', (req, res) => {
    4. const context = { url: req.url }
    5. createApp(context).then(app => {
    6. renderer.renderToString(app, (err, html) => {
    7. if (err) {
    8. if (err.code === 404) {
    9. res.status(404).end('Page not found')
    10. } else {
    11. res.status(500).end('Internal Server Error')
    12. }
    13. } else {
    14. res.end(html)
    15. }
    16. })
    17. })
    18. })

    代码分割

    应用程序的代码分割或惰性加载,有助于减少浏览器在初始渲染中下载的资源体积,可以极大地改善大体积 bundle 的可交互时间(TTI - time-to-interactive)。这里的关键在于,对初始首屏而言,”只加载所需”。

    Vue 提供异步组件作为第一类的概念,将其与 webpack 2 所支持的使用动态导入作为代码分割点相结合,你需要做的是:

    1. // 这里进行修改……
    2. import Foo from './Foo.vue'
    3. // 改为这样:
    4. const Foo = () => import('./Foo.vue')

    在 Vue 2.5 以下的版本中,服务端渲染时异步组件只能用在路由组件上。然而在 2.5+ 的版本中,得益于核心算法的升级,异步组件现在可以在应用中的任何地方使用。

    需要注意的是,你仍然需要在挂载 app 之前调用 router.onReady,因为路由器必须要提前解析路由配置中的异步组件,才能正确地调用组件中可能存在的路由钩子。这一步我们已经在我们的服务器入口 (server entry) 中实现过了,现在我们只需要更新客户端入口 (client entry):

    1. // entry-client.js
    2. import { createApp } from './app'
    3. const { app, router } = createApp()
    4. router.onReady(() => {
    5. app.$mount('#app')
    6. })

    异步路由组件的路由配置示例:

    1. // router.js
    2. import Vue from 'vue'
    3. import Router from 'vue-router'
    4. Vue.use(Router)
    5. export function createRouter () {
    6. return new Router({
    7. mode: 'history',
    8. routes: [
    9. { path: '/', component: () => import('./components/Home.vue') },
    10. { path: '/item/:id', component: () => import('./components/Item.vue') }
    11. ]
    12. })
    13. }