express-hbs 2.4.2 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/worktrees/node-20-26-migration-7de0c3/.editorconfig +8 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/.github/workflows/publish.yml +73 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/.github/workflows/test.yml +48 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/.jsbeautifyrc +17 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/.nvmrc +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/.nycrc.json +16 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/.oxfmtrc.json +19 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/.oxlintrc.json +32 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/AGENTS.md +40 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/LICENSE +21 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/README.md +333 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/SECURITY.md +9 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/app-layoutsDir.js +99 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/app.js +103 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/public/css/style.css +17 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/fruits/details-layoutsDir.hbs +3 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/fruits/details.hbs +4 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/fruits/index-layoutsDir.hbs +20 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/fruits/index.hbs +20 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/index.hbs +34 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/layout/default.hbs +13 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/layout/fruit-details.hbs +9 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/layout/fruits.hbs +6 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/layout/html-struct.hbs +21 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/layout/veggie-details.hbs +9 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/layout/veggie.hbs +6 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/partials/scripts.hbs +5 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/partials/sub/comment.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/partials-other/other.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/replace.hbs +6 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/tos.txt +3 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/veggies/details.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/example/views/veggies.hbs +17 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/index.js +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/lib/generate-id.js +16 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/lib/hbs.js +671 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/lib/resolver.js +59 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/package.json +61 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/pnpm-lock.yaml +3424 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/pnpm-workspace.yaml +6 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/renovate.json +13 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/apps/async/index.js +139 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/apps/async/views/failer.hbs +4 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/apps/async/views/index.hbs +16 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/apps/async/views/layout.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/apps/i18n/index.js +57 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/apps/i18n/locales/en.json +8 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/apps/i18n/locales/fr.json +8 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/apps/i18n/views/index.hbs +3 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/asyncSpecs.js +69 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/cacheSpecs.js +135 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/customExtension.js +35 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/handlebarsSpecs.js +92 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/helperSpecs.js +58 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/helpers.js +24 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/i18nSpecs.js +36 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/144/index.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/153/index.hbs +3 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/21/views/index.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/21/views/layouts/default.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/21/views/layouts/sub/child.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/21/views/sub/relativeLayout.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/21/views/sub/sub.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/21/views/useLayoutInDir.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/22/layout1.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/22/layout2.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/22/template.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/23/empty.hbs +0 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/23/emptyComment.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/23/emptyPartial.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/23/index.hbs +4 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/23/layout.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/23/partials/custom/comment-only.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/23/partials/custom/empty.hbs +0 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/23/partials/hello.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/49/error.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/49/front/error.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/49/index.hbs +2 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/49/layouts/default.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/49/partial.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/49/partials/partial-error.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/53/default.hbs +15 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/53/index.hbs +7 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/53/partials/.gitkeep +0 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/59/index.hbs +2 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/62/basic.hbs +2 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/62/block.hbs +11 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/73/index.hbs +5 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/73/partials/partial.hbs +6 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/76/mypartial.hbs +0 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/84/index.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues/84/partials/abc/def/ghi/test.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/issues.js +490 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/layoutSpecs.js +146 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/localTemplateOptions/data-access-template.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/localTemplateOptions/template.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/localTemplateOptions.js +125 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/multiple.js +115 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/nonExpress.js +36 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/optionsSpecs.js +22 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/resolverSpecs.js +54 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/beautify/expected.hbs +9 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/beautify/index.hbs +2 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/customExtension/layout.server.view.html +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/customExtension/partialsDir/partial1.server.view.html +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/customExtension/partialsDir/partial2.server.view.html +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/customExtension/partialsDir/subs/subPartial1.server.view.html +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/customExtension/partialsDir/subs/subPartial2.server.view.html +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/customExtension/template.server.view.html +5 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/disableLayoutDirective/aside.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/disableLayoutDirective/index.hbs +2 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/disableLayoutDirective/layouts/default.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/helpers/home/async.hbs +6 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/helpers/home/index.hbs +3 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/helpers/layouts/default.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/multiple/views1/collide.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/multiple/views1/error.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/multiple/views1/test1.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/multiple/views2/collide.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/multiple/views2/error.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/multiple/views2/test2.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/viewsDir/layouts/default.hbs +1 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/viewsDir/sub/directive.hbs +2 -0
- package/.claude/worktrees/node-20-26-migration-7de0c3/test/views/viewsDir/sub/lay.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/.editorconfig +8 -0
- package/.claude/worktrees/security-patches-review-c621ea/.github/workflows/publish.yml +73 -0
- package/.claude/worktrees/security-patches-review-c621ea/.github/workflows/test.yml +48 -0
- package/.claude/worktrees/security-patches-review-c621ea/.jsbeautifyrc +17 -0
- package/.claude/worktrees/security-patches-review-c621ea/.nvmrc +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/.nycrc.json +16 -0
- package/.claude/worktrees/security-patches-review-c621ea/.oxfmtrc.json +19 -0
- package/.claude/worktrees/security-patches-review-c621ea/.oxlintrc.json +32 -0
- package/.claude/worktrees/security-patches-review-c621ea/AGENTS.md +40 -0
- package/.claude/worktrees/security-patches-review-c621ea/LICENSE +21 -0
- package/.claude/worktrees/security-patches-review-c621ea/README.md +333 -0
- package/.claude/worktrees/security-patches-review-c621ea/SECURITY.md +9 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/app-layoutsDir.js +99 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/app.js +103 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/public/css/style.css +17 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/fruits/details-layoutsDir.hbs +3 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/fruits/details.hbs +4 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/fruits/index-layoutsDir.hbs +20 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/fruits/index.hbs +20 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/index.hbs +34 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/layout/default.hbs +13 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/layout/fruit-details.hbs +9 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/layout/fruits.hbs +6 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/layout/html-struct.hbs +21 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/layout/veggie-details.hbs +9 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/layout/veggie.hbs +6 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/partials/scripts.hbs +5 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/partials/sub/comment.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/partials-other/other.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/replace.hbs +6 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/tos.txt +3 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/veggies/details.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/example/views/veggies.hbs +17 -0
- package/.claude/worktrees/security-patches-review-c621ea/index.js +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/lib/generate-id.js +16 -0
- package/.claude/worktrees/security-patches-review-c621ea/lib/hbs.js +665 -0
- package/.claude/worktrees/security-patches-review-c621ea/lib/resolver.js +59 -0
- package/.claude/worktrees/security-patches-review-c621ea/package.json +57 -0
- package/.claude/worktrees/security-patches-review-c621ea/pnpm-lock.yaml +3424 -0
- package/.claude/worktrees/security-patches-review-c621ea/pnpm-workspace.yaml +6 -0
- package/.claude/worktrees/security-patches-review-c621ea/renovate.json +6 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/apps/async/index.js +139 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/apps/async/views/failer.hbs +4 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/apps/async/views/index.hbs +16 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/apps/async/views/layout.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/apps/i18n/index.js +57 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/apps/i18n/locales/en.json +8 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/apps/i18n/locales/fr.json +8 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/apps/i18n/views/index.hbs +3 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/asyncSpecs.js +69 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/cacheSpecs.js +135 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/customExtension.js +35 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/handlebarsSpecs.js +92 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/helperSpecs.js +58 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/helpers.js +24 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/i18nSpecs.js +36 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/144/index.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/153/index.hbs +3 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/21/views/index.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/21/views/layouts/default.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/21/views/layouts/sub/child.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/21/views/sub/relativeLayout.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/21/views/sub/sub.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/21/views/useLayoutInDir.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/22/layout1.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/22/layout2.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/22/template.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/23/empty.hbs +0 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/23/emptyComment.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/23/emptyPartial.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/23/index.hbs +4 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/23/layout.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/23/partials/custom/comment-only.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/23/partials/custom/empty.hbs +0 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/23/partials/hello.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/49/error.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/49/front/error.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/49/index.hbs +2 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/49/layouts/default.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/49/partial.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/49/partials/partial-error.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/53/default.hbs +15 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/53/index.hbs +7 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/53/partials/.gitkeep +0 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/59/index.hbs +2 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/62/basic.hbs +2 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/62/block.hbs +11 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/73/index.hbs +5 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/73/partials/partial.hbs +6 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/76/mypartial.hbs +0 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/84/index.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues/84/partials/abc/def/ghi/test.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/issues.js +490 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/layoutSpecs.js +116 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/localTemplateOptions/data-access-template.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/localTemplateOptions/template.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/localTemplateOptions.js +125 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/multiple.js +115 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/nonExpress.js +36 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/optionsSpecs.js +22 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/resolverSpecs.js +54 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/beautify/expected.hbs +9 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/beautify/index.hbs +2 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/customExtension/layout.server.view.html +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/customExtension/partialsDir/partial1.server.view.html +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/customExtension/partialsDir/partial2.server.view.html +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/customExtension/partialsDir/subs/subPartial1.server.view.html +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/customExtension/partialsDir/subs/subPartial2.server.view.html +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/customExtension/template.server.view.html +5 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/disableLayoutDirective/aside.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/disableLayoutDirective/index.hbs +2 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/disableLayoutDirective/layouts/default.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/helpers/home/async.hbs +6 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/helpers/home/index.hbs +3 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/helpers/layouts/default.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/multiple/views1/collide.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/multiple/views1/error.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/multiple/views1/test1.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/multiple/views2/collide.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/multiple/views2/error.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/multiple/views2/test2.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/viewsDir/layouts/default.hbs +1 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/viewsDir/sub/directive.hbs +2 -0
- package/.claude/worktrees/security-patches-review-c621ea/test/views/viewsDir/sub/lay.hbs +1 -0
- package/README.md +3 -2
- package/example/app-layoutsDir.js +2 -1
- package/example/app.js +2 -1
- package/lib/hbs.js +24 -4
- package/lib/resolver.js +10 -3
- package/package.json +2 -2
- package/test/apps/async/index.js +2 -1
- package/test/apps/i18n/index.js +2 -1
- package/test/asyncSpecs.js +60 -0
- package/test/customExtension.js +2 -1
- package/test/handlebarsSpecs.js +19 -0
- package/test/helperSpecs.js +4 -2
- package/test/issues.js +48 -20
- package/test/layoutSpecs.js +48 -0
- package/test/localTemplateOptions.js +12 -4
- package/test/multiple.js +3 -1
- package/test/nonExpress.js +4 -2
- package/test/optionsSpecs.js +1 -1
- package/test/resolverSpecs.js +70 -0
- package/test/views/asyncPrefix/body.hbs +1 -0
- package/test/views/asyncPrefix/index.hbs +1 -0
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var readdirp = require('readdirp');
|
|
6
|
+
var handlebars = require('handlebars');
|
|
7
|
+
var resolver = require('./resolver');
|
|
8
|
+
var _ = require('lodash');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Regex pattern for layout directive. {{!< layout }}
|
|
12
|
+
*/
|
|
13
|
+
var layoutPattern = /{{!<\s+([A-Za-z0-9._\-/]+)\s*}}/;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Constructor
|
|
17
|
+
*/
|
|
18
|
+
var ExpressHbs = function () {
|
|
19
|
+
this.handlebars = handlebars.create();
|
|
20
|
+
this.SafeString = this.handlebars.SafeString;
|
|
21
|
+
this.Utils = this.handlebars.Utils;
|
|
22
|
+
this.beautify = null;
|
|
23
|
+
this.beautifyrc = null;
|
|
24
|
+
this.cwd = process.cwd();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Defines content for a named block declared in layout.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
*
|
|
32
|
+
* {{#contentFor "pageStylesheets"}}
|
|
33
|
+
* <link rel="stylesheet" href='{{{URL "css/style.css"}}}' />
|
|
34
|
+
* {{/contentFor}}
|
|
35
|
+
*/
|
|
36
|
+
ExpressHbs.prototype.content = function (name, options, context) {
|
|
37
|
+
var block = options.data.root.blockCache[name] || (options.data.root.blockCache[name] = []);
|
|
38
|
+
block.push(options.fn(context));
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Returns the layout filepath given the template filename and layout used.
|
|
43
|
+
* Backward compatible with specifying layouts in locals like 'layouts/foo',
|
|
44
|
+
* but if you have specified a layoutsDir you can specify layouts in locals with just the layout name.
|
|
45
|
+
*
|
|
46
|
+
* @param {String} filename Path to template file.
|
|
47
|
+
* @param {String} layout Layout path.
|
|
48
|
+
*/
|
|
49
|
+
ExpressHbs.prototype.layoutPath = function (filename, layout) {
|
|
50
|
+
var dirs, layoutPath;
|
|
51
|
+
if (layout[0] === '.') {
|
|
52
|
+
dirs = path.dirname(filename);
|
|
53
|
+
} else if (this.layoutsDir) {
|
|
54
|
+
dirs = this.layoutsDir;
|
|
55
|
+
} else {
|
|
56
|
+
dirs = this.viewsDir;
|
|
57
|
+
}
|
|
58
|
+
[].concat(dirs).forEach(function (dir) {
|
|
59
|
+
if (!layoutPath) {
|
|
60
|
+
layoutPath = path.resolve(dir, layout);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return layoutPath;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Find the path of the declared layout in `str`, if any
|
|
68
|
+
*
|
|
69
|
+
* @param {String} str The template string to parse
|
|
70
|
+
* @param {String} filename Path to template
|
|
71
|
+
* @returns {String|undefined} Returns the path to layout.
|
|
72
|
+
*/
|
|
73
|
+
ExpressHbs.prototype.declaredLayoutFile = function (str, filename) {
|
|
74
|
+
var matches = str.match(layoutPattern);
|
|
75
|
+
if (matches) {
|
|
76
|
+
var layout = matches[1];
|
|
77
|
+
// behave like `require`, if '.' then relative, else look in
|
|
78
|
+
// usual location (layoutsDir)
|
|
79
|
+
if (this.layoutsDir && layout[0] !== '.') {
|
|
80
|
+
layout = path.resolve(this.layoutsDir, layout);
|
|
81
|
+
}
|
|
82
|
+
return path.resolve(path.dirname(filename), layout);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Compiles a layout file.
|
|
88
|
+
*
|
|
89
|
+
* The function checks whether the layout file declares a parent layout.
|
|
90
|
+
* If it does, the parent layout is loaded recursively and checked as well
|
|
91
|
+
* for a parent layout, and so on, until the top layout is reached.
|
|
92
|
+
* All layouts are then returned as a stack to the caller via the callback.
|
|
93
|
+
*
|
|
94
|
+
* @param {String} layoutFile The path to the layout file to compile
|
|
95
|
+
* @param {Boolean} useCache Cache the compiled layout?
|
|
96
|
+
* @param {Function} cb Callback called with layouts stack
|
|
97
|
+
*/
|
|
98
|
+
ExpressHbs.prototype.cacheLayout = function (layoutFile, useCache, cb) {
|
|
99
|
+
var self = this;
|
|
100
|
+
|
|
101
|
+
if (this.restrictLayoutsTo) {
|
|
102
|
+
if (!layoutFile.startsWith(this.restrictLayoutsTo)) {
|
|
103
|
+
var err = new Error(
|
|
104
|
+
'Cannot read ' + layoutFile + ' it does not reside in ' + this.restrictLayoutsTo,
|
|
105
|
+
);
|
|
106
|
+
return cb(err, null);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// assume hbs extension
|
|
111
|
+
if (path.extname(layoutFile) === '') layoutFile += this._options.extname;
|
|
112
|
+
|
|
113
|
+
// path is relative in directive, make it absolute
|
|
114
|
+
var layoutTemplates = this.cache[layoutFile];
|
|
115
|
+
if (layoutTemplates) return cb(null, layoutTemplates);
|
|
116
|
+
|
|
117
|
+
fs.readFile(layoutFile, 'utf8', function (err, str) {
|
|
118
|
+
if (err) return cb(err);
|
|
119
|
+
|
|
120
|
+
// File path of eventual declared parent layout
|
|
121
|
+
var parentLayoutFile = self.declaredLayoutFile(str, layoutFile);
|
|
122
|
+
|
|
123
|
+
// This function returns the current layout stack to the caller
|
|
124
|
+
var _returnLayouts = function (layouts) {
|
|
125
|
+
var currentLayout;
|
|
126
|
+
layouts = layouts.slice(0);
|
|
127
|
+
currentLayout = self.compile(str, layoutFile);
|
|
128
|
+
layouts.push(currentLayout);
|
|
129
|
+
if (useCache) {
|
|
130
|
+
self.cache[layoutFile] = layouts.slice(0);
|
|
131
|
+
}
|
|
132
|
+
cb(null, layouts);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
if (parentLayoutFile) {
|
|
136
|
+
// Recursively compile/cache parent layouts
|
|
137
|
+
self.cacheLayout(parentLayoutFile, useCache, function (err, parentLayouts) {
|
|
138
|
+
if (err) return cb(err);
|
|
139
|
+
_returnLayouts(parentLayouts);
|
|
140
|
+
});
|
|
141
|
+
} else {
|
|
142
|
+
// No parent layout: return current layout with an empty stack
|
|
143
|
+
_returnLayouts([]);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Cache partial templates found under directories configure in partialsDir.
|
|
150
|
+
*/
|
|
151
|
+
ExpressHbs.prototype.cachePartials = function (cb) {
|
|
152
|
+
var self = this;
|
|
153
|
+
|
|
154
|
+
if (!(this.partialsDir instanceof Array)) {
|
|
155
|
+
this.partialsDir = [this.partialsDir];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Use to iterate all folder in series
|
|
159
|
+
var count = 0;
|
|
160
|
+
|
|
161
|
+
function readNext() {
|
|
162
|
+
readdirp(self.partialsDir[count], { fileFilter: '*' + self._options.extname })
|
|
163
|
+
.on('warn', function (err) {
|
|
164
|
+
console.warn('Non-fatal error in express-hbs cachePartials.', err);
|
|
165
|
+
})
|
|
166
|
+
.on('error', function (err) {
|
|
167
|
+
console.error('Fatal error in express-hbs cachePartials', err);
|
|
168
|
+
return cb(err);
|
|
169
|
+
})
|
|
170
|
+
.on('data', function (entry) {
|
|
171
|
+
if (!entry) return;
|
|
172
|
+
var source = fs.readFileSync(entry.fullPath, 'utf8');
|
|
173
|
+
var dirname = path.dirname(entry.path);
|
|
174
|
+
dirname = dirname === '.' ? '' : dirname + '/';
|
|
175
|
+
|
|
176
|
+
var name = dirname + path.basename(entry.basename, self._options.extname);
|
|
177
|
+
// fix the path in windows
|
|
178
|
+
name = name.split('\\').join('/');
|
|
179
|
+
self.registerPartial(name, source, entry.fullPath);
|
|
180
|
+
})
|
|
181
|
+
.on('end', function () {
|
|
182
|
+
count += 1;
|
|
183
|
+
|
|
184
|
+
// If all directories aren't read, read the next directory
|
|
185
|
+
if (count < self.partialsDir.length) {
|
|
186
|
+
readNext();
|
|
187
|
+
} else {
|
|
188
|
+
self.isPartialCachingComplete = true;
|
|
189
|
+
if (cb) cb(null, true);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
readNext();
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Express 3.x template engine compliance.
|
|
199
|
+
*
|
|
200
|
+
* @param {Object} options = {
|
|
201
|
+
* handlebars: "override handlebars",
|
|
202
|
+
* defaultLayout: "path to default layout",
|
|
203
|
+
* partialsDir: "absolute path to partials (one path or an array of paths)",
|
|
204
|
+
* layoutsDir: "absolute path to the layouts",
|
|
205
|
+
* extname: "extension to use",
|
|
206
|
+
* contentHelperName: "contentFor",
|
|
207
|
+
* blockHelperName: "block",
|
|
208
|
+
* beautify: "{Boolean} whether to pretty print HTML",
|
|
209
|
+
* onCompile: function(self, source, filename) {
|
|
210
|
+
* return self.handlebars.compile(source);
|
|
211
|
+
* }
|
|
212
|
+
* }
|
|
213
|
+
*/
|
|
214
|
+
ExpressHbs.prototype.express3 = function (options) {
|
|
215
|
+
var self = this;
|
|
216
|
+
|
|
217
|
+
// Set defaults
|
|
218
|
+
if (!options) options = {};
|
|
219
|
+
if (!options.extname) options.extname = '.hbs';
|
|
220
|
+
if (!options.contentHelperName) options.contentHelperName = 'contentFor';
|
|
221
|
+
if (!options.blockHelperName) options.blockHelperName = 'block';
|
|
222
|
+
if (!options.templateOptions) options.templateOptions = {};
|
|
223
|
+
if (options.handlebars) this.handlebars = options.handlebars;
|
|
224
|
+
if (options.onCompile) this.onCompile = options.onCompile;
|
|
225
|
+
|
|
226
|
+
this._options = options;
|
|
227
|
+
if (this._options.handlebars) this.handlebars = this._options.handlebars;
|
|
228
|
+
|
|
229
|
+
if (options.i18n) {
|
|
230
|
+
var i18n = options.i18n;
|
|
231
|
+
this.handlebars.registerHelper('__', function () {
|
|
232
|
+
var args = Array.prototype.slice.call(arguments);
|
|
233
|
+
var options = args.pop();
|
|
234
|
+
return i18n.__.apply(options.data.root, args);
|
|
235
|
+
});
|
|
236
|
+
this.handlebars.registerHelper('__n', function () {
|
|
237
|
+
var args = Array.prototype.slice.call(arguments);
|
|
238
|
+
var options = args.pop();
|
|
239
|
+
return i18n.__n.apply(options.data.root, args);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this.handlebars.registerHelper(this._options.blockHelperName, function (name, options) {
|
|
244
|
+
var val = options.data.root.blockCache[name];
|
|
245
|
+
if (val === undefined && typeof options.fn === 'function') {
|
|
246
|
+
val = options.fn(this);
|
|
247
|
+
}
|
|
248
|
+
if (Array.isArray(val)) {
|
|
249
|
+
val = val.join('\n');
|
|
250
|
+
}
|
|
251
|
+
return val;
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Pass 'this' as context of helper function to don't lose context call of helpers.
|
|
255
|
+
this.handlebars.registerHelper(this._options.contentHelperName, function (name, options) {
|
|
256
|
+
return self.content(name, options, this);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Absolute path to partials directory.
|
|
260
|
+
this.partialsDir = this._options.partialsDir;
|
|
261
|
+
|
|
262
|
+
// Absolute path to the layouts directory
|
|
263
|
+
this.layoutsDir = this._options.layoutsDir;
|
|
264
|
+
|
|
265
|
+
this.restrictLayoutsTo = this._options.restrictLayoutsTo;
|
|
266
|
+
|
|
267
|
+
// express passes this through ___express func, gulp pass in an option
|
|
268
|
+
this.viewsDir = null;
|
|
269
|
+
this.viewsDirOpt = this._options.viewsDir;
|
|
270
|
+
|
|
271
|
+
// Cache for templates, express 3.x doesn't do this for us
|
|
272
|
+
this.cache = {};
|
|
273
|
+
|
|
274
|
+
// Holds the default compiled layout if specified in options configuration.
|
|
275
|
+
this.defaultLayoutTemplates = null;
|
|
276
|
+
|
|
277
|
+
// Keep track of if partials have been cached already or not.
|
|
278
|
+
this.isPartialCachingComplete = false;
|
|
279
|
+
|
|
280
|
+
return this.___express.bind(this);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Express 4.x template engine compliance.
|
|
285
|
+
*
|
|
286
|
+
* @param {Object} options = {
|
|
287
|
+
* handlebars: "override handlebars",
|
|
288
|
+
* defaultLayout: "path to default layout",
|
|
289
|
+
* partialsDir: "absolute path to partials (one path or an array of paths)",
|
|
290
|
+
* layoutsDir: "absolute path to the layouts",
|
|
291
|
+
* extname: "extension to use",
|
|
292
|
+
* contentHelperName: "contentFor",
|
|
293
|
+
* blockHelperName: "block",
|
|
294
|
+
* beautify: "{Boolean} whether to pretty print HTML"
|
|
295
|
+
* }
|
|
296
|
+
*/
|
|
297
|
+
ExpressHbs.prototype.express4 = ExpressHbs.prototype.express3;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Tries to load the default layout.
|
|
301
|
+
*
|
|
302
|
+
* @param {Boolean} useCache Whether to cache.
|
|
303
|
+
*/
|
|
304
|
+
ExpressHbs.prototype.loadDefaultLayout = function (useCache, cb) {
|
|
305
|
+
var self = this;
|
|
306
|
+
if (!this._options.defaultLayout) return cb();
|
|
307
|
+
if (useCache && this.defaultLayoutTemplates) return cb(null, this.defaultLayoutTemplates);
|
|
308
|
+
|
|
309
|
+
this.cacheLayout(this._options.defaultLayout, useCache, function (err, templates) {
|
|
310
|
+
if (err) return cb(err);
|
|
311
|
+
self.defaultLayoutTemplates = templates.slice(0);
|
|
312
|
+
return cb(null, templates);
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Expose useful methods.
|
|
318
|
+
*/
|
|
319
|
+
ExpressHbs.prototype.registerHelper = function (name, fn) {
|
|
320
|
+
this.handlebars.registerHelper(name, fn);
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Registers a partial.
|
|
325
|
+
*
|
|
326
|
+
* @param {String} name The name of the partial as used in a template.
|
|
327
|
+
* @param {String} source String source of the partial.
|
|
328
|
+
*/
|
|
329
|
+
ExpressHbs.prototype.registerPartial = function (name, source, filename) {
|
|
330
|
+
this.handlebars.registerPartial(name, this.compile(source, filename));
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Compiles a string.
|
|
335
|
+
*
|
|
336
|
+
* @param {String} source The source to compile.
|
|
337
|
+
* @param {String} filename The path used to embed into __filename for errors.
|
|
338
|
+
*/
|
|
339
|
+
ExpressHbs.prototype.compile = function (source, filename) {
|
|
340
|
+
// Handlebars has a bug with comment only partial causes errors. This must
|
|
341
|
+
// be a string so the block below can add a space.
|
|
342
|
+
if (typeof source !== 'string') {
|
|
343
|
+
throw new Error('registerPartial must be a string for empty comment workaround');
|
|
344
|
+
}
|
|
345
|
+
if (source.indexOf('}}') === source.length - 2) {
|
|
346
|
+
source += ' ';
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
var compiled;
|
|
350
|
+
if (this.onCompile) {
|
|
351
|
+
compiled = this.onCompile(this, source, filename);
|
|
352
|
+
} else {
|
|
353
|
+
compiled = this.handlebars.compile(source);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (filename) {
|
|
357
|
+
if (Array.isArray(this.viewsDir) && this.viewsDir.length > 0) {
|
|
358
|
+
compiled.__filename = path.relative(this.cwd, filename).replace(path.sep, '/');
|
|
359
|
+
} else {
|
|
360
|
+
compiled.__filename = path.relative(this.viewsDir || '', filename).replace(path.sep, '/');
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return compiled;
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Registers an asynchronous helper.
|
|
368
|
+
*
|
|
369
|
+
* @param {String} name The name of the partial as used in a template.
|
|
370
|
+
* @param {String} fn The `function(options, cb)`
|
|
371
|
+
*/
|
|
372
|
+
ExpressHbs.prototype.registerAsyncHelper = function (name, fn) {
|
|
373
|
+
this.handlebars.registerHelper(name, function (context, options) {
|
|
374
|
+
var resolverCache =
|
|
375
|
+
this.resolverCache ||
|
|
376
|
+
_.get(context, 'data.root.resolverCache') ||
|
|
377
|
+
_.get(options, 'data.root.resolverCache');
|
|
378
|
+
if (!resolverCache) {
|
|
379
|
+
throw new Error('Could not find resolver cache in async helper ' + name + '.');
|
|
380
|
+
}
|
|
381
|
+
if (options && fn.length > 2) {
|
|
382
|
+
var resolveFunc = function (arr, cb) {
|
|
383
|
+
return fn.call(this, arr[0], arr[1], cb);
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
return resolver.resolve(resolverCache, resolveFunc.bind(this), [context, options]);
|
|
387
|
+
}
|
|
388
|
+
return resolver.resolve(resolverCache, fn.bind(this), context);
|
|
389
|
+
});
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
ExpressHbs.prototype.getTemplateOptions = function () {
|
|
393
|
+
return this._options.templateOptions;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
ExpressHbs.prototype.updateTemplateOptions = function (templateOptions) {
|
|
397
|
+
this._options.templateOptions = templateOptions;
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
ExpressHbs.prototype.getLocalTemplateOptions = function (locals) {
|
|
401
|
+
return locals._templateOptions || {};
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
ExpressHbs.prototype.updateLocalTemplateOptions = function (locals, localTemplateOptions) {
|
|
405
|
+
return (locals._templateOptions = localTemplateOptions);
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Creates a new instance of ExpressHbs.
|
|
410
|
+
*/
|
|
411
|
+
ExpressHbs.prototype.create = function () {
|
|
412
|
+
return new ExpressHbs();
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* express 3.x, 4.x template engine compliance
|
|
417
|
+
*
|
|
418
|
+
* @param {String} filename Full path to template.
|
|
419
|
+
* @param {Object} options Is the context or locals for templates. {
|
|
420
|
+
* {Object} settings - subset of Express settings, `settings.views` is
|
|
421
|
+
* the views directory
|
|
422
|
+
* }
|
|
423
|
+
* @param {Function} cb The callback expecting the rendered template as a string.
|
|
424
|
+
*
|
|
425
|
+
* @example
|
|
426
|
+
*
|
|
427
|
+
* Example options from express
|
|
428
|
+
*
|
|
429
|
+
* {
|
|
430
|
+
* settings: {
|
|
431
|
+
* 'x-powered-by': true,
|
|
432
|
+
* env: 'production',
|
|
433
|
+
* views: '/home/coder/barc/code/express-hbs/example/views',
|
|
434
|
+
* 'jsonp callback name': 'callback',
|
|
435
|
+
* 'view cache': true,
|
|
436
|
+
* 'view engine': 'hbs'
|
|
437
|
+
* },
|
|
438
|
+
* cache: true,
|
|
439
|
+
*
|
|
440
|
+
* // the rest are app-defined locals
|
|
441
|
+
* title: 'My favorite veggies',
|
|
442
|
+
* layout: 'layout/veggie'
|
|
443
|
+
* }
|
|
444
|
+
*/
|
|
445
|
+
ExpressHbs.prototype.___express = function ___express(filename, source, options, cb) {
|
|
446
|
+
// support running as a gulp/grunt filter outside of express
|
|
447
|
+
if (arguments.length === 3) {
|
|
448
|
+
cb = options;
|
|
449
|
+
options = source;
|
|
450
|
+
source = null;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
options.blockCache = {};
|
|
454
|
+
options.resolverCache = {};
|
|
455
|
+
|
|
456
|
+
this.viewsDir = options.settings.views || this.viewsDirOpt;
|
|
457
|
+
var self = this;
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Allow a layout to be declared as a handlebars comment to remain spec
|
|
461
|
+
* compatible with handlebars.
|
|
462
|
+
*
|
|
463
|
+
* Valid directives
|
|
464
|
+
*
|
|
465
|
+
* {{!< foo}} # foo.hbs in same directory as template
|
|
466
|
+
* {{!< ../layouts/default}} # default.hbs in parent layout directory
|
|
467
|
+
* {{!< ../layouts/default.html}} # default.html in parent layout directory
|
|
468
|
+
*/
|
|
469
|
+
function parseLayout(str, filename, cb) {
|
|
470
|
+
var layoutFile = self.declaredLayoutFile(str, filename);
|
|
471
|
+
if (layoutFile) {
|
|
472
|
+
self.cacheLayout(layoutFile, options.cache, cb);
|
|
473
|
+
} else {
|
|
474
|
+
cb(null, null);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Renders `template` with given `locals` and calls `cb` with the
|
|
480
|
+
* resulting HTML string.
|
|
481
|
+
*
|
|
482
|
+
* @param template
|
|
483
|
+
* @param locals
|
|
484
|
+
* @param cb
|
|
485
|
+
*/
|
|
486
|
+
function renderTemplate(template, locals, cb) {
|
|
487
|
+
var res;
|
|
488
|
+
|
|
489
|
+
try {
|
|
490
|
+
var localTemplateOptions = self.getLocalTemplateOptions(locals);
|
|
491
|
+
var localsClone = _.extend({}, locals);
|
|
492
|
+
self.updateLocalTemplateOptions(localsClone, undefined);
|
|
493
|
+
res = template(localsClone, _.merge({}, self._options.templateOptions, localTemplateOptions));
|
|
494
|
+
} catch (err) {
|
|
495
|
+
if (err.message) {
|
|
496
|
+
err.message = '[' + template.__filename + '] ' + err.message;
|
|
497
|
+
} else if (typeof err === 'string') {
|
|
498
|
+
return cb('[' + template.__filename + '] ' + err, null);
|
|
499
|
+
}
|
|
500
|
+
return cb(err, null);
|
|
501
|
+
}
|
|
502
|
+
cb(null, res);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Renders `template` with an optional set of nested `layoutTemplates` using
|
|
507
|
+
* data in `locals`.
|
|
508
|
+
*/
|
|
509
|
+
function render(template, locals, layoutTemplates, cb) {
|
|
510
|
+
if (!layoutTemplates) layoutTemplates = [];
|
|
511
|
+
|
|
512
|
+
// We'll render templates from bottom to top of the stack, each template
|
|
513
|
+
// being passed the rendered string of the previous ones as `body`
|
|
514
|
+
var i = layoutTemplates.length - 1;
|
|
515
|
+
|
|
516
|
+
var _stackRenderer = function (err, htmlStr) {
|
|
517
|
+
if (err) return cb(err);
|
|
518
|
+
|
|
519
|
+
if (i >= 0) {
|
|
520
|
+
locals.body = htmlStr;
|
|
521
|
+
renderTemplate(layoutTemplates[i--], locals, _stackRenderer);
|
|
522
|
+
} else {
|
|
523
|
+
cb(null, htmlStr);
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
// Start the rendering with the innermost page template
|
|
528
|
+
renderTemplate(template, locals, _stackRenderer);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Lazy loads js-beautify, which should not be used in production env.
|
|
533
|
+
*/
|
|
534
|
+
function loadBeautify() {
|
|
535
|
+
if (!self.beautify) {
|
|
536
|
+
self.beautify = require('js-beautify').html;
|
|
537
|
+
var rc = path.join(process.cwd(), '.jsbeautifyrc');
|
|
538
|
+
if (fs.existsSync(rc)) {
|
|
539
|
+
self.beautifyrc = JSON.parse(fs.readFileSync(rc, 'utf8'));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Gets the source and compiled template for filename either from the cache
|
|
546
|
+
* or compiling it on the fly.
|
|
547
|
+
*/
|
|
548
|
+
function getSourceTemplate(cb) {
|
|
549
|
+
if (options.cache) {
|
|
550
|
+
var info = self.cache[filename];
|
|
551
|
+
if (info) {
|
|
552
|
+
return cb(null, info.source, info.template);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
fs.readFile(filename, 'utf8', function (err, source) {
|
|
557
|
+
if (err) return cb(err);
|
|
558
|
+
|
|
559
|
+
var template = self.compile(source, filename);
|
|
560
|
+
if (options.cache) {
|
|
561
|
+
self.cache[filename] = {
|
|
562
|
+
source: source,
|
|
563
|
+
template: template,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
return cb(null, source, template);
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Compiles a file into a template and a layoutTemplate, then renders it above.
|
|
572
|
+
*/
|
|
573
|
+
function compileFile(locals, cb) {
|
|
574
|
+
getSourceTemplate(function (err, source, template) {
|
|
575
|
+
if (err) return cb(err);
|
|
576
|
+
|
|
577
|
+
// Try to get the layout
|
|
578
|
+
parseLayout(source, filename, function (err, layoutTemplates) {
|
|
579
|
+
if (err) return cb(err);
|
|
580
|
+
|
|
581
|
+
function renderIt(layoutTemplates) {
|
|
582
|
+
if (self._options.beautify) {
|
|
583
|
+
return render(template, locals, layoutTemplates, function (err, html) {
|
|
584
|
+
if (err) return cb(err);
|
|
585
|
+
loadBeautify();
|
|
586
|
+
return cb(null, self.beautify(html, self.beautifyrc));
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
return render(template, locals, layoutTemplates, cb);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Determine which layout to use
|
|
593
|
+
|
|
594
|
+
if (typeof options.layout !== 'undefined' && !options.layout) {
|
|
595
|
+
// If options.layout is falsy, behave as if no layout should be used - suppress defaults
|
|
596
|
+
renderIt(null);
|
|
597
|
+
} else if (layoutTemplates) {
|
|
598
|
+
// 1. Layout specified in template
|
|
599
|
+
renderIt(layoutTemplates);
|
|
600
|
+
} else if (typeof options.layout !== 'undefined' && options.layout) {
|
|
601
|
+
// 2. Layout specified by options from render
|
|
602
|
+
var layoutFile = self.layoutPath(filename, options.layout);
|
|
603
|
+
self.cacheLayout(layoutFile, options.cache, function (err, layoutTemplates) {
|
|
604
|
+
if (err) return cb(err);
|
|
605
|
+
renderIt(layoutTemplates);
|
|
606
|
+
});
|
|
607
|
+
} else if (self.defaultLayoutTemplates) {
|
|
608
|
+
// 3. Default layout specified when middleware was configured.
|
|
609
|
+
renderIt(self.defaultLayoutTemplates);
|
|
610
|
+
} else {
|
|
611
|
+
// render without a template
|
|
612
|
+
renderIt(null);
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function replaceValue(values, text) {
|
|
619
|
+
if (typeof text === 'string') {
|
|
620
|
+
Object.keys(values).forEach(function (id) {
|
|
621
|
+
text = text.replace(id, function () {
|
|
622
|
+
return values[id];
|
|
623
|
+
});
|
|
624
|
+
text = text.replace(self.Utils.escapeExpression(id), function () {
|
|
625
|
+
return self.Utils.escapeExpression(values[id]);
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
return text;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Handles waiting for async helpers
|
|
633
|
+
function handleAsync(err, res) {
|
|
634
|
+
if (err) return cb(err);
|
|
635
|
+
resolver.done(options.resolverCache, function (err, values) {
|
|
636
|
+
if (err) return cb(err);
|
|
637
|
+
Object.keys(values).forEach(function (key) {
|
|
638
|
+
values[key] = replaceValue(values, values[key]);
|
|
639
|
+
});
|
|
640
|
+
res = replaceValue(values, res);
|
|
641
|
+
if (resolver.hasResolvers(res)) {
|
|
642
|
+
return handleAsync(null, res);
|
|
643
|
+
}
|
|
644
|
+
cb(null, res);
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// kick it off by loading default template (if any)
|
|
649
|
+
this.loadDefaultLayout(options.cache, function (err) {
|
|
650
|
+
if (err) return cb(err);
|
|
651
|
+
|
|
652
|
+
// Force reloading of all partials if caching is not used. Inefficient but there
|
|
653
|
+
// is no loading partial event.
|
|
654
|
+
if (self.partialsDir && (!options.cache || !self.isPartialCachingComplete)) {
|
|
655
|
+
return self.cachePartials(function (err) {
|
|
656
|
+
if (err) return cb(err);
|
|
657
|
+
return compileFile(options, handleAsync);
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return compileFile(options, handleAsync);
|
|
662
|
+
});
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
module.exports = new ExpressHbs();
|