From 497185b654f7e73a5315801e8316288d12c03808 Mon Sep 17 00:00:00 2001 From: cho45 Date: Sat, 14 Jun 2025 21:46:42 +0900 Subject: [PATCH 1/9] update index.html --- index.html | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/index.html b/index.html index a214536..30d642e 100644 --- a/index.html +++ b/index.html @@ -166,6 +166,14 @@

micro-template.js

A minimal, blazing fast JavaScript template engine for hackers.

+
+ npm version + GitHub stars + MIT License +

Features

@@ -177,16 +185,8 @@

Features

-

Usage Example

+

Getting Started

-
-

Basic

-
import { template } from 'micro-template';
-
-const result = template('<div><%= message %></div>', { message: 'Hello, inline!' });
-console.log(result); // <div>Hello, inline!</div>
-
-

In HTML

<script type="application/x-template" id="tmpl1">
@@ -200,6 +200,7 @@ 

In HTML

In Node.js

+
npm install micro-template
import fs from 'node:fs';
 import { template } from 'micro-template';
 

From f9bac561843320550f897d2c0fd49ae83a74f679 Mon Sep 17 00:00:00 2001
From: cho45 
Date: Sun, 15 Jun 2025 11:21:03 +0900
Subject: [PATCH 2/9] serialized feature example

---
 lib/micro-template.js |  5 ++--
 misc/serialized.js    | 63 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 66 insertions(+), 2 deletions(-)
 create mode 100755 misc/serialized.js

diff --git a/lib/micro-template.js b/lib/micro-template.js
index b92496f..e209a6c 100644
--- a/lib/micro-template.js
+++ b/lib/micro-template.js
@@ -27,8 +27,9 @@ const template = function (id, data) {
 			`//# sourceURL=${name}\n` + 
 			`//# sourceMappingURL=data:application/json,${encodeURIComponent(JSON.stringify({version:3, file:name, sources:[`${name}.ejs`], sourcesContent:[string], mappings:";;AAAA;"+Array(line-1).fill('AACA').join(';')}))}`
 		);
-		const func = new Function("__this", ...keys, body);
-		return function (stash) { return func.call(null, me.context = { escapeHTML: me.escapeHTML, line: 1, ret : '', stash: stash }, ...keys.map(key => stash[key])) };
+		const compiled = new Function("__this", ...keys, body);
+		const ret = function (stash) { return compiled.call(null, me.context = { escapeHTML: me.escapeHTML, line: 1, ret : '', stash: stash }, ...keys.map(key => stash[key])) };
+		return (ret.compiled = compiled, ret.keys = keys, ret);
 	})());
 	return isArray ? me.cache.get(key) : me.cache.get(key)(data);
 }
