chore(core): refactor SEO into testable module

pull/211/head
Romain 4 years ago
parent 6886c262db
commit 31b85fc0e1

@ -0,0 +1,47 @@
const fs = require('fs')
const path = require('path')
class SEO {
constructor (output, seoLocation, rootAlbum) {
this.output = output
this.seoPrefix = seoLocation + (seoLocation.endsWith('/') ? '' : '/')
this.album = rootAlbum
}
robots () {
return `User-Agent: *\nDisallow:\nSitemap: ${this.seoPrefix}sitemap.xml\n`
}
sitemap () {
const now = new Date().toISOString()
const prefix = this.seoPrefix
// gather all album pages
const urls = []
addAlbumUrls(urls, this.album)
// create one <url> section per album
const xml = urls.map(url => `
<url>
<loc>${prefix}${url}</loc>
<lastmod>${now}</lastmod>
</url>`)
// return the full document
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${xml.join('')}
</urlset>
`
}
writeFiles () {
const robotsFile = path.join(this.output, 'robots.txt')
const sitemapFile = path.join(this.output, 'sitemap.xml')
fs.writeFileSync(robotsFile, this.robots())
fs.writeFileSync(sitemapFile, this.sitemap())
}
}
function addAlbumUrls (list, album) {
list.push(album.url)
album.albums.forEach(subAlbum => addAlbumUrls(list, subAlbum))
}
module.exports = SEO

@ -3,6 +3,7 @@ const path = require('path')
const async = require('async')
const resolvePkg = require('resolve-pkg')
const Theme = require('./theme')
const SEO = require('./seo')
const pages = require('./pages')
exports.build = function (rootAlbum, opts, callback) {
@ -36,7 +37,11 @@ exports.build = function (rootAlbum, opts, callback) {
next => async.series(tasks, next)
], callback)
outputSEO(opts.output, opts.seoLocation, rootAlbum)
// add robots & sitemap if needed
if (opts.seoLocation) {
const seo = new SEO(opts.output, opts.seoLocation, rootAlbum)
seo.writeFiles()
}
}
function localThemePath (themeName) {
@ -58,37 +63,3 @@ function readThemeSettings (filepath) {
throw new Error('Failed to parse JSON theme settings file: ' + filepath)
}
}
function outputSEO (output, seoLocation, rootAlbum) {
// SEO (sitemap and robots.txt)
if (!seoLocation) {
return
}
// Guaranteed to end with slash
const seoPrefix = seoLocation + (seoLocation.endsWith('/') ? '' : '/')
// Generate robots.txt
const robotsTxt = 'User-Agent: *\nDisallow:\n\nSitemap: ' + seoPrefix + 'sitemap.xml\n'
fs.writeFileSync(path.join(output, 'robots.txt'), robotsTxt)
// Generate sitemap
let sitemapXML = '<?xml version="1.0" encoding="UTF-8"?>\n' +
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
const now = new Date().toISOString()
function addToSitemap (album) {
sitemapXML +=
' <url>\n' +
' <loc>' + seoPrefix + album.url + '</loc>\n' +
' <lastmod>' + now + '</lastmod>\n' +
' </url>\n'
album.albums.forEach((subAlbum) => addToSitemap(subAlbum))
}
addToSitemap(rootAlbum)
sitemapXML += '</urlset>\n'
fs.writeFileSync(path.join(output, 'sitemap.xml'), sitemapXML)
}

@ -0,0 +1,53 @@
const should = require('should/as-function')
const SEO = require('../../src/website/seo')
const Album = require('../../src/model/album')
describe('SEO', function () {
it('normalises the SEO location path (no slash)', function () {
const location = 'https://example.com'
const seo = new SEO('output', location, new Album('test'))
should(seo.seoPrefix).eql('https://example.com/')
})
it('normalises the SEO location path (slash)', function () {
const location = 'https://example.com/'
const seo = new SEO('output', location, new Album('test'))
should(seo.seoPrefix).eql('https://example.com/')
})
describe('robots.txt', function () {
it('allows all user agents', function () {
const seo = new SEO('output', 'https://example.com', new Album('test'))
const robots = seo.robots()
should(robots.includes('User-Agent: *')).eql(true)
})
it('points to the Sitemap', function () {
const seo = new SEO('output', 'https://example.com', new Album('test'))
const robots = seo.robots()
should(robots.includes('Sitemap: https://example.com/sitemap.xml')).eql(true)
})
})
describe('sitemap.xml', function () {
it('includes the root album', function () {
const album = new Album('test')
album.finalize()
const seo = new SEO('output', 'https://example.com', album)
const sitemap = seo.sitemap()
should(sitemap.includes('<loc>https://example.com/index.html</loc>')).eql(true)
})
it('includes nested albums', function () {
const album = new Album({
albums: [new Album('a'), new Album('b')]
})
album.finalize()
const seo = new SEO('output', 'https://example.com', album)
const sitemap = seo.sitemap()
should(sitemap.includes('<loc>https://example.com/index.html</loc>')).eql(true)
should(sitemap.includes('<loc>https://example.com/a.html</loc>')).eql(true)
should(sitemap.includes('<loc>https://example.com/b.html</loc>')).eql(true)
})
})
})
Loading…
Cancel
Save