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

Ability to rename window.__NUXT__ context object #18870

Closed
3 of 4 tasks
mvanalphen opened this issue Feb 7, 2023 · 13 comments
Closed
3 of 4 tasks

Ability to rename window.__NUXT__ context object #18870

mvanalphen opened this issue Feb 7, 2023 · 13 comments

Comments

@mvanalphen
Copy link

mvanalphen commented Feb 7, 2023

Describe the feature

In Nuxt 2, it was possible to rename the __NUXT__ context object assigned to the window through the globals.context option in nuxt.config.js, as documented here.

In Nuxt 3, it seems that it is not possible to rename this context object through the Nuxt configuration. However, in this pull request, the possibility of changing the rootId and rootTag was re-added, which was previously possible through the aforementioned globals configuration option.

As such, the feature request is to allow clients to rename the __NUXT__ context object through their Nuxt configuration.

Context

For context: I'm currently experimenting with loading a new Nuxt application within an existing Nuxt application. The reason for this is that we run a number of micro-frontends as separate Nuxt projects. When a user clicks on an element that exists in some of these Nuxt micro-frontends, we want to show a slide-in component which is actually a separately maintained Nuxt or Vue project. We're still running some experiments in how we want to handle this, with one option being loading an entirely new Nuxt application.

An issue with loading a new Nuxt application, is that the micro-frontends naturally define their own __NUXT__ context, while the dynamically loaded project will also want to do the same. These will conflict with each other. As such, the dynamically loaded project would want to use its own window.__NUXT__ context object, e.g. __NUXT_EXAMPLE__.

This is all still in very early stages and it may be a pretty bad idea, and I may end up just building this project in vanilla Vue 3. However, I figured that it may be worthwhile to re-add this functionality from Nuxt 2.

Additional information

  • Would you be willing to help implement this feature?
  • Could this feature be implemented as a module?

Final checks

@mvanalphen
Copy link
Author

mvanalphen commented Feb 8, 2023

As a workaround for my experimentation, I'm temporarily resolving this by extending the Vite and Nitro configs in nuxt.config.ts by adding the '@rollup/plugin-replace plugin, like so:

import replace from '@rollup/plugin-replace';

const replacePlugin = replace({
  __NUXT__: '__NUXT_EXAMPLE__',
});

const isDevelopmentEnvironment = process.env.NODE_ENV === 'development';

export default defineNuxtConfig({
  vite: {
    build: {
      rollupOptions: {
        plugins: isDevelopmentEnvironment ? [] : [renameNuxtContextPlugin],
      },
    },
  },

  nitro: {
    rollupConfig: {
      plugins: isDevelopmentEnvironment ? [] : [renameNuxtContextPlugin],
    },
  },
});

@mvanalphen
Copy link
Author

mvanalphen commented Feb 16, 2023

The experimental.noScripts option in nuxt.config.ts will remove the context object entirely, and also solves this problem. It was fixed in 3.2.0.

@Baroshem
Copy link
Contributor

Baroshem commented Mar 1, 2023

@danielroe

I looked at the description and it seems to me that there is no need for additional work here. If I am mistaken, please let me know. I could take a look at this issue :)

@mvanalphen
Copy link
Author

It's worth noting that experimental.noScripts does limit the Nuxt application in the sense that there'll be no scripts generated using nuxi generate, so that'll definitely not suit a lot of use cases. There is a workaround available, but it is not ideal.

@danielroe
Copy link
Member

We're not inclined to support renaming __NUXT__ on its own but we absolutely want to support multi-app, which we can track in #21635.

@danielroe danielroe closed this as not planned Won't fix, can't repro, duplicate, stale Jun 19, 2023
@victorgarciaesgi
Copy link

HI @mvanalphen , even if this issue is closed, did you encountered more issues?
Is what you linked for the window.__NUXT__ was the only blocker into enabling a CSR nuxt app into a micro-frontend?
We have the same case and would love to know! :)

@mvanalphen
Copy link
Author

Hey @victorgarciaesgi! For us, the solution of renaming window.__NUXT__ using the @rollup/plugin dependency worked like a charm and allowed two Nuxt instances to run alongside each other on the page, together with the app.rootId and app.rootTag configuration options.

However, as our second Nuxt application was dynamically loaded into the first Nuxt application based on an action by the user, we had to ensure the Nuxt window object was automatically injected onto the page. As by default the Nuxt window object is added as an inline script to the index.html file generated during the build stage like so:

<script>window.__NUXT_EXAMPLE__=(function(a){return {serverRendered:a,config:{public:{isDevelopmentEnvironment:a},app:{baseURL:"\u002F",buildAssetsDir:"\u002F_nuxt\u002F",cdnURL:""}},data:{},state:{}}}(false))</script>

But of course, we can't rely on an index.html file as the user is already on the page. As such, we created a custom Nitro plugin which takes this inline script and prepends it to the entry.js file:

// nitro-plugins/inject-context.ts
import { readFileSync, writeFileSync } from 'fs';

export default defineNitroPlugin(nitroApp => {
  nitroApp.hooks.hook('render:html', (html, { event }) => {
    if (!event.context.params?._.includes('index.html')) {
      return;
    }

    const nuxtContextScript = html.bodyAppend.find(
      (element: string) => element.includes('<script>') && element.includes(`window.__NUXT_EXAMPLE__`)
    );

    if (!nuxtContextScript) {
      return;
    }

    const strippedContext = `${nuxtContextScript
      .replace('<script>', '')
      .replace('</script>', '')};`;

    const entryFilePath = './.output/public/_nuxt/entry.js';

    const entryFileData = readFileSync(entryFilePath);
    writeFileSync(entryFilePath, `${strippedContext}${entryFileData}`);
  });
});

So entry.js would upon being loaded by the client now automatically create the window.__NUXT_EXAMPLE__ object prior to initalizing Nuxt.

At that point, it's just a matter of creating the element on the page (<div id="__nuxt_example"></div>) and dynamically loading the entry.js and entry.css files that are outputted by nuxi generate.

Definitely not the most elegant or perfect solution, but it works well for our use case and performance seems to be fine.

@victorgarciaesgi
Copy link

Thanks for the detailed response @mvanalphen ! Super cool solution we'll try that
We were also looking at iframes (like how nuxt devtools works) but were not super fan of the idea so yours can really be ideal

@mvanalphen
Copy link
Author

Thanks for the detailed response @mvanalphen ! Super cool solution we'll try that We were also looking at iframes (like how nuxt devtools works) but were not super fan of the idea so yours can really be ideal

Happy to help! Hopefully it'll work for you as well!

And here's to hoping that the upcoming multi-app solution will make this a breeze 😄.

@victorgarciaesgi
Copy link

Sorry to bother you again @mvanalphen , but did you by chance encountered a nuxt context error ?
image

The rollup replace plugin worked, and I have the 2nd app loaded in the first one dynamically (user action)

But it seems when mounting Nuxt still have 2 instances cohabiting, is it something your nitro plugin prevented?

BTW: I'm in dev mode with a different build for MF, enabled the rollup plugin in Vite dev too. I saw the experimental.noScripts is only working on a full build and not in dev so maybe it's related
Thanks :D

@mvanalphen
Copy link
Author

@victorgarciaesgi No worries! I can't say that's an error I've ever run into.

Am I understanding correctly that you're only running into this error in development mode (nuxi dev)? Are you also experiencing this problem on production builds?

If you're only running into this issue in development mode, I'm guessing that's because the Nitro plugin only works during yarn generate with static site builds. Development mode works its own magic to spin up a local server, so the plugin wouldn't work there I think.

@victorgarciaesgi
Copy link

I managed to made it working in dev without the nitro plugin.
It's super hacky for now as i'm patching some lines of Nuxt core to avoid the Nuxt context conflict.

const replacePlugin: any = replace({
    delimiters: ['', ''],
    values: {
        __NUXT__: '__NUXT_MF__',
        [`getContext("nuxt-app")`]: `getContext("nuxt-app-mf")`,
    },
});

Had to remove the baseUrl of the app and update the buildAssetsDir property too.
Deactivated the pages property also.
To handle the redirection I used routeRules like so

routeRules: {
        '/mf/project-client': {redirect: '/'},
}

Still a lot of hacky stuff, working on dev, not tested on prod build yet

Thanks again for your help and infos @mvanalphen :D

@mvanalphen
Copy link
Author

No worries, and glad to hear you found a solution and thanks for sharing it here. Small steps, but closely getting there 😄.

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

No branches or pull requests

4 participants