linaria icon indicating copy to clipboard operation
linaria copied to clipboard

Stale styles: Multiple `<style>` are injected on `<head>` on save, for the same component - Monorepo structure

Open JoaoFGuiomar opened this issue 2 years ago • 1 comments

Environment

This particular case is happening in a Monorepo configure with rush

  • Linaria version:
    • linaria/vite 4.5.3
    • linaria/react 4.5.3
    • linaria/core 4.5.3
  • Bundler (+ version): Vite 4.3.9
  • Node.js version: 18.12
  • OS: Mac OS Ventura 13.4.1

Description

Best way to describe this is to check the video below.

Quick TLDR: When changing styles on a component, the <head> element is injected with multiple <style> elements for the same component. Previous styles for the same component don't seem to be cleared.

This makes it look like the page doesn't react to style changes on save. A hard refresh (refresh the browser manually) clears all old styles and the page looks good.

This only happens when we change to previously used styles

Example

  • font-size is in the beginning 3rem
  • update font-size to 4rem
  • app refreshes, styles are correct ✅
  • update font-size to 3rem (previous value)
  • app refreshes, styles are incorrect ❌
  • checking the DOM, we see multiple <style> elements for the same component

⚠️ This only seems to happen to imported components. ⚠️ The "Dotted" styled component in the "App.tsx" file updates fine if we change its values.

Reproducible Demo

I created a minimal reproducible repository: https://github.com/JoaoFGuiomar/minimal-repo

Instructions are on the README. Will also drop a quick video below

https://github.com/callstack/linaria/assets/2249046/147dc1ec-1750-49be-a893-c7218d687c47

Thanks 👍

JoaoFGuiomar avatar Jul 27 '23 09:07 JoaoFGuiomar

It seems that there is a way to workaround it, using vite plugin to transform every single JS/TS file and add this line to all those files:

import.meta.hot;

so the plugin would be as simple as:

{
  name: "inject-hmr-for-linaria",
  transform(code: string, id: string) {
    if ([".js", ".ts", ".tsx"].some((x) => id.endsWith(x))) {
      return `${code}

      import.meta.hot;
      `;
    }
  },
}
Full vite.config.ts example:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import linaria from "@linaria/vite";

const jsExtension = [".js", ".ts", ".tsx"]

export default ({ command, mode, ssrBuild }) => {
  const isDev = mode === "development";

  return defineConfig({
    build: {
      outDir: "build",
    },
    plugins: [
      linaria(),
      react({
        jsxRuntime: "classic",
      }),

      isDev && {
        name: "inject-hmr-for-linaria",
        transform(code: string, id: string) {
          if (jsExtension.some((x) => id.endsWith(x))) {
            return `${code}

            import.meta.hot;
            `;
          }
        },
      },
    ].filter((x) => !!x),
  });
};

However, I would love that I wouldn't have to do such "workaround"

lukaskl avatar Jul 27 '23 20:07 lukaskl