Skip to content

Commit

Permalink
feat: using serve/watch, after a partial file is modified all entry p…
Browse files Browse the repository at this point in the history
…oint templates will be rebuilt, #127
  • Loading branch information
webdiscus committed Dec 7, 2024
1 parent 3a54887 commit 2c03059
Show file tree
Hide file tree
Showing 34 changed files with 326 additions and 34 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change log

## 4.9.0 (2024-12-07)

- feat: using serve/watch, after a partial file is modified all entry point templates will be rebuilt, #127.\
**The problem:**
Webpack doesn't know which partials are used in which templates, so Webpack can't rebuild the main template (entrypoint) where a partial has changed.

## 4.8.1 (2024-12-06)

- fix: if template is imported in JS in compile mode and the same template function called with different variables set
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "html-bundler-webpack-plugin",
"version": "4.8.1",
"version": "4.9.0",
"description": "HTML Bundler Plugin for Webpack renders HTML templates containing source files of scripts, styles, images. Supports template engines: Eta, EJS, Handlebars, Nunjucks, Pug, TwigJS. Alternative to html-webpack-plugin.",
"keywords": [
"html",
Expand Down
19 changes: 19 additions & 0 deletions src/Common/Helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,24 @@ const deleteQueryParam = (request, name) => {
return newQuery ? file + '?' + newQuery : file;
};

/**
* Get valid url with parameters.
*
* If request has the query w/o value, e.g. `?param`,
* then Webpack generates the `resource` path with `=` at the end, e.g. `?param=`,
* that brake the workflow the plugin!
*
* @param {string} request
* @return {string}
*/
const getFixedUrlWithParams = (request) => {
if (request.endsWith('=')) {
request = request.slice(0, -1);
}

return request;
};

/**
* Merge two objects.
* @param {Object} a
Expand Down Expand Up @@ -206,6 +224,7 @@ module.exports = {
getQueryParam,
addQueryParam,
deleteQueryParam,
getFixedUrlWithParams,
deepMerge,
detectIndent,
outToConsole,
Expand Down
47 changes: 41 additions & 6 deletions src/Plugin/AssetCompiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const { PluginError, afterEmitException } = require('./Messages/Exception');

const loaderPath = require.resolve('../Loader');
const LoaderFactory = require('../Loader/LoaderFactory');
const { yellowBright, cyanBright, green, greenBright } = require('ansis');

const { pluginName } = Config.get();

Expand Down Expand Up @@ -489,16 +490,17 @@ class AssetCompiler {
if (this.dataFileEntryMap.has(fileName)) {
const entryFiles = this.dataFileEntryMap.get(fileName);

if (this.pluginOption.isVerbose()) {
console.log(yellowBright`Modified data file: ${cyanBright(fileName)}`);
}

for (const module of this.compilation.modules) {
const moduleResource = module.resource || '';

if (moduleResource && entryFiles.find((file) => file === moduleResource)) {
this.compilation.rebuildModule(module, (err) => {
this.compilation.rebuildModule(module, (error) => {
if (this.pluginOption.isVerbose()) {
// TODO: prettify the logging
console.log(
`>>> The data file is changed: ${fileName}${'\n'} -> Rebuild the dependency: ${moduleResource}`
);
console.log(greenBright` -> Rebuild dependency: ${cyanBright(moduleResource)}`);
}
});
}
Expand Down Expand Up @@ -563,7 +565,7 @@ class AssetCompiler {
module._errors = [];

// after rename a js file, try to rebuild the module of the entry file where the js file was linked
this.compilation.rebuildModule(module, (err) => {
this.compilation.rebuildModule(module, (error) => {
// after rebuild, remove the missing file to avoid double rebuilding by another exception
Snapshot.deleteMissingFile(issuer, missingFile);
this.assetEntry.deleteMissingFile(missingFile);
Expand All @@ -582,6 +584,39 @@ class AssetCompiler {
if (inCollection && (actionType === 'remove' || actionType === 'rename')) {
this.assetEntry.deleteEntry(oldFileName);
}

return;
}

// 3. if a partial is changed then rebuild all entry templates,
// because we don't have the dependency graph of the partial on the main template
const isEntry = this.assetEntry.isEntryResource(fileName);

if (!isEntry) {
const isTemplate = this.pluginOption.isEntry(fileName);

if (isTemplate) {
if (this.pluginOption.isVerbose()) {
console.log(yellowBright`Modified partial: ${cyanBright(fileName)}`);
}

for (const module of this.compilation.modules) {
const moduleResource = module.resource || '';

if (moduleResource && this.assetEntry.isEntryResource(moduleResource)) {
this.compilation.rebuildModule(module, (error) => {
if (error) {
// TODO: research the strange error - "Cannot read properties of undefined (reading 'state')"
// in node_modules/webpack/lib/util/AsyncQueue.js:196
}

if (this.pluginOption.isVerbose()) {
console.log(greenBright` -> Rebuild entrypoint: ${cyanBright(moduleResource)}`);
}
});
}
}
}
}
}

Expand Down
14 changes: 5 additions & 9 deletions src/Plugin/Collection.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const path = require('path');
const { minify } = require('html-minifier-terser');
const { HtmlParser, comparePos } = require('../Common/HtmlParser');
const { getFixedUrlWithParams } = require('../Common/Helpers');
const Integrity = require('./Extras/Integrity');
const Preload = require('./Preload');
const { noHeadException } = require('./Messages/Exception');
Expand Down Expand Up @@ -284,18 +285,11 @@ class Collection {
const splitChunkIds = new Set();
const chunkCache = new Map();

//console.log('### chunks: ', chunks);
//console.log('### assets: ', assets); // 'img/apple.02a7c382.png': CachedSource
//console.log('### namedChunkGroups: ', namedChunkGroups);
//console.log('### chunkGraph: ', chunkGraph.moduleGraph._moduleMap);

for (let [resource, { type, name, entries }] of this.assets) {
if (type !== Collection.type.script) continue;

const entrypoint = namedChunkGroups.get(name);

//console.log('### auxiliaryFiles: ', { name, entrypoint });

// prevent error when in watch mode after removing a script in the template
if (!entrypoint) continue;

Expand All @@ -314,8 +308,6 @@ class Collection {
if (isJavascript && info.hotModuleReplacement !== true) chunkFiles.add(file);
}
splitChunkIds.add(id);

//console.log('### auxiliaryFiles: ', { id, files, auxiliaryFiles });
}

const hasSplitChunks = chunkFiles.size > 1;
Expand Down Expand Up @@ -640,6 +632,7 @@ class Collection {
// set entry dependencies
for (const item of this.assets.values()) {
const entryFilenames = item.entries.get(resource);

if (entryFilenames) {
entryFilenames.add(filename);
}
Expand All @@ -663,6 +656,8 @@ class Collection {
let inline = false;
let name;

issuer = getFixedUrlWithParams(issuer);

switch (type) {
case Collection.type.script:
// Save resources by entry points in the order their location in the source code.
Expand Down Expand Up @@ -710,6 +705,7 @@ class Collection {
};
this.assets.set(resource, item);
}

item.entries.set(issuer, new Set());
}

Expand Down
4 changes: 2 additions & 2 deletions test/cases/option-hotUpdate/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ module.exports = {
plugins: [
new HtmlBundlerPlugin({
entry: {
index: './src/index.html',
index: './src/index.html?query', // test: resolve `hot-update.js` in the output html, if used a query w/o value
},
hotUpdate: true,
hotUpdate: true, // test: when a entrypoint containing a query w/o value
}),
],
};
2 changes: 1 addition & 1 deletion test/manual/handlebars/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ module.exports = {
},

hotUpdate: true, // test this option
minify: true,
//minify: true,
}),
],

Expand Down
7 changes: 7 additions & 0 deletions test/manual/watch-hbs-partials-multipage/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
root = true

[*.html]
insert_final_newline = true

[*.hbs]
insert_final_newline = true
17 changes: 17 additions & 0 deletions test/manual/watch-hbs-partials-multipage/data-global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"articleTitle": "Fruits #: 123",
"articles": [
{
"name": "apple",
"price": "0.99 €"
},
{
"name": "kiwi",
"price": "1.09 €"
},
{
"name": "cocos",
"price": "3.29 €"
}
]
}
11 changes: 11 additions & 0 deletions test/manual/watch-hbs-partials-multipage/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"description": "IMPORTANT: don't install webpack here because the Webpack instance MUST be one, otherwise appear the error: The 'compilation' argument must be an instance of Compilation.",
"scripts": {
"start": "webpack serve",
"watch": "webpack watch --mode development",
"build": "webpack --mode=production --progress"
},
"devDependencies": {
"html-bundler-webpack-plugin": "file:../../.."
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('>> about.js');
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('>> home.js');
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('>> main.js');
14 changes: 14 additions & 0 deletions test/manual/watch-hbs-partials-multipage/src/views/about.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>About</title>
<script src="../scripts/main.js" defer="defer"></script>
<script src="../scripts/about.js" defer="defer"></script>
</head>
<body>
<h1>About</h1>
{{> header }}
<p>About content</p>
{{> footer }}
</body>
</html>
11 changes: 11 additions & 0 deletions test/manual/watch-hbs-partials-multipage/src/views/contact.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Contact</title>
</head>
<body>
<h1>Contact</h1>
<p>Contact content</p>
{{> footer }}
</body>
</html>
14 changes: 14 additions & 0 deletions test/manual/watch-hbs-partials-multipage/src/views/home.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
<script src="../scripts/main.js" defer="defer"></script>
<script src="../scripts/home.js" defer="defer"></script>
</head>
<body>
<h1>Home</h1>
{{> header }}
<p>Home content</p>
{{> footer }}
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div>Footer</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h2>Header</h2>
67 changes: 67 additions & 0 deletions test/manual/watch-hbs-partials-multipage/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const path = require('path');
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');

module.exports = {
//mode: 'production',
mode: 'development',
//stats: 'minimal',

output: {
path: path.join(__dirname, 'dist/'),
},

plugins: [
new HtmlBundlerPlugin({
verbose: true,

// TODO: test separate with filesystem cache
// cache: {
// type: 'filesystem',
// },

// issue: https://github.com/webdiscus/html-bundler-webpack-plugin/issues/127
// TODO: fix watch changes in partials
// - in production mode all changes works fine
// - in development mode changes works only in entry template and in a partial only after 1st change
// - changes anywhere works only for last entry
entry: [
{
import: 'src/views/home.hbs',
filename: 'index.html',
},
{
import: 'src/views/about.hbs',
filename: 'about.html',
},
{
import: 'src/views/contact.hbs',
filename: 'contact.html',
},
],

js: {
filename: 'js/[name].[contenthash:8].js',
},

preprocessor: 'handlebars',
preprocessorOptions: {
partials: ['src/views/partials'],
},

hotUpdate: true,
}),
],

// enable live reload
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
watchFiles: {
paths: ['src/**/*.*', 'data-global.json'],
options: {
usePolling: true,
},
},
},
};
2 changes: 1 addition & 1 deletion test/manual/watch-pug-dependencies/src/deps/file.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"type": "JSON"
}
}
2 changes: 1 addition & 1 deletion test/manual/watch-pug-dependencies/src/deps/file.pug
Original file line number Diff line number Diff line change
@@ -1 +1 @@
p Hello Pug!
p Hello Pug!
4 changes: 2 additions & 2 deletions test/manual/watch-pug-dependencies/src/deps/file.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SCSS
h1 {
color: #C21F39;
}
color: #c21f39;
}
1 change: 1 addition & 0 deletions test/manual/watch-pug-dependencies/src/header.pug
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
h1 Hello World!
Loading

0 comments on commit 2c03059

Please sign in to comment.