From 2d07cd7ed5c2ed8f9b9721d6bed7ba6dac2d2867 Mon Sep 17 00:00:00 2001 From: jlpereira Date: Mon, 24 Apr 2023 21:08:38 -0300 Subject: [PATCH 1/2] Create fake client components for SSR --- src/entry-server.js | 4 ++++ src/ssr/utils/registerFakeClientComponents.js | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/ssr/utils/registerFakeClientComponents.js diff --git a/src/entry-server.js b/src/entry-server.js index 1742ef0..f695cee 100644 --- a/src/entry-server.js +++ b/src/entry-server.js @@ -4,12 +4,16 @@ import { createApp } from './main' import { registerGlobalComponents } from '@/components/globalComponents' import { getActiveHead } from 'unhead' import { renderSSRHead } from '@unhead/ssr' +import { registerFakeClientComponents } from '@/ssr/utils/registerFakeClientComponents' import devalue from '@nuxt/devalue' export async function render(url, manifest) { const { app, router, store } = createApp() + // Register global components, create a fake server components for components that should only render on client side registerGlobalComponents(app) + registerFakeClientComponents(app) + // set the router to the desired URL before rendering await router.push(url) await router.isReady() diff --git a/src/ssr/utils/registerFakeClientComponents.js b/src/ssr/utils/registerFakeClientComponents.js new file mode 100644 index 0000000..91cce62 --- /dev/null +++ b/src/ssr/utils/registerFakeClientComponents.js @@ -0,0 +1,18 @@ +import { defineComponent } from 'vue' +import glob from 'glob' + +export function registerFakeClientComponents(app) { + const filePaths = glob.sync('src/components/**/*.client.vue') + const vueComponent = defineComponent({ + template: '
' + }) + + filePaths.forEach((path) => { + const componentName = path + .split('/') + .pop() + .replace(/\.client.\w+$/, '') + + app.component(componentName, vueComponent) + }) +} From b498400bf7ef7216d7d5956507dc13624f518d03 Mon Sep 17 00:00:00 2001 From: jlpereira Date: Mon, 24 Apr 2023 21:10:12 -0300 Subject: [PATCH 2/2] Add scaffold for bioschema --- package-lock.json | 87 +++++++++++++-------- package.json | 7 +- src/App.vue | 5 +- src/main.js | 17 +++- src/modules/otus/components/CommonNames.vue | 17 ++-- src/modules/otus/helpers/schema.js | 68 ++++++++++++++++ src/modules/otus/store/store.js | 4 +- src/modules/otus/views/Index.vue | 20 ++++- 8 files changed, 174 insertions(+), 51 deletions(-) create mode 100644 src/modules/otus/helpers/schema.js diff --git a/package-lock.json b/package-lock.json index fd365d1..8540de7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "dependencies": { "@geoman-io/leaflet-geoman-free": "^2.14.2", "@nuxt/devalue": "^2.0.0", - "@unhead/ssr": "^1.1.26", "axios": "^1.3.6", "js-yaml": "^4.1.0", "leaflet": "^1.9.3", @@ -21,17 +20,19 @@ }, "devDependencies": { "@tailwindcss/typography": "^0.5.9", + "@unhead/schema-org": "^0.5.0", + "@unhead/ssr": "^1.1.26", "@vitejs/plugin-vue": "^4.1.0", "autoprefixer": "^10.4.14", "compression": "^1.7.4", - "eslint": "^8.38.0", + "eslint": "^8.39.0", "eslint-plugin-vue": "^9.11.0", "express": "^4.18.2", "minimist": "^1.2.8", "postcss": "^8.4.23", "sass": "^1.62.0", "tailwindcss": "^3.3.1", - "vite": "^4.3.0", + "vite": "^4.3.1", "vite-plugin-md": "^0.21.5", "vite-plugin-pages": "^0.29.0" } @@ -447,9 +448,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", - "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -932,6 +933,18 @@ "url": "https://github.com/sponsors/harlan-zw" } }, + "node_modules/@unhead/schema-org": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@unhead/schema-org/-/schema-org-0.5.0.tgz", + "integrity": "sha512-UrO/wUFzGnRjUAYmkEZmgtknTjn9wPHwtXnEIgCxYcTDwOr9fiU/kFYTKBf6OF1VhRWrFAVNTz/fIzLZ1f/Hdw==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "unhead": ">=1.1.9" + } + }, "node_modules/@unhead/shared": { "version": "1.1.26", "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.1.26.tgz", @@ -947,6 +960,7 @@ "version": "1.1.26", "resolved": "https://registry.npmjs.org/@unhead/ssr/-/ssr-1.1.26.tgz", "integrity": "sha512-KYJDGgVNtU2i+NHu17o2zFXqsoLukOFEz81XrWQ8nQdY5+VNjy7IiTLp1dlx3umn1ohZjHySz4LXQCT4zUApSw==", + "dev": true, "dependencies": { "@unhead/schema": "1.1.26", "@unhead/shared": "1.1.26" @@ -2063,15 +2077,15 @@ } }, "node_modules/eslint": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", - "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.38.0", + "@eslint/js": "8.39.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -2081,7 +2095,7 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", + "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.0", "espree": "^9.5.1", "esquery": "^1.4.2", @@ -2141,9 +2155,9 @@ } }, "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -2151,6 +2165,9 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { @@ -4994,9 +5011,9 @@ } }, "node_modules/vite": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.0.tgz", - "integrity": "sha512-JTGFgDh3dVxeGBpuQX04Up+JZmuG6wu9414Ei36vQzaEruY/M4K0AgwtuB2b4HaBgB7R8l+LHxjB0jcgz4d2qQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.1.tgz", + "integrity": "sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==", "dev": true, "dependencies": { "esbuild": "^0.17.5", @@ -5574,9 +5591,9 @@ } }, "@eslint/js": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", - "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", "dev": true }, "@geoman-io/leaflet-geoman-free": { @@ -5964,6 +5981,13 @@ "zhead": "^2.0.4" } }, + "@unhead/schema-org": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@unhead/schema-org/-/schema-org-0.5.0.tgz", + "integrity": "sha512-UrO/wUFzGnRjUAYmkEZmgtknTjn9wPHwtXnEIgCxYcTDwOr9fiU/kFYTKBf6OF1VhRWrFAVNTz/fIzLZ1f/Hdw==", + "dev": true, + "requires": {} + }, "@unhead/shared": { "version": "1.1.26", "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.1.26.tgz", @@ -5976,6 +6000,7 @@ "version": "1.1.26", "resolved": "https://registry.npmjs.org/@unhead/ssr/-/ssr-1.1.26.tgz", "integrity": "sha512-KYJDGgVNtU2i+NHu17o2zFXqsoLukOFEz81XrWQ8nQdY5+VNjy7IiTLp1dlx3umn1ohZjHySz4LXQCT4zUApSw==", + "dev": true, "requires": { "@unhead/schema": "1.1.26", "@unhead/shared": "1.1.26" @@ -6833,15 +6858,15 @@ "dev": true }, "eslint": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", - "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.38.0", + "@eslint/js": "8.39.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -6851,7 +6876,7 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", + "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.0", "espree": "^9.5.1", "esquery": "^1.4.2", @@ -6896,9 +6921,9 @@ } }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -8933,9 +8958,9 @@ "dev": true }, "vite": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.0.tgz", - "integrity": "sha512-JTGFgDh3dVxeGBpuQX04Up+JZmuG6wu9414Ei36vQzaEruY/M4K0AgwtuB2b4HaBgB7R8l+LHxjB0jcgz4d2qQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.1.tgz", + "integrity": "sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==", "dev": true, "requires": { "esbuild": "^0.17.5", diff --git a/package.json b/package.json index 772eac7..838148a 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "dependencies": { "@geoman-io/leaflet-geoman-free": "^2.14.2", "@nuxt/devalue": "^2.0.0", - "@unhead/ssr": "^1.1.26", "axios": "^1.3.6", "js-yaml": "^4.1.0", "leaflet": "^1.9.3", @@ -28,17 +27,19 @@ }, "devDependencies": { "@tailwindcss/typography": "^0.5.9", + "@unhead/schema-org": "^0.5.0", + "@unhead/ssr": "^1.1.26", "@vitejs/plugin-vue": "^4.1.0", "autoprefixer": "^10.4.14", "compression": "^1.7.4", - "eslint": "^8.38.0", + "eslint": "^8.39.0", "eslint-plugin-vue": "^9.11.0", "express": "^4.18.2", "minimist": "^1.2.8", "postcss": "^8.4.23", "sass": "^1.62.0", "tailwindcss": "^3.3.1", - "vite": "^4.3.0", + "vite": "^4.3.1", "vite-plugin-md": "^0.21.5", "vite-plugin-pages": "^0.29.0" } diff --git a/src/App.vue b/src/App.vue index 90164cc..6fa3154 100644 --- a/src/App.vue +++ b/src/App.vue @@ -6,10 +6,9 @@ \ No newline at end of file + diff --git a/src/modules/otus/helpers/schema.js b/src/modules/otus/helpers/schema.js new file mode 100644 index 0000000..2d3fcf0 --- /dev/null +++ b/src/modules/otus/helpers/schema.js @@ -0,0 +1,68 @@ +const { schema_host } = __APP_ENV__ + +const TAXON_RANK = { + species: [ + 'http://rs.tdwg.org/ontology/voc/TaxonRank#Species', + 'http://www.wikidata.org/entity/Q7432' + ], + genus: [ + 'genus', + 'http://rs.tdwg.org/ontology/voc/TaxonRank#Genus', + 'http://www.wikidata.org/entity/Q34740' + ] +} + +function removeEmptyProperties(obj) { + const copyObj = { ...obj } + + for (const key in obj) { + const value = obj[key] + + if (!value || (Array.isArray(value) && !value.length)) { + delete copyObj[key] + } + } + + return copyObj +} + +function makeUrlPath(path) { + return schema_host ? `${schema_host}${path}` : '' +} + +export function defineTaxon({ + id, + childTaxon, + parentTaxon, + taxonRank, + name, + scientificName, + identifier +}) { + return removeEmptyProperties({ + '@type': 'Taxon', + '@id': makeUrlPath(id), + 'dct:conformsTo': { + '@id': 'https://bioschemas.org/profiles/Taxon/0.8-DRAFT' + }, + additionalType: [ + 'dwc:Taxon', + 'http://rs.tdwg.org/ontology/voc/TaxonConcept#TaxonConcept' + ], + name, + childTaxon, + parentTaxon, + scientificName: defineTaxonName(scientificName), + identifier, + taxonRank + }) +} + +function defineTaxonName({ name, author, taxonRank }) { + return removeEmptyProperties({ + '@type': 'TaxonName', + author, + name, + taxonRank + }) +} diff --git a/src/modules/otus/store/store.js b/src/modules/otus/store/store.js index 9f731cb..b9b9277 100644 --- a/src/modules/otus/store/store.js +++ b/src/modules/otus/store/store.js @@ -37,5 +37,5 @@ export const useOtuStore = defineStore('otuStore', { this.images = (await TaxonWorks.getOtuImages(otuId, params)).data } - }, -}) \ No newline at end of file + } +}) diff --git a/src/modules/otus/views/Index.vue b/src/modules/otus/views/Index.vue index c41ab6b..1988553 100644 --- a/src/modules/otus/views/Index.vue +++ b/src/modules/otus/views/Index.vue @@ -66,7 +66,9 @@ import { useRoute, useRouter } from 'vue-router' import { useOtuStore } from '../store/store' import Breadcrumb from '../components/Breadcrumb/Breadcrumb.vue' import TaxaInfo from '../components/TaxaInfo.vue' -import { useHead, createHead } from 'unhead' +import { useHead } from 'unhead' +import { defineWebPage, useSchemaOrg } from '@unhead/schema-org' +import { defineTaxon } from '../helpers/schema.js' //import useChildrenRoutes from '../composables/useChildrenRoutes' @@ -96,9 +98,9 @@ watch( } ) -onMounted(() => { +onMounted(async () => { if (!otu.value || otu.value.id !== Number(route.params.id)) { - loadInitialData() + await loadInitialData() } }) @@ -106,6 +108,18 @@ async function loadInitialData() { store.$reset() await store.loadInit(route.params.id) + useSchemaOrg([ + defineTaxon({ + id: route.fullPath, + name: taxon.value.full_name, + scientificName: { + name: taxon.value.full_name, + author: taxon.value.author, + taxonRank: taxon.value.rank + } + }) + ]) + useHead({ title: `${__APP_ENV__.project_name} - ${taxon.value.full_name}` })