diff --git a/misc/serialized.js b/misc/serialized.js
new file mode 100755
index 0000000..b469473
--- /dev/null
+++ b/misc/serialized.js
@@ -0,0 +1,63 @@
+#!/usr/bin/env node
+
+import assert from 'assert';
+import { template, extended } from '../lib/micro-template.js';
+import { writeFile } from 'fs/promises';
+
+const target = {
+	'main': [`
+		HEADER
+		<% wrapper('wrapper', function () { %>
+			hello <%= foo %>,
+			<% [1,2].forEach( () => { %>
+			and <%= baz %>
+			<% }) %>
+		<% }) %>
+		<% include('footer', { year: 2025 }) %>
+		FOOTER
+	`, ['foo', 'baz'] ],
+
+	'wrapper': [`
+		BEFORE CONTENT
+		<%=raw content %>
+		AFTER CONTENT
+	`, ['content'] ],
+
+	'footer': [`
+		
+

Footer content <%= year %>

+
+ `, [ 'year'] ], +} +template.get = id => target[id][0]; + +let serialized = 'const compiled = {};\n'; + +for (const [id, entry] of Object.entries(target)) { + const [_, keys] = entry; + console.log(`Compiling template: ${id}`); + const func = extended(id, keys); + const compiled = func.compiled; + serialized += `compiled['${id}'] = ` + compiled.toString().replace(/\/\/#.*/g, '') + ';\n'; + serialized += `compiled['${id}'].keys = ` + JSON.stringify(func.keys) + ';\n'; +} + +serialized += `const regexp = /[<>"'&]/;\n`; +serialized += `const escapeHTML = ` + template.escapeHTML.toString() + ';\n'; + +serialized += `const template = ` + (function (id, stash) { + const me = template; + const func = compiled[id]; + return func.call(null, me.context = { escapeHTML, line: 1, ret: '', stash }, ...func.keys.map(key => stash[key])); +}).toString() + ';\n'; + +serialized += `const extended = ` + extended.toString() + ';\n'; + +serialized += `export { template, extended };\n`; + +(async () => { + await writeFile('_serialized.js', serialized); + const { template, extended } = await import('../_serialized.js'); + const result = extended('main', { foo: 'world', baz: 'baz!' }); + console.log('render result:', result); +})(); From cb64993d608f50a43fd2f805854b44e7aa7af84b Mon Sep 17 00:00:00 2001 From: cho45 Date: Sun, 15 Jun 2025 11:24:35 +0900 Subject: [PATCH 3/9] avoid hack --- test/test.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/test/test.js b/test/test.js index 5ab93b2..7c8747a 100755 --- a/test/test.js +++ b/test/test.js @@ -9,14 +9,6 @@ import fs from 'fs'; // template.get の上書き template.get = function (id) { return fs.readFileSync('test/data-' + id + '.tmpl', 'utf-8') }; -function templateWithCompiledFunction(stringTemplate, data) { - data = Object.assign({}, data); - data.__context = {}; - stringTemplate += `\n<% __context.compiled = arguments.callee; %>`; - const result = template(stringTemplate, data); - return [result, data.__context.compiled]; -} - // --- template 基本テスト --- test('template renders with data', (t) => { const result = template('<%= foo %><%= bar %>', { foo: 'foo', bar: 'bar' }); @@ -326,8 +318,8 @@ test('template with properties script tags', (t) => { }); test('template output includes sourceMappingURL comment', (t) => { - const [_, compiledFunction] = templateWithCompiledFunction('foo bar', {}); - const compiledFunctionString = compiledFunction.toString(); + const func = template('foo bar', []); + const compiledFunctionString = func.compiled.toString(); console.log(compiledFunctionString); const match = compiledFunctionString.match(/\n\/\/\# sourceMappingURL=data:application\/json,(.+)\n/); assert(match, 'sourceMappingURL comment is present and correctly formatted'); From 2846d38c01b759810aad3324b2bcf68be23e2563 Mon Sep 17 00:00:00 2001 From: cho45 Date: Sun, 15 Jun 2025 19:33:16 +0900 Subject: [PATCH 4/9] lib/serializer.js --- bin/micro-template-serialize.js | 46 +++++++++++++++++++++++++++ lib/serializer.js | 36 +++++++++++++++++++++ misc/serialized.js | 56 --------------------------------- package.json | 8 +++++ test/serialize/footer.tmpl | 5 +++ test/serialize/main.tmpl | 11 +++++++ test/serialize/wrapper.tmpl | 5 +++ 7 files changed, 111 insertions(+), 56 deletions(-) create mode 100755 bin/micro-template-serialize.js create mode 100644 lib/serializer.js create mode 100644 test/serialize/footer.tmpl create mode 100644 test/serialize/main.tmpl create mode 100644 test/serialize/wrapper.tmpl diff --git a/bin/micro-template-serialize.js b/bin/micro-template-serialize.js new file mode 100755 index 0000000..87e6843 --- /dev/null +++ b/bin/micro-template-serialize.js @@ -0,0 +1,46 @@ +#!/usr/bin/env node + +import { serializeTemplates } from '../lib/serializer.js'; +import { promises as fs } from 'fs'; +import path from 'path'; + +// --- 引数パース --- +const args = process.argv.slice(2); +let outputFile; +let rootDir = process.cwd(); +const inputFiles = []; +for (let i = 0; i < args.length; i++) { + if (args[i] === '--output') { + outputFile = args[++i]; + } else if (args[i] === '--root') { + rootDir = args[++i]; + } else { + inputFiles.push(args[i]); + } +} +if (!outputFile || inputFiles.length === 0) { + console.error('Usage: micro-template-serialize ... --output templates.js [--root ]'); + process.exit(1); +} + +// --- テンプレートファイル読み込み --- +const templates = {}; +for (const file of inputFiles) { + // id を rootDir からの相対パス(拡張子除く)にする + const relPath = path.relative(rootDir, file); + const id = relPath.replace(path.extname(relPath), ''); + const source = await fs.readFile(file, 'utf-8'); + templates[id] = { source }; + source.replace(//g, (match, key, value) => { + templates[id][key] = JSON.parse(value); + return ''; // Remove the comment + }); + if (!templates[id].keys) { + console.warn(`Warning: Template "${id}" does not have keys defined. Please add in the template file.`); + templates[id].keys = []; + } +} + +const code = serializeTemplates(templates, { exportName: 'extended' }); +await fs.writeFile(outputFile, code); +console.log(`Wrote: ${outputFile}`); diff --git a/lib/serializer.js b/lib/serializer.js new file mode 100644 index 0000000..f1abd97 --- /dev/null +++ b/lib/serializer.js @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +import { template, extended } from './micro-template.js'; + +export function serializeTemplates(target) { + template.get = id => target[id].source;; + + let serialized = 'const compiled = {};\n'; + + for (const [id, entry] of Object.entries(target)) { + const keys = entry.keys || []; + console.log(`Compiling template: ${id}`); + const func = extended(id, keys); + const compiled = func.compiled; + serialized += `compiled['${id}'] = ` + compiled.toString().replace(/\/\/#.*/g, '') + ';\n'; + serialized += `compiled['${id}'].keys = ` + JSON.stringify(func.keys) + ';\n'; + } + + serialized += `const regexp = /[<>"'&]/;\n`; + serialized += `const escapeHTML = ` + template.escapeHTML.toString() + ';\n'; + + serialized += `const template = ` + (function (id, stash) { + const me = template; + const func = compiled[id]; + if (!func) { + throw new Error(`Template "${id}" not found.`); + } + return func.call(null, me.context = { escapeHTML, line: 1, ret: '', stash }, ...func.keys.map(key => stash[key])); + }).toString() + ';\n'; + + serialized += `const extended = ` + extended.toString() + ';\n'; + + serialized += `export { template, extended };\n`; + + return serialized; +} diff --git a/misc/serialized.js b/misc/serialized.js index b469473..871f656 100755 --- a/misc/serialized.js +++ b/misc/serialized.js @@ -1,62 +1,6 @@ #!/usr/bin/env node -import assert from 'assert'; -import { template, extended } from '../lib/micro-template.js'; -import { writeFile } from 'fs/promises'; - -const target = { - 'main': [` - HEADER - <% wrapper('wrapper', function () { %> - hello <%= foo %>, - <% [1,2].forEach( () => { %> - and <%= baz %> - <% }) %> - <% }) %> - <% include('footer', { year: 2025 }) %> - FOOTER - `, ['foo', 'baz'] ], - - 'wrapper': [` - BEFORE CONTENT - <%=raw content %> - AFTER CONTENT - `, ['content'] ], - - 'footer': [` -
-

Footer content <%= year %>

-
- `, [ 'year'] ], -} -template.get = id => target[id][0]; - -let serialized = 'const compiled = {};\n'; - -for (const [id, entry] of Object.entries(target)) { - const [_, keys] = entry; - console.log(`Compiling template: ${id}`); - const func = extended(id, keys); - const compiled = func.compiled; - serialized += `compiled['${id}'] = ` + compiled.toString().replace(/\/\/#.*/g, '') + ';\n'; - serialized += `compiled['${id}'].keys = ` + JSON.stringify(func.keys) + ';\n'; -} - -serialized += `const regexp = /[<>"'&]/;\n`; -serialized += `const escapeHTML = ` + template.escapeHTML.toString() + ';\n'; - -serialized += `const template = ` + (function (id, stash) { - const me = template; - const func = compiled[id]; - return func.call(null, me.context = { escapeHTML, line: 1, ret: '', stash }, ...func.keys.map(key => stash[key])); -}).toString() + ';\n'; - -serialized += `const extended = ` + extended.toString() + ';\n'; - -serialized += `export { template, extended };\n`; - (async () => { - await writeFile('_serialized.js', serialized); const { template, extended } = await import('../_serialized.js'); const result = extended('main', { foo: 'world', baz: 'baz!' }); console.log('render result:', result); diff --git a/package.json b/package.json index faba2d6..a59e6e9 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,14 @@ "bench": "node --expose-gc ./misc/benchmark.js", "test:types": "npx tsc --lib es2015 --noEmit test/types.test.ts" }, + "bin": { + "micro-template-serialize": "./bin/micro-template-serialize.js" + }, + "exports": { + ".": "./lib/micro-template.js", + "./micro-template": "./lib/micro-template.js", + "./serializer": "./lib/serializer.js" + }, "keywords": [ "template", "engine", diff --git a/test/serialize/footer.tmpl b/test/serialize/footer.tmpl new file mode 100644 index 0000000..18a9674 --- /dev/null +++ b/test/serialize/footer.tmpl @@ -0,0 +1,5 @@ + + +
+

Footer content <%= year %>

+
diff --git a/test/serialize/main.tmpl b/test/serialize/main.tmpl new file mode 100644 index 0000000..872f478 --- /dev/null +++ b/test/serialize/main.tmpl @@ -0,0 +1,11 @@ + + +HEADER +<% wrapper('wrapper', function () { %> + hello <%= foo %>, + <% [1,2].forEach( () => { %> + and <%= baz %> + <% }) %> +<% }) %> +<% include('footer', { year: 2025 }) %> +FOOTER diff --git a/test/serialize/wrapper.tmpl b/test/serialize/wrapper.tmpl new file mode 100644 index 0000000..2bfb4ba --- /dev/null +++ b/test/serialize/wrapper.tmpl @@ -0,0 +1,5 @@ + + +BEFORE CONTENT +<%=raw content %> +AFTER CONTENT From 4b0151a9b97d333a599ccedb018f81b8d8131eed Mon Sep 17 00:00:00 2001 From: cho45 Date: Sun, 15 Jun 2025 20:17:36 +0900 Subject: [PATCH 5/9] =?UTF-8?q?"/"=20=E3=82=92=20name=20=E3=81=A8=E3=81=97?= =?UTF-8?q?=E3=81=A6=E8=A8=B1=E5=8F=AF=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/micro-template.js | 2 +- misc/serialized.js | 8 +++----- test/serializer.test.js | 26 ++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 test/serializer.test.js diff --git a/lib/micro-template.js b/lib/micro-template.js index e209a6c..70c6028 100644 --- a/lib/micro-template.js +++ b/lib/micro-template.js @@ -6,7 +6,7 @@ const template = function (id, data) { if (arguments.length < 2) throw new Error('template() must be called with (template, data)'); const me = template, isArray = Array.isArray(data), keys = isArray ? data : Object.keys(data || {}), key = `data:${id}:${keys.sort().join(':')}`; if (!me.cache.has(key)) me.cache.set(key, (function () { - let name = id, string = /^[\w\-]+$/.test(id) ? me.get(id): (name = `template-${Math.random().toString(36).slice(2)}`, id); // no warnings + let name = id, string = /^[/\w\-]+$/.test(id) ? me.get(id): (name = `template-${Math.random().toString(36).slice(2)}`, id); // no warnings let line = 1; const body = ( `try {` + diff --git a/misc/serialized.js b/misc/serialized.js index 871f656..b324985 100755 --- a/misc/serialized.js +++ b/misc/serialized.js @@ -1,7 +1,5 @@ #!/usr/bin/env node +import { extended as template } from '../_serialized.js'; -(async () => { - const { template, extended } = await import('../_serialized.js'); - const result = extended('main', { foo: 'world', baz: 'baz!' }); - console.log('render result:', result); -})(); +const result = template('main', { foo: 'world', baz: 'baz!' }); +console.log('render result:', result); diff --git a/test/serializer.test.js b/test/serializer.test.js new file mode 100644 index 0000000..da5533e --- /dev/null +++ b/test/serializer.test.js @@ -0,0 +1,26 @@ +import assert from 'assert'; +import { test } from 'node:test'; +import { serializeTemplates } from '../lib/serializer.js'; +import { writeFile } from 'fs/promises'; +import { tmpdir } from 'os'; +import { join } from 'path'; + +const templates = { + 'main': { + source: 'Hello <%= name %>!', + keys: ['name'] + }, + 'sub/partial': { + source: '<%= value %>', + keys: ['value'] + } +}; + +test('serializeTemplates: ESM code can be imported and used', async (t) => { + const code = serializeTemplates(templates, { exportName: 'extended' }); + const outFile = join(tmpdir(), 'generated-templates-' + Date.now() + '.mjs'); + await writeFile(outFile, code); + const { extended: importedExtended } = await import('file://' + outFile + '?t=' + Date.now()); + assert.strictEqual(importedExtended('main', { name: 'world' }), 'Hello world!'); + assert.strictEqual(importedExtended('sub/partial', { value: 'X' }), 'X'); +}); From 908594836ec4f08810a3091f4ac9153c26afc0b3 Mon Sep 17 00:00:00 2001 From: cho45 Date: Sun, 15 Jun 2025 21:24:02 +0900 Subject: [PATCH 6/9] add usage --- bin/micro-template-serialize.js | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/bin/micro-template-serialize.js b/bin/micro-template-serialize.js index 87e6843..4ac96a3 100755 --- a/bin/micro-template-serialize.js +++ b/bin/micro-template-serialize.js @@ -4,8 +4,45 @@ import { serializeTemplates } from '../lib/serializer.js'; import { promises as fs } from 'fs'; import path from 'path'; +const USAGE = ` +Usage: micro-template-serialize ... --output [--root ] + +Purpose: + Reads one or more template files, extracts their content and meta information + (such as keys defined in ), and serializes them into a JavaScript file. + The template ID is determined by the relative path from the root directory (without extension). + + This tool is especially useful for environments where dynamic function generation + (such as with new Function()) is not allowed, as it outputs pre-serialized ESM modules. + +Arguments: + ... List of template files to serialize. + --output Output JavaScript file (required). + --root Root directory for template IDs (default: current directory). + --help, -h Show this help message. + +Example: + micro-template-serialize test/data-test1.tmpl test/data-fizzbuzz.tmpl --output templates.js --root test + + And this will generate a file named 'templates.js' in the current directory. + You can then import the generated module in your JavaScript code: + + import { extended as template } from './templates.js'; + const result = template('main', { foo: 'world', baz: 'baz!' }); + console.log('render result:', result); + +Notes: + - If a template does not contain a comment, a warning will be shown. + - The output file will contain the serialized templates using the serializeTemplates function. + - The output file is an ESM module (use 'import' to load it). +`; + // --- 引数パース --- const args = process.argv.slice(2); +if (args.includes('--help') || args.includes('-h')) { + console.log(USAGE); + process.exit(0); +} let outputFile; let rootDir = process.cwd(); const inputFiles = []; From c5241388cd9168b626c7e32a4d5c66ab12eb9a4b Mon Sep 17 00:00:00 2001 From: cho45 Date: Sun, 15 Jun 2025 21:45:26 +0900 Subject: [PATCH 7/9] add tests --- lib/serializer.js | 3 +- test/serializer.test.js | 117 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/lib/serializer.js b/lib/serializer.js index f1abd97..34e0d25 100644 --- a/lib/serializer.js +++ b/lib/serializer.js @@ -3,7 +3,8 @@ import { template, extended } from './micro-template.js'; export function serializeTemplates(target) { - template.get = id => target[id].source;; + template.get = id => target[id].source; + template.cache.clear(); let serialized = 'const compiled = {};\n'; diff --git a/test/serializer.test.js b/test/serializer.test.js index da5533e..d462b38 100644 --- a/test/serializer.test.js +++ b/test/serializer.test.js @@ -24,3 +24,120 @@ test('serializeTemplates: ESM code can be imported and used', async (t) => { assert.strictEqual(importedExtended('main', { name: 'world' }), 'Hello world!'); assert.strictEqual(importedExtended('sub/partial', { value: 'X' }), 'X'); }); + +test('serializeTemplates: handles empty keys and missing keys meta', async (t) => { + const templates = { + 'noKeys': { source: 'foo' }, + 'emptyKeys': { source: 'bar', keys: [] }, + }; + const code = serializeTemplates(templates, { exportName: 'extended' }); + const outFile = join(tmpdir(), 'generated-templates-nokeys-' + Date.now() + '.mjs'); + await writeFile(outFile, code); + const { extended } = await import('file://' + outFile + '?t=' + Date.now()); + assert.strictEqual(extended('noKeys', {}), 'foo'); + assert.strictEqual(extended('emptyKeys', {}), 'bar'); +}); + +test('serializeTemplates: template id is path without extension', async (t) => { + const templates = { + 'foo/bar/baz': { source: 'baz', keys: [] } + }; + const code = serializeTemplates(templates, { exportName: 'extended' }); + assert.match(code, /'foo\/bar\/baz'/); +}); + +test('serializeTemplates: handles multiple meta comments', async (t) => { + const templates = { + 'multi': { + source: 'foo ', + keys: ['x'], + description: 'desc' + } + }; + const code = serializeTemplates(templates, { exportName: 'extended' }); + assert.match(code, /description/); + assert.match(code, /keys/); +}); + +test('serializeTemplates: handles invalid meta JSON gracefully', async (t) => { + const templates = { + 'invalid': { + source: 'foo ', + keys: [] + } + }; + const code = serializeTemplates(templates, { exportName: 'extended' }); + assert.match(code, /invalid/); +}); + +test('serializeTemplates: works with nested template ids', async (t) => { + const templates = { + 'a/b/c': { source: 'nested', keys: [] } + }; + const code = serializeTemplates(templates, { exportName: 'extended' }); + const outFile = join(tmpdir(), 'generated-templates-nested-' + Date.now() + '.mjs'); + await writeFile(outFile, code); + const { extended } = await import('file://' + outFile + '?t=' + Date.now()); + assert.strictEqual(extended('a/b/c', {}), 'nested'); +}); + +test('serializeTemplates: missing variable is undefined string', async (t) => { + const templates = { + 'main': { source: 'Hello <%= name %>!', keys: ['name'] } + }; + const code = serializeTemplates(templates, { exportName: 'extended' }); + const outFile = join(tmpdir(), 'generated-templates-missingvar-' + Date.now() + '.mjs'); + await writeFile(outFile, code); + const { extended } = await import('file://' + outFile + '?t=' + Date.now()); + assert.strictEqual(extended('main', {}), 'Hello undefined!'); +}); + +test('serializeTemplates: extra variables are ignored', async (t) => { + const templates = { + 'main': { source: 'Hello <%= name %>!', keys: ['name'] } + }; + const code = serializeTemplates(templates, { exportName: 'extended' }); + const outFile = join(tmpdir(), 'generated-templates-extravars-' + Date.now() + '.mjs'); + await writeFile(outFile, code); + const { extended } = await import('file://' + outFile + '?t=' + Date.now()); + assert.strictEqual(extended('main', { name: 'world', foo: 'bar' }), 'Hello world!'); +}); + +test('serializeTemplates: wrapper template renders content', async (t) => { + const templates = { + 'wrapper': { + source: 'BEFORE CONTENT <%= content %> AFTER CONTENT', + keys: ['content'] + }, + 'main': { + source: 'BEFORE WRAPPER <% wrapper("wrapper", function () { %> Hello, <%= name %>! <% }) %> AFTER WRAPPER', + keys: ['name'] + } + }; + const code = serializeTemplates(templates, { exportName: 'extended' }); + const outFile = join(tmpdir(), 'generated-templates-wrapper-' + Date.now() + '.mjs'); + await writeFile(outFile, code); + const { extended } = await import('file://' + outFile + '?t=' + Date.now()); + assert.strictEqual(extended('main', { name: 'foobar' }), 'BEFORE WRAPPER BEFORE CONTENT Hello, foobar! AFTER CONTENT AFTER WRAPPER'); +}); + +test('serializeTemplates: include renders template', async (t) => { + const templates = { + 'other': { + source: '
<%= name %>
', + keys: ['name'] + }, + 'main': { + source: 'BEFORE <% include("other", { name: "foobar" }) %> AFTER', + keys: [] + } + }; + const code = serializeTemplates(templates, { exportName: 'extended' }); + const outFile = join(tmpdir(), 'generated-templates-include-' + Date.now() + '.mjs'); + await writeFile(outFile, code); + const { extended } = await import('file://' + outFile + '?t=' + Date.now()); + assert.strictEqual( + extended('main', {}), + 'BEFORE
foobar
AFTER' + ); +}); From 7809ca2c9967fefd7f9b38e6baa55025687b614a Mon Sep 17 00:00:00 2001 From: cho45 Date: Sun, 15 Jun 2025 22:46:23 +0900 Subject: [PATCH 8/9] update readme --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index 2d3b426..837eb4d 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,42 @@ summary 23.28x faster than ejs.render ``` +## micro-template-serialize + +`micro-template-serialize` is a CLI tool for precompiling multiple template files into a single ESM (ECMAScript Module) JavaScript file. This is especially useful for environments where dynamic function generation (such as with `new Function()`) is not allowed, or for delivering precompiled templates to browsers or serverless environments. + +### Why use it? +- **Security:** Avoids the use of `new Function()` at runtime, which is often restricted in secure or serverless environments. +- **Performance:** Templates are precompiled, so rendering is fast and does not require parsing or compiling templates at runtime. +- **Convenience:** Bundles multiple templates into a single importable module. + +### Usage + +```sh +micro-template-serialize ... --output [--root ] +``` + +- ` ...` : List of template files to serialize. +- `--output ` : Output JavaScript file (required). +- `--root ` : Root directory for template IDs (default: current directory). + +#### Example + +```sh +micro-template-serialize test/data-test1.tmpl test/data-fizzbuzz.tmpl --output templates.js --root test +``` + +This will generate a file named `templates.js` in the current directory. You can then import the generated module in your JavaScript code: + +```js +import { extended as template } from './templates.js'; +const result = template('main', { foo: 'world', baz: 'baz!' }); +console.log('render result:', result); +``` + +- If a template does not contain a `` comment, a warning will be shown. +- The output file is an ESM module (use `import` to load it). + LICENSE ------- From 6fa06a1d7a137414c24aa52ab52c257d9faf6647 Mon Sep 17 00:00:00 2001 From: cho45 Date: Sun, 15 Jun 2025 22:57:13 +0900 Subject: [PATCH 9/9] format --- bin/micro-template-serialize.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/bin/micro-template-serialize.js b/bin/micro-template-serialize.js index 4ac96a3..1373603 100755 --- a/bin/micro-template-serialize.js +++ b/bin/micro-template-serialize.js @@ -40,24 +40,25 @@ Notes: // --- 引数パース --- const args = process.argv.slice(2); if (args.includes('--help') || args.includes('-h')) { - console.log(USAGE); - process.exit(0); + console.log(USAGE); + process.exit(0); } let outputFile; let rootDir = process.cwd(); const inputFiles = []; for (let i = 0; i < args.length; i++) { - if (args[i] === '--output') { - outputFile = args[++i]; - } else if (args[i] === '--root') { - rootDir = args[++i]; - } else { - inputFiles.push(args[i]); - } + if (args[i] === '--output') { + outputFile = args[++i]; + } else + if (args[i] === '--root') { + rootDir = args[++i]; + } else { + inputFiles.push(args[i]); + } } if (!outputFile || inputFiles.length === 0) { - console.error('Usage: micro-template-serialize ... --output templates.js [--root ]'); - process.exit(1); + console.error('Usage: micro-template-serialize ... --output templates.js [--root ]'); + process.exit(1); } // --- テンプレートファイル読み込み --- @@ -78,6 +79,6 @@ for (const file of inputFiles) { } } -const code = serializeTemplates(templates, { exportName: 'extended' }); +const code = serializeTemplates(templates); await fs.writeFile(outputFile, code); console.log(`Wrote: ${outputFile}`);