Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inlining imported css doesn't work in watch mode if a leaf component content is changed #74

Open
sahilmob opened this issue Feb 14, 2024 · 12 comments
Labels
bug Something isn't working
Milestone

Comments

@sahilmob
Copy link

sahilmob commented Feb 14, 2024

Current behaviour

I have and index.html that imports index.jsx which has a Layout Component, and the Layout Component imports a stylesheet from a package in node_modules, and the Layout component renders a child component, when I run webpack --watch --progress --mode development for the first time I get all the js and css injected in the html however, if I change the child component file and save, the generated html won't have the inline css, furthermore, if I go to the Layout component and save it to trigger rebuild, I get the css in the generated html.

Expected behaviour

The css should be included in the generated html in watch mode every time

Reproduction Example

./index.html

<div id="root"></div>
<script src="./index.js"></script>

./index.js

import React from "react";
import * as ReactDOM from "react-dom/client";
import Layout from "./Layout";
Import SomeComponent from "./SomeComponent";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(<Layout><SomeComponent /></Layout>);

./Layout.jsx

import "some-package/dist/index.css";
import React from "react";

export default function Layout({children}){
  reutrn <div>{children}</div>
}

./SomeComponent.jsx

import React from "react";

export default function SomeComponent(){
  reutrn <div>Some content</div>
}

webpack.config.js

const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");
const HtmlBundlerPlugin = require("html-bundler-webpack-plugin");

module.exports = (env) => {
  return {
    resolve: {
      extensions: [".tsx", ".jsx", ".ts", ".js"],
    },
    plugins: [
      new CopyPlugin({
        patterns: [
          {
            from: "./",
            to: "resources",
            filter: (file) => !file.endsWith(".jsx") && !file.endsWith(".html"),
          },
        ],
      }),
      new HtmlBundlerPlugin({
        filename: "[name].ftl",
        entry: "./",
        postprocess: (content) => {
          return content.concat("<head></head>");
        },
        js: {
          inline: true,
        },
        css: {
          inline: true,
        },
      }),
      {
        apply(compiler) {
          const pluginName = "inline-template-plugin";

          compiler.hooks.compilation.tap(pluginName, (compilation) => {
            const hooks = HtmlBundlerPlugin.getHooks(compilation);

            hooks.beforeEmit.tap(pluginName, (content) => {
              return (
                '<#import "template.ftl" as layout>' +
                "<@layout.registrationLayout displayInfo=social.displayInfo; section>" +
                content +
                "</@layout.registrationLayout>"
              );
            });
          });
        },
      },
    ],
    module: {
      rules: [
        {
          test: /.(js|jsx|ts|tsx)$/,
          use: {
            loader: "babel-loader",
            options: {
              plugins: [["remove-comments"]],
              presets: [["@babel/preset-env"], "@babel/preset-react"],
            },
          },
        },
        {
          test: /.(js|jsx|ts|tsx)$/,
          include: /node_modules/,
          use: {
            loader: "babel-loader",
          },
        },
        {
          test: /.(ts|tsx)$/,
          use: "ts-loader",
          exclude: /node_modules/,
        },
        {
          test: /\.(css|sass|scss)$/,
          use: ["css-loader", "sass-loader"],
        },
        {
          test: /\.(ico|png|jp?g|webp|svg)$/,
          type: "asset/resource",
          generator: {
            outputPath: () => {
              return "resources";
            },
            filename: ({ filename }) => {
              const base = path.basename(filename);
              return "/img/" + base;
            },
          },
        },
        {
          test: /[\\/]fonts|node_modules[\\/].+(woff(2)?|ttf|otf|eot)$/i,
          type: "asset/resource",

          generator: {
            outputPath: () => {
              return "resources";
            },
            filename: ({ filename }) => {
              const base = path.basename(filename);
              return "/fonts/" + base;
            },
          },
        },
      ],
    },
    output: {
      clean: true,
      publicPath: "public",
    },
    watchOptions: {
      ignored: ["node_modules", "dist"],
    },
    cache: {
      type: "memory",
      cacheUnaffected: false,
    },
    devtool: false,
  };
};

Environment

  • OS: Windows
  • version of Node.js: 21.2.0
  • version of Webpack: 5.90.1
  • version of the Plugin: 3.4.12

Additional context

I noticed that the beforeEmit hook isn't being called after saving SomeComponent while its being called for Layout component

@webdiscus
Copy link
Owner

Hallo @sahilmob,

Thanks for the issue report.
I'll try to fix it over the weekend.

@webdiscus webdiscus added this to the backlog milestone Feb 14, 2024
@webdiscus webdiscus added the bug Something isn't working label Feb 14, 2024
@webdiscus webdiscus changed the title Inclining imported css doesn't work in watch mode if a leaf component content is changed Inlining imported css doesn't work in watch mode if a leaf component content is changed Feb 19, 2024
@webdiscus webdiscus modified the milestones: backlog, analysis Feb 19, 2024
@webdiscus
Copy link
Owner

webdiscus commented Feb 19, 2024

