Add JS helper to string templates
This commit is contained in:
parent
98ca5da073
commit
4fd31b9eac
|
@ -6,8 +6,9 @@ class Helper {
|
|||
|
||||
register(handlebars) {
|
||||
// wrap the function so that no helper can cause handlebars to break
|
||||
handlebars.registerHelper(this.name, value => {
|
||||
return this.fn(value) || value
|
||||
handlebars.registerHelper(this.name, (value, info) => {
|
||||
const context = info?.data?.root
|
||||
return this.fn(value, context || {}) || value
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ module.exports.HelperFunctionNames = {
|
|||
OBJECT: "object",
|
||||
ALL: "all",
|
||||
LITERAL: "literal",
|
||||
JS: "js",
|
||||
}
|
||||
|
||||
module.exports.LITERAL_MARKER = "%LITERAL%"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const Helper = require("./Helper")
|
||||
const { SafeString } = require("handlebars")
|
||||
const externalHandlebars = require("./external")
|
||||
const { processJS } = require("./javascript")
|
||||
const {
|
||||
HelperFunctionNames,
|
||||
HelperFunctionBuiltin,
|
||||
|
@ -17,6 +18,8 @@ const HELPERS = [
|
|||
new Helper(HelperFunctionNames.OBJECT, value => {
|
||||
return new SafeString(JSON.stringify(value))
|
||||
}),
|
||||
// javascript helper
|
||||
new Helper(HelperFunctionNames.JS, processJS),
|
||||
// this help is applied to all statements
|
||||
new Helper(HelperFunctionNames.ALL, value => {
|
||||
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 { removeHandlebarsStatements } = require("./utilities")
|
||||
const manifest = require("../manifest.json")
|
||||
const JS = require("./helpers/javascript")
|
||||
|
||||
const hbsInstance = handlebars.create()
|
||||
registerAll(hbsInstance)
|
||||
|
@ -159,3 +160,10 @@ module.exports.isValid = string => {
|
|||
module.exports.getManifest = () => {
|
||||
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 makePropSafe = templates.makePropSafe
|
||||
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 processObjectSync = templates.processObjectSync
|
||||
export const processString = templates.processString
|
||||
|
|
Loading…
Reference in New Issue