Switch to ESM (#2138)
This commit is contained in:
+1
-1
@@ -116,7 +116,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["tests/bench/**/*.mjs"],
|
||||
"files": ["tests/bench/**/*.js"],
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"$schema": "https://swc.rs/schema.json",
|
||||
"module": {
|
||||
"type": "commonjs",
|
||||
"importInterop": "swc"
|
||||
},
|
||||
"sourceMaps": "inline"
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { createRequire } from 'node:module';
|
||||
import { loadTemplates, cli } from '../lib/precompiler.js';
|
||||
import yargs from 'yargs';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const Precompiler = require('../dist/cjs/precompiler');
|
||||
|
||||
const parser = yargs(process.argv.slice(2))
|
||||
.usage('Precompile handlebar templates.\nUsage: $0 [template|directory]...')
|
||||
.help(false)
|
||||
@@ -19,23 +16,6 @@ const parser = yargs(process.argv.slice(2))
|
||||
type: 'string',
|
||||
description: 'Source Map File',
|
||||
})
|
||||
.option('a', {
|
||||
type: 'boolean',
|
||||
description: 'Exports amd style (require.js)',
|
||||
alias: 'amd',
|
||||
})
|
||||
.option('c', {
|
||||
type: 'string',
|
||||
description: 'Exports CommonJS style, path to Handlebars module',
|
||||
alias: 'commonjs',
|
||||
default: null,
|
||||
})
|
||||
.option('h', {
|
||||
type: 'string',
|
||||
description: 'Path to handlebar.js (only valid for amd-style)',
|
||||
alias: 'handlebarPath',
|
||||
default: '',
|
||||
})
|
||||
.option('k', {
|
||||
type: 'string',
|
||||
description: 'Known helpers',
|
||||
@@ -117,7 +97,7 @@ const argv = parser.parseSync();
|
||||
argv.files = argv._;
|
||||
delete argv._;
|
||||
|
||||
Precompiler.loadTemplates(argv, function (err, opts) {
|
||||
loadTemplates(argv, function (err, opts) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
@@ -127,7 +107,7 @@ Precompiler.loadTemplates(argv, function (err, opts) {
|
||||
} else {
|
||||
// cli() is async (returns a Promise), so errors would become unhandled
|
||||
// rejections. Re-throw via nextTick to surface them as uncaught exceptions.
|
||||
Promise.resolve(Precompiler.cli(opts)).catch((error) => {
|
||||
Promise.resolve(cli(opts)).catch((error) => {
|
||||
process.nextTick(() => {
|
||||
throw error;
|
||||
});
|
||||
+9
-5
@@ -5,14 +5,18 @@ import {
|
||||
Visitor,
|
||||
} from '@handlebars/parser';
|
||||
|
||||
import runtime from './handlebars.runtime';
|
||||
import runtime from './handlebars.runtime.js';
|
||||
|
||||
// Compiler imports
|
||||
import AST from './handlebars/compiler/ast';
|
||||
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
|
||||
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
|
||||
import AST from './handlebars/compiler/ast.js';
|
||||
import {
|
||||
Compiler,
|
||||
compile,
|
||||
precompile,
|
||||
} from './handlebars/compiler/compiler.js';
|
||||
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler.js';
|
||||
|
||||
import noConflict from './handlebars/no-conflict';
|
||||
import noConflict from './handlebars/no-conflict.js';
|
||||
|
||||
let _create = runtime.create;
|
||||
function create() {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import * as base from './handlebars/base';
|
||||
import * as base from './handlebars/base.js';
|
||||
|
||||
// Each of these augment the Handlebars object. No need to setup here.
|
||||
// (This is done to easily share code between commonjs and browse envs)
|
||||
import SafeString from './handlebars/safe-string';
|
||||
import * as Utils from './handlebars/utils';
|
||||
import * as runtime from './handlebars/runtime';
|
||||
// (This is done to easily share code between module systems and browser envs)
|
||||
import SafeString from './handlebars/safe-string.js';
|
||||
import * as Utils from './handlebars/utils.js';
|
||||
import * as runtime from './handlebars/runtime.js';
|
||||
|
||||
import noConflict from './handlebars/no-conflict';
|
||||
import noConflict from './handlebars/no-conflict.js';
|
||||
|
||||
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
|
||||
function create() {
|
||||
@@ -37,4 +37,15 @@ noConflict(inst);
|
||||
|
||||
inst['default'] = inst;
|
||||
|
||||
// Named re-exports for CJS interop.
|
||||
// See the comment in lib/index.js for the full explanation. In short:
|
||||
// require('handlebars/runtime').COMPILER_REVISION must be directly accessible
|
||||
// for tools like handlebars-loader that compare compiler and runtime revisions.
|
||||
export {
|
||||
VERSION,
|
||||
COMPILER_REVISION,
|
||||
LAST_COMPATIBLE_COMPILER_REVISION,
|
||||
REVISION_CHANGES,
|
||||
} from './handlebars/base.js';
|
||||
|
||||
export default inst;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { createFrame, extend, toString } from './utils';
|
||||
import { registerDefaultHelpers } from './helpers';
|
||||
import { registerDefaultDecorators } from './decorators';
|
||||
import logger from './logger';
|
||||
import { resetLoggedProperties } from './internal/proto-access';
|
||||
import { createFrame, extend, toString } from './utils.js';
|
||||
import { registerDefaultHelpers } from './helpers.js';
|
||||
import { registerDefaultDecorators } from './decorators.js';
|
||||
import logger from './logger.js';
|
||||
import { resetLoggedProperties } from './internal/proto-access.js';
|
||||
|
||||
export const VERSION = '4.7.7';
|
||||
export const COMPILER_REVISION = 8;
|
||||
|
||||
@@ -1,56 +1,5 @@
|
||||
/* global define */
|
||||
import { isArray } from '../utils';
|
||||
|
||||
let SourceNode;
|
||||
|
||||
try {
|
||||
/* v8 ignore next */
|
||||
if (typeof define !== 'function' || !define.amd) {
|
||||
// We don't support this in AMD environments. For these environments, we assume that
|
||||
// they are running on the browser and thus have no need for the source-map library.
|
||||
// The variable indirection prevents bundlers from statically resolving and bundling
|
||||
// source-map (which requires Node-built-ins). The stub SourceNode below handles
|
||||
// browser/bundled usage. Bundlers may emit a "Critical dependency" warning — this
|
||||
// is expected and harmless.
|
||||
let mod = 'source-map';
|
||||
let SourceMap = require(mod);
|
||||
SourceNode = SourceMap.SourceNode;
|
||||
}
|
||||
// oxlint-disable-next-line no-unused-vars -- Babel 5 requires named catch param
|
||||
} catch (err) {
|
||||
/* NOP */
|
||||
}
|
||||
|
||||
/* v8 ignore next -- tested but not covered due to dist build */
|
||||
if (!SourceNode) {
|
||||
SourceNode = function (line, column, srcFile, chunks) {
|
||||
this.src = '';
|
||||
if (chunks) {
|
||||
this.add(chunks);
|
||||
}
|
||||
};
|
||||
/* v8 ignore next */
|
||||
SourceNode.prototype = {
|
||||
add: function (chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src += chunks;
|
||||
},
|
||||
prepend: function (chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src = chunks + this.src;
|
||||
},
|
||||
toStringWithSourceMap: function () {
|
||||
return { code: this.toString() };
|
||||
},
|
||||
toString: function () {
|
||||
return this.src;
|
||||
},
|
||||
};
|
||||
}
|
||||
import { isArray } from '../utils.js';
|
||||
import { SourceNode } from '#source-node';
|
||||
|
||||
function castChunk(chunk, codeGen, loc) {
|
||||
if (isArray(chunk)) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { isArray, indexOf, extend } from '../utils';
|
||||
import AST from './ast';
|
||||
import { isArray, indexOf, extend } from '../utils.js';
|
||||
import AST from './ast.js';
|
||||
|
||||
const slice = [].slice;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
|
||||
import { isArray } from '../utils';
|
||||
import CodeGen from './code-gen';
|
||||
import { COMPILER_REVISION, REVISION_CHANGES } from '../base.js';
|
||||
import { isArray } from '../utils.js';
|
||||
import CodeGen from './code-gen.js';
|
||||
|
||||
function Literal(value) {
|
||||
this.value = value;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { isArray } from '../utils.js';
|
||||
|
||||
// Lightweight stub for browser environments where the source-map package
|
||||
// (which depends on Node.js built-ins) is not available.
|
||||
export function SourceNode(line, column, srcFile, chunks) {
|
||||
this.src = '';
|
||||
if (chunks) {
|
||||
this.add(chunks);
|
||||
}
|
||||
}
|
||||
|
||||
SourceNode.prototype = {
|
||||
add(chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src += chunks;
|
||||
},
|
||||
prepend(chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src = chunks + this.src;
|
||||
},
|
||||
toStringWithSourceMap() {
|
||||
return { code: this.toString() };
|
||||
},
|
||||
toString() {
|
||||
return this.src;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { SourceNode } from 'source-map';
|
||||
@@ -1,4 +1,4 @@
|
||||
import registerInline from './decorators/inline';
|
||||
import registerInline from './decorators/inline.js';
|
||||
|
||||
export function registerDefaultDecorators(instance) {
|
||||
registerInline(instance);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { extend } from '../utils';
|
||||
import { extend } from '../utils.js';
|
||||
|
||||
export default function (instance) {
|
||||
instance.registerDecorator(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import registerBlockHelperMissing from './helpers/block-helper-missing';
|
||||
import registerEach from './helpers/each';
|
||||
import registerHelperMissing from './helpers/helper-missing';
|
||||
import registerIf from './helpers/if';
|
||||
import registerLog from './helpers/log';
|
||||
import registerLookup from './helpers/lookup';
|
||||
import registerWith from './helpers/with';
|
||||
import registerBlockHelperMissing from './helpers/block-helper-missing.js';
|
||||
import registerEach from './helpers/each.js';
|
||||
import registerHelperMissing from './helpers/helper-missing.js';
|
||||
import registerIf from './helpers/if.js';
|
||||
import registerLog from './helpers/log.js';
|
||||
import registerLookup from './helpers/lookup.js';
|
||||
import registerWith from './helpers/with.js';
|
||||
|
||||
export function registerDefaultHelpers(instance) {
|
||||
registerBlockHelperMissing(instance);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isArray } from '../utils';
|
||||
import { isArray } from '../utils.js';
|
||||
|
||||
export default function (instance) {
|
||||
instance.registerHelper('blockHelperMissing', function (context, options) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { createFrame, isArray, isFunction, isMap, isSet } from '../utils';
|
||||
import { createFrame, isArray, isFunction, isMap, isSet } from '../utils.js';
|
||||
|
||||
export default function (instance) {
|
||||
instance.registerHelper('each', function (context, options) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { isEmpty, isFunction } from '../utils';
|
||||
import { isEmpty, isFunction } from '../utils.js';
|
||||
|
||||
export default function (instance) {
|
||||
instance.registerHelper('if', function (conditional, options) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { isEmpty, isFunction } from '../utils';
|
||||
import { isEmpty, isFunction } from '../utils.js';
|
||||
|
||||
export default function (instance) {
|
||||
instance.registerHelper('with', function (context, options) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { extend } from '../utils';
|
||||
import logger from '../logger';
|
||||
import { extend } from '../utils.js';
|
||||
import logger from '../logger.js';
|
||||
|
||||
const loggedProperties = Object.create(null);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { indexOf } from './utils';
|
||||
import { indexOf } from './utils.js';
|
||||
|
||||
let logger = {
|
||||
methodMap: ['debug', 'info', 'warn', 'error'],
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import * as Utils from './utils';
|
||||
import * as Utils from './utils.js';
|
||||
import {
|
||||
COMPILER_REVISION,
|
||||
createFrame,
|
||||
LAST_COMPATIBLE_COMPILER_REVISION,
|
||||
REVISION_CHANGES,
|
||||
} from './base';
|
||||
import { moveHelperToHooks } from './helpers';
|
||||
import { wrapHelper } from './internal/wrapHelper';
|
||||
} from './base.js';
|
||||
import { moveHelperToHooks } from './helpers.js';
|
||||
import { wrapHelper } from './internal/wrapHelper.js';
|
||||
import {
|
||||
createProtoAccessControl,
|
||||
resultIsAllowed,
|
||||
} from './internal/proto-access';
|
||||
} from './internal/proto-access.js';
|
||||
|
||||
export function checkRevision(compilerInfo) {
|
||||
const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,
|
||||
|
||||
+44
-23
@@ -1,25 +1,46 @@
|
||||
// USAGE:
|
||||
// var handlebars = require('handlebars');
|
||||
/* eslint-disable no-var */
|
||||
import handlebars from './handlebars.js';
|
||||
import { PrintVisitor, print } from '@handlebars/parser';
|
||||
|
||||
// var local = handlebars.create();
|
||||
handlebars.PrintVisitor = PrintVisitor;
|
||||
handlebars.print = print;
|
||||
|
||||
var handlebars = require('../dist/cjs/handlebars')['default'];
|
||||
|
||||
var parser = require('@handlebars/parser');
|
||||
handlebars.PrintVisitor = parser.PrintVisitor;
|
||||
handlebars.print = parser.print;
|
||||
|
||||
module.exports = handlebars;
|
||||
|
||||
// Publish a Node.js require() handler for .handlebars and .hbs files
|
||||
function extension(module, filename) {
|
||||
var fs = require('fs');
|
||||
var templateString = fs.readFileSync(filename, 'utf8');
|
||||
module.exports = handlebars.compile(templateString);
|
||||
}
|
||||
/* v8 ignore next 4 */
|
||||
if (typeof require !== 'undefined' && require.extensions) {
|
||||
require.extensions['.handlebars'] = extension;
|
||||
require.extensions['.hbs'] = extension;
|
||||
}
|
||||
// Named exports for CJS interop.
|
||||
//
|
||||
// When Node.js (v22+) runs require() on an ESM module, it returns the module
|
||||
// namespace object — only named exports become direct properties. Without these,
|
||||
// require('handlebars') would return { default: inst } and calls like
|
||||
// Handlebars.precompile() or Handlebars.COMPILER_REVISION would be undefined.
|
||||
//
|
||||
// Tools like handlebars-loader rely on these being directly accessible via
|
||||
// require('handlebars').create(), require('handlebars').COMPILER_REVISION, etc.
|
||||
export const {
|
||||
create,
|
||||
compile,
|
||||
precompile,
|
||||
parse,
|
||||
parseWithoutProcessing,
|
||||
COMPILER_REVISION,
|
||||
LAST_COMPATIBLE_COMPILER_REVISION,
|
||||
REVISION_CHANGES,
|
||||
VERSION,
|
||||
AST,
|
||||
Compiler,
|
||||
JavaScriptCompiler,
|
||||
Parser,
|
||||
Visitor,
|
||||
SafeString,
|
||||
Exception,
|
||||
Utils,
|
||||
escapeExpression,
|
||||
VM,
|
||||
template,
|
||||
log,
|
||||
registerHelper,
|
||||
unregisterHelper,
|
||||
registerPartial,
|
||||
unregisterPartial,
|
||||
registerDecorator,
|
||||
unregisterDecorator,
|
||||
} = handlebars;
|
||||
export { PrintVisitor, print };
|
||||
export default handlebars;
|
||||
|
||||
+16
-45
@@ -1,11 +1,11 @@
|
||||
/* eslint-disable no-console */
|
||||
import Async from 'neo-async';
|
||||
import fs from 'fs';
|
||||
import Handlebars from './handlebars';
|
||||
import Handlebars from './handlebars.js';
|
||||
import { basename } from 'path';
|
||||
import { SourceMapConsumer, SourceNode } from 'source-map';
|
||||
|
||||
module.exports.loadTemplates = function (opts, callback) {
|
||||
export function loadTemplates(opts, callback) {
|
||||
loadStrings(opts, function (err, strings) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
@@ -20,7 +20,7 @@ module.exports.loadTemplates = function (opts, callback) {
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function loadStrings(opts, callback) {
|
||||
let strings = arrayCast(opts.string),
|
||||
@@ -152,7 +152,7 @@ function loadFiles(opts, callback) {
|
||||
);
|
||||
}
|
||||
|
||||
module.exports.cli = async function (opts) {
|
||||
export async function cli(opts) {
|
||||
if (opts.version) {
|
||||
console.log(Handlebars.VERSION);
|
||||
return;
|
||||
@@ -176,12 +176,7 @@ module.exports.cli = async function (opts) {
|
||||
}
|
||||
|
||||
// Force simple mode if we have only one template and it's unnamed.
|
||||
if (
|
||||
!opts.amd &&
|
||||
!opts.commonjs &&
|
||||
opts.templates.length === 1 &&
|
||||
!opts.templates[0].name
|
||||
) {
|
||||
if (opts.templates.length === 1 && !opts.templates[0].name) {
|
||||
opts.simple = true;
|
||||
}
|
||||
|
||||
@@ -200,17 +195,7 @@ module.exports.cli = async function (opts) {
|
||||
|
||||
let output = new SourceNode();
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
output.add(
|
||||
"define(['" +
|
||||
opts.handlebarPath +
|
||||
'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'
|
||||
);
|
||||
} else if (opts.commonjs) {
|
||||
output.add('var Handlebars = require("' + opts.commonjs + '");');
|
||||
} else {
|
||||
output.add('(function() {\n');
|
||||
}
|
||||
output.add('(function() {\n');
|
||||
output.add(' var template = Handlebars.template, templates = ');
|
||||
if (opts.namespace) {
|
||||
output.add(opts.namespace);
|
||||
@@ -253,9 +238,6 @@ module.exports.cli = async function (opts) {
|
||||
throw new Handlebars.Exception('Name missing for template');
|
||||
}
|
||||
|
||||
if (opts.amd && !multiple) {
|
||||
output.add('return ');
|
||||
}
|
||||
output.add([
|
||||
objectName,
|
||||
"['",
|
||||
@@ -269,14 +251,7 @@ module.exports.cli = async function (opts) {
|
||||
|
||||
// Output the content
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
if (multiple) {
|
||||
output.add(['return ', objectName, ';\n']);
|
||||
}
|
||||
output.add('});');
|
||||
} else if (!opts.commonjs) {
|
||||
output.add('})();');
|
||||
}
|
||||
output.add('})();');
|
||||
}
|
||||
|
||||
if (opts.map) {
|
||||
@@ -287,7 +262,7 @@ module.exports.cli = async function (opts) {
|
||||
output.map = output.map + '';
|
||||
|
||||
if (opts.min) {
|
||||
output = minify(output, opts.map);
|
||||
output = await minify(output, opts.map);
|
||||
}
|
||||
|
||||
if (opts.map) {
|
||||
@@ -300,7 +275,7 @@ module.exports.cli = async function (opts) {
|
||||
} else {
|
||||
console.log(output);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function arrayCast(value) {
|
||||
value = value != null ? value : [];
|
||||
@@ -313,29 +288,25 @@ function arrayCast(value) {
|
||||
/**
|
||||
* Run uglify to minify the compiled template, if uglify exists in the dependencies.
|
||||
*
|
||||
* We are using `require` instead of `import` here, because es6-modules do not allow
|
||||
* dynamic imports and uglify-js is an optional dependency. Since we are inside NodeJS here, this
|
||||
* should not be a problem.
|
||||
*
|
||||
* @param {string} output the compiled template
|
||||
* @param {string} sourceMapFile the file to write the source map to.
|
||||
*/
|
||||
function minify(output, sourceMapFile) {
|
||||
async function minify(output, sourceMapFile) {
|
||||
let uglify;
|
||||
try {
|
||||
// Try to resolve uglify-js in order to see if it does exist
|
||||
require.resolve('uglify-js');
|
||||
uglify = await import('uglify-js');
|
||||
// Handle both default and named exports
|
||||
uglify = uglify.default || uglify;
|
||||
} catch (e) {
|
||||
if (e.code !== 'MODULE_NOT_FOUND') {
|
||||
// Something else seems to be wrong
|
||||
if (e.code !== 'ERR_MODULE_NOT_FOUND' && e.code !== 'MODULE_NOT_FOUND') {
|
||||
throw e;
|
||||
}
|
||||
// it does not exist!
|
||||
console.error(
|
||||
'Code minimization is disabled due to missing uglify-js dependency'
|
||||
);
|
||||
return output;
|
||||
}
|
||||
return require('uglify-js').minify(output.code, {
|
||||
return uglify.minify(output.code, {
|
||||
sourceMap: {
|
||||
content: output.map,
|
||||
url: sourceMapFile,
|
||||
|
||||
Generated
+12
-3468
File diff suppressed because it is too large
Load Diff
+25
-22
@@ -16,26 +16,40 @@
|
||||
"url": "https://github.com/handlebars-lang/handlebars.js.git"
|
||||
},
|
||||
"bin": {
|
||||
"handlebars": "bin/handlebars.mjs"
|
||||
"handlebars": "bin/handlebars.js"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"dist/*.js",
|
||||
"dist/cjs/**/*.js",
|
||||
"lib",
|
||||
"release-notes.md",
|
||||
"runtime.js",
|
||||
"types/*.d.ts",
|
||||
"runtime.d.ts"
|
||||
],
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
"browser": "./dist/cjs/handlebars.js",
|
||||
"types": "types/index.d.ts",
|
||||
"imports": {
|
||||
"#source-node": {
|
||||
"node": "./lib/handlebars/compiler/source-node.node.js",
|
||||
"default": "./lib/handlebars/compiler/source-node.browser.js"
|
||||
}
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"./runtime": {
|
||||
"types": "./runtime.d.ts",
|
||||
"default": "./lib/handlebars.runtime.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true});require('fs').rmSync('tmp',{recursive:true,force:true})\"",
|
||||
"build:cjs": "swc lib --out-dir dist/cjs --strip-leading-paths --ignore \"**/index.js\"",
|
||||
"build:bundles": "rspack build",
|
||||
"build": "npm run clean && npm run build:cjs && npm run build:bundles",
|
||||
"clean": "node --input-type=module -e \"import fs from 'fs';fs.rmSync('dist',{recursive:true,force:true});fs.rmSync('tmp',{recursive:true,force:true})\"",
|
||||
"build": "npm run clean && rspack build",
|
||||
"release": "npm run build",
|
||||
"publish:aws": "npm run build && npm run test:tasks && node tasks/publish-to-aws.js",
|
||||
"format": "oxfmt --write . && oxlint --fix .",
|
||||
@@ -52,14 +66,14 @@
|
||||
"test:browser-smoke": "playwright test --config tests/browser/playwright.config.js",
|
||||
"test:serve": "npx serve -l 9999 .",
|
||||
"test:integration": "npm run build && ./tests/integration/run-integration-tests.sh",
|
||||
"bench": "node tests/bench/perf.mjs",
|
||||
"bench:compare": "node tests/bench/compare.mjs",
|
||||
"bench:size": "node tests/bench/size.mjs",
|
||||
"bench": "node tests/bench/perf.js",
|
||||
"bench:compare": "node tests/bench/compare.js",
|
||||
"bench:size": "node tests/bench/size.js",
|
||||
"--- combined tasks ---": "",
|
||||
"check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@handlebars/parser": "^2.1.0",
|
||||
"@handlebars/parser": "^2.2.2",
|
||||
"neo-async": "^2.6.2",
|
||||
"source-map": "^0.7.6",
|
||||
"yargs": "^18.0.0"
|
||||
@@ -69,8 +83,6 @@
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@rspack/cli": "^1.7.8",
|
||||
"@rspack/core": "^1.7.8",
|
||||
"@swc/cli": "^0.8.0",
|
||||
"@swc/core": "^1.15.18",
|
||||
"@vitest/browser": "^4.0.18",
|
||||
"@vitest/browser-playwright": "^4.0.18",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
@@ -116,14 +128,5 @@
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"jspm": {
|
||||
"main": "handlebars",
|
||||
"directories": {
|
||||
"lib": "dist/cjs"
|
||||
},
|
||||
"buildConfig": {
|
||||
"minify": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+38
-23
@@ -1,8 +1,14 @@
|
||||
const { rspack } = require('@rspack/core');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
import { rspack } from '@rspack/core';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const pkg = require('./package.json');
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const pkg = JSON.parse(
|
||||
fs.readFileSync(path.resolve(__dirname, 'package.json'), 'utf8')
|
||||
);
|
||||
const license = fs.readFileSync(path.resolve(__dirname, 'LICENSE'), 'utf8');
|
||||
const banner = `/*!
|
||||
|
||||
@@ -29,28 +35,11 @@ function createConfig(entry, filename, minimize) {
|
||||
filename,
|
||||
library: {
|
||||
name: 'Handlebars',
|
||||
type: 'umd',
|
||||
type: 'self',
|
||||
export: 'default',
|
||||
},
|
||||
globalObject: 'this',
|
||||
clean: false,
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'builtin:swc-loader',
|
||||
options: {
|
||||
jsc: {
|
||||
parser: { syntax: 'ecmascript' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
optimization: {
|
||||
minimize,
|
||||
minimizer: minimize
|
||||
@@ -71,12 +60,38 @@ function createConfig(entry, filename, minimize) {
|
||||
: [],
|
||||
},
|
||||
plugins,
|
||||
resolve: {
|
||||
extensions: ['.js', '.json'],
|
||||
mainFields: ['main'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'builtin:swc-loader',
|
||||
options: {
|
||||
jsc: {
|
||||
parser: { syntax: 'ecmascript' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
target: ['web', 'browserslist'],
|
||||
devtool: false,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = [
|
||||
export default [
|
||||
createConfig('./lib/handlebars.js', 'handlebars.js', false),
|
||||
createConfig('./lib/handlebars.runtime.js', 'handlebars.runtime.js', false),
|
||||
createConfig('./lib/handlebars.js', 'handlebars.min.js', true),
|
||||
|
||||
Vendored
+3
-2
@@ -1,3 +1,4 @@
|
||||
import Handlebars = require('handlebars');
|
||||
import Handlebars from 'handlebars';
|
||||
|
||||
declare module 'handlebars/runtime' {}
|
||||
declare const runtime: typeof Handlebars;
|
||||
export default runtime;
|
||||
|
||||
+3
-3
@@ -1,3 +1,3 @@
|
||||
// Create a simple path alias to allow browserify to resolve
|
||||
// the runtime on a supported path.
|
||||
module.exports = require('./dist/cjs/handlebars.runtime')['default'];
|
||||
import runtime from './lib/handlebars.runtime.js';
|
||||
|
||||
export default runtime;
|
||||
|
||||
Vendored
+6
-3
@@ -1,7 +1,10 @@
|
||||
require('./common');
|
||||
import './common.js';
|
||||
|
||||
var fs = require('fs'),
|
||||
vm = require('vm');
|
||||
import fs from 'fs';
|
||||
import vm from 'vm';
|
||||
import { createRequire } from 'module';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
global.Handlebars = 'no-conflict';
|
||||
|
||||
|
||||
Vendored
+3
-2
@@ -1,6 +1,7 @@
|
||||
require('./common');
|
||||
import './common.js';
|
||||
import Handlebars from '../../lib/index.js';
|
||||
|
||||
global.Handlebars = require('../../lib');
|
||||
global.Handlebars = Handlebars;
|
||||
|
||||
global.CompilerContext = {
|
||||
compile: function (template, options) {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
return templates['bom'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "a";
|
||||
},"useData":true});
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
return templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "";
|
||||
},"useData":true});
|
||||
});
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
define(["handlebars.runtime"],function(e){var t=(e=e.default).template;return(e.templates=e.templates||{}).empty=t({compiler:[8,">= 4.3.0"],main:function(e,t,a,n,r){return""},useData:!0})});
|
||||
@@ -1,6 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = CustomNamespace.templates = CustomNamespace.templates || {};
|
||||
return templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "";
|
||||
},"useData":true});
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
{"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "";
|
||||
},"useData":true}
|
||||
@@ -1,10 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
templates['firstTemplate'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "<div>1</div>";
|
||||
},"useData":true});
|
||||
templates['secondTemplate'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "<div>2</div>";
|
||||
},"useData":true});
|
||||
return templates;
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
(function() {
|
||||
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
var template = Handlebars.template, templates = CustomNamespace.templates = CustomNamespace.templates || {};
|
||||
templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "";
|
||||
},"useData":true});
|
||||
@@ -1,6 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
return templates['artifacts/partial.template'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "<div>Test Partial</div>";
|
||||
},"useData":true});
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
define(['some-path/handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
return templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "";
|
||||
},"useData":true});
|
||||
});
|
||||
+19
-22
@@ -1,25 +1,22 @@
|
||||
Precompile handlebar templates.
|
||||
Usage: handlebars.mjs [template|directory]...
|
||||
Usage: handlebars.js [template|directory]...
|
||||
|
||||
Options:
|
||||
-f, --output Output File [string]
|
||||
--map Source Map File [string]
|
||||
-a, --amd Exports amd style (require.js) [boolean]
|
||||
-c, --commonjs Exports CommonJS style, path to Handlebars module [string] [default: null]
|
||||
-h, --handlebarPath Path to handlebar.js (only valid for amd-style) [string] [default: ""]
|
||||
-k, --known Known helpers [string]
|
||||
-o, --knownOnly Known helpers only [boolean]
|
||||
-m, --min Minimize output [boolean]
|
||||
-n, --namespace Template namespace [string] [default: "Handlebars.templates"]
|
||||
-s, --simple Output template function only. [boolean]
|
||||
-N, --name Name of passed string templates. Optional if running in a simple mode. Required when operating on
|
||||
multiple templates. [string]
|
||||
-i, --string Generates a template from the passed CLI argument.
|
||||
"-" is treated as a special value and causes stdin to be read for the template value. [string]
|
||||
-r, --root Template root. Base value that will be stripped from template names. [string]
|
||||
-p, --partial Compiling a partial template [boolean]
|
||||
-d, --data Include data when compiling [boolean]
|
||||
-e, --extension Template extension. [string] [default: "handlebars"]
|
||||
-b, --bom Removes the BOM (Byte Order Mark) from the beginning of the templates. [boolean]
|
||||
-v, --version Prints the current compiler version [boolean]
|
||||
--help Outputs this message [boolean]
|
||||
-f, --output Output File [string]
|
||||
--map Source Map File [string]
|
||||
-k, --known Known helpers [string]
|
||||
-o, --knownOnly Known helpers only [boolean]
|
||||
-m, --min Minimize output [boolean]
|
||||
-n, --namespace Template namespace [string] [default: "Handlebars.templates"]
|
||||
-s, --simple Output template function only. [boolean]
|
||||
-N, --name Name of passed string templates. Optional if running in a simple mode. Required when operating on
|
||||
multiple templates. [string]
|
||||
-i, --string Generates a template from the passed CLI argument.
|
||||
"-" is treated as a special value and causes stdin to be read for the template value. [string]
|
||||
-r, --root Template root. Base value that will be stripped from template names. [string]
|
||||
-p, --partial Compiling a partial template [boolean]
|
||||
-d, --data Include data when compiling [boolean]
|
||||
-e, --extension Template extension. [string] [default: "handlebars"]
|
||||
-b, --bom Removes the BOM (Byte Order Mark) from the beginning of the templates. [boolean]
|
||||
-v, --version Prints the current compiler version [boolean]
|
||||
--help Outputs this message [boolean]
|
||||
@@ -1,10 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = someNameSpace = someNameSpace || {};
|
||||
templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "";
|
||||
},"useData":true});
|
||||
templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "";
|
||||
},"useData":true});
|
||||
return templates;
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
return templates['non.default.extension'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "<div>This is a test</div>";
|
||||
},"useData":true});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
return templates['known.helpers'] = template({"1":function(container,depth0,helpers,partials,data) {
|
||||
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||
return parent[propertyName];
|
||||
}
|
||||
return undefined
|
||||
};
|
||||
return " <div>Some known helper</div>\n"
|
||||
+ ((stack1 = lookupProperty(helpers,"anotherHelper").call(depth0 != null ? depth0 : (container.nullContext || {}),true,{"name":"anotherHelper","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":3,"column":4},"end":{"line":5,"column":22}}})) != null ? stack1 : "");
|
||||
},"2":function(container,depth0,helpers,partials,data) {
|
||||
return " <div>Another known helper</div>\n";
|
||||
},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||
return parent[propertyName];
|
||||
}
|
||||
return undefined
|
||||
};
|
||||
return ((stack1 = lookupProperty(helpers,"someHelper").call(depth0 != null ? depth0 : (container.nullContext || {}),true,{"name":"someHelper","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":15}}})) != null ? stack1 : "");
|
||||
},"useData":true});
|
||||
});
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
return Handlebars.partials['partial.template'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "<div>Test Partial</div>";
|
||||
},"useData":true});
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
define(["handlebars.runtime"],function(e){var t=(e=e.default).template;return(e.templates=e.templates||{}).test=t({compiler:[8,">= 4.3.0"],main:function(e,t,a,n,i){return"<div>1</div>"},useData:!0})});
|
||||
//# sourceMappingURL=./spec/tmp/source.map.amd.txt
|
||||
+25
-114
@@ -1,15 +1,19 @@
|
||||
/* eslint-disable no-console */
|
||||
import Handlebars from '../lib/index.js';
|
||||
import * as Precompiler from '../lib/precompiler.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import uglify from 'uglify-js';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
describe('precompiler', function () {
|
||||
// NOP Under non-node environments
|
||||
if (typeof process === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var Handlebars = require('../lib'),
|
||||
Precompiler = require('../dist/cjs/precompiler'),
|
||||
fs = require('fs'),
|
||||
uglify = require('uglify-js');
|
||||
|
||||
var log,
|
||||
logFunction,
|
||||
errorLog,
|
||||
@@ -25,34 +29,6 @@ describe('precompiler', function () {
|
||||
content,
|
||||
writeFileSync;
|
||||
|
||||
/**
|
||||
* Mock the Module.prototype.require-function such that an error is thrown, when "uglify-js" is loaded.
|
||||
*
|
||||
* The function cleans up its mess when "callback" is finished
|
||||
*
|
||||
* @param {Error} loadError the error that should be thrown if uglify is loaded
|
||||
* @param {function} callback a callback-function to run when the mock is active.
|
||||
*/
|
||||
async function mockRequireUglify(loadError, callback) {
|
||||
var Module = require('module');
|
||||
var _resolveFilename = Module._resolveFilename;
|
||||
delete require.cache[require.resolve('uglify-js')];
|
||||
delete require.cache[require.resolve('../dist/cjs/precompiler')];
|
||||
Module._resolveFilename = function (request, mod) {
|
||||
if (request === 'uglify-js') {
|
||||
throw loadError;
|
||||
}
|
||||
return _resolveFilename.call(this, request, mod);
|
||||
};
|
||||
try {
|
||||
await callback();
|
||||
} finally {
|
||||
Module._resolveFilename = _resolveFilename;
|
||||
delete require.cache[require.resolve('uglify-js')];
|
||||
delete require.cache[require.resolve('../dist/cjs/precompiler')];
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
precompile = Handlebars.precompile;
|
||||
minify = uglify.minify;
|
||||
@@ -117,7 +93,10 @@ describe('precompiler', function () {
|
||||
});
|
||||
it('should throw when missing name', async function () {
|
||||
await expect(
|
||||
Precompiler.cli({ templates: [{ source: '' }], amd: true })
|
||||
Precompiler.cli({
|
||||
templates: [{ source: '' }, { source: '' }],
|
||||
hasDirectory: true,
|
||||
})
|
||||
).rejects.toThrow('Name missing for template');
|
||||
});
|
||||
it('should throw when combining simple and directories', async function () {
|
||||
@@ -140,56 +119,14 @@ describe('precompiler', function () {
|
||||
await Precompiler.cli({ templates: [{ source: '' }] });
|
||||
expect(log).toBe('simple\n');
|
||||
});
|
||||
it('should output amd templates', async function () {
|
||||
|
||||
it('should output wrapped templates', async function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
return 'wrapped';
|
||||
};
|
||||
await Precompiler.cli({ templates: [emptyTemplate], amd: true });
|
||||
expect(log).toMatch(/template\(amd\)/);
|
||||
});
|
||||
it('should output multiple amd', async function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
await Precompiler.cli({
|
||||
templates: [emptyTemplate, emptyTemplate],
|
||||
amd: true,
|
||||
namespace: 'foo',
|
||||
});
|
||||
expect(log).toMatch(/templates = foo = foo \|\|/);
|
||||
expect(log).toMatch(/return templates/);
|
||||
expect(log).toMatch(/template\(amd\)/);
|
||||
});
|
||||
it('should output amd partials', async function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
await Precompiler.cli({
|
||||
templates: [emptyTemplate],
|
||||
amd: true,
|
||||
partial: true,
|
||||
});
|
||||
expect(log).toMatch(/return Handlebars\.partials\['empty'\]/);
|
||||
expect(log).toMatch(/template\(amd\)/);
|
||||
});
|
||||
it('should output multiple amd partials', async function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
await Precompiler.cli({
|
||||
templates: [emptyTemplate, emptyTemplate],
|
||||
amd: true,
|
||||
partial: true,
|
||||
});
|
||||
expect(log).not.toMatch(/return Handlebars\.partials\[/);
|
||||
expect(log).toMatch(/template\(amd\)/);
|
||||
});
|
||||
it('should output commonjs templates', async function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'commonjs';
|
||||
};
|
||||
await Precompiler.cli({ templates: [emptyTemplate], commonjs: true });
|
||||
expect(log).toMatch(/template\(commonjs\)/);
|
||||
await Precompiler.cli({ templates: [emptyTemplate] });
|
||||
expect(log).toMatch(/template\(wrapped\)/);
|
||||
expect(log).toMatch(/\(function\(\)/);
|
||||
});
|
||||
|
||||
it('should set data flag', async function () {
|
||||
@@ -233,7 +170,7 @@ describe('precompiler', function () {
|
||||
|
||||
it('should output minimized templates', async function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
return 'iife';
|
||||
};
|
||||
uglify.minify = function () {
|
||||
return { code: 'min' };
|
||||
@@ -242,33 +179,6 @@ describe('precompiler', function () {
|
||||
expect(log).toBe('min');
|
||||
});
|
||||
|
||||
it('should omit minimization gracefully, if uglify-js is missing', async function () {
|
||||
var error = new Error("Cannot find module 'uglify-js'");
|
||||
error.code = 'MODULE_NOT_FOUND';
|
||||
await mockRequireUglify(error, async function () {
|
||||
var Precompiler = require('../dist/cjs/precompiler');
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
await Precompiler.cli({ templates: [emptyTemplate], min: true });
|
||||
expect(log).toMatch(/template\(amd\)/);
|
||||
expect(log).toMatch(/\n/);
|
||||
expect(errorLog).toMatch(/Code minimization is disabled/);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail on errors (other than missing module) while loading uglify-js', async function () {
|
||||
await mockRequireUglify(new Error('Mock Error'), async function () {
|
||||
var Precompiler = require('../dist/cjs/precompiler');
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
await expect(
|
||||
Precompiler.cli({ templates: [emptyTemplate], min: true })
|
||||
).rejects.toThrow('Mock Error');
|
||||
});
|
||||
});
|
||||
|
||||
it('should output map', async function () {
|
||||
await Precompiler.cli({ templates: [emptyTemplate], map: 'foo.js.map' });
|
||||
|
||||
@@ -367,11 +277,12 @@ describe('precompiler', function () {
|
||||
expect(opts.templates[1].source).toBe('bar');
|
||||
});
|
||||
it('should accept stdin input', async function () {
|
||||
var stdin = require('mock-stdin').stdin();
|
||||
var { stdin } = await import('mock-stdin');
|
||||
var stdinMock = stdin();
|
||||
var promise = loadTemplatesAsync({ string: '-' });
|
||||
stdin.send('fo');
|
||||
stdin.send('o');
|
||||
stdin.end();
|
||||
stdinMock.send('fo');
|
||||
stdinMock.send('o');
|
||||
stdinMock.end();
|
||||
var opts = await promise;
|
||||
expect(opts.templates[0].source).toBe('foo');
|
||||
});
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
if (typeof require !== 'undefined' && require.extensions['.handlebars']) {
|
||||
describe('Require', function () {
|
||||
it('Load .handlebars files with require()', function () {
|
||||
var template = require('./artifacts/example_1');
|
||||
expect(template).toBe(require('./artifacts/example_1.handlebars'));
|
||||
|
||||
var expected = 'foo\n';
|
||||
var result = template({ foo: 'foo' });
|
||||
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('Load .hbs files with require()', function () {
|
||||
var template = require('./artifacts/example_2');
|
||||
expect(template).toBe(require('./artifacts/example_2.hbs'));
|
||||
|
||||
var expected = 'Hello, World!\n';
|
||||
var result = template({ name: 'World' });
|
||||
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
});
|
||||
}
|
||||
+5
-4
@@ -1,8 +1,9 @@
|
||||
import { createRequire } from 'module';
|
||||
|
||||
var SourceMap, SourceMapConsumer;
|
||||
try {
|
||||
if (typeof define !== 'function' || !define.amd) {
|
||||
var SourceMap = require('source-map'),
|
||||
SourceMapConsumer = SourceMap.SourceMapConsumer;
|
||||
}
|
||||
SourceMap = createRequire(import.meta.url)('source-map');
|
||||
SourceMapConsumer = SourceMap.SourceMapConsumer;
|
||||
} catch {
|
||||
/* NOP for in browser */
|
||||
}
|
||||
|
||||
+7
-3
@@ -1,16 +1,20 @@
|
||||
import fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import path from 'path';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
describe('spec', function () {
|
||||
// NOP Under non-node environments
|
||||
if (typeof process === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var fs = require('fs');
|
||||
|
||||
var specDir = __dirname + '/mustache/specs/';
|
||||
var specs = fs.readdirSync(specDir).filter((name) => /.*\.json$/.test(name));
|
||||
|
||||
specs.forEach(function (name) {
|
||||
var spec = require(specDir + name);
|
||||
var spec = JSON.parse(fs.readFileSync(specDir + name, 'utf8'));
|
||||
spec.tests.forEach(function (test) {
|
||||
// Our lambda implementation knowingly deviates from the optional Mustache lambda spec
|
||||
// We also do not support alternative delimiters
|
||||
|
||||
@@ -1,796 +0,0 @@
|
||||
function shouldMatchTokens(result, tokens) {
|
||||
for (var index = 0; index < result.length; index++) {
|
||||
expect(result[index].name).toBe(tokens[index]);
|
||||
}
|
||||
}
|
||||
function shouldBeToken(result, name, text) {
|
||||
expect(result.name).toBe(name);
|
||||
expect(result.text).toBe(text);
|
||||
}
|
||||
|
||||
describe('Tokenizer', function () {
|
||||
if (!Handlebars.Parser) {
|
||||
return;
|
||||
}
|
||||
|
||||
function tokenize(template) {
|
||||
var parser = Handlebars.Parser,
|
||||
lexer = parser.lexer;
|
||||
|
||||
lexer.setInput(template);
|
||||
var out = [],
|
||||
token;
|
||||
|
||||
while ((token = lexer.lex())) {
|
||||
var result = parser.terminals_[token] || token;
|
||||
if (!result || result === 'EOF' || result === 'INVALID') {
|
||||
break;
|
||||
}
|
||||
out.push({ name: result, text: lexer.yytext });
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
it('tokenizes a simple mustache as "OPEN ID CLOSE"', function () {
|
||||
var result = tokenize('{{foo}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('supports unescaping with &', function () {
|
||||
var result = tokenize('{{&bar}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[0], 'OPEN', '{{&');
|
||||
shouldBeToken(result[1], 'ID', 'bar');
|
||||
});
|
||||
|
||||
it('supports unescaping with {{{', function () {
|
||||
var result = tokenize('{{{bar}}}');
|
||||
shouldMatchTokens(result, ['OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED']);
|
||||
|
||||
shouldBeToken(result[1], 'ID', 'bar');
|
||||
});
|
||||
|
||||
it('supports escaping delimiters', function () {
|
||||
var result = tokenize('{{foo}} \\{{bar}} {{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[3], 'CONTENT', ' ');
|
||||
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
|
||||
});
|
||||
|
||||
it('supports escaping multiple delimiters', function () {
|
||||
var result = tokenize('{{foo}} \\{{bar}} \\{{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[3], 'CONTENT', ' ');
|
||||
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
|
||||
shouldBeToken(result[5], 'CONTENT', '{{baz}}');
|
||||
});
|
||||
|
||||
it('supports escaping a triple stash', function () {
|
||||
var result = tokenize('{{foo}} \\{{{bar}}} {{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[4], 'CONTENT', '{{{bar}}} ');
|
||||
});
|
||||
|
||||
it('supports escaping escape character', function () {
|
||||
var result = tokenize('{{foo}} \\\\{{bar}} {{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[3], 'CONTENT', ' \\');
|
||||
shouldBeToken(result[5], 'ID', 'bar');
|
||||
});
|
||||
|
||||
it('supports escaping multiple escape characters', function () {
|
||||
var result = tokenize('{{foo}} \\\\{{bar}} \\\\{{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[3], 'CONTENT', ' \\');
|
||||
shouldBeToken(result[5], 'ID', 'bar');
|
||||
shouldBeToken(result[7], 'CONTENT', ' \\');
|
||||
shouldBeToken(result[9], 'ID', 'baz');
|
||||
});
|
||||
|
||||
it('supports escaped mustaches after escaped escape characters', function () {
|
||||
var result = tokenize('{{foo}} \\\\{{bar}} \\{{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[3], 'CONTENT', ' \\');
|
||||
shouldBeToken(result[4], 'OPEN', '{{');
|
||||
shouldBeToken(result[5], 'ID', 'bar');
|
||||
shouldBeToken(result[7], 'CONTENT', ' ');
|
||||
shouldBeToken(result[8], 'CONTENT', '{{baz}}');
|
||||
});
|
||||
|
||||
it('supports escaped escape characters after escaped mustaches', function () {
|
||||
var result = tokenize('{{foo}} \\{{bar}} \\\\{{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
|
||||
shouldBeToken(result[5], 'CONTENT', '\\');
|
||||
shouldBeToken(result[6], 'OPEN', '{{');
|
||||
shouldBeToken(result[7], 'ID', 'baz');
|
||||
});
|
||||
|
||||
it('supports escaped escape character on a triple stash', function () {
|
||||
var result = tokenize('{{foo}} \\\\{{{bar}}} {{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN_UNESCAPED',
|
||||
'ID',
|
||||
'CLOSE_UNESCAPED',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[3], 'CONTENT', ' \\');
|
||||
shouldBeToken(result[5], 'ID', 'bar');
|
||||
});
|
||||
|
||||
it('tokenizes a simple path', function () {
|
||||
var result = tokenize('{{foo/bar}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('allows dot notation', function () {
|
||||
var result = tokenize('{{foo.bar}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
|
||||
shouldMatchTokens(tokenize('{{foo.bar.baz}}'), [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
});
|
||||
|
||||
it('allows path literals with []', function () {
|
||||
var result = tokenize('{{foo.[bar]}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('allows multiple path literals on a line with []', function () {
|
||||
var result = tokenize('{{foo.[bar]}}{{foo.[baz]}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
});
|
||||
|
||||
it('allows escaped literals in []', function () {
|
||||
var result = tokenize('{{foo.[bar\\]]}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes {{.}} as OPEN ID CLOSE', function () {
|
||||
var result = tokenize('{{.}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a path as "OPEN (ID SEP)* ID CLOSE"', function () {
|
||||
var result = tokenize('{{../foo/bar}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[1], 'ID', '..');
|
||||
});
|
||||
|
||||
it('tokenizes a path with .. as a parent path', function () {
|
||||
var result = tokenize('{{../foo.bar}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[1], 'ID', '..');
|
||||
});
|
||||
|
||||
it('tokenizes a path with this/foo as OPEN ID SEP ID CLOSE', function () {
|
||||
var result = tokenize('{{this/foo}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], 'ID', 'this');
|
||||
shouldBeToken(result[3], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('tokenizes a simple mustache with spaces as "OPEN ID CLOSE"', function () {
|
||||
var result = tokenize('{{ foo }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('tokenizes a simple mustache with line breaks as "OPEN ID ID CLOSE"', function () {
|
||||
var result = tokenize('{{ foo \n bar }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('tokenizes raw content as "CONTENT"', function () {
|
||||
var result = tokenize('foo {{ bar }} baz');
|
||||
shouldMatchTokens(result, ['CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT']);
|
||||
shouldBeToken(result[0], 'CONTENT', 'foo ');
|
||||
shouldBeToken(result[4], 'CONTENT', ' baz');
|
||||
});
|
||||
|
||||
it('tokenizes a partial as "OPEN_PARTIAL ID CLOSE"', function () {
|
||||
var result = tokenize('{{> foo}}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial with context as "OPEN_PARTIAL ID ID CLOSE"', function () {
|
||||
var result = tokenize('{{> foo bar }}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial without spaces as "OPEN_PARTIAL ID CLOSE"', function () {
|
||||
var result = tokenize('{{>foo}}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function () {
|
||||
var result = tokenize('{{>foo }}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function () {
|
||||
var result = tokenize('{{>foo/bar.baz }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_PARTIAL',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
});
|
||||
|
||||
it('tokenizes partial block declarations', function () {
|
||||
var result = tokenize('{{#> foo}}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL_BLOCK', 'ID', 'CLOSE']);
|
||||
});
|
||||
it('tokenizes a comment as "COMMENT"', function () {
|
||||
var result = tokenize('foo {{! this is a comment }} bar {{ baz }}');
|
||||
shouldMatchTokens(result, [
|
||||
'CONTENT',
|
||||
'COMMENT',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[1], 'COMMENT', '{{! this is a comment }}');
|
||||
});
|
||||
|
||||
it('tokenizes a block comment as "COMMENT"', function () {
|
||||
var result = tokenize('foo {{!-- this is a {{comment}} --}} bar {{ baz }}');
|
||||
shouldMatchTokens(result, [
|
||||
'CONTENT',
|
||||
'COMMENT',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[1], 'COMMENT', '{{!-- this is a {{comment}} --}}');
|
||||
});
|
||||
|
||||
it('tokenizes a block comment with whitespace as "COMMENT"', function () {
|
||||
var result = tokenize(
|
||||
'foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}'
|
||||
);
|
||||
shouldMatchTokens(result, [
|
||||
'CONTENT',
|
||||
'COMMENT',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[1], 'COMMENT', '{{!-- this is a\n{{comment}}\n--}}');
|
||||
});
|
||||
|
||||
it('tokenizes open and closing blocks as OPEN_BLOCK, ID, CLOSE ..., OPEN_ENDBLOCK ID CLOSE', function () {
|
||||
var result = tokenize('{{#foo}}content{{/foo}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_BLOCK',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN_ENDBLOCK',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
});
|
||||
|
||||
it('tokenizes directives', function () {
|
||||
shouldMatchTokens(tokenize('{{#*foo}}content{{/foo}}'), [
|
||||
'OPEN_BLOCK',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN_ENDBLOCK',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldMatchTokens(tokenize('{{*foo}}'), ['OPEN', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes inverse sections as "INVERSE"', function () {
|
||||
shouldMatchTokens(tokenize('{{^}}'), ['INVERSE']);
|
||||
shouldMatchTokens(tokenize('{{else}}'), ['INVERSE']);
|
||||
shouldMatchTokens(tokenize('{{ else }}'), ['INVERSE']);
|
||||
});
|
||||
|
||||
it('tokenizes inverse sections with ID as "OPEN_INVERSE ID CLOSE"', function () {
|
||||
var result = tokenize('{{^foo}}');
|
||||
shouldMatchTokens(result, ['OPEN_INVERSE', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('tokenizes inverse sections with ID and spaces as "OPEN_INVERSE ID CLOSE"', function () {
|
||||
var result = tokenize('{{^ foo }}');
|
||||
shouldMatchTokens(result, ['OPEN_INVERSE', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('tokenizes mustaches with params as "OPEN ID ID ID CLOSE"', function () {
|
||||
var result = tokenize('{{ foo bar baz }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
shouldBeToken(result[2], 'ID', 'bar');
|
||||
shouldBeToken(result[3], 'ID', 'baz');
|
||||
});
|
||||
|
||||
it('tokenizes mustaches with String params as "OPEN ID ID STRING CLOSE"', function () {
|
||||
var result = tokenize('{{ foo bar "baz" }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[3], 'STRING', 'baz');
|
||||
});
|
||||
|
||||
it('tokenizes mustaches with String params using single quotes as "OPEN ID ID STRING CLOSE"', function () {
|
||||
var result = tokenize("{{ foo bar 'baz' }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[3], 'STRING', 'baz');
|
||||
});
|
||||
|
||||
it('tokenizes String params with spaces inside as "STRING"', function () {
|
||||
var result = tokenize('{{ foo bar "baz bat" }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[3], 'STRING', 'baz bat');
|
||||
});
|
||||
|
||||
it('tokenizes String params with escapes quotes as STRING', function () {
|
||||
var result = tokenize('{{ foo "bar\\"baz" }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'STRING', 'bar"baz');
|
||||
});
|
||||
|
||||
it('tokenizes String params using single quotes with escapes quotes as STRING', function () {
|
||||
var result = tokenize("{{ foo 'bar\\'baz' }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'STRING', "bar'baz");
|
||||
});
|
||||
|
||||
it('tokenizes numbers', function () {
|
||||
var result = tokenize('{{ foo 1 }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'NUMBER', '1');
|
||||
|
||||
result = tokenize('{{ foo 1.1 }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'NUMBER', '1.1');
|
||||
|
||||
result = tokenize('{{ foo -1 }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'NUMBER', '-1');
|
||||
|
||||
result = tokenize('{{ foo -1.1 }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'NUMBER', '-1.1');
|
||||
});
|
||||
|
||||
it('tokenizes booleans', function () {
|
||||
var result = tokenize('{{ foo true }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'BOOLEAN', 'true');
|
||||
|
||||
result = tokenize('{{ foo false }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'BOOLEAN', 'false');
|
||||
});
|
||||
|
||||
it('tokenizes undefined and null', function () {
|
||||
var result = tokenize('{{ foo undefined null }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'UNDEFINED', 'NULL', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'UNDEFINED', 'undefined');
|
||||
shouldBeToken(result[3], 'NULL', 'null');
|
||||
});
|
||||
|
||||
it('tokenizes hash arguments', function () {
|
||||
var result = tokenize('{{ foo bar=baz }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
|
||||
result = tokenize('{{ foo bar baz=bat }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{ foo bar baz=1 }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'NUMBER',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{ foo bar baz=true }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'BOOLEAN',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{ foo bar baz=false }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'BOOLEAN',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{ foo bar\n baz=bat }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{ foo bar baz="bat" }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'STRING',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{ foo bar baz="bat" bam=wot }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'STRING',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{foo omg bar=baz bat="bam"}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'STRING',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[2], 'ID', 'omg');
|
||||
});
|
||||
|
||||
it('tokenizes special @ identifiers', function () {
|
||||
var result = tokenize('{{ @foo }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'DATA', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'ID', 'foo');
|
||||
|
||||
result = tokenize('{{ foo @bar }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'DATA', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[3], 'ID', 'bar');
|
||||
|
||||
result = tokenize('{{ foo bar=@baz }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'DATA',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[5], 'ID', 'baz');
|
||||
});
|
||||
|
||||
it('does not time out in a mustache with a single } followed by EOF', function () {
|
||||
shouldMatchTokens(tokenize('{{foo}'), ['OPEN', 'ID']);
|
||||
});
|
||||
|
||||
it('does not time out in a mustache when invalid ID characters are used', function () {
|
||||
shouldMatchTokens(tokenize('{{foo & }}'), ['OPEN', 'ID']);
|
||||
});
|
||||
|
||||
it('tokenizes subexpressions', function () {
|
||||
var result = tokenize('{{foo (bar)}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'CLOSE_SEXPR',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
shouldBeToken(result[3], 'ID', 'bar');
|
||||
|
||||
result = tokenize('{{foo (a-x b-y)}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'ID',
|
||||
'CLOSE_SEXPR',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
shouldBeToken(result[3], 'ID', 'a-x');
|
||||
shouldBeToken(result[4], 'ID', 'b-y');
|
||||
});
|
||||
|
||||
it('tokenizes nested subexpressions', function () {
|
||||
var result = tokenize('{{foo (bar (lol rofl)) (baz)}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'ID',
|
||||
'CLOSE_SEXPR',
|
||||
'CLOSE_SEXPR',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'CLOSE_SEXPR',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[3], 'ID', 'bar');
|
||||
shouldBeToken(result[5], 'ID', 'lol');
|
||||
shouldBeToken(result[6], 'ID', 'rofl');
|
||||
shouldBeToken(result[10], 'ID', 'baz');
|
||||
});
|
||||
|
||||
it('tokenizes nested subexpressions: literals', function () {
|
||||
var result = tokenize(
|
||||
'{{foo (bar (lol true) false) (baz 1) (blah \'b\') (blorg "c")}}'
|
||||
);
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'BOOLEAN',
|
||||
'CLOSE_SEXPR',
|
||||
'BOOLEAN',
|
||||
'CLOSE_SEXPR',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'NUMBER',
|
||||
'CLOSE_SEXPR',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'STRING',
|
||||
'CLOSE_SEXPR',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'STRING',
|
||||
'CLOSE_SEXPR',
|
||||
'CLOSE',
|
||||
]);
|
||||
});
|
||||
|
||||
it('tokenizes block params', function () {
|
||||
var result = tokenize('{{#foo as |bar|}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_BLOCK',
|
||||
'ID',
|
||||
'OPEN_BLOCK_PARAMS',
|
||||
'ID',
|
||||
'CLOSE_BLOCK_PARAMS',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{#foo as |bar baz|}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_BLOCK',
|
||||
'ID',
|
||||
'OPEN_BLOCK_PARAMS',
|
||||
'ID',
|
||||
'ID',
|
||||
'CLOSE_BLOCK_PARAMS',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{#foo as | bar baz |}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_BLOCK',
|
||||
'ID',
|
||||
'OPEN_BLOCK_PARAMS',
|
||||
'ID',
|
||||
'ID',
|
||||
'CLOSE_BLOCK_PARAMS',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{#foo as as | bar baz |}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_BLOCK',
|
||||
'ID',
|
||||
'ID',
|
||||
'OPEN_BLOCK_PARAMS',
|
||||
'ID',
|
||||
'ID',
|
||||
'CLOSE_BLOCK_PARAMS',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{else foo as |bar baz|}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_INVERSE_CHAIN',
|
||||
'ID',
|
||||
'OPEN_BLOCK_PARAMS',
|
||||
'ID',
|
||||
'ID',
|
||||
'CLOSE_BLOCK_PARAMS',
|
||||
'CLOSE',
|
||||
]);
|
||||
});
|
||||
|
||||
it('tokenizes raw blocks', function () {
|
||||
var result = tokenize(
|
||||
'{{{{a}}}} abc {{{{/a}}}} aaa {{{{a}}}} abc {{{{/a}}}}'
|
||||
);
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_RAW_BLOCK',
|
||||
'ID',
|
||||
'CLOSE_RAW_BLOCK',
|
||||
'CONTENT',
|
||||
'END_RAW_BLOCK',
|
||||
'CONTENT',
|
||||
'OPEN_RAW_BLOCK',
|
||||
'ID',
|
||||
'CLOSE_RAW_BLOCK',
|
||||
'CONTENT',
|
||||
'END_RAW_BLOCK',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Handlebars Runtime UMD Smoke Test</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Handlebars Runtime UMD Smoke Test</h1>
|
||||
<pre id="results"></pre>
|
||||
<script src="/spec/vendor/require.js"></script>
|
||||
<script>
|
||||
var results = document.getElementById('results');
|
||||
var failures = 0;
|
||||
var tests = 0;
|
||||
|
||||
function assert(condition, message) {
|
||||
tests++;
|
||||
if (!condition) {
|
||||
failures++;
|
||||
results.textContent += 'FAIL: ' + message + '\n';
|
||||
} else {
|
||||
results.textContent += 'PASS: ' + message + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
requirejs.config({
|
||||
paths: {
|
||||
'handlebars.runtime': '/dist/handlebars.runtime',
|
||||
},
|
||||
});
|
||||
|
||||
require(['handlebars.runtime'], function (Handlebars) {
|
||||
try {
|
||||
assert(
|
||||
typeof Handlebars !== 'undefined',
|
||||
'Handlebars runtime loaded via RequireJS'
|
||||
);
|
||||
assert(
|
||||
typeof Handlebars.template === 'function',
|
||||
'Handlebars.template exists'
|
||||
);
|
||||
assert(
|
||||
typeof Handlebars.VERSION === 'string',
|
||||
'Handlebars.VERSION exists'
|
||||
);
|
||||
} catch (e) {
|
||||
failures++;
|
||||
results.textContent += 'ERROR: ' + e.message + '\n';
|
||||
}
|
||||
|
||||
results.textContent +=
|
||||
'\n' + tests + ' tests, ' + failures + ' failures\n';
|
||||
window.mochaResults = { passes: tests - failures, failures: failures };
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,66 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Handlebars UMD Module Smoke Test</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Handlebars UMD Module Smoke Test</h1>
|
||||
<pre id="results"></pre>
|
||||
<script src="/spec/vendor/require.js"></script>
|
||||
<script>
|
||||
var results = document.getElementById('results');
|
||||
var failures = 0;
|
||||
var tests = 0;
|
||||
|
||||
function assert(condition, message) {
|
||||
tests++;
|
||||
if (!condition) {
|
||||
failures++;
|
||||
results.textContent += 'FAIL: ' + message + '\n';
|
||||
} else {
|
||||
results.textContent += 'PASS: ' + message + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
requirejs.config({
|
||||
paths: {
|
||||
handlebars: '/dist/handlebars',
|
||||
},
|
||||
});
|
||||
|
||||
require(['handlebars'], function (Handlebars) {
|
||||
try {
|
||||
assert(
|
||||
typeof Handlebars !== 'undefined',
|
||||
'Handlebars loaded via RequireJS'
|
||||
);
|
||||
assert(
|
||||
typeof Handlebars.compile === 'function',
|
||||
'Handlebars.compile exists'
|
||||
);
|
||||
assert(
|
||||
typeof Handlebars.template === 'function',
|
||||
'Handlebars.template exists'
|
||||
);
|
||||
assert(
|
||||
typeof Handlebars.VERSION === 'string',
|
||||
'Handlebars.VERSION exists'
|
||||
);
|
||||
|
||||
var template = Handlebars.compile('Hello {{name}}!');
|
||||
var output = template({ name: 'UMD' });
|
||||
assert(output === 'Hello UMD!', 'Basic compilation works: ' + output);
|
||||
} catch (e) {
|
||||
failures++;
|
||||
results.textContent += 'ERROR: ' + e.message + '\n';
|
||||
}
|
||||
|
||||
results.textContent +=
|
||||
'\n' + tests + ' tests, ' + failures + ' failures\n';
|
||||
window.mochaResults = { passes: tests - failures, failures: failures };
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
-2054
File diff suppressed because it is too large
Load Diff
+12
-19
@@ -1,10 +1,10 @@
|
||||
/* eslint-disable no-console */
|
||||
const fs = require('fs');
|
||||
const { S3, PutObjectCommand } = require('@aws-sdk/client-s3');
|
||||
const git = require('./util/git');
|
||||
const semver = require('semver');
|
||||
import fs from 'fs';
|
||||
import { S3, PutObjectCommand } from '@aws-sdk/client-s3';
|
||||
import * as git from './util/git.js';
|
||||
import semver from 'semver';
|
||||
|
||||
const PUBLISHED_FILES = [
|
||||
export const PUBLISHED_FILES = [
|
||||
'handlebars.js',
|
||||
'handlebars.min.js',
|
||||
'handlebars.runtime.js',
|
||||
@@ -29,7 +29,7 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
function buildSuffixes(commitInfo) {
|
||||
export function buildSuffixes(commitInfo) {
|
||||
const suffixes = [];
|
||||
|
||||
if (commitInfo.isMaster) {
|
||||
@@ -44,7 +44,7 @@ function buildSuffixes(commitInfo) {
|
||||
return suffixes;
|
||||
}
|
||||
|
||||
function validateS3Env() {
|
||||
export function validateS3Env() {
|
||||
const bucket = process.env.S3_BUCKET_NAME,
|
||||
region = process.env.S3_REGION,
|
||||
key = process.env.S3_ACCESS_KEY_ID,
|
||||
@@ -55,7 +55,7 @@ function validateS3Env() {
|
||||
}
|
||||
}
|
||||
|
||||
async function publish(suffixes, overrides) {
|
||||
export async function publish(suffixes, overrides) {
|
||||
const publishPromises = suffixes.map((suffix) =>
|
||||
publishSuffix(suffix, overrides)
|
||||
);
|
||||
@@ -98,24 +98,17 @@ function getS3Client() {
|
||||
return s3Client;
|
||||
}
|
||||
|
||||
function getNameInBucket(filename, suffix) {
|
||||
export function getNameInBucket(filename, suffix) {
|
||||
return filename.replace(/\.js$/, suffix + '.js');
|
||||
}
|
||||
|
||||
function getLocalFile(filename) {
|
||||
export function getLocalFile(filename) {
|
||||
return 'dist/' + filename;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
PUBLISHED_FILES,
|
||||
buildSuffixes,
|
||||
validateS3Env,
|
||||
publish,
|
||||
getNameInBucket,
|
||||
getLocalFile,
|
||||
};
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
if (require.main === module) {
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
|
||||
+28
-113
@@ -1,9 +1,9 @@
|
||||
const fs = require('fs');
|
||||
const { exec } = require('child_process');
|
||||
const { execCommand, FileTestHelper } = require('cli-testlab');
|
||||
const Handlebars = require('../../lib');
|
||||
import fs from 'fs';
|
||||
import { exec } from 'child_process';
|
||||
import { execCommand, FileTestHelper } from 'cli-testlab';
|
||||
import Handlebars from '../../lib/index.js';
|
||||
|
||||
const cli = 'node ./bin/handlebars.mjs';
|
||||
const cli = 'node ./bin/handlebars.js';
|
||||
|
||||
expect.extend({
|
||||
toEqualWithRelaxedSpace(received, expected) {
|
||||
@@ -58,71 +58,22 @@ describe('bin/handlebars', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('AMD output', function () {
|
||||
it('-a produces AMD output', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} -a spec/artifacts/empty.handlebars`
|
||||
);
|
||||
expect(result.stdout).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/empty.amd.js')
|
||||
);
|
||||
});
|
||||
|
||||
it('-a -s produces simple AMD output', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} -a -s spec/artifacts/empty.handlebars`
|
||||
);
|
||||
expect(result.stdout).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/empty.amd.simple.js')
|
||||
);
|
||||
});
|
||||
|
||||
it('-a -m produces minified AMD output', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} -a -m spec/artifacts/empty.handlebars`
|
||||
);
|
||||
expect(result.stdout).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/empty.amd.min.js')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CommonJS output', function () {
|
||||
it('-c produces CommonJS output', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} spec/artifacts/empty.handlebars -c`
|
||||
);
|
||||
expect(result.stdout).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/empty.common.js')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('namespace', function () {
|
||||
it('-n sets custom namespace', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} -a -n CustomNamespace.templates spec/artifacts/empty.handlebars`
|
||||
`${cli} -n CustomNamespace.templates spec/artifacts/empty.handlebars`
|
||||
);
|
||||
expect(result.stdout).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/empty.amd.namespace.js')
|
||||
expectedFile('./spec/expected/empty.namespace.js')
|
||||
);
|
||||
});
|
||||
|
||||
it('--namespace sets custom namespace', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} -a --namespace CustomNamespace.templates spec/artifacts/empty.handlebars`
|
||||
`${cli} --namespace CustomNamespace.templates spec/artifacts/empty.handlebars`
|
||||
);
|
||||
expect(result.stdout).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/empty.amd.namespace.js')
|
||||
);
|
||||
});
|
||||
|
||||
it('multiple files share a namespace', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} spec/artifacts/empty.handlebars spec/artifacts/empty.handlebars -a -n someNameSpace`
|
||||
);
|
||||
expect(result.stdout).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/namespace.amd.js')
|
||||
expectedFile('./spec/expected/empty.namespace.js')
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -144,14 +95,12 @@ describe('bin/handlebars', function () {
|
||||
files.registerForCleanup(outputFile);
|
||||
|
||||
await execCommand(
|
||||
`${cli} -a -f ${outputFile} spec/artifacts/empty.handlebars`
|
||||
`${cli} -f ${outputFile} spec/artifacts/empty.handlebars`
|
||||
);
|
||||
|
||||
expect(files.fileExists(outputFile)).toBe(true);
|
||||
const content = files.getFileTextContent(outputFile);
|
||||
expect(content).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/empty.amd.js')
|
||||
);
|
||||
expect(content).toMatch(/template\(/);
|
||||
});
|
||||
|
||||
it('--map writes source map and appends sourceMappingURL', async function () {
|
||||
@@ -159,7 +108,7 @@ describe('bin/handlebars', function () {
|
||||
files.registerForCleanup(mapFile);
|
||||
|
||||
const result = await execCommand(
|
||||
`${cli} -i "<div>1</div>" -a -m -N test --map ${mapFile}`
|
||||
`${cli} -i "<div>1</div>" -m -N test --map ${mapFile}`
|
||||
);
|
||||
|
||||
expect(result.stdout).toContain('sourceMappingURL=');
|
||||
@@ -174,75 +123,52 @@ describe('bin/handlebars', function () {
|
||||
describe('template options', function () {
|
||||
it('-e sets custom extension', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} -a -e hbs ./spec/artifacts/non.default.extension.hbs`
|
||||
);
|
||||
expect(result.stdout).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/non.default.extension.amd.js')
|
||||
`${cli} -e hbs ./spec/artifacts/non.default.extension.hbs`
|
||||
);
|
||||
expect(result.stdout).toMatch(/template\(/);
|
||||
});
|
||||
|
||||
it('-p compiles as partial', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} -a -p ./spec/artifacts/partial.template.handlebars`
|
||||
);
|
||||
expect(result.stdout).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/partial.template.js')
|
||||
`${cli} -p ./spec/artifacts/partial.template.handlebars`
|
||||
);
|
||||
expect(result.stdout).toMatch(/Handlebars\.partials/);
|
||||
});
|
||||
|
||||
it('-r strips root from template names', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} spec/artifacts/partial.template.handlebars -r spec -a`
|
||||
);
|
||||
expect(result.stdout).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/empty.root.amd.js')
|
||||
`${cli} spec/artifacts/partial.template.handlebars -r spec`
|
||||
);
|
||||
expect(result.stdout).toMatch(/template\(/);
|
||||
});
|
||||
|
||||
it('-b strips BOM', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} ./spec/artifacts/bom.handlebars -b -a`
|
||||
);
|
||||
expect(result.stdout).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/bom.amd.js')
|
||||
);
|
||||
});
|
||||
|
||||
it('-h sets custom handlebar path', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} spec/artifacts/empty.handlebars -h some-path/ -a`
|
||||
);
|
||||
expect(result.stdout).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/handlebar.path.amd.js')
|
||||
`${cli} ./spec/artifacts/bom.handlebars -b`
|
||||
);
|
||||
expect(result.stdout).toMatch(/template\(/);
|
||||
});
|
||||
|
||||
it('-k -o sets known helpers only', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} spec/artifacts/known.helpers.handlebars -a -k someHelper -k anotherHelper -o`
|
||||
);
|
||||
expect(result.stdout).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/non.empty.amd.known.helper.js')
|
||||
`${cli} spec/artifacts/known.helpers.handlebars -k someHelper -k anotherHelper -o`
|
||||
);
|
||||
expect(result.stdout).toMatch(/template\(/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('inline templates', function () {
|
||||
it('-i compiles inline template', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} -i "<div>hello</div>" -a -N myTemplate`
|
||||
`${cli} -i "<div>hello</div>" -N myTemplate`
|
||||
);
|
||||
expect(result.stdout).toContain("define(['handlebars.runtime']");
|
||||
expect(result.stdout).toContain("templates['myTemplate']");
|
||||
});
|
||||
|
||||
it('-i compiles multiple inline templates with -N', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} -i "<div>1</div>" -i "<div>2</div>" -N firstTemplate -N secondTemplate -a`
|
||||
);
|
||||
expect(result.stdout).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/empty.name.amd.js')
|
||||
);
|
||||
it('-i compiles simple unnamed inline template', async function () {
|
||||
const result = await execCommand(`${cli} -i "<div>hello</div>"`);
|
||||
// Unnamed single template defaults to simple mode
|
||||
expect(result.stdout).toMatch(/function/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -250,7 +176,7 @@ describe('bin/handlebars', function () {
|
||||
it('should not produce unhandled promise rejections on async errors', async function () {
|
||||
const { stderr } = await new Promise((resolve) => {
|
||||
exec(
|
||||
`node --unhandled-rejections=warn ./bin/handlebars.mjs -i "<div>test</div>" -N test --map /nonexistent/dir/test.map`,
|
||||
`node --unhandled-rejections=warn ./bin/handlebars.js -i "<div>test</div>" -N test --map /nonexistent/dir/test.map`,
|
||||
(error, stdout, stderr) => {
|
||||
resolve({ exitCode: error ? error.code : 0, stderr });
|
||||
}
|
||||
@@ -260,15 +186,4 @@ describe('bin/handlebars', function () {
|
||||
expect(stderr).not.toMatch(/unhandled/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('negated boolean flags', function () {
|
||||
it('--no-amd negates --amd (issue #1673)', async function () {
|
||||
const result = await execCommand(
|
||||
`${cli} --amd --no-amd spec/artifacts/empty.handlebars`
|
||||
);
|
||||
expect(result.stdout).toEqualWithRelaxedSpace(
|
||||
expectedFile('./spec/expected/empty.common.js')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
const http = require('http');
|
||||
import http from 'http';
|
||||
|
||||
/**
|
||||
* Minimal in-memory S3-compatible server for testing.
|
||||
* Supports PutObject and GetObject via path-style requests.
|
||||
*/
|
||||
function createFakeS3() {
|
||||
export function createFakeS3() {
|
||||
const buckets = new Map();
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
@@ -77,5 +77,3 @@ function parsePath(url) {
|
||||
key: parts.slice(1).join('/') || null,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { createFakeS3 };
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const git = require('../util/git');
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import { spawnSync } from 'child_process';
|
||||
import * as git from '../util/git.js';
|
||||
|
||||
const tmpBaseDir = path.join(os.tmpdir(), 'handlebars-task-tests');
|
||||
const tmpDir = path.join(tmpBaseDir, Date.now().toString(36));
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
const fs = require('fs');
|
||||
const { S3, GetObjectCommand } = require('@aws-sdk/client-s3');
|
||||
const { createFakeS3 } = require('./fake-s3');
|
||||
import fs from 'fs';
|
||||
import { S3, GetObjectCommand } from '@aws-sdk/client-s3';
|
||||
import { createFakeS3 } from './fake-s3.js';
|
||||
|
||||
const BUCKET = 'test-bucket';
|
||||
|
||||
const {
|
||||
import {
|
||||
PUBLISHED_FILES,
|
||||
buildSuffixes,
|
||||
validateS3Env,
|
||||
publish,
|
||||
getNameInBucket,
|
||||
getLocalFile,
|
||||
} = require('../publish-to-aws');
|
||||
} from '../publish-to-aws.js';
|
||||
|
||||
let server;
|
||||
let s3Client;
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
const childProcess = require('child_process');
|
||||
|
||||
module.exports = {
|
||||
execNodeJsScriptWithInheritedOutput,
|
||||
};
|
||||
|
||||
async function execNodeJsScriptWithInheritedOutput(command, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = childProcess.fork(command, args, { stdio: 'inherit' });
|
||||
child.on('close', (code) => {
|
||||
if (code !== 0) {
|
||||
reject(new Error(`Child process failed with exit-code ${code}`));
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
+29
-26
@@ -1,30 +1,33 @@
|
||||
const childProcess = require('child_process');
|
||||
import childProcess from 'child_process';
|
||||
|
||||
module.exports = {
|
||||
async remotes() {
|
||||
return git('remote', '-v');
|
||||
},
|
||||
async branches() {
|
||||
return git('branch', '-a');
|
||||
},
|
||||
async commitInfo() {
|
||||
const headSha = await getHeadSha();
|
||||
const masterSha = await getMasterSha();
|
||||
return {
|
||||
headSha,
|
||||
masterSha,
|
||||
tagName: await getTagName(),
|
||||
isMaster: headSha === masterSha,
|
||||
};
|
||||
},
|
||||
async add(path) {
|
||||
return git('add', '-f', path);
|
||||
},
|
||||
async commit(message) {
|
||||
return git('commit', '--message', message);
|
||||
},
|
||||
git, // visible for testing
|
||||
};
|
||||
export async function remotes() {
|
||||
return git('remote', '-v');
|
||||
}
|
||||
|
||||
export async function branches() {
|
||||
return git('branch', '-a');
|
||||
}
|
||||
|
||||
export async function commitInfo() {
|
||||
const headSha = await getHeadSha();
|
||||
const masterSha = await getMasterSha();
|
||||
return {
|
||||
headSha,
|
||||
masterSha,
|
||||
tagName: await getTagName(),
|
||||
isMaster: headSha === masterSha,
|
||||
};
|
||||
}
|
||||
|
||||
export async function add(path) {
|
||||
return git('add', '-f', path);
|
||||
}
|
||||
|
||||
export async function commit(message) {
|
||||
return git('commit', '--message', message);
|
||||
}
|
||||
|
||||
export { git };
|
||||
|
||||
async function getHeadSha() {
|
||||
const stdout = await git('rev-parse', '--short', 'HEAD');
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const { execSync } = require('child_process');
|
||||
const git = require('./util/git');
|
||||
const semver = require('semver');
|
||||
import fs from 'fs';
|
||||
import { execSync } from 'child_process';
|
||||
import * as git from './util/git.js';
|
||||
import semver from 'semver';
|
||||
|
||||
async function main() {
|
||||
const version = process.argv[2];
|
||||
@@ -50,16 +50,16 @@ async function main() {
|
||||
execSync('npm run build', { stdio: 'inherit' });
|
||||
}
|
||||
|
||||
async function replaceAndAdd(filePath, regex, value) {
|
||||
export async function replaceAndAdd(filePath, regex, value) {
|
||||
let content = fs.readFileSync(filePath, 'utf8');
|
||||
content = content.replace(regex, value);
|
||||
fs.writeFileSync(filePath, content);
|
||||
await git.add(filePath);
|
||||
}
|
||||
|
||||
module.exports = { replaceAndAdd };
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
if (require.main === module) {
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
main().catch((err) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { basename, dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
import { formatOps } from './report.mjs';
|
||||
import { formatOps } from './report.js';
|
||||
|
||||
// ─── Resolve files ───────────────────────────────────────────────────────────
|
||||
|
||||
@@ -36,7 +36,7 @@ if (args.length >= 2) {
|
||||
: 'Only one benchmark result found. Run benchmarks on another branch first.'
|
||||
);
|
||||
console.error('');
|
||||
console.error('Usage: node bench/compare.mjs [<baseline.md> <current.md>]');
|
||||
console.error('Usage: node bench/compare.js [<baseline.md> <current.md>]');
|
||||
console.error('');
|
||||
console.error(
|
||||
'With no arguments, auto-selects the two most recent results.'
|
||||
@@ -1,12 +1,12 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
import { Bench } from 'tinybench';
|
||||
import Handlebars from '../../lib/index.js';
|
||||
import { templates as allTemplates } from './templates.mjs';
|
||||
import { templates as allTemplates } from './templates.js';
|
||||
import {
|
||||
printResults,
|
||||
printSectionHeader,
|
||||
saveMarkdownReport,
|
||||
} from './report.mjs';
|
||||
} from './report.js';
|
||||
|
||||
// ─── Configuration ───────────────────────────────────────────────────────────
|
||||
// Compilation happens once at app startup — low warmup reflects real-world conditions.
|
||||
@@ -6,7 +6,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
import { gzip } from 'node:zlib';
|
||||
import { promisify } from 'node:util';
|
||||
import Handlebars from '../../lib/index.js';
|
||||
import { templates } from './templates.mjs';
|
||||
import { templates } from './templates.js';
|
||||
|
||||
const gzipAsync = promisify(gzip);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { devices } = require('@playwright/test');
|
||||
import { devices } from '@playwright/test';
|
||||
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||
const config = {
|
||||
@@ -24,4 +24,4 @@ const config = {
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
export default config;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { test, expect } = require('@playwright/test');
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
async function waitForMochaAndAssertResult(page) {
|
||||
await page.waitForFunction(() => window.mochaResults); // eslint-disable-line no-undef
|
||||
@@ -11,13 +11,3 @@ test('Spec handlebars.js', async ({ page, baseURL }) => {
|
||||
await page.goto(`${baseURL}/spec/?headless=true`);
|
||||
await waitForMochaAndAssertResult(page);
|
||||
});
|
||||
|
||||
test('Spec handlebars.js (UMD)', async ({ page, baseURL }) => {
|
||||
await page.goto(`${baseURL}/spec/umd.html?headless=true`);
|
||||
await waitForMochaAndAssertResult(page);
|
||||
});
|
||||
|
||||
test('Spec handlebars.runtime.js (UMD)', async ({ page, baseURL }) => {
|
||||
await page.goto(`${baseURL}/spec/umd-runtime.html?headless=true`);
|
||||
await waitForMochaAndAssertResult(page);
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"keywords": [],
|
||||
"license": "MIT",
|
||||
"author": "Nils Knappmeier",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "node run-handlebars.js",
|
||||
"test-precompile": "handlebars precompile-test-template.txt.hbs"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// This test should run successfully with node 0.10++ as long as Handlebars has been compiled before
|
||||
var assert = require('assert');
|
||||
var Handlebars = require('handlebars');
|
||||
import assert from 'assert';
|
||||
import Handlebars from 'handlebars';
|
||||
|
||||
console.log('Testing built Handlebars with Node version ' + process.version);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Various tests with Handlebars and rollup",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "rollup --config rollup.config.js"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Handlebars from 'handlebars/lib/handlebars';
|
||||
import Handlebars from 'handlebars';
|
||||
|
||||
const template = Handlebars.compile('Author: {{author}}');
|
||||
const result = template({ author: 'Yehuda' });
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"plugins": [
|
||||
"@roundingwellos/babel-plugin-handlebars-inline-precompile"
|
||||
// "istanbul"
|
||||
],
|
||||
"presets": [["@babel/preset-env"]]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
node_modules
|
||||
dist
|
||||
package-lock.json
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "webpack-babel-test",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"build": "webpack --config webpack.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.27.0",
|
||||
"@babel/preset-env": "^7.27.0",
|
||||
"@roundingwellos/babel-plugin-handlebars-inline-precompile": "^3.0.1",
|
||||
"babel-loader": "^10.0.0",
|
||||
"babel-plugin-istanbul": "^7.0.0",
|
||||
"handlebars": "file:../../..",
|
||||
"handlebars-loader": "^1.7.1",
|
||||
"webpack": "^5.99.0",
|
||||
"webpack-cli": "^6.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import Handlebars from 'handlebars/runtime';
|
||||
import hbs from 'handlebars-inline-precompile';
|
||||
import { assertEquals } from '../../webpack-test/src/lib/assert';
|
||||
|
||||
Handlebars.registerHelper('loud', function (text) {
|
||||
return text.toUpperCase();
|
||||
});
|
||||
|
||||
const template = hbs`{{loud name}}`;
|
||||
const output = template({ name: 'yehuda' });
|
||||
|
||||
assertEquals(output, 'YEHUDA');
|
||||
@@ -1,5 +0,0 @@
|
||||
export function assertEquals(actual, expected) {
|
||||
if (actual !== expected) {
|
||||
throw new Error(`Expected "${actual}" to equal "${expected}"`);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Cleanup: package-lock and "npm ci" is not working with local dependencies
|
||||
rm -rf dist package-lock.json
|
||||
npm install --legacy-peer-deps
|
||||
npm run build
|
||||
|
||||
for i in dist/*-test.js ; do
|
||||
echo "----------------------"
|
||||
echo "-- Running $i"
|
||||
echo "----------------------"
|
||||
node "$i"
|
||||
echo "Success"
|
||||
done
|
||||
@@ -1,34 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const testFiles = fs.readdirSync('src');
|
||||
const entryPoints = {};
|
||||
testFiles
|
||||
.filter((file) => file.match(/-test.js$/))
|
||||
.forEach((file) => {
|
||||
entryPoints[file] = `./src/${file}`;
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
entry: entryPoints,
|
||||
mode: 'production',
|
||||
target: 'web',
|
||||
output: {
|
||||
filename: '[name]',
|
||||
path: __dirname + '/dist',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js?$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: { cacheDirectory: false },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import Handlebars from 'handlebars/lib/handlebars';
|
||||
import Handlebars from 'handlebars';
|
||||
import { assertEquals } from './lib/assert';
|
||||
|
||||
const template = Handlebars.compile('Author: {{author}}');
|
||||
|
||||
+17
-101
@@ -1,8 +1,10 @@
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const distDir = path.resolve(__dirname, '../../dist');
|
||||
|
||||
const EXPECTED_BUNDLES = [
|
||||
@@ -14,11 +16,8 @@ const EXPECTED_BUNDLES = [
|
||||
|
||||
describe('rspack build output', () => {
|
||||
beforeAll(() => {
|
||||
// Ensure full build has been run (bundles + CJS)
|
||||
if (
|
||||
!fs.existsSync(path.join(distDir, 'handlebars.js')) ||
|
||||
!fs.existsSync(path.join(distDir, 'cjs/handlebars.js'))
|
||||
) {
|
||||
// Ensure full build has been run
|
||||
if (!fs.existsSync(path.join(distDir, 'handlebars.js'))) {
|
||||
execSync('npm run build', {
|
||||
cwd: path.resolve(__dirname, '../..'),
|
||||
stdio: 'inherit',
|
||||
@@ -36,70 +35,21 @@ describe('rspack build output', () => {
|
||||
}
|
||||
});
|
||||
|
||||
describe('CJS output', () => {
|
||||
it('produces dist/cjs/ with correct structure', () => {
|
||||
expect(fs.existsSync(path.join(distDir, 'cjs/handlebars.js'))).toBe(true);
|
||||
expect(
|
||||
fs.existsSync(path.join(distDir, 'cjs/handlebars.runtime.js'))
|
||||
).toBe(true);
|
||||
expect(fs.existsSync(path.join(distDir, 'cjs/handlebars/base.js'))).toBe(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
fs.existsSync(path.join(distDir, 'cjs/handlebars/runtime.js'))
|
||||
).toBe(true);
|
||||
expect(fs.existsSync(path.join(distDir, 'cjs/handlebars/utils.js'))).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('does not compile lib/index.js into CJS', () => {
|
||||
// lib/index.js is already CJS, so it should be excluded from SWC compilation
|
||||
expect(fs.existsSync(path.join(distDir, 'cjs/index.js'))).toBe(false);
|
||||
});
|
||||
|
||||
it('produces valid CommonJS modules', () => {
|
||||
const content = fs.readFileSync(
|
||||
path.join(distDir, 'cjs/handlebars.js'),
|
||||
'utf8'
|
||||
);
|
||||
expect(content).toContain('"use strict"');
|
||||
expect(content).toContain('exports');
|
||||
// Should not contain ES module import/export syntax
|
||||
expect(content).not.toMatch(/^import /m);
|
||||
expect(content).not.toMatch(/^export /m);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UMD format', () => {
|
||||
it('wraps handlebars.js as UMD', () => {
|
||||
describe('global export', () => {
|
||||
it('handlebars.js assigns to self.Handlebars', () => {
|
||||
const content = fs.readFileSync(
|
||||
path.join(distDir, 'handlebars.js'),
|
||||
'utf8'
|
||||
);
|
||||
// UMD pattern: checks for AMD define, CommonJS module, and falls back to global
|
||||
expect(content).toContain('define');
|
||||
expect(content).toContain('exports');
|
||||
expect(content).toContain('Handlebars');
|
||||
expect(content).toContain('self.Handlebars');
|
||||
});
|
||||
|
||||
it('wraps handlebars.runtime.js as UMD', () => {
|
||||
it('handlebars.runtime.js assigns to self.Handlebars', () => {
|
||||
const content = fs.readFileSync(
|
||||
path.join(distDir, 'handlebars.runtime.js'),
|
||||
'utf8'
|
||||
);
|
||||
expect(content).toContain('define');
|
||||
expect(content).toContain('exports');
|
||||
expect(content).toContain('Handlebars');
|
||||
});
|
||||
|
||||
it('exposes Handlebars as global library name', () => {
|
||||
const content = fs.readFileSync(
|
||||
path.join(distDir, 'handlebars.js'),
|
||||
'utf8'
|
||||
);
|
||||
// UMD should assign to root["Handlebars"]
|
||||
expect(content).toMatch(/root\["Handlebars"\]/);
|
||||
expect(content).toContain('self.Handlebars');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -143,51 +93,28 @@ describe('rspack build output', () => {
|
||||
});
|
||||
|
||||
describe('functional correctness', () => {
|
||||
it('CJS entry point loads and compiles templates', () => {
|
||||
const Handlebars = require('../../lib');
|
||||
it('ESM entry point loads and compiles templates', async () => {
|
||||
const Handlebars = (await import('../../lib/index.js')).default;
|
||||
const template = Handlebars.compile('Hello {{name}}!');
|
||||
expect(template({ name: 'World' })).toBe('Hello World!');
|
||||
});
|
||||
|
||||
it('CJS runtime entry loads correctly', () => {
|
||||
const runtime = require('../../runtime');
|
||||
it('ESM runtime entry loads correctly', async () => {
|
||||
const runtime = (await import('../../runtime.js')).default;
|
||||
expect(typeof runtime.template).toBe('function');
|
||||
expect(typeof runtime.VERSION).toBe('string');
|
||||
});
|
||||
|
||||
it('UMD bundle loads in Node.js (CommonJS mode)', () => {
|
||||
const Handlebars = require('../../dist/handlebars');
|
||||
expect(typeof Handlebars.compile).toBe('function');
|
||||
expect(typeof Handlebars.template).toBe('function');
|
||||
expect(typeof Handlebars.VERSION).toBe('string');
|
||||
|
||||
const template = Handlebars.compile('{{greeting}} {{target}}');
|
||||
expect(template({ greeting: 'Hi', target: 'UMD' })).toBe('Hi UMD');
|
||||
});
|
||||
|
||||
it('UMD runtime bundle loads in Node.js', () => {
|
||||
const runtime = require('../../dist/handlebars.runtime');
|
||||
expect(typeof runtime.template).toBe('function');
|
||||
expect(typeof runtime.SafeString).toBe('function');
|
||||
expect(typeof runtime.VERSION).toBe('string');
|
||||
});
|
||||
|
||||
it('minified UMD bundle loads and works correctly', () => {
|
||||
const Handlebars = require('../../dist/handlebars.min');
|
||||
const template = Handlebars.compile('{{a}} + {{b}} = {{c}}');
|
||||
expect(template({ a: 1, b: 2, c: 3 })).toBe('1 + 2 = 3');
|
||||
});
|
||||
|
||||
it('precompile and template round-trip works', () => {
|
||||
const Handlebars = require('../../lib');
|
||||
it('precompile and template round-trip works', async () => {
|
||||
const Handlebars = (await import('../../lib/index.js')).default;
|
||||
const spec = Handlebars.precompile('{{name}} is {{age}}');
|
||||
// eslint-disable-next-line no-eval
|
||||
const templateFn = Handlebars.template(eval('(' + spec + ')'));
|
||||
expect(templateFn({ name: 'Alice', age: 30 })).toBe('Alice is 30');
|
||||
});
|
||||
|
||||
it('VM runtime functions are overridable', () => {
|
||||
const Handlebars = require('../../lib');
|
||||
it('VM runtime functions are overridable', async () => {
|
||||
const Handlebars = (await import('../../lib/index.js')).default;
|
||||
const env = Handlebars.create();
|
||||
const originalCheckRevision = env.VM.checkRevision;
|
||||
|
||||
@@ -206,9 +133,6 @@ describe('rspack build output', () => {
|
||||
path.join(distDir, 'handlebars.js'),
|
||||
'utf8'
|
||||
);
|
||||
// Arrow functions should be transpiled for browser compat.
|
||||
// Check that no "=>" appears outside of comments/strings in a meaningful way.
|
||||
// Simple heuristic: no "=> {" pattern which indicates arrow function bodies.
|
||||
expect(content).not.toMatch(/[=]>\s*\{/);
|
||||
});
|
||||
|
||||
@@ -217,7 +141,6 @@ describe('rspack build output', () => {
|
||||
path.join(distDir, 'handlebars.js'),
|
||||
'utf8'
|
||||
);
|
||||
// For broad browser compat, const/let should be transpiled to var
|
||||
expect(content).not.toMatch(/^\s*(const|let)\s+/m);
|
||||
});
|
||||
|
||||
@@ -226,15 +149,10 @@ describe('rspack build output', () => {
|
||||
path.join(distDir, 'handlebars.js'),
|
||||
'utf8'
|
||||
);
|
||||
// Remove comments and string literals, then check for backticks
|
||||
// Backticks in comments/strings/regexes are fine — only actual template
|
||||
// literal usage (e.g. `foo ${bar}`) indicates untranspiled code
|
||||
const lines = content.split('\n');
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
// Skip comment lines
|
||||
if (trimmed.startsWith('//') || trimmed.startsWith('*')) continue;
|
||||
// Check for template literal interpolation pattern
|
||||
expect(trimmed).not.toMatch(/`[^`]*\$\{/);
|
||||
}
|
||||
});
|
||||
@@ -244,8 +162,6 @@ describe('rspack build output', () => {
|
||||
path.join(distDir, 'handlebars.js'),
|
||||
'utf8'
|
||||
);
|
||||
// Match actual class declarations at statement level (not "class" in
|
||||
// strings, comments, or reserved word lists)
|
||||
const classDeclarations = content.split('\n').filter((line) => {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed.startsWith('//') || trimmed.startsWith('*')) return false;
|
||||
|
||||
Vendored
+34
-1
@@ -289,7 +289,40 @@ export interface Logger {
|
||||
export type CompilerInfo = [number /* revision */, string /* versions */];
|
||||
|
||||
declare module 'handlebars/runtime' {
|
||||
export = Handlebars;
|
||||
const runtime: typeof Handlebars;
|
||||
export default runtime;
|
||||
}
|
||||
|
||||
// Named exports matching lib/index.js for ESM/CJS interop
|
||||
export const create: typeof Handlebars.create;
|
||||
export const compile: typeof Handlebars.compile;
|
||||
export const precompile: typeof Handlebars.precompile;
|
||||
export const template: typeof Handlebars.template;
|
||||
export { parse, parseWithoutProcessing } from '@handlebars/parser';
|
||||
export const VERSION: typeof Handlebars.VERSION;
|
||||
export const COMPILER_REVISION: number;
|
||||
export const LAST_COMPATIBLE_COMPILER_REVISION: number;
|
||||
export const REVISION_CHANGES: { [revision: number]: string };
|
||||
export import AST = Handlebars.AST;
|
||||
export const Compiler: Handlebars.ICompiler;
|
||||
export const JavaScriptCompiler: any;
|
||||
export const Parser: any;
|
||||
export import Visitor = Handlebars.Visitor;
|
||||
export import SafeString = Handlebars.SafeString;
|
||||
export import Exception = Handlebars.Exception;
|
||||
export import Utils = Handlebars.Utils;
|
||||
export const escapeExpression: typeof Handlebars.escapeExpression;
|
||||
export import VM = Handlebars.VM;
|
||||
export const log: typeof Handlebars.log;
|
||||
export const registerHelper: typeof Handlebars.registerHelper;
|
||||
export const unregisterHelper: typeof Handlebars.unregisterHelper;
|
||||
export const registerPartial: typeof Handlebars.registerPartial;
|
||||
export const unregisterPartial: typeof Handlebars.unregisterPartial;
|
||||
export const registerDecorator: typeof Handlebars.registerDecorator;
|
||||
export const unregisterDecorator: typeof Handlebars.unregisterDecorator;
|
||||
export function print(ast: hbs.AST.Program): string;
|
||||
export class PrintVisitor {
|
||||
accept(node: hbs.AST.Node): void;
|
||||
}
|
||||
|
||||
export default Handlebars;
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"module": "es2015",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["es6"],
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
|
||||
+7
-7
@@ -46,7 +46,6 @@ export default defineConfig({
|
||||
'spec/.eslintrc.js',
|
||||
'spec/precompiler.js',
|
||||
'spec/spec.js',
|
||||
'spec/require.js',
|
||||
'spec/source-map.js',
|
||||
],
|
||||
setupFiles: [
|
||||
@@ -64,15 +63,16 @@ export default defineConfig({
|
||||
],
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
include: ['dist/cjs/**/*.js'],
|
||||
include: ['lib/**/*.js'],
|
||||
exclude: [
|
||||
'lib/handlebars/compiler/source-node.browser.js',
|
||||
'lib/handlebars/compiler/source-node.node.js',
|
||||
],
|
||||
thresholds: {
|
||||
// Slightly below 100% because SWC injects helper functions
|
||||
// (e.g. _sliced_to_array, _non_iterable_rest, for-of try/catch/finally)
|
||||
// with branches that are unreachable in practice.
|
||||
statements: 99,
|
||||
branches: 92,
|
||||
branches: 98,
|
||||
functions: 100,
|
||||
lines: 100,
|
||||
lines: 99,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user