Switch to ESM (#2138)

This commit is contained in:
Igor Savin
2026-04-21 22:02:44 +03:00
committed by GitHub
parent b10cec2322
commit 0e1d35582a
85 changed files with 464 additions and 7378 deletions
+1 -1
View File
@@ -116,7 +116,7 @@
}
},
{
"files": ["tests/bench/**/*.mjs"],
"files": ["tests/bench/**/*.js"],
"rules": {
"no-console": "off"
}
-8
View File
@@ -1,8 +0,0 @@
{
"$schema": "https://swc.rs/schema.json",
"module": {
"type": "commonjs",
"importInterop": "swc"
},
"sourceMaps": "inline"
}
+3 -23
View File
@@ -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
View File
@@ -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() {
+17 -6
View File
@@ -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;
+5 -5
View File
@@ -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;
+2 -53
View File
@@ -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)) {
+2 -2
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
import registerInline from './decorators/inline';
import registerInline from './decorators/inline.js';
export function registerDefaultDecorators(instance) {
registerInline(instance);
+1 -1
View File
@@ -1,4 +1,4 @@
import { extend } from '../utils';
import { extend } from '../utils.js';
export default function (instance) {
instance.registerDecorator(
+7 -7
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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) {
+2 -2
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
import { indexOf } from './utils';
import { indexOf } from './utils.js';
let logger = {
methodMap: ['debug', 'info', 'warn', 'error'],
+5 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+12 -3468
View File
File diff suppressed because it is too large Load Diff
+25 -22
View File
@@ -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
View File
@@ -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),
+3 -2
View File
@@ -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
View File
@@ -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;
+6 -3
View File
@@ -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';
+3 -2
View File
@@ -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) {
-6
View File
@@ -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});
});
-6
View File
@@ -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});
});
-1
View File
@@ -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})});
-6
View File
@@ -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});
});
-3
View File
@@ -1,3 +0,0 @@
{"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
return "";
},"useData":true}
-10
View File
@@ -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});
-6
View File
@@ -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});
});
-6
View File
@@ -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
View File
@@ -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]
-10
View File
@@ -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});
});
-6
View File
@@ -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});
});
-2
View File
@@ -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
View File
@@ -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');
});
-23
View File
@@ -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
View File
@@ -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
View File
@@ -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
-796
View File
@@ -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',
]);
});
});
-58
View File
@@ -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>
-66
View File
@@ -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>
-2054
View File
File diff suppressed because it is too large Load Diff
+12 -19
View File
@@ -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
View File
@@ -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')
);
});
});
});
+2 -4
View File
@@ -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 };
+5 -6
View File
@@ -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));
+5 -5
View File
@@ -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;
-17
View File
@@ -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
View File
@@ -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
View File
@@ -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.'
+2 -2
View File
@@ -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.
+1 -1
View File
@@ -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);
+2 -2
View File
@@ -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 -11
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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;
+34 -1
View File
@@ -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
View File
@@ -1,6 +1,7 @@
{
"compilerOptions": {
"module": "commonjs",
"module": "es2015",
"moduleResolution": "bundler",
"lib": ["es6"],
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true,
+7 -7
View File
@@ -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,
},
},
},