WJSoftware / vite-plugin-single-spa

Vite plugin to convert Vite-based projects to single-spa root or micro-frontend applications.
MIT License
61 stars 4 forks source link

CSS of MIFE not getting imported in root app #87

Closed imsatyam2111 closed 8 months ago

imsatyam2111 commented 8 months ago

Describe the bug

I am migrating my CRA + Craco microfrontend app to vite using vite-plugin-single-spa. I have converted the root app as well in vite using the same plugin.

The problem I am facing is css is not being imported when loading my microfrontend into the root app with default built settings.

Project type: [ ] Root Config [✅ ] Micro-frontend/parcel Project Framework: [ ✅] React [ ] Svelte [ ] Vue [ ] Preact [ ] Other:

To Reproduce

Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Also include Vite's configuration (the contents of vite.config.js/ts).

import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import vitePluginSingleSpa from "vite-plugin-single-spa";
import tsconfigPaths from "vite-tsconfig-paths";

export default ({ mode }) => {
  // load app-level env vars to node-level env vars.
  process.env = { ...process.env, ...loadEnv(mode, process.cwd() )};

  return defineConfig({
    plugins: [
      react(),
      tsconfigPaths(),
      vitePluginSingleSpa({
        type: "mife",
        serverPort: 4101,
        spaEntryPoints: "src/app-login.jsx",
      }),
    ],
    base: process.env.VITE_BASE_URL,
    // build: {
    //   rollupOptions: {
    //     output: {
    //       manualChunks: undefined,
    //       inlineDynamicImports: true,
    //     }
    //   }
    // }
  });
}

app-login.jsx

import React from 'react';
import ReactDOMClient from 'react-dom/client';
import singleSpaReact from 'single-spa-react';
import { cssLifecycleFactory } from 'vite-plugin-single-spa/ex';

import App from './App'

const lc = singleSpaReact({
    React,
    ReactDOMClient,
    rootComponent: App,
    errorBoundary() {
        return <div>Error</div>
    }
});

const cssLc = cssLifecycleFactory('app-login');
export const bootstrap = [cssLc.bootstrap, lc.bootstrap];
export const mount = [cssLc.mount, lc.mount];
export const unmount = [cssLc.unmount, lc.unmount];

Expected behavior

CSS should get imported when integrated in root App

Screenshots

dist folder with default build settings image

browser console JS got fetched image

No css for MIFE image

Browser list:

Browser Name Browser Version Platform
Acme Browser (example row) v1.0.0 Windows x64

Additional context

Root app vite.config.js

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import vitePluginSingleSpa from "vite-plugin-single-spa";

export default defineConfig({
  plugins: [
    react(),
    vitePluginSingleSpa({
      type: "root",
      imo: "2.4.2",
    }),
  ],
});

The interesting thing is when I am adding following piece in the MIFE vite.config.js file, css does get imported in the root app. The difference I see is the default build settings divide the js and css files in chunks, whereas the following build configs, builds the app in a single js file and a single css file.

build: {
   rollupOptions: {
     output: {
          manualChunks: undefined,
          inlineDynamicImports: true,
     }
  }
}

dist folder screenshot image

Browser console: CSS does get imported image

webJose commented 8 months ago

Hello, @imsatyam2111. The plug-in's algorithm to determine the CSS files to mount relies on Vite's output. If the entry point JS file contains no CSS files listed, then the map is empty and mounting the MFE triggers no CSS uploads. This is what is most likely happening in your case. This means that all CSS is tied to JS chunks that are dynamically imported.

Vite is the one that imports CSS dynamically imported. This plug-in only controls their disabled HTML attribute when mounting or unmounting. I would say this particular problem seems to be related to Vite, not this plug-in.

If you trigger the dynamic imports, do you see any more CSS files being fetched?

webJose commented 8 months ago

From Vite's documentation:

CSS Code Splitting

Vite automatically extracts the CSS used by modules in an async chunk and generates a separate file for it. The CSS file is automatically loaded via a tag when the associated async chunk is loaded, and the async chunk is guaranteed to only be evaluated after the CSS is loaded to avoid FOUC.

If you'd rather have all the CSS extracted into a single file, you can disable CSS code splitting by setting build.cssCodeSplit to false.

