国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

詳解Vue SSR( Vue2 + Koa2 + Webpack4)配置指南

2019-11-19 12:31:57
字體:
來源:轉載
供稿:網友

正如Vue官方所說,SSR配置適合已經熟悉 Vue, webpack 和 Node.js 開發的開發者閱讀。請先移步ssr.vuejs.org 了解手工進行SSR配置的基本內容。

從頭搭建一個服務端渲染的應用是相當復雜的。如果您有SSR需求,對Webpack及Koa不是很熟悉,請直接使用NUXT.js

本文所述內容示例在 Vue SSR Koa2 腳手架 : https://github.com/yi-ge/Vue-SSR-Koa2-Scaffold

我們以撰寫本文時的最新版:Vue 2,Webpack 4,Koa 2為例。

特別說明

此文描述的是API與WEB同在一個項目的情況下進行的配置,且API、SSR Server、Static均使用了同一個Koa示例,目的是闡述配置方法,所有的報錯顯示在一個終端,方便調試。

初始化項目

git inityarn inittouch .gitignore

在 .gitignore 文件,將常見的目錄放于其中。

.DS_Storenode_modules# 編譯后的文件以下兩個目錄/dist/web/dist/api# Log filesnpm-debug.log*yarn-debug.log*yarn-error.log*# Editor directories and files.idea.vscode*.suo*.ntvs**.njsproj*.sln*.sw*

根據經驗來預先添加肯定會用到的依賴項:

echo "yarn add cross-env # 跨平臺的環境變量設置工具 koa koa-body # 可選,推薦 koa-compress # 壓縮數據 compressible # https://github.com/jshttp/compressible axios # 此項目作為API請求工具 es6-promise  vue vue-router # vue 路由 注意,SSR必選 vuex # 可選,但推薦使用,本文基于此做Vuex在SSR的優化 vue-template-compiler vue-server-renderer # 關鍵 lru-cache # 配合上面一個插件緩存數據 vuex-router-sync" | sed 's/#[[:space:]].*//g' | tr '/n' ' ' | sed 's/[ ][ ]*/ /g' | bashecho "yarn add -D webpack webpack-cli webpack-dev-middleware # 關鍵 webpack-hot-middleware # 關鍵 webpack-merge # 合并多個Webpack配置文件的配置 webpack-node-externals # 不打包node_modules里面的模塊 friendly-errors-webpack-plugin # 顯示友好的錯誤提示插件 case-sensitive-paths-webpack-plugin # 無視路徑大小寫插件 copy-webpack-plugin # 用于拷貝文件的Webpack插件 mini-css-extract-plugin # CSS壓縮插件 chalk # console著色 @babel/core # 不解釋 babel-loader @babel/plugin-syntax-dynamic-import # 支持動態import @babel/plugin-syntax-jsx # 兼容JSX寫法 babel-plugin-syntax-jsx # 不重復,必須的 babel-plugin-transform-vue-jsx babel-helper-vue-jsx-merge-props @babel/polyfill @babel/preset-env file-loader json-loader url-loader css-loader vue-loader vue-style-loader vue-html-loader" | sed 's/#[[:space:]].*//g' | tr '/n' ' ' | sed 's/[ ][ ]*/ /g' | bash

現在的npm模塊命名越來越語義化,基本上都是見名知意。關于Eslint以及Stylus、Less等CSS預處理模塊我沒有添加,其不是本文研究的重點,況且既然您在閱讀本文,這些配置相信早已不在話下了。

效仿 electorn 分離main及renderer,在 src 中創建 api 及 web 目錄。效仿 vue-cli ,在根目錄下創建 public 目錄用于存放根目錄下的靜態資源文件。

|-- public # 靜態資源|-- src |-- api # 后端代碼 |-- web # 前端代碼

譬如 NUXT.js ,前端服務器代理API進行后端渲染,我們的配置可以選擇進行一層代理,也可以配置減少這層代理,直接返回渲染結果。通常來說,SSR的服務器端渲染只渲染首屏,因此API服務器最好和前端服務器在同一個內網。

配置 package.json 的 scripts :

"scripts": { "serve": "cross-env NODE_ENV=development node config/server.js", "start": "cross-env NODE_ENV=production node config/server.js"}
  • yarn serve : 啟動開發調試
  • yarn start : 運行編譯后的程序
  • config/app.js 導出一些常見配置:
