diff --git a/CHANGELOG.md b/CHANGELOG.md index ca686fda..82fbaf33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change log +## 2.14.1 (2023-09-24) +- fix: remove unused `isEntry` property from the `info` argument of the `postprocess` callback + the `isEntry` property was always true, because template is defined as an entrypoint +- chore: code cleanup +- chore: add examples of using `PureCSS` as a plugin for Webpack and for PostCSS +- docs: fix formatting in readme +- docs: fix some inaccuracies in readme + ## 2.14.0 (2023-09-17) - feat: add the `integrityHashes` hook to allow retrieving the integrity values diff --git a/README.md b/README.md index dcdc5a5c..ff698274 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,16 @@ In an HTML template can be referenced any source files, similar to how it works #### See: [install and quick start](#install) | [contents](#contents) | [simple example](#example) ---- -### 💡 Highlights +## ❤️ Sponsors + + + + + + +
JetBrains LogoJetBrains provides a free license of their IDEs for this OSS development.
+ +## 💡 Highlights - An [entry point](#option-entry) is any template. - Allows to **include** **script** and **style** source files directly **in HTML**: @@ -56,29 +64,37 @@ In an HTML template can be referenced any source files, similar to how it works See the [full list of features](#features). -### ⚙️ How works the plugin +## ⚙️ How works the plugin 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 output filenames in the generated HTML. +Webpack will automatically process the source files, and the plugin replaces the references with their output filenames in the generated HTML. See the [example](#example). -### ✅ Profit +## ✅ Profit + +**Start from HTML**, not from JS. Define an **HTML** file as an **entry point**. -You can specify script and style source files directly in an HTML template, +Specify script and style **source files** directly in an **HTML** template, and you no longer need to define them in Webpack entry or import styles in JavaScript. + +Use **any template engine** without additional plugins and loaders. +Most popular template engines supported "**out of the box**". + Use one powerful plugin instead of [many different plugins](#list-of-plugins). -### ❓Question / Feature Request / Bug +## ❓Question / Feature Request / Bug If you have discovered a bug or have a feature suggestion, feel free to create an [issue](https://github.com/webdiscus/html-bundler-webpack-plugin/issues) on GitHub. -### 📚 Read it +## 📚 Read it - [Using HTML Bundler Plugin for Webpack to generate HTML files](https://dev.to/webdiscus/using-html-bundler-plugin-for-webpack-to-generate-html-files-30gd) - [Keep output directory structure in Webpack](https://dev.to/webdiscus/how-to-keep-the-folder-structure-of-source-templates-in-webpack-for-output-html-files-39bj) - [Auto generate an integrity hash for `link` and `script` tags](https://dev.to/webdiscus/webpack-auto-generate-an-integrity-hash-for-link-and-script-tags-in-an-html-template-48p5) +- [Use a HTML file as an entry point?](https://github.com/webpack/webpack/issues/536) (Webpack issue, #536) +- [Comparison and Benchmarks of Node.js libraries to colorize text in terminal](https://dev.to/webdiscus/comparison-of-nodejs-libraries-to-colorize-text-in-terminal-4j3a) (_offtopic_) ## 🔆 What's New in v2 @@ -196,7 +212,6 @@ See [boilerplate](https://github.com/webdiscus/webpack-html-scss-boilerplate) ## Contents -1. [Sponsors](#sponsors) 1. [Features](#features) 1. [Install and Quick start](#install) 1. [Webpack options](#webpack-options) @@ -894,7 +909,7 @@ If type is `string` then following substitutions (see [output.filename](https:// If type is `Function` then following arguments are available in the function: - `@param {PathData} pathData` has the useful properties (see the [type PathData](https://webpack.js.org/configuration/output/#outputfilename)): - - `pathData.filename` the full path to source file + - `pathData.filename` the absolute path to source file - `pathData.chunk.name` the name of entry key - `@return {string}` The name or template string of output file. @@ -1217,11 +1232,10 @@ Please see the details below under the [data](#loader-option-data) loader option Type: ```ts -type postprocess = (content: string, info: ResourceInfo, compilation: Compilation) => string | undefined; +type postprocess = (content: string, info: TemplateInfo, compilation: Compilation) => string | undefined; -type ResourceInfo = { +type TemplateInfo = { verbose: boolean; - isEntry: boolean; filename: string | ((pathData: PathData) => string); outputPath: string; sourceFile: string; @@ -1231,22 +1245,21 @@ type ResourceInfo = { Default: `null` -Called after a source of an asset module is rendered, but not yet processed by other plugins. +Called after the template has been rendered, but not yet finalized, before the split chunks and inline assets are injected. The `postprocess` have the following arguments: - `content: string` - a content of processed file -- `info: ResourceInfo` - info about current file +- `info: TemplateInfo` - info about current file - `compilation: Compilation` - the Webpack [compilation object](https://webpack.js.org/api/compilation-object/) -The `ResourceInfo` have the following properties: +The `TemplateInfo` have the following properties: -- `verbose: boolean` - the value defined in the [`verbose`](#option-verbose) option -- `isEntry: boolean` - if is `true`, the resource is the entry point, otherwise is a resource loaded in the entry point -- `filename: string|function` - a filename of the resource, see [filename](https://webpack.js.org/configuration/output/#outputfilename) -- `outputPath: string` - a full path of the output directory -- `sourceFile: string` - a full path of the source file, without URL query -- `assetFile: string` - an output asset file relative to the `outputPath` +- `verbose: boolean` - the [`verbose`](#option-verbose) option +- `filename: string|function` - the [`filename`](#option-filename) option of the template +- `outputPath: string` - the absolute path of the output directory +- `sourceFile: string` - the absolute path of the source file, without URL query +- `assetFile: string` - the output file relative to the `outputPath` Return new content as a `string`. If return `undefined`, the result processed via Webpack plugin is ignored and will be saved a result processed via the loader. @@ -2443,8 +2456,10 @@ src/views/partials/menu/top/desktop.html Include the partials in the `src/views/page/home.html` template with the `include()`: ```html -<%~ include('teaser.html') %> <%~ include('menu/nav.html') %> <%~ include('menu/top/desktop.html') %> <%~ -include('footer.html') %> +<%~ include('teaser.html') %> +<%~ include('menu/nav.html') %> +<%~ include('menu/top/desktop.html') %> +<%~ include('footer.html') %> ``` If partials have `.eta` extensions, then the extension can be omitted in the include argument. @@ -2489,8 +2504,10 @@ Include the partials in the `src/views/page/home.html` template with the `includ <%- include('/includes/gallery.html') %> -<%- include('teaser.html') %> <%- include('menu/nav.html') %> <%- include('menu/top/desktop.html') %> <%- -include('footer.html') %> +<%- include('menu/top/desktop.html') %> +<%- include('menu/nav.html') %> +<%- include('teaser.html') %> +<%- include('footer.html') %> ``` If you have partials with `.ejs` extensions, then the extension can be omitted. @@ -2536,7 +2553,10 @@ Include the partials in the `src/views/page/home.html` template with the `includ {{ include '/includes/gallery' }} -{{ include 'teaser' }} {{ include 'menu/nav' }} {{ include 'menu/top/desktop' }} {{ include 'footer' }} +{{ include 'menu/top/desktop' }} +{{ include 'menu/nav' }} +{{ include 'teaser' }} +{{ include 'footer' }} ``` The `include` helper automatically resolves `.html` and `.hbs` extensions, it can be omitted. @@ -2571,8 +2591,8 @@ loaderOptions: { preprocessorOptions: { // define partials manually partials: { - gallery: path.join(__dirname, 'src/views/includes/gallery.html'), teaser: path.join(__dirname, 'src/views/includes/teaser.html'), + gallery: path.join(__dirname, 'src/views/includes/gallery.html'), footer: path.join(__dirname, 'src/views/partials/footer.html'), 'menu/nav': path.join(__dirname, 'src/views/partials/menu/nav.html'), 'menu/top/desktop': path.join(__dirname, 'src/views/partials/menu/top/desktop.html'), @@ -2584,7 +2604,11 @@ loaderOptions: { Include the partials in the `src/views/page/home.html` template: ```html -{{> gallery }} {{> teaser }} {{> menu/nav }} {{> menu/top/desktop }} {{> footer }} +{{> menu/top/desktop }} +{{> menu/nav }} +{{> teaser }} +{{> gallery }} +{{> footer }} ``` **The `helpers` option** @@ -4281,8 +4305,8 @@ For example, in a template are used the scripts and styles from `node_modules`: > **Note** > -> In the generated HTML all script tags remain in their original places and split chunks will be added there, -> in the order that Webpack generated. +> In the generated HTML, all script tags remain in their original places, and the split chunks will be added there +> in the order in which Webpack generated them. In this use case the `optimization.cacheGroups.{cacheGroup}.test` option must match exactly only JS files from `node_modules`: @@ -4317,13 +4341,13 @@ module.exports = { > **Warning** > -> Splitting CSS to many chunks is principal impossible. Splitting works only for JS files. +> Splitting CSS to many chunks is principally impossible. Splitting works only for JS files. -Using the bundler plugin all your styles MUST be specified directly in template, not in Webpack entry. -Unlike using the mini-css-extract-plugin and html-webpack-plugin, using the bundler plugin you cannot import a style in JavaScript. -Importing a style in JavaScript is a dirty hack, BAD practice. +Using the bundler plugin, all your style source files should be specified directly in the template. +You can import style files in JavaScript, like it works using the `mini-css-extract-plugin` and `html-webpack-plugin`, +but it is a **dirty hack**, **bad practice**, processing is **slow**, avoid it if possible. -So far as the style files must be manually defined in the template, you can separate the styles into multiple bundles yourself. +You can separate the styles into multiple bundles yourself. For example, there are style files used in your app: @@ -4496,24 +4520,10 @@ dist/js/app-5fa74877.1aceb2db.js ``` ---- - #### [↑ back to contents](#contents) --- - - -## Sponsors - -Thank you to our sponsors! - -| Sponsors | | -| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -| JetBrains Logo | [JetBrains](https://www.jetbrains.com/) provides a free license of their IDEs for this OSS development. | - -#### [↑ back to contents](#contents) - ## Also See - [ansis][ansis] - The Node.js lib for ANSI color styling of text in terminal diff --git a/package.json b/package.json index bba67a99..710099f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-bundler-webpack-plugin", - "version": "2.14.0", + "version": "2.14.1", "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", diff --git a/src/Plugin/AssetCompiler.js b/src/Plugin/AssetCompiler.js index 23676bd4..0512c2af 100644 --- a/src/Plugin/AssetCompiler.js +++ b/src/Plugin/AssetCompiler.js @@ -74,7 +74,7 @@ const cssLoader = { * @property {string} [sourcePath = options.context] The absolute path to sources. * @property {string} [outputPath = options.output.path] The output directory for an asset. * @property {string|function(PathData, AssetInfo): string} filename The file name of output file. - * @property {function(string, ResourceInfo, Compilation): string|null =} postprocess The postprocess for extracted content from entry. + * @property {function(string, TemplateInfo, Compilation): string|null =} postprocess The postprocess for rendered template. * @property {function(compilation: Compilation, {source: string, assetFile: string, inline: boolean} ): string|null =} extract */ @@ -83,16 +83,6 @@ const cssLoader = { * @property {boolean} [inline = false] Whether inline CSS should contain an inline source map. */ -/** - * @typedef {Object} ResourceInfo - * @property {boolean} isEntry True if is the asset from entry, false if asset is required from template. - * @property {boolean} verbose Whether information should be displayed. - * @property {string|(function(PathData, AssetInfo): string)} filename The filename template or function. - * @property {string} sourceFile The absolute path to source file. - * @property {string} outputPath The absolute path to output directory of asset. - * @property {string} assetFile The output asset file relative by outputPath. - */ - /** * @typedef {Object} FileInfo * @@ -1170,17 +1160,16 @@ class AssetCompiler { break; case 'template': if (Options.hasPostprocess()) { - const resourceInfo = { - isEntry: true, + /**/ + const info = { verbose: Options.isVerbose(), - inline, + filename, outputPath, sourceFile, assetFile, - filename, }; - result = Options.postprocess(result, resourceInfo, this.compilation); + result = Options.postprocess(result, info, this.compilation); } break; } diff --git a/src/Plugin/Messages/Exception.js b/src/Plugin/Messages/Exception.js index 9ee7cb63..ed1c47c0 100644 --- a/src/Plugin/Messages/Exception.js +++ b/src/Plugin/Messages/Exception.js @@ -118,7 +118,7 @@ const executeFunctionException = (error, sourceFile) => { /** * @param {Error} error - * @param {ResourceInfo} info + * @param {TemplateInfo} info * @throws {Error} */ const postprocessException = (error, info) => { diff --git a/src/Plugin/Options.js b/src/Plugin/Options.js index c8a31833..4d43925d 100644 --- a/src/Plugin/Options.js +++ b/src/Plugin/Options.js @@ -5,38 +5,6 @@ const { postprocessException, afterProcessException } = require('./Messages/Exce const pluginName = require('../config'); -/** - * @typedef {Object} PluginOptions - * @property {RegExp} test The search for a match of entry files. - * @property {boolean} [enabled = true] Enable/disable the plugin. - * @property {boolean|string} [verbose = false] Show the information at processing entry files. - * @property {string} [sourcePath = options.context] The absolute path to sources. - * @property {string} [outputPath = options.output.path] The output directory for an asset. - * @property {string|function(PathData, AssetInfo): string} filename The file name of output file. - * See https://webpack.js.org/configuration/output/#outputfilename. - * Must be an absolute or a relative by the context path. - * @property {CssOptions?} css The options for embedded plugin module to extract CSS. - * @property {JsOptions?} js The options for embedded plugin module to extract CSS. - * @property {function(string, ResourceInfo, Compilation): string|null ?} postprocess The post-process for extracted content from entry. - * @property {function(content: string, {sourceFile: string, assetFile: string})} afterProcess Called after processing all plugins. - * @property {boolean} [extractComments = false] Whether comments should be extracted to a separate file. - * If the original filename is foo.js, then the comments will be stored to foo.js.LICENSE.txt. - * This option enables/disable storing of *.LICENSE.txt file. - * For more flexibility use terser-webpack-plugin https://webpack.js.org/plugins/terser-webpack-plugin/#extractcomments. - * @property {'auto'|boolean|IntegrityOptions} integrity Enable/disable the integrity attribute. - * @property {Object|string} entry The entry points. - * The key is route to output file w/o an extension, value is a template source file. - * When the entry is a string, this should be a relative or absolute path to pages. - * @property {{paths: Array, files: Array, ignore: Array}} watchFiles Paths and files to watch file changes. - * @property {boolean?} [hotUpdate=false] Whether in serve/watch mode should be added hot-update.js file in html. - * Use it only if you don't have a referenced source file of a script in html. - * If you already have a js file, this setting should be false as Webpack automatically injects the hot update code into the compiled js file. - * @property {Object?} loaderOptions Options defined in plugin but provided for the loader. - * @property {Array|boolean?} preload Options to generate preload link tags for assets. - * @property {boolean|Object|'auto'|null} [minify = false] Minify generated HTML. - * @property {boolean|Object|'auto'|null} [minifyOptions = null] Minification options, it is used for auto minify. - */ - /** * @typedef {Object} JsOptions * @property {string|null} [outputPath = options.output.path] The output directory for an asset. @@ -60,7 +28,6 @@ class Options { static options = {}; /** @type {AssetEntry} */ static assetEntry = null; - static Collection = null; // TODO static webpackOptions = {}; static productionMode = true; static dynamicEntry = false; @@ -267,48 +234,83 @@ class Options { } } + /** + * @return {boolean} + */ static isProduction() { return this.productionMode; } + /** + * @return {boolean} + */ static isDynamicEntry() { return this.dynamicEntry; } + /** + * @return {boolean} + */ static isEnabled() { return this.options.enabled !== false; } + /** + * @return {boolean} + */ static isMinify() { return this.options.minify; } + /** + * @return {boolean} + */ static isVerbose() { - return this.verbose; + return this.verbose === true; } + /** + * @return {boolean} + */ static isExtractComments() { return this.options.extractComments === true; } + /** + * @return {boolean} + */ static isIntegrityEnabled() { return this.options.integrity.enabled !== false; } + /** + * @param {string} resource + * @return {boolean} + */ static isStyle(resource) { const [file] = resource.split('?', 1); return this.options.css.enabled && this.options.css.test.test(file); } + /** + * @param {string} resource + * @return {boolean} + */ static isScript(resource) { const [file] = resource.split('?', 1); return this.options.js.enabled && this.options.js.test.test(file); } + /** + * @return {boolean} + */ static isRealContentHash() { return this.webpackOptions.optimization.realContentHash === true; } + /** + * @return {boolean} + */ static isCacheable() { return this.cacheable; } @@ -376,6 +378,9 @@ class Options { return this.options.preload != null && this.options.preload !== false; } + /** + * @return {boolean} + */ static isAutoPublicPath() { return this.autoPublicPath === true; } @@ -554,7 +559,7 @@ class Options { /** * @param {string} content A content of processed file. - * @param {ResourceInfo} info The resource info object. + * @param {TemplateInfo} info The resource info object. * @param {Compilation} compilation The Webpack compilation object. * @return {string} * @throws diff --git a/types.d.ts b/types.d.ts index 479177c2..39504489 100644 --- a/types.d.ts +++ b/types.d.ts @@ -24,13 +24,17 @@ declare namespace HtmlBundlerPlugin { } export interface PluginOptions { + // match of entry template files test?: RegExp; /** + * The key is route to output file w/o an extension, value is a template source file. * If the value is string, it should be an absolute or relative path to templates. * If the entry is undefined, then must be defined the Webpack entry option. */ entry?: EntryObject | string; + // defaults is options.output.path outputPath?: string; + // html output filename filename?: FilenameTemplate; js?: JsOptions; css?: CssOptions; @@ -42,14 +46,27 @@ declare namespace HtmlBundlerPlugin { beforePreprocessor?: BeforePreprocessor; preprocessor?: Preprocessor; preprocessorOptions?: Object; - // plugin options + // postprocess of rendered template postprocess?: Postprocess; + // generates preload link tags for assets preload?: Preload; minify?: 'auto' | boolean | MinifyOptions; minifyOptions?: MinifyOptions; + /** + * Whether comments should be extracted to a separate file. + * If the file foo.js contains the license banner, then the comments will be stored to foo.js.LICENSE.txt. + * This option enables/disable storing of *.LICENSE.txt file. + * For more flexibility use terser-webpack-plugin https://webpack.js.org/plugins/terser-webpack-plugin/#extractcomments. + */ extractComments?: boolean; integrity?: 'auto' | boolean | IntegrityOptions; + // paths and files to watch file changes watchFiles?: WatchFiles; + /** + * Whether in serve/watch mode should be added hot-update.js file in html. + * Use it only if you don't have a referenced source file of a script in html. + * If you already have a js file, this setting should be false as Webpack automatically injects the hot update code into the compiled js file. + */ hotUpdate?: boolean; verbose?: 'auto' | boolean; /** @@ -164,11 +181,14 @@ type Preprocessor = loaderContext: LoaderContext & { data: { [k: string]: any } | string } ) => string | Promise | undefined); -type Postprocess = (content: string, info: ResourceInfo, compilation: Compilation) => string | undefined; +/** + * Called after the template has been rendered, but not yet finalized, + * before the split chunks and inline assets are injected. + */ +type Postprocess = (content: string, info: TemplateInfo, compilation: Compilation) => string | undefined; -type ResourceInfo = { +type TemplateInfo = { verbose: boolean; - isEntry: boolean; filename: string | ((pathData: PathData) => string); outputPath: string; sourceFile: string;