From 76803bb767b3daea0233517f6c473f74d278161b Mon Sep 17 00:00:00 2001 From: Lea Rosema Date: Mon, 23 Sep 2024 11:38:48 +0200 Subject: [PATCH] feat: support waterfall includes --- src/css.js | 24 ++------ src/html.js | 37 +++++------ src/md.js | 24 ++------ src/resolver.js | 23 +++++-- src/sissi.js | 61 +++++-------------- src/transforms/bundle.js | 37 +++++++++++ src/transforms/template-data.js | 53 ++++++++++++++++ tests/css.test.js | 6 +- tests/data.test.js | 2 +- .../data}/_data/jsdata.js | 0 .../data}/_data/jsondata.json | 0 .../data}/_data/yamldata.yaml | 0 tests/fixtures/smallsite/_data/meta.js | 4 ++ .../fixtures/smallsite/_includes/footer.html | 4 ++ .../fixtures/smallsite/_includes/header.html | 4 ++ tests/fixtures/smallsite/_layouts/base.html | 16 +++++ tests/fixtures/smallsite/css/_globals.css | 43 +++++++++++++ tests/fixtures/smallsite/css/_vars.css | 13 ++++ tests/fixtures/smallsite/css/styles.css | 2 + tests/fixtures/smallsite/imprint.html | 12 ++++ tests/fixtures/smallsite/index.html | 7 +++ tests/fixtures/smallsite/test.md | 19 ++++++ tests/html.test.js | 19 +++++- tests/resolver.test.js | 4 +- tests/sissi.test.js | 34 ++++++++++- tests/transforms/template-data.test.js | 36 ++++++++++- 26 files changed, 365 insertions(+), 119 deletions(-) create mode 100644 src/transforms/bundle.js rename tests/{fixture => fixtures/data}/_data/jsdata.js (100%) rename tests/{fixture => fixtures/data}/_data/jsondata.json (100%) rename tests/{fixture => fixtures/data}/_data/yamldata.yaml (100%) create mode 100644 tests/fixtures/smallsite/_data/meta.js create mode 100644 tests/fixtures/smallsite/_includes/footer.html create mode 100644 tests/fixtures/smallsite/_includes/header.html create mode 100644 tests/fixtures/smallsite/_layouts/base.html create mode 100644 tests/fixtures/smallsite/css/_globals.css create mode 100644 tests/fixtures/smallsite/css/_vars.css create mode 100644 tests/fixtures/smallsite/css/styles.css create mode 100644 tests/fixtures/smallsite/imprint.html create mode 100644 tests/fixtures/smallsite/index.html create mode 100644 tests/fixtures/smallsite/test.md diff --git a/src/css.js b/src/css.js index 0402309..774db05 100644 --- a/src/css.js +++ b/src/css.js @@ -1,6 +1,6 @@ import path from 'path'; -import { resolve } from './resolver.js'; +import { handleTemplateFile } from './transforms/template-data.js'; // TODO: add a regex for layer syntax const INCLUDE_REGEX = /@import [\"\']([\w:\/\\]+\.css)[\"\'];/g; @@ -11,29 +11,15 @@ export default (config) => { config.addExtension('css', { outputFileExtension: 'css', compile: async function (inputContent, inputPath) { - let parsed = path.parse(inputPath); - if (parsed.name.startsWith('_')) { - // Omit files prefixed with an underscore. - return; - } - - return async () => { + + return async (data) => { const includes = new Map(); const matches = inputContent.matchAll(INCLUDE_REGEX); for (const [, file] of matches) { - - const fullPath = path.resolve(config.dir.input, parsed.dir, file); - try { - const content = await (config.resolve || resolve)(fullPath); - includes.set(file, content); - } catch (err) { - console.error('error processing file:', fullPath, err); - // silently fail if there is no include - includes.set(file, `@import "${file}";`); - } + const tpl = await handleTemplateFile(config, data, path.join(parsed.dir, file)); + includes.set(file, tpl ? tpl.content : `@import url("${file}");`); } - return inputContent.replace(INCLUDE_REGEX, (_, file) => includes.get(file)) }; }, diff --git a/src/html.js b/src/html.js index d2cccff..6c0a273 100644 --- a/src/html.js +++ b/src/html.js @@ -1,6 +1,7 @@ import path from 'path'; -import { resolve } from './resolver.js'; -const INCLUDE_REGEX = //g; +import { handleTemplateFile } from './transforms/template-data.js'; + +const INCLUDE_REGEX = //g; export default (config) => { config.addTemplateFormats('html'); @@ -10,29 +11,21 @@ export default (config) => { compile: async function (inputContent, inputPath) { let parsed = path.parse(inputPath); - if (parsed.name.startsWith('_')) { - // Omit files prefixed with an underscore. - return; - } - return async () => { + return async (data) => { const includes = new Map(); - const matches = inputContent.matchAll(INCLUDE_REGEX); - for (const [, file] of matches) { - - const fullPath = path.join(config.dir.input, config.dir.includes, file); - try { - const content = await (config.resolve || resolve)(fullPath); - includes.set(file, content); - } catch (err) { - console.error('error processing file:', fullPath, err); - // silently fail if there is no include - includes.set(file, ``); + let content = inputContent, matches; + + while ((matches = Array.from(content.matchAll(INCLUDE_REGEX))).length > 0) { + for (const [, file] of matches) { + const include = await handleTemplateFile(config, data, path.join(config.dir.includes, file)); + includes.set(file, include ? include.content : ``); } - } - return inputContent.replace(INCLUDE_REGEX, (_, file) => { - return includes.get(file) - }); + content = content.replace(INCLUDE_REGEX, (_, file) => { + return includes.get(file) + }); + } + return content; }; }, }); diff --git a/src/md.js b/src/md.js index 0d04b80..1766fb6 100644 --- a/src/md.js +++ b/src/md.js @@ -1,6 +1,8 @@ import path from 'path'; -import { resolve } from './resolver.js'; + +import { handleTemplateFile } from './transforms/template-data.js'; import { markdown } from './transforms/markdown.js'; + const INCLUDE_REGEX = //g; export default (config) => { @@ -10,27 +12,13 @@ export default (config) => { outputFileExtension: 'html', compile: async function (inputContent, inputPath) { - let parsed = path.parse(inputPath); - if (parsed.name.startsWith('_')) { - // Omit files prefixed with an underscore. - return; - } - - return async () => { + return async (data) => { const includes = new Map(); const content = markdown(inputContent); const matches = content.matchAll(INCLUDE_REGEX); for (const [, file] of matches) { - - const fullPath = path.join(config.dir.input, config.dir.includes, file); - try { - const content = await (config.resolve || resolve)(fullPath); - includes.set(file, content); - } catch (err) { - console.error('error processing file:', fullPath, err); - // silently fail if there is no include - includes.set(file, ``); - } + const include = await handleTemplateFile(config, data, path.join(config.dir.includes, file)); + includes.set(file, include ? include.content : ``); } return content.replace(INCLUDE_REGEX, (_, file) => { return includes.get(file) diff --git a/src/resolver.js b/src/resolver.js index 73d6318..cfd763b 100644 --- a/src/resolver.js +++ b/src/resolver.js @@ -1,9 +1,16 @@ -import { readFile } from 'node:fs/promises' +import { readFile, stat } from 'node:fs/promises' import path from 'node:path'; -export async function resolve(resource) { - if (/^\w+:\/\//.test(resource)) { - // seems to be an URI, fetch it +/** + * Read a file from the input dir or from the internet. + * @param {string[]} paths + * @returns + */ +export async function resolve(...paths) { + const last = paths.slice(-1)[0]; + if (/^\w+:\/\//.test(last)) { + // seems to be an URL, fetch it + const resource = last; const response = await fetch(resource); const contentType = response.headers.get('Content-Type'); if (!contentType || !contentType.startsWith('text')) { @@ -11,6 +18,12 @@ export async function resolve(resource) { } return await response.text(); } + // otherwise, readFile it. - return await readFile(path.resolve(resource), 'utf8'); + const resource = path.normalize(path.join(...paths)); + const absResource = path.resolve(resource); + if ((await stat(absResource)).isDirectory()) { + return null; + } + return await readFile(absResource, 'utf8'); } diff --git a/src/sissi.js b/src/sissi.js index 486e328..e6687ec 100644 --- a/src/sissi.js +++ b/src/sissi.js @@ -6,8 +6,7 @@ import { SissiConfig } from './sissi-config.js'; import { serve } from './httpd.js'; import EventEmitter from 'node:stream'; import { readDataDir } from './data.js'; -import { template } from './transforms/template-data.js' -import { frontmatter } from './transforms/frontmatter.js'; +import { handleTemplateFile } from './transforms/template-data.js'; export class Sissi { @@ -30,9 +29,11 @@ export class Sissi { if (filter instanceof RegExp) return filter.test(file); } ); + const writtenFiles = [] for (const file of files) { - await this.processFile(file, eventEmitter); + writtenFiles.push(await this.processFile(file, eventEmitter)); } + return writtenFiles.filter(Boolean); } /** @@ -96,61 +97,27 @@ export class Sissi { if (! this.data) { this.data = await readDataDir(this.config); } - const absInputFileName = path.resolve(this.config.dir.input, inputFileName); - if (inputFileName.startsWith('_') || inputFileName.includes(path.sep + '_')) { + if (inputFileName.startsWith('_') || inputFileName.includes(path.sep + '_') || path.parse(inputFileName).name.startsWith('_')) { return; } - const stats = await stat(absInputFileName); - if (stats.isDirectory()) { - return; - } - let content = await readFile(absInputFileName, 'utf8'); - const parsed = path.parse(inputFileName); - const extension = parsed.ext?.slice(1); - - let ext = null; - if (this.config.extensions.has(extension)) { - ext = this.config.extensions.get(extension); - const { data: matterData, body } = frontmatter(content); - content = body; - const fileData = Object.assign({}, structuredClone(this.data), matterData); - const processor = await ext.compile(content, inputFileName); - content = template(await processor(fileData))(fileData); - - if (fileData.layout) { - fileData.content = content; - const relLayoutDir = path.normalize( - path.join(this.config.dir.input, this.config.dir.layouts || '_layouts') - ); - const absLayoutFilePath = path.resolve(relLayoutDir, fileData.layout); - const layoutExtKey = path.parse(absLayoutFilePath).ext?.slice(1); - let layoutContent = await readFile(absLayoutFilePath, 'utf8'); - - const layoutExt = layoutExtKey ? this.config.extensions.get(layoutExtKey) : null; - if (layoutExt) { - const processor = await layoutExt.compile(layoutContent, inputFileName); - layoutContent = await processor(fileData); - } - - content = template(layoutContent)(fileData); - } - + const tpl = await handleTemplateFile(this.config, this.data, inputFileName); + if (! tpl) { + return null; } - - let outputFileName =this.config.naming(this.config.dir.output, inputFileName, ext?.outputFileExtension); - console.log(`[write]\t${outputFileName}`); + + console.log(`[write]\t${tpl.filename}`); if (eventEmitter) { eventEmitter.emit('watch-event', { eventType: 'change', filename: inputFileName }); } - if (this.dryMode) { - return; + if (! this.dryMode) { + await mkdir(path.parse(tpl.filename).dir, {recursive: true}); + await writeFile(tpl.filename, tpl.content, {}); } - await mkdir(path.parse(outputFileName).dir, {recursive: true}); - await writeFile(outputFileName, content, {}); + return tpl.filename; } /** diff --git a/src/transforms/bundle.js b/src/transforms/bundle.js new file mode 100644 index 0000000..989dd21 --- /dev/null +++ b/src/transforms/bundle.js @@ -0,0 +1,37 @@ +const SYNTAXES = { + html: //g, + css: /@import [\"\']([\w:\/\\]+\.css)[\"\'](?: layer\((\w+)\))?;/g, +}; + +/** + * Bundle assets into one file. + * + * @param {string} inputContent + * @param {(resource: string) => Promise} resolve + * @param {'html'|'css'} syntax + * @returns {Promise} return the bundled resource + */ +async function bundle(inputContent, resolve, syntax, processor) { + const includes = new Map(); + let content = inputContent, matches; + const includePattern = SYNTAXES[syntax]; + + while ((matches = Array.from(content.matchAll(includePattern))).length > 0) { + for (const [, file] of matches) { + + const fullPath = path.join(config.dir.input, config.dir.includes, file); + try { + const content = await resolve(fullPath); + + includes.set(file, await (await processor(content, file)).compile(data)); + } catch (err) { + console.error('error processing file:', fullPath, err); + // silently fail if there is no include + includes.set(file, ``); + } + } + content = content.replace(includePattern, (_, file) => { + return includes.get(file); + }); + } +} diff --git a/src/transforms/template-data.js b/src/transforms/template-data.js index bc7eb6e..03e37ea 100644 --- a/src/transforms/template-data.js +++ b/src/transforms/template-data.js @@ -1,3 +1,9 @@ +import path from 'node:path'; + +import { frontmatter } from './frontmatter.js'; +import { resolve } from '../resolver.js'; +import { SissiConfig } from "../sissi-config.js"; + const TEMPLATE_REGEX = /\{\{\s*([\w\.\[\]]+)\s*\}\}/g; const JSON_PATH_REGEX = /^\w+((?:\.\w+)|(?:\[\d+\]))*$/ const JSON_PATH_TOKEN = /(^\w+)|(\.\w+)|(\[\d+\])/g @@ -41,3 +47,50 @@ export function template(str) { }); } } + +/** + * Complete Template processing function + * @param {SissiConfig} config + * @param {any} data + * @param {string} inputFile + * @returns {Promise<{content: Buffer|string, filename}>} the content file name and the output file name + */ +export async function handleTemplateFile(config, data, inputFile) { + const content = await (config.resolve || resolve)(config.dir.input, inputFile); + if (content === null) { + return null; + } + + const parsed = path.parse(inputFile); + const ext = parsed.ext?.slice(1); + if (! config.extensions.has(ext)) { + return { + content, + filename: config.naming(config.dir.output, inputFile) + }; + } + + const plugin = config.extensions.get(ext); + + const { data: matterData, body } = frontmatter(content); + const fileData = Object.assign({}, structuredClone(data), matterData); + + const outputFile = config.naming(config.dir.output, inputFile, plugin?.outputFileExtension); + Object.assign(fileData, { + inputFile, + outputFile, + }) + + const processor = await plugin.compile(body, inputFile); + + let fileContent = template(await processor(fileData))(fileData); + + if (fileData.layout) { + const layoutFilePath = path.normalize(path.join(config.dir.layouts, fileData.layout)); + const l = await handleTemplateFile(config, + {...fileData, content: fileContent, layout: null}, layoutFilePath); + fileContent = l.content; + } + + return {content: fileContent, filename: outputFile}; +} diff --git a/tests/css.test.js b/tests/css.test.js index 2a081ab..6c19730 100644 --- a/tests/css.test.js +++ b/tests/css.test.js @@ -1,5 +1,6 @@ import { describe, it, before } from 'node:test'; import assert from 'node:assert/strict'; +import path from 'node:path'; import { SissiConfig } from '../src/sissi-config.js'; import css from '../src/css.js' @@ -18,7 +19,8 @@ describe('css plugin', () => { virtualFileSystem.set('A.css', '.a {color: red; }'); virtualFileSystem.set('B.css', '.b {color: green }'); - function dummyResolver(resource) { + function dummyResolver(...paths) { + const resource = path.normalize(path.join(...paths)); const match = resource.match(/\\?\/?(\w+.css)$/); if (!match || !virtualFileSystem.has(match[1]) ) { throw new Error('Virtual File not found') @@ -51,4 +53,4 @@ describe('css plugin', () => { assert(result === expectedFile); }); -}); \ No newline at end of file +}); diff --git a/tests/data.test.js b/tests/data.test.js index 3937ae4..10442df 100644 --- a/tests/data.test.js +++ b/tests/data.test.js @@ -9,7 +9,7 @@ describe('readDataDir', () => { before(() => { config = new SissiConfig({dir: { - input: 'tests/fixture', + input: 'tests/fixtures/data', data: '_data', output: 'dist' }}); diff --git a/tests/fixture/_data/jsdata.js b/tests/fixtures/data/_data/jsdata.js similarity index 100% rename from tests/fixture/_data/jsdata.js rename to tests/fixtures/data/_data/jsdata.js diff --git a/tests/fixture/_data/jsondata.json b/tests/fixtures/data/_data/jsondata.json similarity index 100% rename from tests/fixture/_data/jsondata.json rename to tests/fixtures/data/_data/jsondata.json diff --git a/tests/fixture/_data/yamldata.yaml b/tests/fixtures/data/_data/yamldata.yaml similarity index 100% rename from tests/fixture/_data/yamldata.yaml rename to tests/fixtures/data/_data/yamldata.yaml diff --git a/tests/fixtures/smallsite/_data/meta.js b/tests/fixtures/smallsite/_data/meta.js new file mode 100644 index 0000000..ceca947 --- /dev/null +++ b/tests/fixtures/smallsite/_data/meta.js @@ -0,0 +1,4 @@ +export default { + author: 'Lea Rosema', + fediverse: 'https://lea.lgbt/@lea', +} diff --git a/tests/fixtures/smallsite/_includes/footer.html b/tests/fixtures/smallsite/_includes/footer.html new file mode 100644 index 0000000..721ef22 --- /dev/null +++ b/tests/fixtures/smallsite/_includes/footer.html @@ -0,0 +1,4 @@ + diff --git a/tests/fixtures/smallsite/_includes/header.html b/tests/fixtures/smallsite/_includes/header.html new file mode 100644 index 0000000..aaabbf4 --- /dev/null +++ b/tests/fixtures/smallsite/_includes/header.html @@ -0,0 +1,4 @@ +
+ +

