Skip to content

Commit

Permalink
Merge pull request #7 from mbland/strict-typing
Browse files Browse the repository at this point in the history
Add strict type checking via TypeScript
  • Loading branch information
mbland committed Jan 10, 2024
2 parents 4400723 + eb5b9a8 commit 82145e2
Show file tree
Hide file tree
Showing 14 changed files with 515 additions and 84 deletions.
8 changes: 4 additions & 4 deletions .eslintrc
@@ -1,7 +1,7 @@
{
"env" : {
"env": {
"node": true,
"es2023" : true
"es2023": true
},
"parserOptions": {
"ecmaVersion": "latest",
Expand All @@ -14,7 +14,7 @@
],
"extends": [
"eslint:recommended",
"plugin:jsdoc/recommended"
"plugin:jsdoc/recommended-typescript-flavor-error"
],
"overrides": [
{
Expand All @@ -25,7 +25,7 @@
]
}
],
"rules" : {
"rules": {
"@stylistic/js/comma-dangle": [
"error", "never"
],
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -10,4 +10,6 @@ node_modules/
out/
pnpm-debug.log
tmp/
types/
*.log
*.tgz
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -5,7 +5,7 @@
_**Status**: I've still got a bit of work to do before publishing v1.0.0. I need
to add tests based on the mbland/tomcat-servlet-testing-example project from
whence this came and add more documentation. I plan to finish this by
2024-01-08._
2024-01-11._

Source: <https://github.com/mbland/rollup-plugin-handlebars-precompiler>

Expand Down
2 changes: 1 addition & 1 deletion ci/vitest.config.js
@@ -1,5 +1,5 @@
import { defineConfig, mergeConfig } from 'vitest/config'
import baseConfig from '../vitest.config'
import baseConfig from '../vitest.config.js'

export default mergeConfig(baseConfig, defineConfig({
test: {
Expand Down
42 changes: 37 additions & 5 deletions index.js
Expand Up @@ -37,24 +37,56 @@
*/

import PluginImpl, { PLUGIN_NAME } from './lib/index.js'
// eslint-disable-next-line no-unused-vars
import { PluginOptions, Transform } from './lib/types.js'

/**
* A Rollup plugin object for precompiling Handlebars templates.
* @module rollup-plugin-handlebars-precompiler
*/

/**
* @typedef {object} RollupPlugin
* @property {string} name - plugin name
* @property {Function} resolveId - resolves the plugin's own import ID
* @property {Function} load - emits the plugin's helper module code
* @property {Function} transform - emits JavaScript code compiled from
* Handlebars templates
* @see https://rollupjs.org/plugin-development/
*/

/**
* Returns a Rollup plugin object for precompiling Handlebars templates.
* @function default
* @param {object} options object containing Handlebars compiler API options
* @returns {object} a Rollup plugin that precompiles Handlebars templates
* @param {PluginOptions} options - plugin configuration options
* @returns {RollupPlugin} - the configured plugin object
*/
export default function HandlebarsPrecompiler(options) {
const p = new PluginImpl(options)
return {
name: PLUGIN_NAME,
resolveId(id) { if (p.shouldEmitHelpersModule(id)) return id },
load(id) { if (p.shouldEmitHelpersModule(id)) return p.helpersModule() },
transform(code, id) { if (p.isTemplate(id)) return p.compile(code, id) }

/**
* @param {string} id - import identifier to resolve
* @returns {(string | undefined)} - the plugin ID if id matches it
* @see https://rollupjs.org/plugin-development/#resolveid
*/
resolveId: function (id) {
return p.shouldEmitHelpersModule(id) ? id : undefined
},

/**
* @param {string} id - import identifier to load
* @returns {(string | undefined)} - the plugin helper module if id matches
* @see https://rollupjs.org/plugin-development/#load
*/
load: function (id) {
return p.shouldEmitHelpersModule(id) ? p.helpersModule() : undefined
},

/** @type {Transform} */
transform: function (code, id) {
return p.isTemplate(id) ? p.compile(code, id) : undefined
}
}
}
16 changes: 16 additions & 0 deletions jsconfig.json
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"checkJs": true,
"lib": [
"ES2022"
],
"module": "node16",
"target": "es2020",
"strict": true
},
"exclude": [
"node_modules/**",
"coverage*/**",
"jsdoc/**"
]
}
49 changes: 45 additions & 4 deletions lib/index.js
Expand Up @@ -37,19 +37,27 @@
*/

import collectPartials from './partials.js'
import {
// eslint-disable-next-line no-unused-vars
Compiled, PartialName, PartialPath, PluginOptions, SourceMap, Transform
} from './types.js'
import { createFilter } from '@rollup/pluginutils'
import Handlebars from 'handlebars'

export const PLUGIN_NAME = 'handlebars-precompiler'
const DEFAULT_INCLUDE = ['**/*.hbs', '**/*.handlebars', '**/*.mustache']
const DEFAULT_EXCLUDE = 'node_modules/**'
const DEFAULT_PARTIALS = '**/_*'
const DEFAULT_PARTIAL_NAME = id => {

/** @type {PartialName} */
const DEFAULT_PARTIAL_NAME = function (id) {
return id.replace(/.*\//, '') // extract the basename
.replace(/\.[^.]*$/, '') // remove the file extension, if present
.replace(/^[^[:alnum:]]*/, '') // strip leading non-alphanumeric characters
}
const DEFAULT_PARTIAL_PATH = (partialName, importerPath) => {

/** @type {PartialPath} */
const DEFAULT_PARTIAL_PATH = function (partialName, importerPath) {
return `./_${partialName}.${importerPath.replace(/.*\./, '')}`
}

Expand All @@ -58,6 +66,21 @@ const HANDLEBARS_PATH = 'handlebars/lib/handlebars.runtime'
const IMPORT_HANDLEBARS = `import Handlebars from '${HANDLEBARS_PATH}'`
const IMPORT_HELPERS = `import Render from '${PLUGIN_ID}'`

/**
* @callback CompilerOpts
* @param {string} id - import ID of module to compile
* @returns {object} - Handlebars compiler options based on id
*/

/**
* @callback AdjustSourceMap
* @param {string} map - the Handlebars source map as a JSON string
* @param {number} numLinesBeforeTmpl - number of empty lines to add to the
* beginning of the source mappings to account for the generated code before
* the precompiled template
* @returns {SourceMap} - potentially modified Handlebars source map
*/

/**
* Rollup Handlebars precompiler implementation
*/
Expand All @@ -67,10 +90,15 @@ export default class PluginImpl {
#isPartial
#partialName
#partialPath
/** @type {CompilerOpts} */
#compilerOpts
/** @type {AdjustSourceMap} */
#adjustSourceMap

constructor(options = {}) {
/**
* @param {PluginOptions} options - plugin configuration options
*/
constructor(options = /** @type {PluginOptions} */ ({})) {
this.#helpers = options.helpers || []
this.#isTemplate = createFilter(
options.include || DEFAULT_INCLUDE,
Expand Down Expand Up @@ -101,6 +129,10 @@ export default class PluginImpl {
}
}

/**
* @param {string} id - import identifier
* @returns {boolean} - true if id is the plugin's import identifier
*/
shouldEmitHelpersModule(id) { return id === PLUGIN_ID }

helpersModule() {
Expand All @@ -118,12 +150,17 @@ export default class PluginImpl {
].join('\n')
}

/**
* @param {string} id - import identifier
* @returns {boolean} - true if id matches the filter for template files
*/
isTemplate(id) { return this.#isTemplate(id) }

/** @type {Transform} */
compile(code, id) {
const opts = this.#compilerOpts(id)
const ast = Handlebars.parse(code, opts)
const compiled = Handlebars.precompile(ast, opts)
const compiled = /** @type {Compiled} */ (Handlebars.precompile(ast, opts))
const { code: tmpl = compiled, map: srcMap } = compiled

const beforeTmpl = [
Expand All @@ -143,6 +180,10 @@ export default class PluginImpl {
}
}

/**
* @param {string} id - id of the partial to register
* @returns {string} - Handlebars.registerPartial statement for the partial
*/
#partialRegistration(id) {
return `Handlebars.registerPartial('${this.#partialName(id)}', RawTemplate)`
}
Expand Down
22 changes: 16 additions & 6 deletions lib/partials.js
Expand Up @@ -43,18 +43,28 @@ import Handlebars from 'handlebars'
* @see https://github.com/handlebars-lang/handlebars.js/blob/master/docs/compiler-api.md
*/
class PartialCollector extends Handlebars.Visitor {
/** @type {string[]} */
partials = []

/**
* @param {hbs.AST.PartialStatement} partial - partial name to evaluate
*/
PartialStatement(partial) {
this.collect(partial.name)
return super.PartialStatement(partial)
super.PartialStatement(partial)
}

/**
* @param {hbs.AST.PartialBlockStatement} partial - partial name to evaluate
*/
PartialBlockStatement(partial) {
this.collect(partial.name)
return super.PartialBlockStatement(partial)
super.PartialBlockStatement(partial)
}

/**
* @param {hbs.AST.PathExpression | hbs.AST.SubExpression} n - potential
* partial name to collect
*/
collect(n) {
if (n.type === 'PathExpression' && n.original !== '@partial-block') {
this.partials.push(n.original)
Expand All @@ -64,11 +74,11 @@ class PartialCollector extends Handlebars.Visitor {

/**
* Returns the partial names parsed from a Handlebars template
* @param {hbs.AST.Program} ast - abstract syntax tree for a Handlebars template
* returned by Handlebars.parse()
* @returns {string[]} - a list of partial names parsed from the template
* @see https://handlebarsjs.com/guide/partials.html
* @see https://github.com/handlebars-lang/handlebars.js/blob/master/docs/compiler-api.md
* @param {object} ast - abstract syntax tree for a Handlebars template returned
* by Handlebars.parse()
* @returns {string[]} - a list of partial names parsed from the template
*/
export default function collectPartials(ast) {
const collector = new PartialCollector()
Expand Down

0 comments on commit 82145e2

Please sign in to comment.