UNPKG

@micro-app/plugin-vuepress

Version:
217 lines (198 loc) 8.01 kB
const { POWERBY_NAME, RSS_FILENAME } = require('../config'); const moment = require('moment'); const defaultOptions = { siteTitle: ($page, $site) => $page.localeConfig.title || $site.title, title: ($page, $site) => $page.title || $page.localeConfig.title || $site.title, description: ($page, $site) => { const desc = $page.frontmatter.description || $page.frontmatter.summary; if (!desc && !$page.frontmatter.home) { const removeMd = require('remove-markdown'); const strippedContent = $page._strippedContent; if (strippedContent) { return removeMd( strippedContent .trim() .replace(/^#+\s+(.*)/igm, '') .replace(/:::/igm, '') .replace(/\s+/igm, ' ') .trim() ).slice(0, 200); } } const _desc = desc || $page.localeConfig.description || $site.themeConfig.description || $site.description; return _desc && _desc.trim(); }, keywords: ($page, $site) => { const categories = [].concat($page.frontmatter.categories || []); const tags = [].concat($page.frontmatter.tags || []); const keys = [].concat(categories, tags); if (keys.length) { return keys.join(','); } return $page.frontmatter.keywords || $page.localeConfig.keywords || $site.themeConfig.keywords || $site.keywords; }, author: ($page, $site) => { const name = $page.frontmatter.author || $page.localeConfig.author || $site.themeConfig.author || $site.title; return { name, }; }, tags: $page => $page.frontmatter.tags, twitterCard: () => 'summary_large_image', type: ($page, $site) => { const type = $page.type || $site.type || $site.themeConfig.type || 'doc'; return [ 'blog', 'post', 'article' ].some(key => type.startsWith(key)) ? 'article' : 'website'; }, url: ($page, $site, path) => { const siteUrl = $site.themeConfig.siteUrl || $site.siteUrl; return (siteUrl || '') + path; }, image: ($page, $site) => { if ($page.frontmatter.banner) { const banner = $page.frontmatter.banner; const siteUrl = $site.themeConfig.siteUrl || $site.siteUrl; if (!banner.startsWith(siteUrl)) { return (siteUrl || '') + $page.frontmatter.banner; } return banner; } }, publishedAt: $page => { if ($page.frontmatter.date) { return moment($page.frontmatter.date).utc().toISOString(); } if ($page.birthTimestamp) { return moment($page.birthTimestamp).utc().toISOString(); } return (new Date()).toISOString(); }, modifiedAt: $page => { if ($page.lastUpdatedTimestamp) { return moment($page.lastUpdatedTimestamp).utc().toISOString(); } return (new Date()).toISOString(); }, customMeta: () => {}, defaultMeta(add, ctx) { const { author, tags, $page, $site } = ctx; add('X-UA-Compatible', 'IE=Edge,chrome=1', 'http-equiv'); // <!-- Alternative links --> const locales = $site.locales || {}; const siteUrl = $site.themeConfig.siteUrl || $site.siteUrl || ''; for (const path in locales) { const item = locales[path] || {}; const lang = item.lang; if (lang) { add('alternative', lang, 'link', { rel: 'alternative', hreflang: lang, href: siteUrl + path, }); } } // RSS const blogConfig = $site.themeConfig.blogConfig || {}; if (blogConfig.rss) { add('alternate', 'rss', 'link', { rel: 'alternate', title: $page.localeConfig.title || $site.title, href: `${siteUrl}/${RSS_FILENAME}`, }); } // Basics. add('article:published_time', ctx.publishedAt); add('article:modified_time', ctx.modifiedAt); add('og:site_name', ctx.siteTitle); add('og:title', ctx.title); add('og:description', ctx.description); add('og:type', ctx.type); add('og:url', ctx.url); add('og:image', ctx.image); add('twitter:title', ctx.title); add('twitter:description', ctx.description); add('twitter:url', ctx.url); add('twitter:card', ctx.twitterCard); add('twitter:image', ctx.image); // Author. if (author) { add('twitter:label1', 'Written by'); add('twitter:data2', author.name); if (author.twitter) { add('twitter:creator', author.twitter); } } // Tags. if (tags) { add('twitter:label2', 'Filed under'); let _tags = tags; if (!Array.isArray(tags)) { _tags = tags.split(','); } add('twitter:data2', _tags.join(',')); _tags.forEach(tag => add('article:tag', tag)); } // keywords add('keywords', ctx.keywords); // description add('description', ctx.description); // powerby add('powerby', POWERBY_NAME); }, }; module.exports = (options = {}, ctx) => { options = Object.assign(defaultOptions, options); return { name: 'seo', extendPageData($page) { const $site = ctx.siteConfig; $page.localeConfig = localeConfig($page, $site) || {}; // In Vuepress core, permalinks are built after enhancers. const pageClone = Object.assign(Object.create(Object.getPrototypeOf($page)), $page); pageClone.buildPermalink(); const meta = $page.frontmatter.meta || []; const addMeta = getAddMeta(meta); const optionArgs = [ $page, $site, pageClone.path ]; const metaContext = { $page, $site, siteTitle: options.siteTitle(...optionArgs), title: options.title(...optionArgs), description: options.description(...optionArgs), keywords: options.keywords(...optionArgs), author: options.author(...optionArgs), tags: options.tags(...optionArgs), twitterCard: options.twitterCard(...optionArgs), type: options.type(...optionArgs), url: options.url(...optionArgs), image: options.image(...optionArgs), publishedAt: options.publishedAt(...optionArgs), modifiedAt: options.modifiedAt(...optionArgs), }; options.defaultMeta(addMeta, metaContext); options.customMeta(addMeta, metaContext); $page.frontmatter.meta = meta; }, }; }; function getAddMeta(meta) { return (name, content, attribute = null, attrs = {}) => { if (!content) return; if (!attribute) attribute = [ 'article:', 'og:' ].some(type => name.startsWith(type)) ? 'property' : 'name'; if (meta.some(item => item[attribute] === name)) { // 重复 update const info = meta.find(item => item[attribute] === name); Object.assign(info, { ...attrs, [attribute]: name, content, 'data-auto-meta': true, 'data-update-meta': true }); } else { meta.push({ ...attrs, [attribute]: name, content, 'data-auto-meta': true }); } }; } function localeConfig($page, $site) { const { locales = {} } = $site; let targetLang; let defaultLang; for (const path in locales) { if (path === '/') { defaultLang = locales[path]; } else if ($page.path.indexOf(path) === 0) { targetLang = locales[path]; } } return targetLang || defaultLang || {}; }