Skip to content
Permalink
Browse files

refactor: separate concerns

  • Loading branch information...
NicoPennec committed Sep 4, 2019
1 parent 9b10bf4 commit 3428a30cc20b6d520b9285e684390dda422a12b6
Showing with 295 additions and 193 deletions.
  1. +59 −0 lib/builder.js
  2. +91 −0 lib/cache.js
  3. +46 −0 lib/generator.js
  4. +53 −0 lib/middleware.js
  5. +14 −193 lib/module.js
  6. +32 −0 lib/routes.js
@@ -0,0 +1,59 @@
const { hostname } = require('os')
const { promisify } = require('util')

const isHTTPS = require('is-https')
const sm = require('sitemap')

/**
* Initialize a fresh sitemap instance
*
* @param {Object} options
* @param {Array} routes
* @param {Request} req
* @returns {Sitemap} sitemap builder
*/
function createSitemap(options, routes, req = null) {
const sitemapConfig = {}

// Set sitemap hostname
sitemapConfig.hostname =
options.hostname || (req && `${isHTTPS(req) ? 'https' : 'http'}://${req.headers.host}`) || `http://${hostname()}`

routes = routes.map(route => ({ ...options.defaults, ...route }))

// Add a trailing slash to each route URL
if (options.trailingSlash) {
routes = routes.map(route => {
if (!route.url.endsWith('/')) route.url = `${route.url}/`
return route
})
}

// Enable filter function for each declared route
if (typeof options.filter === 'function') {
routes = options.filter({
routes,
options: { ...options, ...sitemapConfig }
})
}

// Set urls and ensure they are unique
sitemapConfig.urls = [...new Set(routes)]

// Set cacheTime
sitemapConfig.cacheTime = options.cacheTime || 0

// Set XML namespaces
sitemapConfig.xmlNs = options.xmlNs

// Set XSL url
sitemapConfig.xslUrl = options.xslUrl

// Create promisified instance and return
const sitemap = sm.createSitemap(sitemapConfig)
sitemap.toXML = promisify(sitemap.toXML)

return sitemap
}

module.exports = { createSitemap }
@@ -0,0 +1,91 @@
const { promisify } = require('util')

const AsyncCache = require('async-cache')
const unionBy = require('lodash.unionby')

/**
* Initialize a AsyncCache instance for routes
*
* @param {string[]} staticRoutes
* @param {Object} options
* @returns {AsyncCache.Cache<any>} Cache instance
*/
function createCache(staticRoutes, options) {
const cache = new AsyncCache({
maxAge: options.cacheTime,
load(_, callback) {
promisifyRoute(options.routes)
.then(routes => routesUnion(staticRoutes, routes))
.then(routes => {
callback(null, routes)
})
.catch(err => {
/* istanbul ignore next */
callback(err)
})
}
})
cache.get = promisify(cache.get)

return cache
}

/* istanbul ignore next */

/**
* Promisify the `options.routes` option
*
* @remarks Borrowed from nuxt/common/utils
*
* @param {Function} fn Function that fetch dynamic routes
* @returns {Promise.<Array>} Promise that return a list of routes
*/
function promisifyRoute(fn) {
// If routes is an array
if (Array.isArray(fn)) {
return Promise.resolve(fn)
}
// If routes is a function expecting a callback
if (fn.length === 1) {
return new Promise((resolve, reject) => {
fn(function(err, routeParams) {
if (err) {
reject(err)
}
resolve(routeParams)
})
})
}
let promise = fn()
if (!promise || (!(promise instanceof Promise) && typeof promise.then !== 'function')) {
promise = Promise.resolve(promise)
}
return promise
}

/**
* Join static and options-defined routes into single array
*
* @param {string[]} staticRoutes
* @param {Array} optionsRoutes
* @returns {Array} List of routes
*/
function routesUnion(staticRoutes, optionsRoutes) {
// Make sure any routes passed as strings are converted to objects with url properties
staticRoutes = staticRoutes.map(ensureRouteIsObject)
optionsRoutes = optionsRoutes.map(ensureRouteIsObject)
// Add static routes to options routes, discarding any defined in options
return unionBy(optionsRoutes, staticRoutes, 'url')
}

/**
* Make sure a passed route is an object
*
* @param {Object | string} route Route Object or String value
* @returns {Object} A valid route object
*/
function ensureRouteIsObject(route) {
return typeof route === 'object' ? route : { url: route }
}

module.exports = { createCache }
@@ -0,0 +1,46 @@
const path = require('path')

const consola = require('consola')
const fs = require('fs-extra')

const { createSitemap } = require('./builder.js')

/**
* Generating sitemap
*
* @param {Object} options
* @param {Object} cache
* @param {Nuxt} nuxtInstance
*/
async function generateSitemap(options, cache, nuxtInstance) {
consola.info('Generating sitemap')

// Generate sitemap.xml
const routes = await cache.sitemap.get('routes')
const sitemap = await createSitemap(options, routes)
const xml = await sitemap.toXML()
const xmlGeneratePath = path.join(nuxtInstance.options.generate.dir, options.path)
fs.outputFileSync(xmlGeneratePath, xml)
consola.success('Generated', getPathname(nuxtInstance.options.generate.dir, xmlGeneratePath))

// Generate sitemap.xml.gz
if (options.gzip) {
const gzip = await sitemap.toGzip()
const gzipGeneratePath = path.join(nuxtInstance.options.generate.dir, options.pathGzip)
fs.outputFileSync(gzipGeneratePath, gzip)
consola.success('Generated', getPathname(nuxtInstance.options.generate.dir, gzipGeneratePath))
}
}

/**
* Convert a file path to a URL pathname
*
* @param {string} dirPath
* @param {string} filePath
* @returns {string} URL pathname
*/
function getPathname(dirPath, filePath) {
return [, ...path.relative(dirPath, filePath).split(path.sep)].join('/')
}

module.exports = { generateSitemap }
@@ -0,0 +1,53 @@
const { createSitemap } = require('./builder.js')

/**
* Register a middleware to serve a sitemap
*
* @param {Object} options
* @param {Object} cache
* @param {Nuxt} nuxtInstance
*/
function registerSitemap(options, cache, nuxtInstance) {
if (options.gzip) {
// Add server middleware for sitemap.xml.gz
nuxtInstance.addServerMiddleware({
path: options.pathGzip,
handler(req, res, next) {
cache.sitemap
.get('routes')
.then(routes => createSitemap(options, routes, req))
.then(sitemap => sitemap.toGzip())
.then(gzip => {
res.setHeader('Content-Type', 'application/x-gzip')
res.setHeader('Content-Encoding', 'gzip')
res.end(gzip)
})
.catch(err => {
/* istanbul ignore next */
next(err)
})
}
})
}

// Add server middleware for sitemap.xml
nuxtInstance.addServerMiddleware({
path: options.path,
handler(req, res, next) {
cache.sitemap
.get('routes')
.then(routes => createSitemap(options, routes, req))
.then(sitemap => sitemap.toXML())
.then(xml => {
res.setHeader('Content-Type', 'application/xml')
res.end(xml)
})
.catch(err => {
/* istanbul ignore next */
next(err)
})
}
})
}

module.exports = { registerSitemap }

0 comments on commit 3428a30

Please sign in to comment.
You can’t perform that action at this time.