{"id":13563464,"url":"https://github.com/Neveryu/vue-ssr-lessons","last_synced_at":"2025-04-03T20:30:54.442Z","repository":{"id":35544310,"uuid":"172858239","full_name":"Neveryu/vue-ssr-lessons","owner":"Neveryu","description":"包教会的 vue 服务端渲染课程","archived":false,"fork":false,"pushed_at":"2022-12-09T16:47:36.000Z","size":1347,"stargazers_count":91,"open_issues_count":61,"forks_count":38,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-27T14:52:42.418Z","etag":null,"topics":["ssr","vue","vue-router","vue-ssr","vue2","vuejs","vuex","webpack"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Neveryu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-02-27T06:38:14.000Z","updated_at":"2024-09-12T08:30:15.000Z","dependencies_parsed_at":"2023-01-15T23:15:44.154Z","dependency_job_id":null,"html_url":"https://github.com/Neveryu/vue-ssr-lessons","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Neveryu%2Fvue-ssr-lessons","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Neveryu%2Fvue-ssr-lessons/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Neveryu%2Fvue-ssr-lessons/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Neveryu%2Fvue-ssr-lessons/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Neveryu","download_url":"https://codeload.github.com/Neveryu/vue-ssr-lessons/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247074380,"owners_count":20879246,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ssr","vue","vue-router","vue-ssr","vue2","vuejs","vuex","webpack"],"created_at":"2024-08-01T13:01:19.569Z","updated_at":"2025-04-03T20:30:53.692Z","avatar_url":"https://github.com/Neveryu.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# 这是一个 vue ssr 的教学课程\n\n\u003e手把手带你学会 vue 的服务端渲染\n\n本课程的内容是基于 vue ssr 的文档来的，课程的结果是完成一个 vue 服务端渲染的实例。\n所以在学习本课程的时候，建议你打开 [vue ssr](https://ssr.vuejs.org/zh/) 的文档，这样有助于理解。\n\n本课程所包含的几个小节，都是**循序渐进**的，**由易到难**，每一个小节都是在前一个小节的基础上，添加新的内容，幅度平缓，易学易懂。\n\n小提示：建议配合着 [vue ssr](https://ssr.vuejs.org/zh/) 的文档来学习本课程。\n\n本课程内容如下：\n\n|【基础篇】|【中级篇】|【附加篇】\n|:---:|:---:|:---:|\n|[Lesson1](#lesson1)|[Lesson4](#lesson4)|[Lesson7](#lesson7)|\n|[Lesson2](#lesson2)|[Lesson5](#lesson5)|\n|[Lesson3](#lesson3)|[Lesson6](#lesson6)|\n\n### 预览 - preview\n\n![](./preview1.png)\n\n**[⬆ 返回顶部](#这是一个-vue-ssr-的教学课程)**\n\n**[➷ 开始学习吧](#lesson1)**\n\n\n--------------\n\n\n\u003cp align=\"center\"\u003e\u003cb\u003e【基础篇】\u003c/b\u003e\u003c/p\u003e\n\n## lesson1\n\n用最简单，明白的方式创建一个 vue 服务端渲染的应用程序。\n``` js\n// 第 1 步：创建一个 Vue 实例\nconst Vue = require('vue')\nconst app = new Vue({\n  template: `\u003cdiv\u003eHello World\u003c/div\u003e`\n})\n\n// 第 2 步：创建一个 renderer\nconst renderer = require('vue-server-renderer').createRenderer()\n\n// 第 3 步：将 Vue 实例渲染为 HTML\n// 在 2.5.0+，如果没有传入回调函数，则会返回 Promise：\nrenderer.renderToString(app).then(html =\u003e {\n  console.log(html)\n  // =\u003e \u003cdiv data-server-rendered=\"true\"\u003eHello World\u003c/div\u003e\n}).catch(err =\u003e {\n  console.error(err)\n})\n```\n课程 1 的运行步骤：\n``` bash\ncd lesson1\nnpm i\nnpm start\n```\n然后会在命令行看到输出内容： \n\n``` html\n\u003cdiv data-server-rendered=\"true\"\u003eHello World\u003c/div\u003e\n```\n\n**[⬆ 返回顶部](#这是一个-vue-ssr-的教学课程)**\n\n-------------------\n\n## lesson2\n\n**内容**：与服务器集成，使用 Express 作为服务器提供服务；\n\n课程 2 的运行步骤：\n``` bash\ncd lesson2\nnpm i\nnpm start\n```\n浏览器访问 [http://localhost:8888](http://localhost:8888)\n\n**[⬆ 返回顶部](#这是一个-vue-ssr-的教学课程)**\n\n-------------------\n\n## lesson3\n\n**内容**：使用一个页面模板 `index.template.html`。\n\n课程 3 的运行步骤：\n``` bash\ncd lesson3\nnpm i\nnpm start\n```\n浏览器访问 [http://localhost:8888](http://localhost:8888)\n\n\u003cp align=\"center\"\u003e\u003cb\u003e本节内容讲解\u003c/b\u003e\u003c/p\u003e\n\n`renderer` 只从应用程序生成 HTML 标记(markup)，如果不提供模板的话，那么生成的 HTML 标记就应该是一个完整的 html 内容。\n\n在这节中，我们提供一个额外的 HTML 模板，作为页面包裹容器，来包裹 `renderer` 生成的 HTML 标记。\n\n同时，模板还支持简单插值。我们可以通过传入一个 “渲染上下文对象” `context`，作为 `renderToString` 函数的第二个参数，来提供插值数据。\n\n**[⬆ 返回顶部](#这是一个-vue-ssr-的教学课程)**\n\n-------------------\n\n\u003cp align=\"center\"\u003e\u003cb\u003e【中级篇】\u003c/b\u003e\u003c/p\u003e\n\n## lesson4\n\n![](./assets/vue-ssr-summarize.png)\n\n与纯客户端开发模式不同，服务端渲染的最终结果是生成 `Client Bundle` 和 `Server Bundle`。\n\n在这一章，我们会添加 `entry-client.js` 和 `entry-server.js` 分别作为客户端和服务端的入口。`app.js` 是通用的入口，供 `entry-client.js` 和 `entry-server.js` 共同使用。所以 `entry-client.js` 和 `entry-server.js` 中都会引入 `app.js`。\n\n课程 4 的运行步骤：\n``` bash\ncd lesson4\nnpm i\nnpm start\n```\n浏览器访问 [http://localhost:8888](http://localhost:8888)\n\n与此同时，这一节中，`webpack` 也要加上了。\n\n【至于 `webpack` 的配置，感兴趣的可以看一下，比较简单。】\n\n**[⬆ 返回顶部](#这是一个-vue-ssr-的教学课程)**\n\n------------------------\n\n## lesson5\n**内容**：这一节，加入 `vue-router` 来实现路由的功能。\n\n课程 5 的运行步骤：\n``` bash\ncd lesson5\nnpm i\nnpm start\n```\n浏览器访问 [http://localhost:8888](http://localhost:8888)\n\n路由 `vue-router` 的添加是比较容易的；新建一个目录 `router` 并创建一个路由实例，然后 `app.js`、`entry-client.js`、`entry-server.js` 有小幅改动。\n\n**[⬆ 返回顶部](#这是一个-vue-ssr-的教学课程)**\n\n------\n\n## lesson6\n**内容**：这一节实现【数据】\n\n\u003cp align=\"center\"\u003e\u003cb\u003e本节内容讲解\u003c/b\u003e\u003c/p\u003e\n\n我们知道，服务端渲染最重要的部分就是“数据”了。既然是服务端渲染，那么就应该是服务端将运算处理后的数据填到应用程序中，然后将整个应用程序返回到前端。所以**在服务端渲染过程之前，需要先预取和解析好这些数据。**\n\n另一个需要关注的问题是在客户端，在挂载 (mount) 到客户端应用程序之前，需要获取到与服务器端应用程序完全相同的数据 - 否则，客户端应用程序会因为使用与服务器端应用程序不同的状态，然后导致混合失败。\n\n【在这里需要好好的思考一下，vue 的服务端渲染是如何实现的？如何理解 “客户端应用程序会因为使用与服务器端应用程序不同的状态，然后导致混合失败” 这句话？第 4 节中的 vue 服务端渲染结构图是否理解清楚了？】\n\n我们在服务端预取和解析数据的时候，将这些数据填充到“状态容器(state container)”中，然后客户端也使用这里面的数据，就可以保证客户端与服务器端拥有相同的状态了。【状态容器如何实现？】\n\n我们使用 vuex 来作为“状态容器”，此外，我们将在 HTML 中序列化(serialize)和内联预置(inline)状态。这样，在挂载(mount)到客户端应用程序之前，可以直接从 store 获取到内联预置(inline)状态。\n\n课程 6 的运行步骤：\n``` bash\ncd lesson6\nnpm i\nnpm start\n```\n浏览器访问 [http://localhost:8888](http://localhost:8888)\n\n本次更新内容较多，详细的说明请看这里 =\u003e [Lesson6](./lesson6)\n\n**[⬆ 返回顶部](#这是一个-vue-ssr-的教学课程)**\n\n\u003cp align=\"center\"\u003e\u003cb\u003e【附加篇】\u003c/b\u003e\u003c/p\u003e\n\n## lesson7\n**内容**：这一节增加了一些额外的辅助工具【`gzip`、缓存、`favicon`、`title`】；然后增加了生产环境的打包和运行命令。\n\n课程 7 的开发运行步骤：\n\n``` bash\ncd lesson7\n\n# install dependencies\nnpm install\n\n# serve in dev mode, with hot reload at localhost:8080\nnpm run dev\n```\n\n课程 7 打包 server 运行步骤：\n\n``` bash\n# build for production（打生产环境的包）\nnpm run build\n\n# serve in production mode（运行生产环境）\nnpm run server\n```\n本次更新内容较多，详细的说明请看这里 =\u003e [Lesson7](./lesson7)\n\n**[⬆ 返回顶部](#这是一个-vue-ssr-的教学课程)**\n\n# 交流\n\n交流学习 QQ 群：685486827，\u003ca target=\"_blank\" href=\"//shang.qq.com/wpa/qunwpa?idkey=32da7a18744756b0d8ffdd05b84999afecb5265dbad0fb119033e122abe803f3\"\u003e一键加群\u003c/a\u003e\n\n\u003cimg src=\"./qq-group.jpg\" alt=\"加入qq群\" width=\"250\"\u003e\n\n**[⬆ 返回顶部](#这是一个-vue-ssr-的教学课程)**\n\n# 额外知识点总结\n\n1、我们在前三节使用的都是 `vue-server-renderer` 中的 `createRenderer` 方法；从第四节开始，我们使用的是 `createBundleRenderer`，所创建的 `bundle renderer`，用法和普通 `renderer` 相同。\n`createBundleRenderer` 接收一个 `server bundle` 生成的特殊 JSON 文件。但是 `bundle renderer` 提供以下优点：\n\n- 内置的 `source map` 支持（在 `webpack` 配置中使用 `devtool: 'source-map'`）\n\n- 在开发环境甚至部署过程中热重载（通过读取更新后的 `bundle`，然后重新创建 `renderer` 实例）\n\n- 关键 `CSS(critical CSS)` 注入（在使用 `*.vue` 文件时）：自动内联在渲染过程中用到的组件所需的CSS。更多细节请查看 CSS 章节。\n\n- 使用 `clientManifest` 进行资源注入：自动推断出最佳的预加载(preload)和预取(prefetch)指令，以及初始渲染所需的代码分割 chunk。\n\n2、 **手动资源注入**\n\n默认情况下，当提供 `template` 渲染选项时，资源注入是自动执行的。但是有时候，你可能需要对资源注入的模板进行更细粒度 (`finer-grained`) 的控制，或者你根本不使用模板。在这种情况下，你可以在创建 `renderer` 并手动执行资源注入时，传入 `inject: false`。  [\u003e\u003e点击查看详情](https://ssr.vuejs.org/zh/guide/build-config.html#%E5%AE%A2%E6%88%B7%E7%AB%AF%E9%85%8D%E7%BD%AE-client-config)\n\n3、 **css提取**\n\n**css提取**可以说是一个很重要的知识点；不光在 vue 的服务端项目中，几乎在任何前端项目中，我们都希望 `css`可以单独提取出来，这样可以对文件进行缓存以及部署 CDN。\n\n【启用 `css` 提取】\n\n\u003e css 提取应该只用于生产环境，这样我们在开发过程中仍然可以热重载\n\n要从 `*.vue` 文件中提取 CSS，可以使用 `vue-loader` 的 `extractCSS` 选项（需要 `vue-loader 12.0.0+`）\n\n``` js\n// webpack.config.js\nconst ExtractTextPlugin = require('extract-text-webpack-plugin')\n\n// CSS 提取应该只用于生产环境\n// 这样我们在开发过程中仍然可以热重载\nconst isProduction = process.env.NODE_ENV === 'production'\n\nmodule.exports = {\n  // ...\n  module: {\n    rules: [\n      {\n        test: /\\.vue$/,\n        loader: 'vue-loader',\n        options: {\n          // enable CSS extraction\n          extractCSS: isProduction\n        }\n      },\n      // ...\n    ]\n  },\n  plugins: isProduction\n    // 确保添加了此插件！\n    ? [new ExtractTextPlugin({ filename: 'common.[chunkhash].css' })]\n    : []\n}\n```\n\n请注意，上述配置仅适用于 `*.vue` 文件中的样式，然而你也可以使用 `\u003cstyle src=\"./foo.css\"\u003e` 将外部 CSS 导入 Vue 组件。\n\n如果你想从 JavaScript 中导入 CSS，例如，`import 'foo.css'`，你需要配置合适的 loader：\n``` js\nmodule.exports = {\n  // ...\n  module: {\n    rules: [\n      {\n        test: /\\.css$/,\n        // 重要：使用 vue-style-loader 替代 style-loader\n        use: isProduction\n          ? ExtractTextPlugin.extract({\n              use: 'css-loader',\n              fallback: 'vue-style-loader'\n            })\n          : ['vue-style-loader', 'css-loader']\n      }\n    ]\n  },\n  // ...\n}\n```\n\n【从依赖模块导入样式】\n\n在使用 CSS 提取 + 使用 `CommonsChunkPlugin` 插件提取 vendor 时，如果提取的 CSS 位于提取的 `vendor chunk` 之中，`extract-text-webpack-plugin` 会遇到问题。为了解决这个问题，请避免在 `vendor chunk` 中包含 CSS 文件。客户端 webpack 配置示例如下：\n``` js\nmodule.exports = {\n  // ...\n  plugins: [\n    // 将依赖模块提取到 vendor chunk 以获得更好的缓存，是很常见的做法。\n    new webpack.optimize.CommonsChunkPlugin({\n      name: 'vendor',\n      minChunks: function (module) {\n        // 一个模块被提取到 vendor chunk 时……\n        return (\n          // 如果它在 node_modules 中\n          /node_modules/.test(module.context) \u0026\u0026\n          // 如果 request 是一个 CSS 文件，则无需外置化提取\n          !/\\.css$/.test(module.request)\n        )\n      }\n    }),\n    // 提取 webpack 运行时和 manifest\n    new webpack.optimize.CommonsChunkPlugin({\n      name: 'manifest'\n    }),\n    // ...\n  ]\n}\n```\n\n**[⬆ 返回顶部](#这是一个-vue-ssr-的教学课程)**\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNeveryu%2Fvue-ssr-lessons","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FNeveryu%2Fvue-ssr-lessons","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNeveryu%2Fvue-ssr-lessons/lists"}