My Fancy Site

+
diff --git a/tests/fixtures/smallsite/_layouts/base.html b/tests/fixtures/smallsite/_layouts/base.html new file mode 100644 index 0000000..22aedbb --- /dev/null +++ b/tests/fixtures/smallsite/_layouts/base.html @@ -0,0 +1,16 @@ + + + + + + {{ title }} – Sissi Demo Site + + + + +
+ {{ content }} +
+ + + diff --git a/tests/fixtures/smallsite/css/_globals.css b/tests/fixtures/smallsite/css/_globals.css new file mode 100644 index 0000000..fce7821 --- /dev/null +++ b/tests/fixtures/smallsite/css/_globals.css @@ -0,0 +1,43 @@ +*, *::before, *::after { + box-sizing: border-box; +} + +*:focus { + outline: 2px solid #fff; +} + +body { + font-family: system-ui, sans-serif; + background: var(--bg); + color: var(--fg); + margin: 0; + display: flex; + flex-direction: column; + min-height: 100vh; +} + +a { + color: var(--anchor, #f0f); +} + +header { + padding: 2rem; + background: #163; + color: #fff; + display: flex; + align-items: center; + gap: 1rem; +} + +main { + flex: 1 auto; + width: min(100% - 3rem, var(--container-max, 60ch)); + margin-inline: auto; +} + +footer { + padding: 2rem; + + background: #000; +} + diff --git a/tests/fixtures/smallsite/css/_vars.css b/tests/fixtures/smallsite/css/_vars.css new file mode 100644 index 0000000..c4da6bb --- /dev/null +++ b/tests/fixtures/smallsite/css/_vars.css @@ -0,0 +1,13 @@ +:root { + --bg: #222; + --fg: white; + --anchor: #7df; +} + +@media screen and (prefers-color-scheme: light) { + :root { + --bg: #fff; + --fg: #222; + --anchor: #027; + } +} diff --git a/tests/fixtures/smallsite/css/styles.css b/tests/fixtures/smallsite/css/styles.css new file mode 100644 index 0000000..5d0bb2e --- /dev/null +++ b/tests/fixtures/smallsite/css/styles.css @@ -0,0 +1,2 @@ +@import '_vars.css'; +@import '_globals.css'; diff --git a/tests/fixtures/smallsite/imprint.html b/tests/fixtures/smallsite/imprint.html new file mode 100644 index 0000000..d42a62b --- /dev/null +++ b/tests/fixtures/smallsite/imprint.html @@ -0,0 +1,12 @@ +--- +title: Imprint +layout: base.html +--- +

Imprint

+

This is an Imprint. That's the address of my employer:

+
+ Lea Rosema c/o adesso
+ Willy-Brandt-Straße 1, Etage 4 Mitte
+
+ 20457 Hamburg
+
diff --git a/tests/fixtures/smallsite/index.html b/tests/fixtures/smallsite/index.html new file mode 100644 index 0000000..3fd446f --- /dev/null +++ b/tests/fixtures/smallsite/index.html @@ -0,0 +1,7 @@ +--- +title: Hello World +layout: base.html +--- +

Hello World!

+

This is a Demo website built with Sissi, the Small Indieweb Static SIte generator.

+

This is another page

diff --git a/tests/fixtures/smallsite/test.md b/tests/fixtures/smallsite/test.md new file mode 100644 index 0000000..ec36dd2 --- /dev/null +++ b/tests/fixtures/smallsite/test.md @@ -0,0 +1,19 @@ +--- +layout: base.html +title: This is a markdown test page +--- +# Muh + +Lorem ipsum dolor sit amet. + +It has _some_ *basic* features: + +- Unordered Lists +- Like this one + 1. but also ordered ones. + 2. nesting them also works + +Adding links with angle brackets: + +Addmin links with custom text: [Lea's Mastodon profile page](https://lea.lgbt/@lea) + diff --git a/tests/html.test.js b/tests/html.test.js index 85b6b45..8681210 100644 --- a/tests/html.test.js +++ b/tests/html.test.js @@ -1,5 +1,6 @@ import { describe, it, before } from 'node:test'; import assert from 'node:assert/strict'; +import path from 'node:path'; import { SissiConfig } from '../src/sissi-config.js'; import html from '../src/html.js' @@ -17,8 +18,14 @@ describe('html plugin', () => { ].join('\n')); virtualFileSystem.set('_includes/header.html', '
'); virtualFileSystem.set('_includes/main.html', '
'); + virtualFileSystem.set('_includes/nav.html', ''); - function dummyResolver(resource) { + virtualFileSystem.set('_includes/waterfall-header.html', '
'); + virtualFileSystem.set('waterfall.html', ''); + + + function dummyResolver(...paths) { + const resource = path.normalize(path.join(...paths)); return virtualFileSystem.get(resource); } @@ -46,4 +53,14 @@ describe('html plugin', () => { assert.equal(result, expectedFile); }); + it('should handle waterfall includes nicely', async () => { + const expectedFile = '
'; + const file = 'waterfall.html'; + + const transform = await config.extensions.get('html').compile(virtualFileSystem.get(file), file); + const result = await transform(); + + assert.equal(result, expectedFile); + }); + }); diff --git a/tests/resolver.test.js b/tests/resolver.test.js index 0bd48a0..f72faa9 100644 --- a/tests/resolver.test.js +++ b/tests/resolver.test.js @@ -20,7 +20,7 @@ describe('resolve', () => { }); it('should resolve files from the local file system', async () => { - const content = await resolve(path.join(config.dir.input, 'index.html')); + const content = await resolve(config.dir.input, 'index.html'); assert(content.startsWith('---\n')); }); @@ -36,7 +36,7 @@ describe('resolve', () => { }) }); - const content = await resolve('https://unpkg.com/open-props@1.7.6/open-props.min.css'); + const content = await resolve(config.dir.input, 'https://unpkg.com/open-props@1.7.6/open-props.min.css'); assert.strictEqual(globalThis.fetch.mock.callCount(), 1); assert(content.startsWith(':where')); diff --git a/tests/sissi.test.js b/tests/sissi.test.js index 593e277..daab8d8 100644 --- a/tests/sissi.test.js +++ b/tests/sissi.test.js @@ -1 +1,33 @@ -// none yet :D. Try and error mode for now. It's just a silly idea. \ No newline at end of file +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { SissiConfig } from '../src/sissi-config.js'; +import { Sissi } from '../src/sissi.js'; + +import html from '../src/html.js'; +import css from '../src/css.js'; + +describe('sissi', () => { + + it('should successfully build a smallsite', async () => { + + + const config = new SissiConfig({ + dir: { + input: 'tests/fixtures/smallsite', + output: '' + } + }); + config.addPlugin(html); + config.addPlugin(css); + const sissi = new Sissi(config); + sissi.dryMode = true; + + const writtenFiles = await sissi.build(); + + writtenFiles.sort(); + + assert.deepEqual(writtenFiles, + ['css/styles.css', 'imprint.html', 'index.html', 'test.html'] + ); + }); +}); diff --git a/tests/transforms/template-data.test.js b/tests/transforms/template-data.test.js index 25a7814..0ecbab5 100644 --- a/tests/transforms/template-data.test.js +++ b/tests/transforms/template-data.test.js @@ -1,6 +1,10 @@ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; -import { dataPath, template } from '../../src/transforms/template-data.js'; +import path from 'node:path'; + +import { dataPath, handleTemplateFile, template } from '../../src/transforms/template-data.js'; +import { SissiConfig } from '../../src/sissi-config.js'; +import md from '../../src/md.js'; const TEST_DATA = { 'title': 'This is a title', @@ -11,6 +15,15 @@ const TEST_DATA = { 'theMatrix': [[1,2,3],[4,5,6],[7,8,9]], } +const TEST_MD = `--- +layout: base.html +author: Lea Rosema +--- +# {{ title }} + +An article by {{ author }} +` + const TEST_TEMPLATE = `

{{ title }}

Blog article by {{ meta.authors[1] }}

` @@ -52,3 +65,24 @@ describe('template function', () => { assert.equal(template(TEST_TEMPLATE)(TEST_DATA), TEST_TEMPLATE_EXPECTED); }); }) + +describe('handleTemplateFile function', () => { + it('should work with the default markdown plugin', async () => { + const config = new SissiConfig(); + config.addExtension(md); + + const vFS = new Map(); + vFS.set('index.md', TEST_MD); + vFS.set('_layouts/base.html', '{{ content }}'); + + config.resolve = (...paths) => { + const resource = path.normalize(path.join(...paths)); + return vFS.get(resource); + } + + const result = await handleTemplateFile(config, {title: 'Lea was here'}, 'index.md'); + + assert.equal(result.filename, 'public/index.html'); + assert.equal(result.content, '

Lea was here

\n

An article by Lea Rosema

') + }); +});