webdiscus / html-bundler-webpack-plugin

Alternative to html-webpack-plugin ✅ Renders Eta, EJS, Handlebars, Nunjucks, Pug, Twig templates "out of the box" ✅ Resolves source files of scripts, styles, images in HTML ✅ Uses a template as entry point
ISC License
146 stars 15 forks source link

Output file does not contain markup. its more like a webpack js file that outputs markup #90

Closed scott-primacy closed 4 months ago

scott-primacy commented 7 months ago

My html output contains webpack js, instead of html markup

webpack config:

     new HtmlBundlerPlugin({
        entry: {
           article: "/src/views/article.html"
        },
     })

article.html:

<%~ include('/src/views/partials/_markup-top.html',{pageTitle: 'article', bodyClass: 'tier'}) %>
<main>main stuff for articles here.... </main>
<%~ include('/src/views/partials/_markup-bottom.html') %>

I would expect the html output to be something like:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />  
        <link rel="preconnect" href="https://fonts.gstatic.com"> 
        <title>article</title>
        <meta name="description" content="test" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />  
        <link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700" rel="stylesheet">
    </head>
    <body class="tier">
        <!-- Skip Nav -->
        <div id="skip-nav">
            <a href="#main">Skip Navigation</a>
        </div>
        <!-- Main Container -->
        <main class="main-container" id="content">
            <!-- markup -->
        </main>       
    </body>
</html>

Actual output:

/******/ (function() { // webpackBootstrap
/******/    var __webpack_modules__ = ({

/***/ "./src/views/article.html":
/*!********************************!*\
  !*** ./src/views/article.html ***!
  \********************************/
/***/ (function(module) {

module.exports='<!DOCTYPE html>\n<html lang="en">\n\n<head>\n    <meta charset="UTF-8" />\n    <link 
...

it's like it's treating this like a js file.

Environment: Mac OS node v16.13.2

webpack.config:

// Plugins
const path = require("path");
const glob = require("glob");
const TerserPlugin = require("terser-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ESLintPlugin = require("eslint-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const FileManagerPlugin = require("filemanager-webpack-plugin");
// const FileManagerPlugin = require("filemanager-plugin").WebpackFilemanager;
const HtmlBundlerPlugin = require("html-bundler-webpack-plugin");

const isHot = path.basename(require.main.filename) === "webpack-dev-server.js";

// Handle nested html partials
const INCLUDE_PATTERN = /<include src="(.+)"\s*\/?>(?:<\/include>)?/gi;

class CurrentTimePlugin {
   constructor() {
      // You can add any options you might want to pass to the plugin here
   }

   apply(compiler) {
      compiler.hooks.done.tap("CurrentTimePlugin", (stats) => {
         setTimeout(() => {
            const currentTime = new Date().toLocaleString();
            console.log("\n\n" + "=========================================================");
            console.log("\t" + `Webpack compiled at: ${currentTime}`);
            console.log("=========================================================");
         }, 100);
      });
   }
}

const processNestedHtml = (content, loaderContext, dir = null) =>
   !INCLUDE_PATTERN.test(content)
      ? content
      : content.replace(INCLUDE_PATTERN, (m, src) => {
           const filePath = path.resolve(dir || loaderContext.context, src);
           loaderContext.dependency(filePath);
           return processNestedHtml(loaderContext.fs.readFileSync(filePath, "utf8"), loaderContext, path.dirname(filePath));
        });

module.exports = {
   entry: {
      app: [path.join(__dirname, "../src/js/app.js")]
   },
   devServer: {
      static: {
         directory: path.resolve(__dirname, "../dist"),
         serveIndex: true,
         watch: true
      }
   },
   output: {
      path: path.resolve(__dirname, "../dist"),
      filename: "js/[name].js"
   },
   optimization: {
      minimize: false,
      minimizer: [
         new TerserPlugin({
            parallel: true
         }),
         new CssMinimizerPlugin()
      ]
   },
   resolve: {
      extensions: [".js", ".css", ".scss"],
      modules: ["node_modules"],
      alias: {
         request$: "xhr",
         fonts: path.resolve(__dirname, "../src/fonts"),
         components: path.resolve(__dirname, "../src/components"),
         modules: path.resolve(__dirname, "../src/modules"),
         styles: path.resolve(__dirname, "../src/styles")
      }
   },
   module: {
      rules: [
         {
            // babel-loader
            test: /\.js$/,
            exclude: /node_modules/,
            rules: [
               {
                  use: [
                     {
                        loader: "babel-loader",
                        options: {
                           cacheDirectory: true,
                           babelrc: false,
                           rootMode: "upward"
                        }
                     }
                  ]
               }
            ]
         },
         {
            // json loader
            test: /\.json$/,
            loader: "json-loader"
         },
         {
            // style loaders
            test: /\.(css|sass|scss)$/,
            use: [
               MiniCssExtractPlugin.loader,
               {
                  loader: "css-loader",
                  options: {
                     sourceMap: true,
                     importLoaders: 2,
                     esModule: false
                  }
               },
               {
                  loader: "postcss-loader",
                  options: {
                     sourceMap: true
                  }
               },
               {
                  loader: "resolve-url-loader"
               },
               {
                  loader: "sass-loader",
                  options: {
                     sourceMap: true
                  }
               }
            ]
         },
         {
            // Loads fonts
            test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
            loader: "url-loader",
            options: {
               limit: true,
               esModule: false
            }
         },
         {
            // Load images
            test: /\.(png|svg|jpg|jpeg|gif)$/i,
            type: "asset/resource",
            generator: {
               filename: "assets/[name][ext]"
            }
         },
         {
            // Allow partials in html pages.
            test: /\.(html)$/,
            // exclude: /node_modules/,
            include: [path.resolve(__dirname, "../src/views/partials"), path.resolve(__dirname, "../src/components/icon-font/")],
            use: {
               loader: "html-loader",
               options: {
                  preprocessor: processNestedHtml
               }
            }
         }
      ]
   },
   stats: {
      children: true
   },
   plugins: [
      new FileManagerPlugin({
         events: {
            onEnd: [
               {delete: ["./dist/images"]},
               {delete: [path.join(__dirname, "../wp/wp-content/themes/2019-redesign/assets/*")]},
               {
                  copy: [
                     {
                        source: path.join(__dirname, "../src/images"),
                        destination: path.join(__dirname, "../dist/images")
                     }
                  ]
               },
               {
                  copy: [
                     {
                        source: path.join(__dirname, "../dist/images"),
                        destination: path.join(__dirname, "../wp/wp-content/themes/2019-redesign/assets/images")
                     }
                  ]
               },
               {
                  copy: [
                     {
                        source: path.join(__dirname, "../dist/js"),
                        destination: path.join(__dirname, "../wp/wp-content/themes/2019-redesign/assets/js")
                     }
                  ]
               },
               {
                  copy: [
                     {
                        source: path.join(__dirname, "../dist/css"),
                        destination: path.join(__dirname, "../wp/wp-content/themes/2019-redesign/assets/css")
                     }
                  ]
               }
            ]
         }
      }),

      // MiniCssExtractPlugin
      new MiniCssExtractPlugin({
         filename: isHot ? "[name].css" : "css/[name].css",
         chunkFilename: "[id].css"
      }),
      new ESLintPlugin(),

      new CurrentTimePlugin(),

      new HtmlBundlerPlugin({
         entry: {
            article: "/src/views/article.html"
         },
      })
   ]
};
webdiscus commented 7 months ago

@scott-primacy

please read the manual Install and Quick Start.

  1. delete the loader - MiniCssExtractPlugin.loader
  2. delete the plugin - MiniCssExtractPlugin
  3. delete the loader - html-loader

Please create a small repository with reproducible issue, then I can help you.

webdiscus commented 7 months ago

@scott-primacy

the correct webpack config:

const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const FileManagerPlugin = require('filemanager-webpack-plugin');
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');

class CurrentTimePlugin {
  constructor() {
    // You can add any options you might want to pass to the plugin here
  }

  apply(compiler) {
    compiler.hooks.done.tap('CurrentTimePlugin', (stats) => {
      setTimeout(() => {
        const currentTime = new Date().toLocaleString();
        console.log('\n\n' + '=========================================================');
        console.log('\t' + `Webpack compiled at: ${currentTime}`);
        console.log('=========================================================');
      }, 100);
    });
  }
}

module.exports = {
  // NOTE: add the source JS file directly in the template using <script src="scripts/app.js"> tag, NOT here
  // entry: {
  //   app: [path.join(__dirname, '../src/js/app.js')],
  // },
  devServer: {
    static: {
      directory: path.resolve(__dirname, '../dist'),
      serveIndex: true,
      watch: true,
    },
  },
  output: {
    path: path.resolve(__dirname, '../dist'),
    //filename: 'js/[name].js', // <= MOVE into `js.filename` bundler plugin option
  },
  optimization: {
    minimize: false,
    minimizer: [
      new TerserPlugin({
        parallel: true,
      }),
      new CssMinimizerPlugin(),
    ],
  },
  resolve: {
    //extensions: ['.js', '.css', '.scss'], // DON'T mix JS and SCSS extensions
    extensions: ['.js'],
    modules: ['node_modules'],
    alias: {
      request$: 'xhr',
      fonts: path.resolve(__dirname, '../src/fonts'),
      components: path.resolve(__dirname, '../src/components'),
      modules: path.resolve(__dirname, '../src/modules'),
      styles: path.resolve(__dirname, '../src/styles'),
      scripts: path.resolve(__dirname, '../src/js'), // <= use alias to define JS in the template
      images: path.join(__dirname, '../wp/wp-content/themes/2019-redesign/assets/images'), // <= use alias to define source image files
    },
  },
  module: {
    rules: [
      {
        // babel-loader
        test: /\.js$/,
        exclude: /node_modules/,
        rules: [
          {
            use: [
              {
                loader: 'babel-loader',
                options: {
                  cacheDirectory: true,
                  babelrc: false,
                  rootMode: 'upward',
                },
              },
            ],
          },
        ],
      },
      {
        // json loader
        test: /\.json$/,
        loader: 'json-loader',
      },
      {
        // style loaders
        test: /\.(css|sass|scss)$/,
        use: [
          // MiniCssExtractPlugin.loader, // <= DELETE it
          {
            loader: 'css-loader',
            options: {
              sourceMap: true,
              importLoaders: 2,
              esModule: false,
            },
          },
          {
            loader: 'postcss-loader',
            options: {
              sourceMap: true,
            },
          },
          {
            loader: 'resolve-url-loader',
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
            },
          },
        ],
      },
      {
        // Loads fonts
        test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
        loader: 'url-loader',
        options: {
          limit: true,
          esModule: false,
        },
      },
      {
        // Load images
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
        generator: {
          filename: 'assets/[name][ext]',
        },
      },
      // DELETE the html rule, DON'T use the `html-loader`
      // {
      //   // Allow partials in html pages.
      //   test: /\.(html)$/,
      //   // exclude: /node_modules/,
      //   include: [
      //     path.resolve(__dirname, '../src/views/partials'),
      //     path.resolve(__dirname, '../src/components/icon-font/'),
      //   ],
      //   use: {
      //     loader: 'html-loader',
      //     options: {
      //       preprocessor: processNestedHtml,
      //     },
      //   },
      // },
    ],
  },
  stats: {
    children: true,
  },
  plugins: [
    // NOTE: you can define source image files directly in template using the webpack alias to the image directory,
    // then you don't need to copy source images into dist
    new FileManagerPlugin({
      events: {
        onEnd: [
          { delete: ['./dist/images'] },
          {
            delete: [path.join(__dirname, '../wp/wp-content/themes/2019-redesign/assets/*')],
          },
          {
            copy: [
              {
                source: path.join(__dirname, '../src/images'),
                destination: path.join(__dirname, '../dist/images'),
              },
            ],
          },
          {
            copy: [
              {
                source: path.join(__dirname, '../dist/images'),
                destination: path.join(__dirname, '../wp/wp-content/themes/2019-redesign/assets/images'),
              },
            ],
          },
          {
            copy: [
              {
                source: path.join(__dirname, '../dist/js'),
                destination: path.join(__dirname, '../wp/wp-content/themes/2019-redesign/assets/js'),
              },
            ],
          },
          {
            copy: [
              {
                source: path.join(__dirname, '../dist/css'),
                destination: path.join(__dirname, '../wp/wp-content/themes/2019-redesign/assets/css'),
              },
            ],
          },
        ],
      },
    }),

    // MiniCssExtractPlugin // <= DELETE it
    // new MiniCssExtractPlugin({
    //   filename: isHot ? '[name].css' : 'css/[name].css',
    //   chunkFilename: '[id].css',
    // }),
    new ESLintPlugin(),

    new CurrentTimePlugin(),

    new HtmlBundlerPlugin({
      entry: {
        article: './src/views/article.html',
      },
      js: {
        filename: 'js/[name].js',
      },
      css: {
        filename: 'css/[name].css',
        chunkFilename: 'css/[name].chunk.css',
      },
    }),
  ],
};

Please create a repo, then I can help you more.

scott-primacy commented 7 months ago

Thanks so much for your help. I created this little repo to help me get this working: https://github.com/scott-primacy/webpack

scott-primacy commented 7 months ago

Unfortunately, it can't compile the html page tho.

Module build failed (from ./node_modules/html-bundler-webpack-plugin/src/Loader/index.js):
LoaderException: 
 HTML Bundler Plugin  Resolving of source files in the template file failed.
File: src/views/article.html
Error: Cannot find module '/Users/scottr/work/webpack-testing/src/styles/app.css'
webdiscus commented 7 months ago

@scott-primacy

Cannot find module '/Users/scottr/work/webpack-testing/src/styles/app.css'

of course is not found. Did you see my PR? Firstly merge my PR into your demo and see how it works.

You should load the source SCSS file, not CSS, because css not exists and will be generate by the bundler plugin. Just add the source SCSS file into HTML:

<link href="@styles/app.scss" rel="stylesheet">

and remove this file from JS.

@styles is the webpack alias to /Users/scottr/work/webpack-testing/src/styles/

webdiscus commented 4 months ago

@scott-primacy is the issue still actual?