Reimplement 'babel-template' with better caching and tagged literal utilities.
This commit is contained in:
131
packages/babel-template/src/builder.js
Normal file
131
packages/babel-template/src/builder.js
Normal file
@@ -0,0 +1,131 @@
|
||||
// @flow
|
||||
|
||||
import {
|
||||
merge,
|
||||
validate,
|
||||
type TemplateOpts,
|
||||
type PublicOpts,
|
||||
type PublicReplacements,
|
||||
} from "./options";
|
||||
import type { Formatter } from "./formatters";
|
||||
|
||||
import stringTemplate from "./string";
|
||||
import literalTemplate from "./literal";
|
||||
|
||||
export type TemplateBuilder<T> = {
|
||||
// Build a new builder, merging the given options with the previous ones.
|
||||
(opts: PublicOpts): TemplateBuilder<T>,
|
||||
|
||||
// Building from a string produces an AST builder function by default.
|
||||
(tpl: string, opts: ?PublicOpts): (?PublicReplacements) => T,
|
||||
|
||||
// Building from a template literal produces an AST builder function by default.
|
||||
(tpl: Array<string>, ...args: Array<mixed>): (?PublicReplacements) => T,
|
||||
|
||||
// Allow users to explicitly create templates that produce ASTs, skipping
|
||||
// the need for an intermediate function.
|
||||
ast: {
|
||||
(tpl: string, opts: ?PublicOpts): T,
|
||||
(tpl: Array<string>, ...args: Array<mixed>): T,
|
||||
},
|
||||
};
|
||||
|
||||
// Prebuild the options that will be used when parsing a `.ast` template.
|
||||
// These do not use a pattern because there is no way for users to pass in
|
||||
// replacement patterns to begin with, and disabling pattern matching means
|
||||
// users have more flexibility in what type of content they have in their
|
||||
// template JS.
|
||||
const NO_PLACEHOLDER: TemplateOpts = validate({
|
||||
placeholderPattern: false,
|
||||
});
|
||||
|
||||
export default function createTemplateBuilder<T>(
|
||||
formatter: Formatter<T>,
|
||||
defaultOpts?: TemplateOpts,
|
||||
): TemplateBuilder<T> {
|
||||
const templateFnCache = new WeakMap();
|
||||
const templateAstCache = new WeakMap();
|
||||
const cachedOpts = defaultOpts || validate(null);
|
||||
|
||||
return Object.assign(
|
||||
((tpl, ...args) => {
|
||||
if (typeof tpl === "string") {
|
||||
if (args.length > 1) throw new Error("Unexpected extra params.");
|
||||
return extendedTrace(
|
||||
stringTemplate(formatter, tpl, merge(cachedOpts, validate(args[0]))),
|
||||
);
|
||||
} else if (Array.isArray(tpl)) {
|
||||
let builder = templateFnCache.get(tpl);
|
||||
if (!builder) {
|
||||
builder = literalTemplate(formatter, tpl, cachedOpts);
|
||||
templateFnCache.set(tpl, builder);
|
||||
}
|
||||
return extendedTrace(builder(args));
|
||||
} else if (typeof tpl === "object" && tpl) {
|
||||
if (args.length > 0) throw new Error("Unexpected extra params.");
|
||||
return createTemplateBuilder(
|
||||
formatter,
|
||||
merge(cachedOpts, validate(tpl)),
|
||||
);
|
||||
}
|
||||
throw new Error(`Unexpected template param ${typeof tpl}`);
|
||||
}: Function),
|
||||
{
|
||||
ast: (tpl, ...args) => {
|
||||
if (typeof tpl === "string") {
|
||||
if (args.length > 1) throw new Error("Unexpected extra params.");
|
||||
return stringTemplate(
|
||||
formatter,
|
||||
tpl,
|
||||
merge(merge(cachedOpts, validate(args[0])), NO_PLACEHOLDER),
|
||||
)();
|
||||
} else if (Array.isArray(tpl)) {
|
||||
let builder = templateAstCache.get(tpl);
|
||||
if (!builder) {
|
||||
builder = literalTemplate(
|
||||
formatter,
|
||||
tpl,
|
||||
merge(cachedOpts, NO_PLACEHOLDER),
|
||||
);
|
||||
templateAstCache.set(tpl, builder);
|
||||
}
|
||||
return builder(args)();
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected template param ${typeof tpl}`);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function extendedTrace<Arg, Result>(fn: Arg => Result): Arg => Result {
|
||||
// Since we lazy parse the template, we get the current stack so we have the
|
||||
// original stack to append if it errors when parsing
|
||||
let rootStack = "";
|
||||
try {
|
||||
// error stack gets populated in IE only on throw
|
||||
// (https://msdn.microsoft.com/en-us/library/hh699850(v=vs.94).aspx)
|
||||
throw new Error();
|
||||
} catch (error) {
|
||||
if (error.stack) {
|
||||
// error.stack does not exists in IE <= 9
|
||||
// We slice off the top 3 items in the stack to remove the call to
|
||||
// 'extendedTrace', and the anonymous builder function, with the final
|
||||
// stripped line being the error message itself since we threw it
|
||||
// in the first place and it doesn't matter.
|
||||
rootStack = error.stack
|
||||
.split("\n")
|
||||
.slice(3)
|
||||
.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
return (arg: Arg) => {
|
||||
try {
|
||||
return fn(arg);
|
||||
} catch (err) {
|
||||
err.stack += `\n =============\n${rootStack}`;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user