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

Add a link rel=prefetch for a css chunks created within your wepbackPrefetch lazy loaded js chunks #1832

Closed
StadnykYura opened this issue Nov 23, 2023 · 2 comments

Comments

@StadnykYura
Copy link

StadnykYura commented Nov 23, 2023

Is your feature request related to a problem? Please describe.
When using a webpackPrefetch hint on a dynamicly imported js module the webpack does not have a support for dynamic runtime injection of related lazy css chunk (extracted with MiniCssExtractPlugin) to the lazy loaded js chunk.

Current behaviour
Currently, as @sokra mentioned #1317 (comment), webpackPrefetch hint on js modules automatically adds something like this <link rel="prefetch" as="script" href="http://host:port/lazy-component.js"> to the html head after the initial chunk evaluation.

There is no need to html-webpack-plugin to add prefetch tags. webpack already adds at runtime and that's not too late as they are intended to download after the other files.

And if you are using the style-loader in a combination with css-loader and sass-loader everything is fine, bcs css included into js, the js is prefetched during runtime on browser idle time. So later, when the js module is used/rendered, the js chunk is fetched from the prefetch cache, and no additional request for other resources is done.

The Problem?/Issue?/Expected behaviour?/Missed case?
If you are using the MiniCssExtractPlugin, you are creating a separate css chunk for the related js chunk during the build. So when you are lazy loading the js module later, your js chunk is grabbed from the prefetch cache, but the related css chunk is additionally loaded through the network.

Describe the solution you'd like
ideally the 1st or 2nd solution i would like:
1) Can the runtime creation of the <link rel="prefetch" href="/lazy-style.css" as="style" /> for the related lazy-loaded css chunk be handled by some available functionality of the webpack, taking into account that it does handle the same for js chunks?
2) Can we handle the static creation (during the build time) of the <link rel="prefetch" href="/lazy-style.css" as="style" /> for that separate css chunk and add it into html head with a help of html-webpack-plugin?

Describe alternatives you've considered
Alternatives:
3) Can we somehow extend the functionality of the webpack to be able during the runtime add the <link rel=prefetch> for the css chunks of the related lazy loaded js chunks?
4) should we handle the static creation (during the build time) of the <link rel="prefetch" href="/lazy-style.css" as="style" /> for that separate css chunk and add it into html head on our own with a help of our custom plugin?

Additional context
Partly related past questions/issues:
#934
#1317 by @jantimon

An example - to reproduce the case when the css chunk is not prefetched.
Project structure
image

package.json
{ "name": "webpack-prefetch-test", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack", "start": "webpack serve --open" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@babel/core": "^7.23.3", "@babel/preset-env": "^7.23.3", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", "babel-loader": "^9.1.3", "css-loader": "^6.8.1", "html-webpack-plugin": "^5.5.3", "mini-css-extract-plugin": "^2.7.6", "sass": "^1.69.5", "sass-loader": "^13.3.2", "style-loader": "^3.3.3", "ts-loader": "^9.5.0", "typescript": "^5.2.2", "webpack": "^5.89.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1" }, "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" } }

webpack.config.js

const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const path = require("path");

const templates = path.resolve(__dirname, "templates");

module.exports = {
  mode: "development",
  entry: "./src/index.tsx",
  devtool: "source-map",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
      {
        test: /\.scss$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
        exclude: /node_modules/,
      },
      {
        test: /\.m?js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env"],
          },
        },
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js", "scss"],
  },
  output: {
    filename: "[name].js",
    chunkFilename: "[name].js",
    path: path.resolve(__dirname, "dist"),
    clean: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(templates, "base-template.html"),
    }),
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[id].css",
    }),
  ],
};

tsconfig.json
{ "compilerOptions": { "outDir": "./dist/", "sourceMap": true, "noImplicitAny": true, "module": "ES2020", "target": "ES2020", "jsx": "react-jsx", "moduleResolution": "Bundler", "allowArbitraryExtensions": true }, "include": [ "./src/*", ] }

templates/base-template.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

src/app-component.scss
h1 { color: red; font-size: 20px; }

src/app-component.tsx

import { FC, Suspense, useState } from "react";
import "./app-component.scss";

import { lazy } from "react";

const LazyComponent = lazy(
  () =>
    import(
      /* webpackPrefetch: true */
      /* webpackChunkName: "lazy-component" */
      "./lazy-component"
    )
);

const App: FC = () => {
  const [showLazy, setShowLazy] = useState(false);

  const toggleLazy = () => {
    setShowLazy(!showLazy);
  };

  return (
    <>
      <h1>This is a variable example</h1>
      <button onClick={toggleLazy}>toggle lazy component</button>
      {showLazy && (
        <Suspense fallback={<div>Loading</div>}>
          <LazyComponent />
        </Suspense>
      )}
    </>
  );
};

export default App;

src/index.tsx

import { createRoot } from "react-dom/client";
import App from "./app-component";
const root = createRoot(document.getElementById("root"));
root.render(<App />);

src/lazy-component.tsx

import "./lazy-compon.scss";

const LazyComponent = () => {
  return <div className="lazy-css">Some lazy Component</div>;
};

export default LazyComponent;

src/lazy-compon.scss
.lazy-css { color: orange; }

The page loads and main.js, main.css are already in the html head (they were added during build time). The webpack during runtime adds (bcs of the wepbackPrefetch hint on js chunk) this piece of html into the head > <link rel="prefetch" as="script" href="http://localhost:8080/lazy-component.js"> . Then when clicked on a button "toggle lazy component", the css is requested from network (it is added as link stylesheet into the head), and the js is grabbed from prefetched cache.

image

image

image

@StadnykYura StadnykYura changed the title Support for adding a link rel=prefetch for a related css chunks which are created within your lazy loaded js chunks Support for adding a link rel=prefetch for a related css chunks which are created within your lazy loaded js chunks with wepbackPrefetch Nov 23, 2023
@StadnykYura StadnykYura changed the title Support for adding a link rel=prefetch for a related css chunks which are created within your lazy loaded js chunks with wepbackPrefetch Add a link rel=prefetch for a css chunks created within your wepbackPrefetch lazy loaded js chunks Nov 23, 2023
@alexander-akait
Copy link
Collaborator

I don't think we can solve it here, in HTML webpack plugin, this reques is more for mini-css-extract-plugin

@alexander-akait
Copy link
Collaborator

Close in favor webpack-contrib/mini-css-extract-plugin#1043, release will be soon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants