Inlining imported css doesn't work in watch mode if a leaf component content is changed
sahilmob opened this issue · 13 comments
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
Hallo @sahilmob,
Thanks for the issue report.
I'll try to fix it over the weekend.
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.
- start development:
npm start
, open in your browser the url : http://localhost:8080 - change
src/home.html
=> ok - change
src/style.css
=> ok - change
src/main.js
=> ok - change
src/component-a/style.css
=> ok - change
src/component-a/index.js
=> ok - change
src/component-b/style.css
=> ok - 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
@sahilmob
Is the issue reproducible if you don't use your custom "inline-template-plugin"?
@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.
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?
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.
- please create a small repo with reproducible issue
- describe exactly and very clear the steps to reproduce the problem.
WARNING
Without your repo, I can't help you, sorry.
Sure. Thanks
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
.
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?
Thanks for your efforts. My repos is private unfortunately.
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 ;-)