module.exports = { app: { port: 3000, // 監聽的端口 devHost: 'localhost', // 開發環境下打開的地址,監聽了0.0.0.0,但是不是所有設備都支持訪問這個地址,用127.0.0.1或localhost代替 open: true // 是否打開瀏覽器 }}

配置SSR

我們以Koa作為調試和實際運行的服務器框架, config/server.js :

const path = require('path')const Koa = req uire('koa')const koaCompress = require('koa-compress')const compressible = require('compressible')const koaStatic = require('./koa/static')const SSR = require('./ssr')const conf = require('./app')const isProd = process.env.NODE_ENV === 'production'const app = new Koa()app.use(koaCompress({ // 壓縮數據 filter: type => !(/event/-stream/i.test(type)) && compressible(type) // eslint-disable-line}))app.use(koaStatic(isProd ? path.resolve(__dirname, '../dist/web') : path.resolve(__dirname, '../public'), { maxAge: 30 * 24 * 60 * 60 * 1000})) // 配置靜態資源目錄及過期時間// vue ssr處理,在SSR中處理APISSR(app).then(server => { server.listen(conf.app.port, '0.0.0.0', () => { console.log(`> server is staring...`) })})

上述文件我們根據是否是開發環境,配置了對應的靜態資源目錄。需要說明的是,我們約定編譯后的API文件位于 dist/api ,前端文件位于 dist/web 。

參考 koa-static 實現靜態資源的處理, config/koa/static.js :

'use strict'/** * From koa-static */const { resolve } = require('path')const assert = require('assert')const send = require('koa-send')/** * Expose `serve()`. */module.exports = serve/** * Serve static files from `root`. * * @param {String} root * @param {Object} [opts] * @return {Function} * @api public */function serve (root, opts) { opts = Object.assign({}, opts) assert(root, 'root directory is required to serve files') // options opts.root = resolve(root) if (opts.index !== false) opts.index = opts.index || 'index.html' if (!opts.defer) { return async function serve (ctx, next) {  let done = false  if (ctx.method === 'HEAD' || ctx.method === 'GET') {  if (ctx.path === '/' || ctx.path === '/index.html') { // exclude index.html file   await next()   return  }  try {   done = await send(ctx, ctx.path, opts)  } catch (err) {   if (err.status !== 404) {   throw err   }  }  }  if (!done) {  await next()  } } } return async function serve (ctx, next) { await next() if (ctx.method !== 'HEAD' && ctx.method !== 'GET') return // response is already handled if (ctx.body != null || ctx.status !== 404) return // eslint-disable-line try {  await send(ctx, ctx.path, opts) } catch (err) {  if (err.status !== 404) {  throw err  } } }}

我們可以看到, koa-static 僅僅是對 koa-send 進行了簡單封裝( yarn add koa-send )。接下來就是重頭戲SSR相關的配置了, config/ssr.js :

const fs = require('fs')const path = require('path')const chalk = require('chalk')const LRU = require('lru-cache')const { createBundleRenderer} = require('vue-server-renderer')const isProd = process.env.NODE_ENV === 'production'const setUpDevServer = require('./setup-dev-server')const HtmlMinifier = require('html-minifier').minifyconst pathResolve = file => path.resolve(__dirname, file)module.exports = app => { return new Promise((resolve, reject) => { const createRenderer = (bundle, options) => {  return createBundleRenderer(bundle, Object.assign(options, {  cache: LRU({   max: 1000,   maxAge: 1000 * 60 * 15  }),  basedir: pathResolve('../dist/web'),  runInNewContext: false  })) } let renderer = null if (isProd) {  // prod mode  const template = HtmlMinifier(fs.readFileSync(pathResolve('../public/index.html'), 'utf-8'), {  collapseWhitespace: true,  removeAttributeQuotes: true,  removeComments: false  })  const bundle = require(pathResolve('../dist/web/vue-ssr-server-bundle.json'))  const clientManifest = require(pathResolve('../dist/web/vue-ssr-client-manifest.json'))  renderer = createRenderer(bundle, {  template,  clientManifest  }) } else {  // dev mode  setUpDevServer(app, (bundle, options, apiMain, apiOutDir) => {  try {   const API = eval(apiMain).default // eslint-disable-line   const server = API(app)   renderer = createRenderer(bundle, options)   resolve(server)  } catch (e) {   console.log(chalk.red('/nServer error'), e)  }  }) } app.use(async (ctx, next) => {  if (!renderer) {  ctx.type = 'html'  ctx.body = 'waiting for compilation... refresh in a moment.'  next()  return  }  let status = 200  let html = null  const context = {  url: ctx.url,  title: 'OK'  }  if (/^//api/.test(ctx.url)) { // 如果請求以/api開頭,則進入api部分進行處理。  next()  return  }  try {  status = 200  html = await renderer.renderToString(context)  } catch (e) {  if (e.message === '404') {   status = 404   html = '404 | Not Found'  } else {   status = 500   console.log(chalk.red('/nError: '), e.message)   html = '500 | Internal Server Error'  }  }  ctx.type = 'html'  ctx.status = status || ctx.status  ctx.body = html  next() }) if (isProd) {  const API = require('../dist/api/api').default  const server = API(app)  resolve(server) } })}

這里新加入了 html-minifier 模塊來壓縮生產環境的 index.html 文件( yarn add html-minifier )。其余配置和官方給出的差不多,不再贅述。只不過Promise返回的是 require('http').createServer(app.callback()) (詳見源碼)。這樣做的目的是為了共用一個koa2實例。此外,這里攔截了 /api 開頭的請求,將請求交由API Server進行處理(因在同一個Koa2實例,這里直接next()了)。在 public 目錄下必須存在 index.html 文件:

<!DOCTYPE html><html lang="zh-cn"><head> <title>{{ title }}</title> ...</head><body> <!--vue-ssr-outlet--></body></html>

開發環境中,處理數據的核心在 config/setup-dev-server.js 文件:

const fs = require('fs')const path = require('path')const chalk = require('chalk')const MFS = require('memory-fs')const webpack = require('webpack')const chokidar = require('chokidar')const apiConfig = require('./webpack.api.config')const serverConfig = require('./webpack.server.config')const webConfig = require('./webpack.web.config')const webpackDevMiddleware = require('./koa/dev')const webpackHotMiddleware = require('./koa/hot')const readline = require('readline')const conf = require('./app')const { hasProjectYarn, openBrowser} = require('./lib')const readFile = (fs, file) => { try { return fs.readFileSync(path.join(webConfig.output.path, file), 'utf-8') } catch (e) {}}module.exports = (app, cb) => { let apiMain, bundle, template, clientManifest, serverTime, webTime, apiTime const apiOutDir = apiConfig.output.path let isFrist = true const clearConsole = () => { if (process.stdout.isTTY) {  // Fill screen with blank lines. Then move to 0 (beginning of visible part) and clear it  const blank = '/n'.repeat(process.stdout.rows)  console.log(blank)  readline.cursorTo(process.stdout, 0, 0)  readline.clearScreenDown(process.stdout) } } const update = () => { if (apiMain && bundle && template && clientManifest) {  if (isFrist) {  const url = 'http://' + conf.app.devHost + ':' + conf.app.port  console.log(chalk.bgGreen.black(' DONE ') + ' ' + chalk.green(`Compiled successfully in ${serverTime + webTime + apiTime}ms`))  console.log()  console.log(` App running at: ${chalk.cyan(url)}`)  console.log()  const buildCommand = hasProjectYarn(process.cwd()) ? `yarn build` : `npm run build`  console.log(` Note that the development build is not optimized.`)  console.log(` To create a production build, run ${chalk.cyan(buildCommand)}.`)  console.log()  if (conf.app.open) openBrowser(url)  isFrist = false  }  cb(bundle, {  template,  clientManifest  }, apiMain, apiOutDir) } } // server for api apiConfig.entry.app = ['webpack-hot-middleware/client?path=/__webpack_hmr&timeout=2000&reload=true', apiConfig.entry.app] apiConfig.plugins.push( new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin() ) const apiCompiler = webpack(apiConfig) const apiMfs = new MFS() apiCompiler.outputFileSystem = apiMfs apiCompiler.watch({}, (err, stats) => { if (err) throw err stats = stats.toJson() if (stats.errors.length) return console.log('api-dev...') apiMfs.readdir(path.join(__dirname, '../dist/api'), function (err, files) {  if (err) {  return console.error(err)  }  files.forEach(function (file) {  console.info(file)  }) }) apiMain = apiMfs.readFileSync(path.join(apiConfig.output.path, 'api.js'), 'utf-8') update() }) apiCompiler.plugin('done', stats => { stats = stats.toJson() stats.errors.forEach(err => console.error(err)) stats.warnings.forEach(err => console.warn(err)) if (stats.errors.length) return apiTime = stats.time // console.log('web-dev') // update() }) // web server for ssr const serverCompiler = webpack(serverConfig) const mfs = new MFS() serverCompiler.outputFileSystem = mfs serverCompiler.watch({}, (err, stats) => { if (err) throw err stats = stats.toJson() if (stats.errors.length) return // console.log('server-dev...') bundle = JSON.parse(readFile(mfs, 'vue-ssr-server-bundle.json')) update() }) serverCompiler.plugin('done', stats => { stats = stats.toJson() stats.errors.forEach(err => console.error(err)) stats.warnings.forEach(err => console.warn(err)) if (stats.errors.length) return serverTime = stats.time }) // web webConfig.entry.app = ['webpack-hot-middleware/client?path=/__webpack_hmr&timeout=2000&reload=true', webConfig.entry.app] webConfig.output.filename = '[name].js' webConfig.plugins.push( new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin() ) const clientCompiler = webpack(webConfig) const devMiddleware = webpackDevMiddleware(clientCompiler, { // publicPath: webConfig.output.publicPath, stats: { // or 'errors-only'  colors: true }, reporter: (middlewareOptions, options) => {  const { log, state, stats } = options  if (state) {  const displayStats = (middlewareOptions.stats !== false)  if (displayStats) {   if (stats.hasErrors()) {   log.error(stats.toString(middlewareOptions.stats))   } else if (stats.hasWarnings()) {   log.warn(stats.toString(middlewareOptions.stats))   } else {   log.info(stats.toString(middlewareOptions.stats))   }  }  let message = 'Compiled successfully.'  if (stats.hasErrors()) {   message = 'Failed to compile.'  } else if (stats.hasWarnings()) {   message = 'Compiled with warnings.'  }  log.info(message)  clearConsole()  update()  } else {  log.info('Compiling...')  } }, noInfo: true, serverSideRender: false }) app.use(devMiddleware) const templatePath = path.resolve(__dirname, '../public/index.html') // read template from disk and watch template = fs.readFileSync(templatePath, 'utf-8') chokidar.watch(templatePath).on('change', () => { template = fs.readFileSync(templatePath, 'utf-8') console.log('index.html template updated.') update() }) clientCompiler.plugin('done', stats => { stats = stats.toJson() stats.errors.forEach(err => console.error(err)) stats.warnings.forEach(err => console.warn(err)) if (stats.errors.length) return clientManifest = JSON.parse(readFile(  devMiddleware.fileSystem,  'vue-ssr-client-manifest.json' )) webTime = stats.time }) app.use(webpackHotMiddleware(clientCompiler))}

由于篇幅限制, koa 及 lib 目錄下的文件參考示例代碼。其中 lib 下的文件均來自 vue-cli ,主要用于判斷用戶是否使用 yarn 以及在瀏覽器中打開URL。 這時,為了適應上述功能的需要,需添加以下模塊(可選):

yarn add memory-fs chokidar readlineyarn add -D opn execa

通過閱讀 config/setup-dev-server.js 文件內容,您將發現此處進行了三個webpack配置的處理。

Server for API // 用于處理`/api`開頭下的API接口,提供非首屏API接入的能力Web server for SSR // 用于服務器端對API的代理請求,實現SSRWEB // 進行常規靜態資源的處理

Webpack 配置

|-- config |-- webpack.api.config.js // Server for API |-- webpack.base.config.js // 基礎Webpack配置 |-- webpack.server.config.js // Web server for SSR |-- webpack.web.config.js // 常規靜態資源

由于Webpack的配置較常規Vue項目以及Node.js項目并沒有太大區別,不再一一贅述,具體配置請翻閱源碼。

值得注意的是,我們為API和WEB指定了別名:

alias: { '@': path.join(__dirname, '../src/web'), '~': path.join(__dirname, '../src/api'), 'vue$': 'vue/dist/vue.esm.js'},

此外, webpack.base.config.js 中設定編譯時拷貝 public 目錄下的文件到 dist/web 目錄時并不包含 index.html 文件。

編譯腳本:

"scripts": { ... "build": "rimraf dist && npm run build:web && npm run build:server && npm run build:api", "build:web": "cross-env NODE_ENV=production webpack --config config/webpack.web.config.js --progress --hide-modules", "build:server": "cross-env NODE_ENV=production webpack --config config/webpack.server.config.js --progress --hide-modules", "build:api": "cross-env NODE_ENV=production webpack --config config/webpack.api.config.js --progress --hide-modules"},

執行 yarn build 進行編譯。編譯后的文件存于 /dist 目錄下。正式環境請盡量分離API及SSR Server。

測試

執行 yarn serve (開發)或 yarn start (編譯后)命令,訪問 http://localhost:3000 。

通過查看源文件可以看到,首屏渲染結果是這樣的:

 ~ curl -s http://localhost:3000/ | grep Hello <div id="app" data-server-rendered="true"><div>Hello World SSR</div></div>

至此,Vue SSR配置完成。希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 民县| 黄梅县| 奉贤区| 门头沟区| 固安县| 安顺市| 从化市| 旬阳县| 台前县| 汉源县| 莎车县| 望都县| 沧州市| 富民县| 登封市| 韶关市| 行唐县| 黎平县| 南宁市| 固始县| 塘沽区| 焦作市| 巴里| 建始县| 邢台市| 诸城市| 永济市| 锡林郭勒盟| 广东省| 金秀| 深州市| 陇西县| 囊谦县| 太原市| 砚山县| 土默特左旗| 庆元县| 扶绥县| 司法| 凉城县| 怀仁县|