Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to Dynamically Load handlebars helper functions #325

Open
esatterwhite opened this issue Nov 11, 2021 · 5 comments
Open

Ability to Dynamically Load handlebars helper functions #325

esatterwhite opened this issue Nov 11, 2021 · 5 comments
Labels
enhancement New feature or request

Comments

@esatterwhite
Copy link
Contributor

esatterwhite commented Nov 11, 2021

Is your feature request related to a problem? Please describe.
Adding helper functions / blocks for handlebars is a tedious process as the are baked into this package with no way to load them.
One has to get a pull request merged into this repo and wait for it to be released and update razee deployments in existing environments to be able to use them.

Describe the solution you'd like
It would be nice to have a way to load functions dynamically into the controller. Generally, The easiest way is to private an environment variable that specifies a location to look for node modules and tries to load them. If it can, it adds them to the handlebars instance.

Additionally, it may be useful to have a config option that allows one to disable specific functions or sets of functions if it is deemed they shouldn't be used by a implements

- name: RAZEE_HANDLEBARS_HELPER_LOCATION
  value: /opt/hbs
- name: RAZEE_HANDLEBARS_HELPERS
  value: '*'  | 'builtin' | 'upper,lower,stringify,concat'

Additional context
This is what we do with our in-house razee tooling that we use to test templates before they are deployed
It really just tries to load files on disk. If it loads a function it adds it by name, if its an object and the value is a function it'll load all of the functions in the object where the key is the name of the helper.

there are a couple of options, but you can point it at:

  • an absolute or relative path to a directory
  • an absolute or relative path to a file
'use strict'

const path = require('path')
const {promisify, debuglog} = require('util')
const glob = promisify(require('glob'))

const log = debuglog('template-handlebars')

module.exports = loadHelpers
async function loadHelpers(dir, cwd = process.cwd()) {
  const out = {}
  if (!dir) return out
  const files = await glob(path.join(dir, '**', '*.js'), {cwd: cwd, absolute: true})
  for (const location of files) {
    log('loading helper file %s', location)
    const helper = require(location)
    switch (typeof helper) {
      case 'object': {
        for (const [name, fn] of Object.entries(helper)) {
          const value_type = typeof fn
          if (value_type !== 'function') {
            const error = new TypeError(
              `Object helpers must contain function properties. Got ${value_type}`
            )
            error.code = 'ETEMPLATEHELPER'
            error.location = location
            error.helper = name

            throw error
          }
          log('helper %s loaded', name)
          out[name] = fn
        }
        break
      }

      case 'function': {
        const name = helper.name
          ? helper.name.toLowerCase()
          : path.parse(location).name

        log('helper %s loaded', name)
        out[name] = helper
        break
      }

      default: {
        const error = new TypeError(
          `Unexpected handlebars helper type ${typeof helper}.`
            + ' Helpers must be functions, or an object containg functions'
        )
        error.code = 'ETEMPLATEHELPER'
        error.location = location
        throw error
      }
    }
  }
  return out
}
@esatterwhite
Copy link
Contributor Author

Another thing to take into consideration with this is that people may want to use existing packages, or arrange their files as packages, rather than single files / js modules - in order to better manage dependencies ( or have dependencies period ).

The above implementation doesn't account for that.

@esatterwhite
Copy link
Contributor Author

helpers/
├─ helper-one/
│  ├─ lib/
│  │  └─ index.js
│  ├─ index.js
│  └─ package.json
├─ helper-two/
│  ├─ lib/
│  │  └─ index.js
│  ├─ index.js
│  └─ package.json
└─ helper-three/
   ├─ lib/
   │  └─ index.js
   ├─ index.js
   └─ package.json

vs

helpers/
├─ lib/
│  └─ index.js
├─ helper-one.js
├─ helper-two.js
├─ helper-three.js
└─ package.json

Being that helpers are generally intended to be simple the flat files feels innocent - but can get out of hand over time and the shared dependency problem can get rather nasty.

Supporting both setups would be desirable, and not all that difficult if you were to just lean on require to resolve the modules and have the restriction of a single level file layout ( only read the directory specified ).

@stale
Copy link

stale bot commented Apr 16, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix This will not be worked on label Apr 16, 2022
@esatterwhite
Copy link
Contributor Author

I think this is still a thing to do

@stale stale bot removed the wontfix This will not be worked on label Apr 16, 2022
@alewitt2 alewitt2 added the enhancement New feature or request label May 3, 2022
@alewitt2
Copy link
Member

alewitt2 commented May 3, 2022

note: once this is implemented, would be useful and probably not too different to allow loading of partials as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants