diff --git a/.prettierignore b/.prettierignore index df7a6372..2a386cf1 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,3 @@ # Ignore all HTML files in test directory: test/cases/**/*.html +test/cases/**/*.hbs diff --git a/CHANGELOG.md b/CHANGELOG.md index 86bc0b07..a6da2a1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change log +## 2.10.0 (2023-09-01) + +- feat: add Handlebars helpers `assign`, `partial` and `block` to extend a template layout with blocks +- chore: add `handlebars-layout` example +- docs: update README + ## 2.9.0 (2023-08-27) - feat(experimental): add support the Webpack `cache.type` as `filesystem`. This is yet an alpha version of the feature. diff --git a/README.md b/README.md index 85a658ef..9f05bcda 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,8 @@ > _HTML as an entry point works in both Vite and Parcel, and now also in Webpack._ -The plugin allows to specify template files as [entry points](#option-entry). -You can use various templates such as HTML, PHTML, EJS, Eta, Handlebars, Nunjucks and others without additional loaders and plugins. - -The plugin resolves references in the HTML template and adds them to the Webpack compilation. -Webpack will automatically process the source files and the plugin replace the references with their URLs to output files in the generated HTML. -Using the [`js.inline`](#option-js) and [`css.inline`](#option-css) options, you can inline JS and CSS into HTML. +The plugin allows to use any [template](#template-engine) file as [entry point](#option-entry). +In an HTML template can be referenced any source files, similar to how it works in [Vite](https://vitejs.dev/guide/#index-html-and-project-root) or [Parcel](https://parceljs.org/).
Entry point is HTML |
---|
About custom block
+{{/partial}} + + +{{> layout}} diff --git a/examples/handlebars-layout/src/views/pages/home/content.hbs b/examples/handlebars-layout/src/views/pages/home/content.hbs new file mode 100644 index 00000000..3e1b5594 --- /dev/null +++ b/examples/handlebars-layout/src/views/pages/home/content.hbs @@ -0,0 +1 @@ +Home content
diff --git a/examples/handlebars-layout/src/views/pages/home/home.js b/examples/handlebars-layout/src/views/pages/home/home.js new file mode 100644 index 00000000..c482e2d8 --- /dev/null +++ b/examples/handlebars-layout/src/views/pages/home/home.js @@ -0,0 +1 @@ +console.log('>> Home page'); diff --git a/examples/handlebars-layout/src/views/pages/home/home.scss b/examples/handlebars-layout/src/views/pages/home/home.scss new file mode 100644 index 00000000..36998cdf --- /dev/null +++ b/examples/handlebars-layout/src/views/pages/home/home.scss @@ -0,0 +1,3 @@ +.container { + color: #118851; +} diff --git a/examples/handlebars-layout/src/views/pages/home/index.hbs b/examples/handlebars-layout/src/views/pages/home/index.hbs new file mode 100644 index 00000000..97b182c6 --- /dev/null +++ b/examples/handlebars-layout/src/views/pages/home/index.hbs @@ -0,0 +1,19 @@ +{{assign title='Homepage' header='Home'}} + + +{{#partial 'scripts'}} + +{{/partial}} + + +{{#partial 'styles'}} + +{{/partial}} + + +{{#partial 'content'}} + {{> home/content}} +{{/partial}} + + +{{> layout}} diff --git a/examples/handlebars-layout/src/views/partials/footer.hbs b/examples/handlebars-layout/src/views/partials/footer.hbs new file mode 100644 index 00000000..5191aac4 --- /dev/null +++ b/examples/handlebars-layout/src/views/partials/footer.hbs @@ -0,0 +1 @@ + diff --git a/examples/handlebars-layout/src/views/partials/header.hbs b/examples/handlebars-layout/src/views/partials/header.hbs new file mode 100644 index 00000000..74eaf8c8 --- /dev/null +++ b/examples/handlebars-layout/src/views/partials/header.hbs @@ -0,0 +1 @@ +default block content
{{/block}} + + {{> footer }} + + diff --git a/examples/handlebars-layout/webpack.config.js b/examples/handlebars-layout/webpack.config.js new file mode 100644 index 00000000..55658b3e --- /dev/null +++ b/examples/handlebars-layout/webpack.config.js @@ -0,0 +1,77 @@ +const path = require('path'); +//const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); +const HtmlBundlerPlugin = require('../../'); + +module.exports = { + //mode: 'development', + mode: 'production', + + output: { + path: path.resolve(__dirname, 'dist'), + clean: true, + }, + + resolve: { + alias: { + '@scripts': path.join(__dirname, 'src/js'), + '@styles': path.join(__dirname, 'src/scss'), + '@images': path.join(__dirname, 'src/images'), + }, + }, + + plugins: [ + new HtmlBundlerPlugin({ + entry: { + // define templates here + index: 'src/views/pages/home/index.hbs', // => dist/index.html + // index: { + // import: 'src/views/pages/home/index.hbs', + // data: { title: 'Home' }, + // }, + about: 'src/views/pages/about/index.hbs', // => dist/about.html + }, + // specify the `handlebars` template engine + preprocessor: 'handlebars', + // define handlebars options + preprocessorOptions: { + //helpers: [path.join(__dirname, 'src/views/helpers')], + partials: ['src/views/pages/', 'src/views/partials/'], + }, + js: { + // output filename of compiled JavaScript + filename: 'js/[name].[contenthash:8].js', + }, + css: { + // output filename of extracted CSS + filename: 'css/[name].[contenthash:8].css', + }, + }), + ], + + module: { + rules: [ + { + test: /\.(scss)$/, + use: ['css-loader', 'sass-loader'], + }, + { + test: /\.(png|svg|jpe?g|webp)$/i, + type: 'asset/resource', + generator: { + filename: 'img/[name].[hash:8][ext]', + }, + }, + ], + }, + + // enable live reload + devServer: { + static: path.resolve(__dirname, 'dist'), + watchFiles: { + paths: ['src/**/*.*'], + options: { + usePolling: true, + }, + }, + }, +}; diff --git a/examples/handlebars/webpack.config.js b/examples/handlebars/webpack.config.js index e7d008ae..80a38fd4 100644 --- a/examples/handlebars/webpack.config.js +++ b/examples/handlebars/webpack.config.js @@ -67,7 +67,7 @@ module.exports = { ], }, - // enable HMR with live reload + // enable live reload devServer: { static: path.resolve(__dirname, 'dist'), watchFiles: { diff --git a/examples/hello-world/webpack.config.js b/examples/hello-world/webpack.config.js index f14395d8..d192e716 100644 --- a/examples/hello-world/webpack.config.js +++ b/examples/hello-world/webpack.config.js @@ -42,7 +42,7 @@ module.exports = { ], }, - // enable HMR with live reload + // enable live reload devServer: { static: path.resolve(__dirname, 'dist'), watchFiles: { diff --git a/examples/simple-site/webpack.config.js b/examples/simple-site/webpack.config.js index aea7d96f..bd068c8a 100644 --- a/examples/simple-site/webpack.config.js +++ b/examples/simple-site/webpack.config.js @@ -53,7 +53,7 @@ module.exports = { ], }, - // enable HMR with live reload + // enable live reload devServer: { static: path.resolve(__dirname, 'dist'), watchFiles: { diff --git a/examples/tailwindcss/webpack.config.js b/examples/tailwindcss/webpack.config.js index 3944157a..c64ef7ac 100644 --- a/examples/tailwindcss/webpack.config.js +++ b/examples/tailwindcss/webpack.config.js @@ -48,7 +48,7 @@ module.exports = { ], }, - // enable HMR with live reload + // enable live reload devServer: { static: path.resolve(__dirname, 'dist'), watchFiles: { diff --git a/package-lock.json b/package-lock.json index a169512d..2f202634 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "css-minimizer-webpack-plugin": "^5.0.1", "ejs": "^3.1.9", "handlebars": "^4.7.8", + "handlebars-loader": "^1.7.3", "jest": "^29.6.4", "liquidjs": "^10.9.1", "mustache": "^4.2.0", @@ -36,6 +37,7 @@ "sass": "^1.65.1", "sass-loader": "^13.3.2", "sharp": "^0.32.5", + "ts-loader": "9.4.4", "webpack": "^5.88.2", "webpack-cli": "5.1.4", "webpack-dev-server": "^4.15.1" @@ -3551,6 +3553,15 @@ "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", "dev": true }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -4815,6 +4826,15 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -5182,6 +5202,12 @@ "node": ">= 4.9.1" } }, + "node_modules/fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -5567,6 +5593,47 @@ "uglify-js": "^3.1.4" } }, + "node_modules/handlebars-loader": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/handlebars-loader/-/handlebars-loader-1.7.3.tgz", + "integrity": "sha512-dDb+8D51vE3OTSE2wuGPWRAegtsEuw8Mk8hCjtRu/pNcBfN5q+M8ZG3kVJxBuOeBrVElpFStipGmaxSBTRR1mQ==", + "dev": true, + "dependencies": { + "async": "^3.2.2", + "fastparse": "^1.0.0", + "loader-utils": "1.4.x", + "object-assign": "^4.1.0" + }, + "peerDependencies": { + "handlebars": ">= 1.3.0 < 5" + } + }, + "node_modules/handlebars-loader/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/handlebars-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -8473,6 +8540,15 @@ "node": ">= 6" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -10815,6 +10891,128 @@ "node": ">=0.6" } }, + "node_modules/ts-loader": { + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", + "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ts-loader/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ts-loader/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-loader/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-loader/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-loader/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-loader/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -10866,6 +11064,20 @@ "node": ">= 0.6" } }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -11542,6 +11754,7 @@ } }, "test/fixtures/modules/import-css": { + "name": "@test/import-css", "dev": true } }, @@ -14167,6 +14380,12 @@ "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", "dev": true }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -15083,6 +15302,12 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -15371,6 +15596,12 @@ "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", "dev": true }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true + }, "fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -15664,6 +15895,40 @@ "wordwrap": "^1.0.0" } }, + "handlebars-loader": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/handlebars-loader/-/handlebars-loader-1.7.3.tgz", + "integrity": "sha512-dDb+8D51vE3OTSE2wuGPWRAegtsEuw8Mk8hCjtRu/pNcBfN5q+M8ZG3kVJxBuOeBrVElpFStipGmaxSBTRR1mQ==", + "dev": true, + "requires": { + "async": "^3.2.2", + "fastparse": "^1.0.0", + "loader-utils": "1.4.x", + "object-assign": "^4.1.0" + }, + "dependencies": { + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -17804,6 +18069,12 @@ } } }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true + }, "object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -19453,6 +19724,93 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, + "ts-loader": { + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", + "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -19489,6 +19847,13 @@ "mime-types": "~2.1.24" } }, + "typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "peer": true + }, "uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", diff --git a/package.json b/package.json index aeb8fa38..00b54555 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-bundler-webpack-plugin", - "version": "2.9.0", + "version": "2.10.0", "description": "HTML bundler plugin for webpack handles a template as an entry point, extracts CSS and JS from their sources referenced in HTML, supports template engines like Eta, EJS, Handlebars, Nunjucks.", "keywords": [ "html", @@ -120,6 +120,7 @@ "sass": "^1.65.1", "sass-loader": "^13.3.2", "sharp": "^0.32.5", + "ts-loader": "9.4.4", "webpack": "^5.88.2", "webpack-cli": "5.1.4", "webpack-dev-server": "^4.15.1" diff --git a/src/Loader/Options.js b/src/Loader/Options.js index 67b16ab6..95316242 100644 --- a/src/Loader/Options.js +++ b/src/Loader/Options.js @@ -69,9 +69,12 @@ class Options { const data = { ...loaderData, ...entryData, ...queryData }; if (Object.keys(data).length > 0) loaderObject.data = data; - Preprocessor.init({ fileSystem: this.fileSystem, rootContext, watch: this.#watch }); if (!Preprocessor.isReady(options.preprocessor)) { - options.preprocessor = Preprocessor.factory(options.preprocessor, options.preprocessorOptions); + options.preprocessor = Preprocessor.factory(loaderContext, { + preprocessor: options.preprocessor, + watch: this.#watch, + options: options.preprocessorOptions, + }); } // clean loaderContext of artifacts diff --git a/src/Loader/Preprocessor.js b/src/Loader/Preprocessor.js index d1ac6baf..dfe17869 100644 --- a/src/Loader/Preprocessor.js +++ b/src/Loader/Preprocessor.js @@ -1,12 +1,8 @@ const { unsupportedPreprocessorException } = require('./Messages/Exeptions'); -class Preprocessor { - static init({ fileSystem, rootContext, watch }) { - this.fileSystem = fileSystem; - this.rootContext = rootContext; - this.watch = watch; - } +/** @typedef {import('webpack').LoaderContext} LoaderContext */ +class Preprocessor { /** * Whether the preprocessor is ready to use. * @@ -39,40 +35,28 @@ class Preprocessor { * Factory preprocessor as a function. * The default preprocessor uses the Eta template engine. * + * @param {LoaderContext} loaderContext The loader context of Webpack. * @param {string|null|*} preprocessor The preprocessor value, should be a string * @param {Object} options The preprocessor options. + * @param {Function} watch The function called by watching. * @return {Function|Promise|Object} * @throws */ - static factory(preprocessor, options = {}) { + static factory(loaderContext, { preprocessor, options = {}, watch }) { if (preprocessor == null) preprocessor = 'eta'; switch (preprocessor) { case 'eta': - return require('./Preprocessors/Eta/index')({ - rootContext: this.rootContext, - options, - }); + return require('./Preprocessors/Eta/index')(loaderContext, options); case 'ejs': - return require('./Preprocessors/Ejs/index')({ - rootContext: this.rootContext, - options, - }); + return require('./Preprocessors/Ejs/index')(loaderContext, options); case 'handlebars': - return require('./Preprocessors/Handlebars/index')({ - fs: this.fileSystem, - rootContext: this.rootContext, - options, - }); + return require('./Preprocessors/Handlebars/index')(loaderContext, options); case 'nunjucks': - return require('./Preprocessors/Nunjucks/index')({ - watch: this.watch, - rootContext: this.rootContext, - options, - }); + return require('./Preprocessors/Nunjucks/index')(loaderContext, options, watch); default: unsupportedPreprocessorException(preprocessor); diff --git a/src/Loader/Preprocessors/Ejs/index.js b/src/Loader/Preprocessors/Ejs/index.js index 9d4aa5e8..2f82ea80 100644 --- a/src/Loader/Preprocessors/Ejs/index.js +++ b/src/Loader/Preprocessors/Ejs/index.js @@ -1,7 +1,8 @@ const { loadModule } = require('../../../Common/FileUtils'); -const preprocessor = ({ rootContext, options }) => { +const preprocessor = (loaderContext, options) => { const Ejs = loadModule('ejs'); + const { rootContext } = loaderContext; return (template, { resourcePath, data = {} }) => Ejs.render(template, data, { diff --git a/src/Loader/Preprocessors/Eta/index.js b/src/Loader/Preprocessors/Eta/index.js index 4da0b849..01c9b5db 100644 --- a/src/Loader/Preprocessors/Eta/index.js +++ b/src/Loader/Preprocessors/Eta/index.js @@ -1,8 +1,9 @@ const path = require('path'); const { loadModule } = require('../../../Common/FileUtils'); -const preprocessor = ({ rootContext, options }) => { +const preprocessor = (loaderContext, options) => { const Eta = loadModule('eta', () => require('eta').Eta); + const { rootContext } = loaderContext; let views = options.views; if (!views) { diff --git a/src/Loader/Preprocessors/Handlebars/helpers/assign.js b/src/Loader/Preprocessors/Handlebars/helpers/assign.js new file mode 100644 index 00000000..ffe9f8b6 --- /dev/null +++ b/src/Loader/Preprocessors/Handlebars/helpers/assign.js @@ -0,0 +1,30 @@ +'use strict'; + +/** @typedef {import('handlebars')} Handlebars */ +/** @typedef {import('handlebars').HelperOptions} HelperOptions */ + +/** + * Assign a value to data variable. + * + * Usage: + * {{assign title='About'}} - define `title` variable + * {{title}} - output variable + * + * @param {Handlebars} Handlebars + * @return {function(string, HelperOptions, *): *} + */ +module.exports = (Handlebars) => { + /** + * @param {HelperOptions} options The options passed via tag attributes into a template. + * @return {void} + */ + return function (options) { + // don't modify `this` in code directly, because it will be compiled in `exports` as an immutable object + const context = this; + const attrs = options.hash; + + for (const key in attrs) { + context[key] = attrs[key]; + } + }; +}; diff --git a/src/Loader/Preprocessors/Handlebars/helpers/block/block.js b/src/Loader/Preprocessors/Handlebars/helpers/block/block.js new file mode 100644 index 00000000..fe0b08f0 --- /dev/null +++ b/src/Loader/Preprocessors/Handlebars/helpers/block/block.js @@ -0,0 +1,34 @@ +'use strict'; + +/** @typedef {import('handlebars')} Handlebars */ +/** @typedef {import('handlebars').HelperOptions} HelperOptions */ + +/** + * Insert the partial content as a block. + * Note: `partial` and `block` are paar helpers. + * + * Usage: + * {{#partial 'BLOCK_NAME'}}BLOCK_CONTENT{{/partial}} - define block content + * {{#block 'BLOCK_NAME'}}{{/block}} - output block content + * + * @param {Handlebars} Handlebars + * @return {function(string, HelperOptions, *): *} + */ +module.exports = (Handlebars) => { + /** + * @param {string} name The block name. + * @param {HelperOptions} options The options passed via tag attributes into a template. + * @return {string} + */ + return function (name, options) { + const context = this; + let partial = context._blocks[name] || options.fn; + + if (typeof partial === 'string') { + partial = Handlebars.compile(partial); + context._blocks[name] = partial; + } + + return partial(context, { data: options.hash }); + }; +}; diff --git a/src/Loader/Preprocessors/Handlebars/helpers/block/partial.js b/src/Loader/Preprocessors/Handlebars/helpers/block/partial.js new file mode 100644 index 00000000..36ef8c10 --- /dev/null +++ b/src/Loader/Preprocessors/Handlebars/helpers/block/partial.js @@ -0,0 +1,33 @@ +'use strict'; + +/** @typedef {import('handlebars')} Handlebars */ +/** @typedef {import('handlebars').HelperOptions} HelperOptions */ + +/** + * Save the partial content for a block. + * Note: `partial` and `block` are paar helpers. + * + * Usage: + * {{#partial 'BLOCK_NAME'}}BLOCK_CONTENT{{/partial}} - define block content + * {{#block 'BLOCK_NAME'}}{{/block}} - output block content + * + * @param {Handlebars} Handlebars + * @return {function(string, HelperOptions, *): *} + */ +module.exports = (Handlebars) => { + /** + * @param {string} name The block name. + * @param {HelperOptions} options The options passed via tag attributes into a template. + * @return {void} + */ + return function (name, options) { + // don't modify `this` in code directly, because it will be compiled in `exports` as an immutable object + const context = this; + + if (!context._blocks) { + context._blocks = {}; + } + + context._blocks[name] = options.fn; + }; +}; diff --git a/src/Loader/Preprocessors/Handlebars/helpers/include.js b/src/Loader/Preprocessors/Handlebars/helpers/include.js index d15fde0d..4caa1433 100644 --- a/src/Loader/Preprocessors/Handlebars/helpers/include.js +++ b/src/Loader/Preprocessors/Handlebars/helpers/include.js @@ -1,21 +1,24 @@ const { resolveFile } = require('../../../../Common/FileUtils'); +/** @typedef {import('handlebars')} Handlebars */ +/** @typedef {import('handlebars').HelperOptions} HelperOptions */ + /** * Return the include helper function. * - * @param {FileSystem} fs The file system. * @param {Handlebars} Handlebars The instance of Handlebars module. + * @param {FileSystem} fs The file system. * @param {string} root The root path to template partials. * @param {Array