You could test build.cssCodeSplit if you like, but as you can see, dynamic imports, by default, split CSS and the corresponding JS files are in charge of mounting the CSS.

webJose commented 8 months ago

I also see a Vite v5 note in this piece (are you using Vite v5?):

Default and named imports from CSS files (e.g import style from './foo.css') are removed since Vite 5. Use the ?inline query instead.

I am unsure if this applies to you. Something to have in mind if you are doing named or default imports of CSS.

imsatyam2111 commented 8 months ago

Hello, @imsatyam2111. The plug-in's algorithm to determine the CSS files to mount relies on Vite's output. If the entry point JS file contains no CSS files listed, then the map is empty and mounting the MFE triggers no CSS uploads. This is what is most likely happening in your case. This means that all CSS is tied to JS chunks that are dynamically imported.

Vite is the one that imports CSS dynamically imported. This plug-in only controls their disabled HTML attribute when mounting or unmounting. I would say this particular problem seems to be related to Vite, not this plug-in.

If you trigger the dynamic imports, do you see any more CSS files being fetched?

Most of CSS in my are coming up from my internal style guide and module level css. Module level css are being imported like import stylse from './style.module.css'. And also I have many components from antd.

webJose commented 8 months ago

Most of CSS in my are coming up from my internal style guide and module level css. Module level css are being imported like import stylse from './style.module.css'. And also I have many components from antd.

Ok, this is no longer supported in Vite v5. If you are using Vite v5, that's it. Move to Vite v4 or stop doing default imports.

webJose commented 8 months ago

Hello, @imsatyam2111. Any updates? Can we close this issue?

webJose commented 8 months ago

Closing the issue due to inactivity. Feel free to reopen if needed. Thanks!

PabloFX commented 5 months ago

Hi @webJose I would like to reopen the ticket as it seems that I have a very similar problem.

My vite.config:

...
vitePluginSingleSpa({
              type: 'mife',
              serverPort: 4041,
              spaEntryPoints: 'src/spa.ts',
            }),
...

My spa.ts file:

import './styles/app.scss';

...

const cssLifecycles = cssLifecycleFactory('spa');

export const bootstrap = [cssLifecycles.bootstrap, vueLifecycles.bootstrap];
export const mount = [cssLifecycles.mount, vueLifecycles.mount];
export const unmount = [cssLifecycles.unmount, vueLifecycles.unmount];

The CSS file is bundled but when I choose to use dynamic imports for vue routes, it doesn't load the bundled filevpss([projectId])style-DdVLbwOG.css

If there are no dynamic imports, then everything is ok

I tried suggestions from this thread but had no success. Maybe in the meantime, you discovered something new that may help me?

webJose commented 5 months ago

Hello, @PabloFX. Currently working, but quickly: Importing CSS in spa.ts looks weird to me. Are we talking parcels or are we talking MFE's?

Version of the plug-in? Version of Vite?

PabloFX commented 5 months ago

Hi,

"vite": "^5.2.8",
"vite-plugin-single-spa": "^0.7.0",
webJose commented 5 months ago

I can't help if you only reply some of the questions. Please complete the information.

PabloFX commented 5 months ago

Sorry I missed the first question. The root app will handle different mife apps served on separate pages. So this is MFE app that uses dynamic imports for routes

PabloFX commented 5 months ago

if this would help - the assets are bundled (at least seems to be) correctly with proper CSS code inside:

image

The issue I see is that it's not appended in dist spa.js file:

image
webJose commented 5 months ago

Move the importing of the CSS file out of spa.ts and into the entry point component. Then rebuild.

PabloFX commented 5 months ago

it seems to be working, thank you!

valeriiavask commented 2 months ago

Jose, using the plugin in production for quite some time, with no issues! thanks for building it!

As our app grew bigger, vite did auto code splitting, and we noticed that the CSS file is not included in the mfe JS - it happens randomly - sometimes we have to trigger another build/deploy so that the CSS is included (to be loaded/bootstrapped)

it's like there's a race condition somewhere - i will look into it more and report back but for now i will try the things mentioned in this thread.

also if you have more tips, or things to check maybe in the plugin level, please let me know