From a1f09b0a1a249d1021be593ab395021afe5e85a9 Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Sat, 30 Jan 2021 02:54:52 +0000 Subject: [PATCH] Some rollup fixes - the handlebars-helper library needs dynamic requires removed from it, likely best to fork it and do this. --- .../builder/src/builderStore/dataBinding.js | 20 +- packages/string-templates/package.json | 3 +- packages/string-templates/rollup.config.js | 19 +- packages/string-templates/src/esIndex.js | 12 + packages/string-templates/src/index.js | 214 +++++++++--------- packages/string-templates/src/utilities.js | 2 +- .../string-templates/test/helpers.spec.js | 7 +- packages/string-templates/yarn.lock | 52 +++-- 8 files changed, 192 insertions(+), 137 deletions(-) create mode 100644 packages/string-templates/src/esIndex.js diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 3e469fbe2e..ed17f4bdab 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -174,7 +174,8 @@ export const getSchemaForDatasource = datasource => { * utility function for the readableToRuntimeBinding and runtimeToReadableBinding. */ function bindingReplacement(bindableProperties, textWithBindings, convertTo) { - const convertFrom = convertTo === "runtimeBinding" ? "readableBinding" : "runtimeBinding" + const convertFrom = + convertTo === "runtimeBinding" ? "readableBinding" : "runtimeBinding" if (typeof textWithBindings !== "string") { return textWithBindings } @@ -190,10 +191,7 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) { for (let from of convertFromProps) { if (newBoundValue.includes(from)) { const binding = bindableProperties.find(el => el[convertFrom] === from) - newBoundValue = newBoundValue.replace( - from, - binding[convertTo], - ) + newBoundValue = newBoundValue.replace(from, binding[convertTo]) } } result = result.replace(boundValue, newBoundValue) @@ -205,12 +203,20 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) { * Converts a readable data binding into a runtime data binding */ export function readableToRuntimeBinding(bindableProperties, textWithBindings) { - return bindingReplacement(bindableProperties, textWithBindings, "runtimeBinding") + return bindingReplacement( + bindableProperties, + textWithBindings, + "runtimeBinding" + ) } /** * Converts a runtime data binding into a readable data binding */ export function runtimeToReadableBinding(bindableProperties, textWithBindings) { - return bindingReplacement(bindableProperties, textWithBindings, "readableBinding") + return bindingReplacement( + bindableProperties, + textWithBindings, + "readableBinding" + ) } diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index d7ea1c2cf4..5c5da0a920 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -20,11 +20,12 @@ "lodash": "^4.17.20" }, "devDependencies": { + "@rollup/plugin-commonjs": "^17.1.0", "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-replace": "^2.3.4", "doctrine": "^3.0.0", "jest": "^26.6.3", "rollup": "^2.36.2", - "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-builtins": "^2.1.2", "rollup-plugin-node-globals": "^1.4.0", "rollup-plugin-node-resolve": "^5.2.0", diff --git a/packages/string-templates/rollup.config.js b/packages/string-templates/rollup.config.js index 549dc24aef..bca8e14e53 100644 --- a/packages/string-templates/rollup.config.js +++ b/packages/string-templates/rollup.config.js @@ -1,23 +1,36 @@ -import commonjs from "rollup-plugin-commonjs" +import commonjs from "@rollup/plugin-commonjs" import resolve from "rollup-plugin-node-resolve" import builtins from "rollup-plugin-node-builtins" import globals from "rollup-plugin-node-globals" import json from "@rollup/plugin-json" +import replace from "@rollup/plugin-replace" import { terser } from "rollup-plugin-terser" const production = !process.env.ROLLUP_WATCH export default { - input: "src/index.js", + input: "src/esIndex.js", output: [ { sourcemap: true, - format: "es", + format: "esm", file: "./dist/bundle.js", name: "templates", exports: "named", }, ], plugins: [ + // this replacement is a crazy hack to fix an issue that + // rollup has with the handlebars-helper package + // if we don't do this then the browser will always error + // with the isNumber function being unavailable + replace({ + include: [ + "node_modules/handlebars-helpers/lib/**", + "node_modules/handlebar-utils/lib/**", + ], + "utils.isNumber(": "!isNaN(", + "isNumber(": "!isNaN(", + }), resolve({ mainFields: ["module", "main"], preferBuiltins: true, diff --git a/packages/string-templates/src/esIndex.js b/packages/string-templates/src/esIndex.js new file mode 100644 index 0000000000..483b0405fa --- /dev/null +++ b/packages/string-templates/src/esIndex.js @@ -0,0 +1,12 @@ +import templates from "./index" + +/** + * This file is simply an entrypoint for rollup - makes a lot of cjs problems go away + */ +export const isValid = templates.isValid +export const makePropSafe = templates.makePropSafe +export const getManifest = templates.getManifest +export const processStringSync = templates.processStringSync +export const processObjectSync = templates.processObjectSync +export const processString = templates.processString +export const processObject = templates.processObject diff --git a/packages/string-templates/src/index.js b/packages/string-templates/src/index.js index 847cb93670..68652684a8 100644 --- a/packages/string-templates/src/index.js +++ b/packages/string-templates/src/index.js @@ -20,117 +20,115 @@ function testObject(object) { } } -module.exports = { - /** - * Given an input object this will recurse through all props to try and update any handlebars statements within. - * @param {object|array} object The input structure which is to be recursed, it is important to note that - * if the structure contains any cycles then this will fail. - * @param {object} context The context that handlebars should fill data from. - * @returns {Promise} The structure input, as fully updated as possible. - */ - processObject: async (object, context) => { - testObject(object) - for (let key of Object.keys(object)) { - if (object[key] != null) { - let val = object[key] - if (typeof val === "string") { - object[key] = await module.exports.processString(object[key], context) - } else if (typeof val === "object") { - object[key] = await module.exports.processObject(object[key], context) - } - } - } - return object - }, - - /** - * This will process a single handlebars containing string. If the string passed in has no valid handlebars statements - * then nothing will occur. - * @param {string} string The template string which is the filled from the context object. - * @param {object} context An object of information which will be used to enrich the string. - * @returns {Promise} The enriched string, all templates should have been replaced if they can be. - */ - processString: async (string, context) => { - // TODO: carry out any async calls before carrying out async call - return module.exports.processStringSync(string, context) - }, - - /** - * Given an input object this will recurse through all props to try and update any handlebars statements within. This is - * a pure sync call and therefore does not have the full functionality of the async call. - * @param {object|array} object The input structure which is to be recursed, it is important to note that - * if the structure contains any cycles then this will fail. - * @param {object} context The context that handlebars should fill data from. - * @returns {object|array} The structure input, as fully updated as possible. - */ - processObjectSync: (object, context) => { - testObject(object) - for (let key of Object.keys(object)) { +/** + * Given an input object this will recurse through all props to try and update any handlebars statements within. + * @param {object|array} object The input structure which is to be recursed, it is important to note that + * if the structure contains any cycles then this will fail. + * @param {object} context The context that handlebars should fill data from. + * @returns {Promise} The structure input, as fully updated as possible. + */ +module.exports.processObject = async (object, context) => { + testObject(object) + for (let key of Object.keys(object)) { + if (object[key] != null) { let val = object[key] if (typeof val === "string") { - object[key] = module.exports.processStringSync(object[key], context) + object[key] = await module.exports.processString(object[key], context) } else if (typeof val === "object") { - object[key] = module.exports.processObjectSync(object[key], context) + object[key] = await module.exports.processObject(object[key], context) } } - return object - }, - - /** - * This will process a single handlebars containing string. If the string passed in has no valid handlebars statements - * then nothing will occur. This is a pure sync call and therefore does not have the full functionality of the async call. - * @param {string} string The template string which is the filled from the context object. - * @param {object} context An object of information which will be used to enrich the string. - * @returns {string} The enriched string, all templates should have been replaced if they can be. - */ - processStringSync: (string, context) => { - let clonedContext = removeNull(cloneDeep(context)) - clonedContext = addConstants(clonedContext) - // remove any null/undefined properties - if (typeof string !== "string") { - throw "Cannot process non-string types." - } - string = processors.preprocess(string) - // this does not throw an error when template can't be fulfilled, have to try correct beforehand - const template = hbsInstance.compile(string) - return processors.postprocess(template(clonedContext)) - }, - - /** - * Simple utility function which makes sure that a templating property has been wrapped in literal specifiers correctly. - * @param {string} property The property which is to be wrapped. - * @returns {string} The wrapped property ready to be added to a templating string. - */ - makePropSafe: property => { - return `[${property}]`.replace("[[", "[").replace("]]", "]") - }, - - /** - * Checks whether or not a template string contains totally valid syntax (simply tries running it) - * @param string The string to test for valid syntax - this may contain no templates and will be considered valid. - * @returns {boolean} Whether or not the input string is valid. - */ - isValid: string => { - const specialCases = ["isNumber", "expected a number"] - // don't really need a real context to check if its valid - const context = {} - try { - hbsInstance.compile(processors.preprocess(string, false))(context) - return true - } catch (err) { - const msg = err ? err.message : "" - const foundCase = specialCases.find(spCase => msg.includes(spCase)) - // special case for maths functions - don't have inputs yet - return !!foundCase - } - }, - - /** - * We have generated a static manifest file from the helpers that this string templating package makes use of. - * This manifest provides information about each of the helpers and how it can be used. - * @returns The manifest JSON which has been generated from the helpers. - */ - getManifest: () => { - return manifest - }, + } + return object +} + +/** + * This will process a single handlebars containing string. If the string passed in has no valid handlebars statements + * then nothing will occur. + * @param {string} string The template string which is the filled from the context object. + * @param {object} context An object of information which will be used to enrich the string. + * @returns {Promise} The enriched string, all templates should have been replaced if they can be. + */ +module.exports.processString = async (string, context) => { + // TODO: carry out any async calls before carrying out async call + return module.exports.processStringSync(string, context) +} + +/** + * Given an input object this will recurse through all props to try and update any handlebars statements within. This is + * a pure sync call and therefore does not have the full functionality of the async call. + * @param {object|array} object The input structure which is to be recursed, it is important to note that + * if the structure contains any cycles then this will fail. + * @param {object} context The context that handlebars should fill data from. + * @returns {object|array} The structure input, as fully updated as possible. + */ +module.exports.processObjectSync = (object, context) => { + testObject(object) + for (let key of Object.keys(object)) { + let val = object[key] + if (typeof val === "string") { + object[key] = module.exports.processStringSync(object[key], context) + } else if (typeof val === "object") { + object[key] = module.exports.processObjectSync(object[key], context) + } + } + return object +} + +/** + * This will process a single handlebars containing string. If the string passed in has no valid handlebars statements + * then nothing will occur. This is a pure sync call and therefore does not have the full functionality of the async call. + * @param {string} string The template string which is the filled from the context object. + * @param {object} context An object of information which will be used to enrich the string. + * @returns {string} The enriched string, all templates should have been replaced if they can be. + */ +module.exports.processStringSync = (string, context) => { + let clonedContext = removeNull(cloneDeep(context)) + clonedContext = addConstants(clonedContext) + // remove any null/undefined properties + if (typeof string !== "string") { + throw "Cannot process non-string types." + } + string = processors.preprocess(string) + // this does not throw an error when template can't be fulfilled, have to try correct beforehand + const template = hbsInstance.compile(string) + return processors.postprocess(template(clonedContext)) +} + +/** + * Simple utility function which makes sure that a templating property has been wrapped in literal specifiers correctly. + * @param {string} property The property which is to be wrapped. + * @returns {string} The wrapped property ready to be added to a templating string. + */ +module.exports.makePropSafe = property => { + return `[${property}]`.replace("[[", "[").replace("]]", "]") +} + +/** + * Checks whether or not a template string contains totally valid syntax (simply tries running it) + * @param string The string to test for valid syntax - this may contain no templates and will be considered valid. + * @returns {boolean} Whether or not the input string is valid. + */ +module.exports.isValid = string => { + const specialCases = ["isNumber", "expected a number"] + // don't really need a real context to check if its valid + const context = {} + try { + hbsInstance.compile(processors.preprocess(string, false))(context) + return true + } catch (err) { + const msg = err ? err.message : "" + const foundCase = specialCases.find(spCase => msg.includes(spCase)) + // special case for maths functions - don't have inputs yet + return !!foundCase + } +} + +/** + * We have generated a static manifest file from the helpers that this string templating package makes use of. + * This manifest provides information about each of the helpers and how it can be used. + * @returns The manifest JSON which has been generated from the helpers. + */ +module.exports.getManifest = () => { + return manifest } diff --git a/packages/string-templates/src/utilities.js b/packages/string-templates/src/utilities.js index bebbc62557..da3aa6ee94 100644 --- a/packages/string-templates/src/utilities.js +++ b/packages/string-templates/src/utilities.js @@ -28,7 +28,7 @@ module.exports.removeNull = obj => { module.exports.addConstants = obj => { if (obj.now == null) { - obj.now = new Date().toISOString() + obj.now = new Date() } return obj } diff --git a/packages/string-templates/test/helpers.spec.js b/packages/string-templates/test/helpers.spec.js index 7dbb36fa40..bbd0e64e6e 100644 --- a/packages/string-templates/test/helpers.spec.js +++ b/packages/string-templates/test/helpers.spec.js @@ -293,7 +293,12 @@ describe("Cover a few complex use cases", () => { it("should make sure case is valid", () => { const validity = isValid("{{ avg [c355ec2b422e54f988ae553c8acd811ea].[a] [c355ec2b422e54f988ae553c8acd811ea].[b] }}") - expect(validity).toBe(true) }) + + it("should be able to solve an example from docs", async () => { + const output = await processString(`{{first ( split "a-b-c" "-") 2}}`, {}) + expect(output).toBe(`a,b`) + + }) }) \ No newline at end of file diff --git a/packages/string-templates/yarn.lock b/packages/string-templates/yarn.lock index 01a31d2150..0cbed748ed 100644 --- a/packages/string-templates/yarn.lock +++ b/packages/string-templates/yarn.lock @@ -465,6 +465,19 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@rollup/plugin-commonjs@^17.1.0": + version "17.1.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz#757ec88737dffa8aa913eb392fade2e45aef2a2d" + integrity sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew== + dependencies: + "@rollup/pluginutils" "^3.1.0" + commondir "^1.0.1" + estree-walker "^2.0.1" + glob "^7.1.6" + is-reference "^1.2.1" + magic-string "^0.25.7" + resolve "^1.17.0" + "@rollup/plugin-json@^4.1.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3" @@ -472,7 +485,15 @@ dependencies: "@rollup/pluginutils" "^3.0.8" -"@rollup/pluginutils@^3.0.8": +"@rollup/plugin-replace@^2.3.4": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-2.3.4.tgz#7dd84c17755d62b509577f2db37eb524d7ca88ca" + integrity sha512-waBhMzyAtjCL1GwZes2jaE9MjuQ/DQF2BatH3fRivUF3z0JBFrU0U6iBNC/4WR+2rLKhaAhPWDNPYp4mI6RqdQ== + dependencies: + "@rollup/pluginutils" "^3.1.0" + magic-string "^0.25.7" + +"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== @@ -1394,6 +1415,11 @@ commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -1793,6 +1819,11 @@ estree-walker@^1.0.1: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== +estree-walker@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -2086,7 +2117,7 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -2565,7 +2596,7 @@ is-potential-custom-element-name@^1.0.0: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397" integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c= -is-reference@^1.1.2: +is-reference@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== @@ -3383,7 +3414,7 @@ magic-string@^0.22.5: dependencies: vlq "^0.2.2" -magic-string@^0.25.2: +magic-string@^0.25.7: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== @@ -4086,7 +4117,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.10.0, resolve@^1.11.0, resolve@^1.11.1, resolve@^1.18.1: +resolve@^1.10.0, resolve@^1.11.1, resolve@^1.17.0, resolve@^1.18.1: version "1.19.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== @@ -4114,17 +4145,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rollup-plugin-commonjs@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz#417af3b54503878e084d127adf4d1caf8beb86fb" - integrity sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q== - dependencies: - estree-walker "^0.6.1" - is-reference "^1.1.2" - magic-string "^0.25.2" - resolve "^1.11.0" - rollup-pluginutils "^2.8.1" - rollup-plugin-node-builtins@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/rollup-plugin-node-builtins/-/rollup-plugin-node-builtins-2.1.2.tgz#24a1fed4a43257b6b64371d8abc6ce1ab14597e9"