Add JS helper to string templates
This commit is contained in:
parent
98ca5da073
commit
4fd31b9eac
|
@ -6,8 +6,9 @@ class Helper {
|
||||||
|
|
||||||
register(handlebars) {
|
register(handlebars) {
|
||||||
// wrap the function so that no helper can cause handlebars to break
|
// wrap the function so that no helper can cause handlebars to break
|
||||||
handlebars.registerHelper(this.name, value => {
|
handlebars.registerHelper(this.name, (value, info) => {
|
||||||
return this.fn(value) || value
|
const context = info?.data?.root
|
||||||
|
return this.fn(value, context || {}) || value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ module.exports.HelperFunctionNames = {
|
||||||
OBJECT: "object",
|
OBJECT: "object",
|
||||||
ALL: "all",
|
ALL: "all",
|
||||||
LITERAL: "literal",
|
LITERAL: "literal",
|
||||||
|
JS: "js",
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.LITERAL_MARKER = "%LITERAL%"
|
module.exports.LITERAL_MARKER = "%LITERAL%"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const Helper = require("./Helper")
|
const Helper = require("./Helper")
|
||||||
const { SafeString } = require("handlebars")
|
const { SafeString } = require("handlebars")
|
||||||
const externalHandlebars = require("./external")
|
const externalHandlebars = require("./external")
|
||||||
|
const { processJS } = require("./javascript")
|
||||||
const {
|
const {
|
||||||
HelperFunctionNames,
|
HelperFunctionNames,
|
||||||
HelperFunctionBuiltin,
|
HelperFunctionBuiltin,
|
||||||
|
@ -17,6 +18,8 @@ const HELPERS = [
|
||||||
new Helper(HelperFunctionNames.OBJECT, value => {
|
new Helper(HelperFunctionNames.OBJECT, value => {
|
||||||
return new SafeString(JSON.stringify(value))
|
return new SafeString(JSON.stringify(value))
|
||||||
}),
|
}),
|
||||||
|
// javascript helper
|
||||||
|
new Helper(HelperFunctionNames.JS, processJS),
|
||||||
// this help is applied to all statements
|
// this help is applied to all statements
|
||||||
new Helper(HelperFunctionNames.ALL, value => {
|
new Helper(HelperFunctionNames.ALL, value => {
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
const CAPTURE_JS = new RegExp(/{{ js "(.*)" }}/)
|
||||||
|
const vm = require("vm")
|
||||||
|
|
||||||
|
// Helper utility to strip square brackets from a value
|
||||||
|
const removeSquareBrackets = value => {
|
||||||
|
if (!value || typeof value !== "string") {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
const regex = /\[+(.+)]+/
|
||||||
|
const matches = value.match(regex)
|
||||||
|
if (matches && matches[1]) {
|
||||||
|
return matches[1]
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our context getter function provided to JS code as $.
|
||||||
|
// Extracts a value from context.
|
||||||
|
const getContextValue = (path, context) => {
|
||||||
|
let data = context
|
||||||
|
path.split(".").forEach(key => {
|
||||||
|
data = data[removeSquareBrackets(key)] || {}
|
||||||
|
})
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluates JS code against a certain context
|
||||||
|
module.exports.processJS = (handlebars, context) => {
|
||||||
|
try {
|
||||||
|
// Wrap JS in a function and immediately invoke it.
|
||||||
|
// This is required to allow the final `return` statement to be valid.
|
||||||
|
const js = `function run(){${atob(handlebars)}};run();`
|
||||||
|
|
||||||
|
// Our $ context function gets a value from context
|
||||||
|
const sandboxContext = { $: path => getContextValue(path, context) }
|
||||||
|
|
||||||
|
// Create a sandbox with out context and run the JS
|
||||||
|
vm.createContext(sandboxContext)
|
||||||
|
return vm.runInNewContext(js, sandboxContext)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error)
|
||||||
|
return "Error while executing JS"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if a HBS expression is a valid JS HBS expression
|
||||||
|
module.exports.isJSBinding = handlebars => {
|
||||||
|
return module.exports.decodeJSBinding(handlebars) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encodes a raw JS string as a JS HBS expression
|
||||||
|
module.exports.encodeJSBinding = javascript => {
|
||||||
|
return `{{ js "${btoa(javascript)}" }}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes a JS HBS expression to the raw JS string
|
||||||
|
module.exports.decodeJSBinding = handlebars => {
|
||||||
|
if (!handlebars || typeof handlebars !== "string") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const match = handlebars.match(CAPTURE_JS)
|
||||||
|
if (!match || match.length < 2) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return atob(match[1])
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ const { registerAll } = require("./helpers/index")
|
||||||
const processors = require("./processors")
|
const processors = require("./processors")
|
||||||
const { removeHandlebarsStatements } = require("./utilities")
|
const { removeHandlebarsStatements } = require("./utilities")
|
||||||
const manifest = require("../manifest.json")
|
const manifest = require("../manifest.json")
|
||||||
|
const JS = require("./helpers/javascript")
|
||||||
|
|
||||||
const hbsInstance = handlebars.create()
|
const hbsInstance = handlebars.create()
|
||||||
registerAll(hbsInstance)
|
registerAll(hbsInstance)
|
||||||
|
@ -159,3 +160,10 @@ module.exports.isValid = string => {
|
||||||
module.exports.getManifest = () => {
|
module.exports.getManifest = () => {
|
||||||
return manifest
|
return manifest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export utilities for working with JS bindings
|
||||||
|
*/
|
||||||
|
module.exports.isJSBinding = JS.isJSBinding
|
||||||
|
module.exports.decodeJSBinding = JS.decodeJSBinding
|
||||||
|
module.exports.encodeJSBinding = JS.encodeJSBinding
|
|
@ -6,6 +6,9 @@ import templates from "./index.cjs"
|
||||||
export const isValid = templates.isValid
|
export const isValid = templates.isValid
|
||||||
export const makePropSafe = templates.makePropSafe
|
export const makePropSafe = templates.makePropSafe
|
||||||
export const getManifest = templates.getManifest
|
export const getManifest = templates.getManifest
|
||||||
|
export const isJSBinding = templates.isJSBinding
|
||||||
|
export const encodeJSBinding = templates.encodeJSBinding
|
||||||
|
export const decodeJSBinding = templates.decodeJSBinding
|
||||||
export const processStringSync = templates.processStringSync
|
export const processStringSync = templates.processStringSync
|
||||||
export const processObjectSync = templates.processObjectSync
|
export const processObjectSync = templates.processObjectSync
|
||||||
export const processString = templates.processString
|
export const processString = templates.processString
|
||||||
|
|
Loading…
Reference in New Issue