@sahilmob

I cannot reproduce the issue.
I have created the manual test watch-imported-css-inline with nested components. After change any file all imported CSS will be inlined into HTML.

  1. start development: npm start, open in your browser the url : http://localhost:8080
  2. change src/home.html => ok
  3. change src/style.css => ok
  4. change src/main.js => ok
  5. change src/component-a/style.css => ok
  6. change src/component-a/index.js => ok
  7. change src/component-b/style.css => ok
  8. change src/component-b/index.js => ok

Please:

  • create a repo based on the watch-imported-css-inline example, without heavy React
  • describe the specific steps of what you are doing: what file (e.g. src/path/to/style.css) are you changing, after which occurs issue

@webdiscus
Copy link
Owner

@sahilmob
Is the issue reproducible if you don't use your custom "inline-template-plugin"?

@sahilmob
Copy link
Author

@webdiscus
Yes its is! what is interesting though is that style-loader seems to be the root cause of the problem, I noticed that style-loader doesn't work very well with this plugin.

Initially my workaround was to create a local .css file and @import "~some-package/dist/index.css"; inside it, and then import the local css file into Layout.tsx, but when I went back to reproduce this issue without my custom plugin (and import some-package/dist/index.css in Layout.tsx component), I noticed that I cannot compile the app with the following error

ERROR in [entry] [initial] Spread syntax requires ...iterable[Symbol.iterator] to be a function

I then disabled style-loader and enabled my custom plugin, it worked fine!

Please note that I've added style-loader very recently, after reporting this bug, and after doing the aforementioned workaround so that's why it was compiling successfully.

@webdiscus
Copy link
Owner

Why you use the bundler plugin with style-loader?
The bundler plugin is designed to replace the style-loader and is absolutely incompatible for together work.
The bundler plugin can extract CSS and inline into HTML.
The style-loader do the same. Only one different: style-loader can HMR without site reloading after changes, the bundler plugin requires the site reloading.

I don't understand what doing your inline-template-plugin. It's look like a wrapper over generated HTML with a templating things: <#import "template.ftl" as layout>.... Why you don't write this wrapper directly in HTML template file?

@sahilmob
Copy link
Author

Yes you are right l, I use style loader for HMR.

Also you are right about the custom plugin. I convert the html into Freemarket template (ftl)and the reason why I don't write it inside the html is that I want to inline js and css, and the ftl syntax breaks html parsing.

@webdiscus
Copy link
Owner

@sahilmob

  1. please create a small repo with reproducible issue
  2. describe exactly and very clear the steps to reproduce the problem.

WARNING

Without your repo, I can't help you, sorry.

@sahilmob
Copy link
Author

Sure. Thanks

@webdiscus
Copy link
Owner

webdiscus commented Feb 21, 2024

here is the test case for .ftl template.

You can use you .ftl template as an entry. Just disable the preprocessor: false option.

Your source tempalte:

<#import "template.ftl" as layout>
<@layout.registrationLayout displayMessage=true displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??; section>
  <#if section = "styles">
    <link rel="stylesheet" href="./scss/styles.scss" />
  </#if>
  <#if section = "scripts">
    <script typo="module" src="./js/main.js"></script>
  </#if>
  <img src="./images/picture.png" />
</@layout.registrationLayout>

the HtmlBundlerPlugin config:

new HtmlBundlerPlugin({
      filename: '[name].ftl', // <=  output filename of template
      test: /\.ftl$/, // <= add it to detect *.ftl files
      entry: {
         index: 'src/index.ftl',
      },
      // OR entry: 'src/',
      js: {
        inline: true,
      },
      css: {
        inline: true,
      },
      preprocessor: false, // <= disable it for processing *.ftl template w/o compilation
    }),

The generated output template file:

<#import "template.ftl" as layout>
<@layout.registrationLayout displayMessage=true displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??; section>
  <#if section = "styles">
    <style>...inlined CSS...</style>
  </#if>
  <#if section = "scripts">
    <script>... inlined JS code ...</script>
  </#if>
  <img src="img/picture.7b396424.png" />
</@layout.registrationLayout>

So you don't need additional inline-template-plugin.

@webdiscus
Copy link
Owner

webdiscus commented Apr 30, 2024

@sahilmob

for info: I'm working on the HMR supporting for styles. So, after changes in a SCSS/CSS file, the generated CSS will be updated in the browser without reloading, similar it works in style-loader. This it takes a lot of time, because it is very very complex. But this works already for styles imported in JS very well. Now I work on HMR supporting for styles defined directly in HTML.

P.S. what is with your test repo for using .ftl templates? Is it yet actual?

@sahilmob
Copy link
Author

sahilmob commented May 2, 2024

Thanks for your efforts. My repos is private unfortunately.

@webdiscus
Copy link
Owner

webdiscus commented May 2, 2024

Thanks for your efforts. My repos is private unfortunately.

you can create a public demo repo with fake data to reproduce your issue.
Without the reproducible issue I can't help you, you should understand it ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants