gedev
@gedev
сисадмин-энтузиаст

Как обработать HTML ответ от API?

Я пишу небольшое приложение, которое состоит из API на Python и SPA фронтенда на Vue.js. Приложение будет показывать каталог статей. Статьи в формате Markdown и возможно на других языках разметки.

Считаю, что имеет смысл конвертировать статью в HTML только один раз — в момент сохранения статьи в редакторе. Обычные запросы к странице не должны приводить к перегенерации HTML. Предполагаю, что HTML будет лежать в базе, но также рассматриваю хранение этого HTML на диске, но хз как с этим потом работать. Важное замечание: статья в формате HTML не содержит весь DOM, его мне далее надо встроить в шаблон.

Итого получается, что:
- Пользователь открывает страницу.
- Фронт идёт в API и спрашивает контент, который надо отобразить.
- API смотрит в БД и возвращает HTML в каком-то виде (экранировать и впихнуть в JSON??).
- Vue.js отрисовывает страницу с контентом.

Нет ясности в вопросе того как такие задачи обычно решаются, буду рад любым подсказкам :з Пока кажется, что я изобретаю что-то очень страшное.
  • Вопрос задан
  • 140 просмотров
Решения вопроса 1
firedragon
@firedragon
Не джун-мидл-сеньор, а трус-балбес-бывалый.
Для рендеринга сырого html используйте v-html

<template>
  <div v-html="legacySystemHTML">
  </div>
</template>

<script>
export default {
  data() {
    return {
      // Just like JSX! Oh wait...
      legacySystemHTML: `
        <FONT color="#faddad" size="-2">
          <MARQUEE>Please enter your name to continue.</MARQUEE>
          ...
        </FONT>
      `
    }
  }
}
</script>


Обязательно проверяйте приходящий код, что бы не было CSRF
https://vuejsexamples.com/tag/editor/
Далее на странице обязательно заполняйте теги meta title meta description и микроразметку для соцсетей.
Все это нужно делать на основе текста статьи.
Для "тупых" поисковиков желательно сделать пререндер страниц, хотя бы самых новых.

вот один из моих проектов

package.json
{
  "name": "eggclodapp",
  "version": "2.0.1",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve --verbosity silent",
    "dev": "yarn serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "postversion": "node scripts/postversion.js",
    "start": "yarn dev"
  },
  "dependencies": {
    "@dreysolano/prerender-spa-plugin": "^1.0.3",
    "axios": "^0.27.2",
    "moment": "^2.29.3",
    "uuid": "^8.3.2",
    "vee-validate": "3.4.14",
    "vue": "^2.6.14",
    "vue-meta": "^2.4.0",
    "vue-router": "^3.5.2",
    "vue-top-progress": "^0.7.0",
    "vuetify": "^2.5.8",
    "vuex": "3"
  },
  "devDependencies": {
    "@babel/core": "^7.15.4",
    "@fortawesome/fontawesome-free": "^6.1.1",
    "@vue/cli-plugin-babel": "^4.5.13",
    "@vue/cli-plugin-router": "~4.5.13",
    "@vue/cli-service": "^4.5.13",
    "@vue/eslint-config-standard": "^6.1.0",
    "@vuetify/vue-cli-plugin-preset-base": "~0.3.2",
    "archiver": "^5.3.0",
    "babel-core": "7.0.0-bridge.0",
    "babel-eslint": "^10.1.0",
    "dotenv": "^8.2.0",
    "eslint": "^6.8.0",
    "eslint-config-standard": "^16.0.2",
    "eslint-config-vuetify": "^0.6.1",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^4.3.1",
    "eslint-plugin-standard": "^5.0.0",
    "eslint-plugin-vue": "^7.9.0",
    "eslint-plugin-vuetify": "^1.0.0-beta.7",
    "lodash": "^4.17.21",
    "open": "^7.0.3",
    "sass": "~1.32.0",
    "sass-loader": "^10.0.0",
    "shelljs": "^0.8.4",
    "vue-cli-plugin-vuetify": "~2.4.2",
    "vue-template-compiler": "^2.6.14",
    "vuetify-loader": "^1.7.3",
    "webfontloader": "^1.6.28",
    "webpack": "^5.52.0"
  }
}


vue.config.js

const path = require('path')
const PrerenderSPAPlugin = require('@dreysolano/prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
const isDev = process.env.NODE_ENV !== 'production'
const plugins = isDev
  ? []
  : [
      new PrerenderSPAPlugin({
        // Required - The path to the webpack-outputted app to prerender.
        staticDir: path.join(__dirname, 'dist'),

        // Optional - The path your rendered app should be output to.
        // (Defaults to staticDir.)
        // outputDir: path.join(__dirname, 'prerendered'),

        // Optional - The location of index.html
        indexPath: path.join(__dirname, 'dist', 'index.html'),

        // Required - Routes to render.
        routes: ['/', '/about', '/description', '/price', '/about'],

        // Optional - Allows you to customize the HTML and output path before
        // writing the rendered contents to a file.
        // renderedRoute can be modified and it or an equivelant should be returned.
        // renderedRoute format:
        // {
        //   route: String, // Where the output file will end up (relative to outputDir)
        //   originalRoute: String, // The route that was passed into the renderer, before redirects.
        //   html: String, // The rendered HTML for this route.
        //   outputPath: String // The path the rendered HTML will be written to.
        // }
        postProcess (renderedRoute) {
          // Ignore any redirects.
          renderedRoute.route = renderedRoute.originalRoute
          // Basic whitespace removal. (Don't use this in production.)
          // renderedRoute.html = renderedRoute.html.split(/>[\s]+</gim).join('><');
          // Remove /index.html from the output path if the dir name ends with a .html file extension.
          // For example: /dist/dir/special.html/index.html -> /dist/dir/special.html
          if (renderedRoute.route.endsWith('.html')) {
            renderedRoute.outputPath = path.join(
              __dirname,
              'dist',
              renderedRoute.route,
            )
          }

          return renderedRoute
        },

        // Optional - Uses html-minifier (https://github.com/kangax/html-minifier)
        // To minify the resulting HTML.
        // Option reference: https://github.com/kangax/html-minifier#options-quick-reference
        minify: {
          collapseBooleanAttributes: true,
          collapseWhitespace: true,
          decodeEntities: true,
          keepClosingSlash: true,
          sortAttributes: true,
        },

        // Server configuration options.
        server: {
          // Normally a free port is autodetected, but feel free to set this if needed.
          port: 8001,
        },

        // The actual renderer to use. (Feel free to write your own)
        // Available renderers: https://github.com/Tribex/prerenderer/tree/master/renderers
        renderer: new Renderer({
          // Optional - The name of the property to add to the window object with the contents of `inject`.
          injectProperty: '__PRERENDER_INJECTED',
          // Optional - Any values you'd like your app to have access to via `window.injectProperty`.
          inject: {
            foo: 'bar',
            prerender: false,
          },

          // Optional - defaults to 0, no limit.
          // Routes are rendered asynchronously.
          // Use this to limit the number of routes rendered in parallel.
          maxConcurrentRoutes: 4,

          // Optional - Wait to render until the specified event is dispatched on the document.
          // eg, with `document.dispatchEvent(new Event('custom-render-trigger'))`
          // renderAfterDocumentEvent: 'custom-render-trigger',

          // Optional - Wait to render until the specified element is detected using `document.querySelector`
          renderAfterElementExists: '#app',

          // Optional - Wait to render until a certain amount of time has passed.
          // NOT RECOMMENDED
          renderAfterTime: 5000, // Wait 5 seconds.

          // Other puppeteer options.
          // (See here: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions)
          headless: true, // Display the browser window when rendering. Useful for debugging.
        }),
      }),
    ]

module.exports = {

  devServer: {
    disableHostCheck: true,
    progress: false,
    before () { // Output the same message as the react dev server to get the Spa middleware working with vue.
      console.info('Starting the development server...')
    },
  },
  transpileDependencies: ['vuetify'],

  configureWebpack: {
    plugins: [...plugins],
  },
}
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы