diff --git a/.eslintrc.json b/.eslintrc.json index e2dc1f64c88e0cb98b1e16c50537c4926d1db9ac..c6f6bd66111e059840ad9df3853d7cacb8b35eea 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,6 @@ { - "extends": "@bark" + "extends": "@bark", + "parserOptions": { + "ecmaVersion": 8 + } } \ No newline at end of file diff --git a/index.js b/index.js index a0bffd9b38c559fbe8c060a46ec289f5fb727581..930c038a5aa359e12b68062539344b2bd24fc4b2 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,89 @@ -module.exports = function createMustacheMiddleware(viewDir) { - +const path = require('path') +const fs = require('fs-jetpack') +const Mustache = require('mustache') + +function createPathsFromContext(context = '', filename) { + const { name } = path.parse(filename) + return [ + path.join(context, filename), + path.join(context, name), + ] +} + +function createFileMap(dir, root, ext, debug) { + const cache = {} + const tree = dir.inspectTree(root) + if (tree.type !== 'dir') { + throw new TypeError(`[koa-mustache] viewDir must be an actual directory, found ${ tree.type }`) + } + + const processing = tree.children + while (processing.length > 0) { + const current = processing.shift() + + if (current.type === 'dir') { + for (const child of current.children) { + child.context = current.context ? path.join(current.context, current.name) : current.name + processing.push(child) + } + } else if (current.type === 'file') { + if (path.extname(current.name) === ext) { + const [pathWithExt, pathWithoutExt] = createPathsFromContext(current.context, current.name) + const content = dir.read(dir.path(root, pathWithExt), 'utf8') + + debug(`[koa-mustache] Loading file ${ pathWithExt }`) + + Mustache.parse(content) + cache[pathWithExt] = content + cache[pathWithoutExt] = content + } + } + } + return cache +} + +module.exports = function createMustacheMiddleware(viewDir, opts = {}) { + const useCache = opts.hasOwnProperty('cache') ? opts.cache : process.env.NODE_ENV === 'production' + const extension = opts.hasOwnProperty('extension') ? opts.extension : '.mustache' + const partialDir = opts.hasOwnProperty('partials') ? opts.partials : 'partials' + const debug = opts.debug || (() => {}) // eslint-disable-line no-empty-function + + const viewRoot = fs.cwd(viewDir) + + const cache = useCache ? createFileMap(viewRoot, '.', extension, debug) : {} + const partials = createFileMap(viewRoot, partialDir, extension, debug) + + return function attachRenderFunc(ctx, next) { + ctx.render = async function renderTemplateData(template, data = {}) { + if (useCache) { + const templateContent = cache[template] + if (templateContent == null) { + this.status = 404 + } else { + this.status = 200 + this.body = Mustache.render(templateContent, data, partials) + } + } else { + let fileData = await viewRoot.inspectAsync(template) + if (fileData == null) { + template += extension + fileData = await viewRoot.inspectAsync(template) + if (fileData == null) { + this.status = 404 + return + } + } + + if (fileData.type === 'file') { + const contents = await viewRoot.readAsync(template, 'utf8') + this.status = 200 + this.body = Mustache.render(contents, data, partials) + } else { + this.status = 500 + } + } + }.bind(ctx) + + return next() + } } diff --git a/package-lock.json b/package-lock.json index 2b5f9527049d71c975695b9b00ed0026a63aaf48..2e0f9162ea5b4a4c6ad6ef2f82eac66dc1e425af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -137,14 +137,12 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", - "dev": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -247,8 +245,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.0", @@ -491,6 +488,14 @@ "write": "0.2.1" } }, + "fs-jetpack": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fs-jetpack/-/fs-jetpack-1.2.0.tgz", + "integrity": "sha512-wEncEDStClGlHrQvqClINppjLdjQMcy3UFhu4DKrbTCIQHmiC+7r3nNvnc+3aiHxHaPfWXdA8UdCHNHGLJKciA==", + "requires": { + "minimatch": "3.0.4" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -732,7 +737,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "1.1.8" } @@ -758,6 +762,11 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "mustache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.0.tgz", + "integrity": "sha1-QCj3d4sXcIpImTCm5SrDvKDaQdA=" + }, "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", diff --git a/package.json b/package.json index ed88a1839b9f329b9af231603b7d4af4279bf8f3..10de4610890bcdb9f6f1382962f9d1a18123789a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@commander-lol/koa-mustache", - "version": "1.0.0", + "version": "1.0.1-beta1", "description": "Mustache rendering for Koa 2+", "main": "index.js", "scripts": { @@ -18,5 +18,9 @@ "devDependencies": { "@bark/eslint-config": "^0.1.2", "eslint": "^4.16.0" + }, + "dependencies": { + "fs-jetpack": "^1.2.0", + "mustache": "^2.3.0" } }