diff --git a/WatchHtmlPlugin.js b/WatchHtmlPlugin.js index ddd4876..96c2fe9 100644 --- a/WatchHtmlPlugin.js +++ b/WatchHtmlPlugin.js @@ -1,9 +1,9 @@ -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const colors = require('ansi-colors'); -const chokidar = require('chokidar'); -const spawn = require('cross-spawn'); -const path = require('path'); -const glob = require('glob'); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const colors = require("ansi-colors"); +const chokidar = require("chokidar"); +const spawn = require("cross-spawn"); +const path = require("path"); +const glob = require("glob"); module.exports = class WatchHtmlPlugin { constructor(options) { @@ -20,55 +20,82 @@ module.exports = class WatchHtmlPlugin { this.addInitialHtmlPlugins(compiler); this.browserSyncInstance = this.findBrowserSyncInstance(compiler); - compiler.hooks.watchRun.tapAsync('WatchHtmlPlugin', (compilation, callback) => { - if (!this.watcher) this.setupFileWatcher(); - callback(); - }); + compiler.hooks.watchRun.tapAsync( + "WatchHtmlPlugin", + (compilation, callback) => { + if (!this.watcher) this.setupFileWatcher(); + callback(); + } + ); } initializeFiles() { - glob.sync(`${this.options.srcDir}/**/*.html`).forEach(file => this.initialFiles.add(path.resolve(file))); + glob + .sync(`${this.options.srcDir}/**/*.html`) + .forEach((file) => this.initialFiles.add(path.resolve(file))); } addInitialHtmlPlugins(compiler) { - this.createHtmlPlugins(Array.from(this.initialFiles)).forEach(plugin => plugin.apply(compiler)); + this.createHtmlPlugins(Array.from(this.initialFiles)).forEach((plugin) => + plugin.apply(compiler) + ); } createHtmlPlugins(files) { - return files.map(filePath => new HtmlWebpackPlugin({ - ...this.options.htmlPluginOptions, - template: filePath, - filename: path.relative(this.options.srcDir, filePath).replace(/\\/g, '/'), - })); + return files.map( + (filePath) => + new HtmlWebpackPlugin({ + ...this.options.htmlPluginOptions, + template: filePath, + filename: path + .relative(this.options.srcDir, filePath) + .replace(/\\/g, "/"), + }) + ); } findBrowserSyncInstance(compiler) { - return compiler.options.plugins?.find(plugin => plugin.browserSync && typeof plugin.browserSync.exit === 'function')?.browserSync || null; + return ( + compiler.options.plugins?.find( + (plugin) => + plugin.browserSync && typeof plugin.browserSync.exit === "function" + )?.browserSync || null + ); } setupFileWatcher() { - this.watcher = chokidar.watch(`${this.options.srcDir}/*.html`, {ignoreInitial: true}); - this.watcher.on('add', this.debounceRestart.bind(this)); - this.watcher.on('unlink', this.debounceRestart.bind(this)); - this.watcher.on('error', error => console.error(colors.bgRed.whiteBright(`[WatchHtmlPlugin] Watcher error: ${error}`))); + this.watcher = chokidar.watch(`${this.options.srcDir}/*.html`, { + ignoreInitial: true, + }); + this.watcher.on("add", this.debounceRestart.bind(this)); + this.watcher.on("unlink", this.debounceRestart.bind(this)); + this.watcher.on("error", (error) => + console.error( + colors.bgRed.whiteBright(`[WatchHtmlPlugin] Watcher error: ${error}`) + ) + ); } stopBrowserSync() { - return this.browserSyncInstance ? new Promise(resolve => { - this.browserSyncInstance.exit(); - this.browserSyncInstance = null; - resolve(); - }) : Promise.resolve(); + return this.browserSyncInstance + ? new Promise((resolve) => { + this.browserSyncInstance.exit(); + this.browserSyncInstance = null; + resolve(); + }) + : Promise.resolve(); } stopServerProcess() { - return this.serverProcess ? new Promise(resolve => { - this.serverProcess.on('exit', () => { - this.serverProcess = null; - resolve(); - }); - this.serverProcess.kill('SIGTERM'); - }) : Promise.resolve(); + return this.serverProcess + ? new Promise((resolve) => { + this.serverProcess.on("exit", () => { + this.serverProcess = null; + resolve(); + }); + this.serverProcess.kill("SIGTERM"); + }) + : Promise.resolve(); } debounceRestart() { @@ -77,9 +104,24 @@ module.exports = class WatchHtmlPlugin { } restartDevServer() { - console.log(colors.bgRed.whiteBright.bold('[WatchHtmlPlugin] Restarting the development server...')); - Promise.all([this.stopBrowserSync(), this.stopServerProcess()]).then(() => { - this.serverProcess = spawn('npm', ['run', 'start'], {stdio: 'inherit'}); - }).catch(error => console.error(colors.bgRed.whiteBright('[WatchHtmlPlugin] Error during restart:', error))); + console.log( + colors.bgRed.whiteBright.bold( + "[WatchHtmlPlugin] Restarting the development server..." + ) + ); + Promise.all([this.stopBrowserSync(), this.stopServerProcess()]) + .then(() => { + this.serverProcess = spawn("npm", ["run", "start"], { + stdio: "inherit", + }); + }) + .catch((error) => + console.error( + colors.bgRed.whiteBright( + "[WatchHtmlPlugin] Error during restart:", + error + ) + ) + ); } }; diff --git a/webpack.config.js b/webpack.config.js index e703244..fad3b97 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,30 +1,30 @@ -const path = require('path'); -const BrowserSyncPlugin = require('browser-sync-webpack-plugin'); -const ESLintPlugin = require('eslint-webpack-plugin'); -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const StylelintPlugin = require('stylelint-webpack-plugin'); -const WatchHtmlPlugin = require('./WatchHtmlPlugin'); +const path = require("path"); +const BrowserSyncPlugin = require("browser-sync-webpack-plugin"); +const ESLintPlugin = require("eslint-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const StylelintPlugin = require("stylelint-webpack-plugin"); +const WatchHtmlPlugin = require("./WatchHtmlPlugin"); // Development server configuration const devServerConfiguration = { - host: 'localhost', + host: "localhost", port: 8080, - open: 'external', + open: "external", hot: true, // Enable Hot Module Replacement (HMR) liveReload: true, // Enable live reloading reload: false, watch: true, notify: true, reloadDelay: 0, - watchFiles: ['src/html/**/*.html'], // Watch HTML files - static: path.resolve(__dirname, 'dist'), + watchFiles: ["src/html/**/*.html"], // Watch HTML files + static: path.resolve(__dirname, "dist"), client: { overlay: true, // Display errors in the browser }, server: { - baseDir: ['dist'], + baseDir: ["dist"], }, - files: [path.resolve(__dirname, 'src/**/*')], + files: [path.resolve(__dirname, "src/**/*")], ghostMode: { location: false, }, @@ -34,17 +34,17 @@ const devServerConfiguration = { module.exports = function (env, args) { return { - entry: './src/index.js', + entry: "./src/index.js", output: { - path: path.resolve(__dirname, 'dist'), - filename: './js/index.bundle.js', + path: path.resolve(__dirname, "dist"), + filename: "./js/index.bundle.js", }, resolve: { alias: { - src: path.resolve(__dirname, 'src'), + src: path.resolve(__dirname, "src"), }, }, - devtool: 'source-map', // Generate sourcemaps for proper error messages + devtool: "source-map", // Generate sourcemaps for proper error messages performance: { hints: false, // Turn off size warnings for entry points }, @@ -57,9 +57,9 @@ module.exports = function (env, args) { { test: /\.(html)$/, use: { - loader: 'html-srcsets-loader', + loader: "html-srcsets-loader", options: { - attrs: [':src', ':srcset'], + attrs: [":src", ":srcset"], interpolate: true, minimize: false, removeComments: false, @@ -73,19 +73,19 @@ module.exports = function (env, args) { loader: MiniCssExtractPlugin.loader, options: { esModule: true, - publicPath: '../', + publicPath: "../", }, }, { - loader: 'css-loader', + loader: "css-loader", options: { importLoaders: 2, sourceMap: true, }, }, - 'resolve-url-loader', + "resolve-url-loader", { - loader: 'postcss-loader', + loader: "postcss-loader", options: { postcssOptions: { plugins: { @@ -96,7 +96,7 @@ module.exports = function (env, args) { }, }, { - loader: 'sass-loader', + loader: "sass-loader", options: { sourceMap: true, }, @@ -105,34 +105,34 @@ module.exports = function (env, args) { }, { test: /\.(js)$/, - loader: 'babel-loader', + loader: "babel-loader", }, { test: /\.(png|svg|jpe?g|gif)$/, - type: 'asset/resource', + type: "asset/resource", generator: { - filename: 'images/[hash][ext]', + filename: "images/[hash][ext]", }, }, { test: /(favicon\.ico|site\.webmanifest|browserconfig\.xml|robots\.txt|humans\.txt)$/, - loader: 'file-loader', + loader: "file-loader", options: { - name: '[name].[ext]', + name: "[name].[ext]", }, }, { test: /\.(woff(2)?|ttf|eot)(\?[a-z0-9=.]+)?$/, - type: 'asset/resource', + type: "asset/resource", generator: { - filename: 'fonts/[name]-[hash][ext][query]', + filename: "fonts/[name]-[hash][ext][query]", }, }, ], }, plugins: [ new WatchHtmlPlugin({ - srcDir: 'src/html', + srcDir: "src/html", htmlPluginOptions: { hash: true, }, @@ -143,16 +143,16 @@ module.exports = function (env, args) { new ESLintPlugin({ emitError: true, emitWarning: true, - context: path.resolve(__dirname, 'src/scripts'), + context: path.resolve(__dirname, "src/scripts"), }), new StylelintPlugin({ emitErrors: true, emitWarning: true, - configFile: path.resolve(__dirname, '.stylelintrc.js'), - context: path.resolve(__dirname, 'src/assets/styles'), + configFile: path.resolve(__dirname, ".stylelintrc.js"), + context: path.resolve(__dirname, "src/assets/styles"), }), new MiniCssExtractPlugin({ - filename: './css/styles.css', + filename: "./css/styles.css", experimentalUseImportModule: false, }), ],