Skip to content

Add strict type checking via TypeScript #7

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

Merged
merged 1 commit into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ node_modules/
out/
pnpm-debug.log
tmp/
types/
*.log
*.tgz
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading