nuxt / rfcs

RFCs for changes to Nuxt.js
98 stars 2 forks source link

pluggable child apps (multiapp) #30

Open pi0 opened 5 years ago

pi0 commented 5 years ago

Background

When the project structure grows, sometimes it is not convenient to use a flat directory structure or keep everything in a single repo.

Why?

Proposed Solution

One can structure a project like this:

├── shop
|  ├── components
|  |  └── nav.vue
|  ├── layouts
|  |  └── admin.vue
|  ├── middleware
|  |  └── admin.js
|  ├── pages
|  |  └── shop
|  |     └── index.vue
|  └── store
|     └── admin.js
├── nuxt.config.js
├── package.json
├── pages
|  └── index.vue
└── store
   └── public.js

The shop/ dir should basically follow the same standard nuxt directory structure. Builder module combines all children app to the main one. And also creates aliases so components inside admin can be required using:

import Nav from '~shop/components/nav

For registering admin app, it should be registered inside nuxt.config. (Items can be npm packages or local dirs)

export default {
  apps: [
    '~/shop'
  ]
}

Limitations

These are problems with current POC. We can progressively enhance this functionality and workaround these limitations.

Koc commented 5 years ago

Do we really need care of this? IMHO it easier create 2 separate apps with different Nuxt config, packages.json and build/deploy them independently. Otherwise we got big monolith which is hard to maintain.

pi0 commented 5 years ago

@Koc Exactly that's also my concern/preference to use separate projects. But I know monolith projects that may benefit from this feature to structure their project better. Also, we have the possibility to add parts using an NPM package. (Probably admin was not a good example tough!)

Koc commented 5 years ago

Also, we have the possibility to add parts using an NPM package.

For sharing some code between apps we can use monorepo approach, extract common business logic/api clients/etc into separate package and require it using path package or npm-link or tools like Rush

clarkdo commented 5 years ago

Agree with @Koc concern, we should consider more about this.

In my perspective, I would like leverage yarn workspace for mono repo, like:

├── cms
|  ├── pages
|  └── store
|  ├── nuxt.config.js
|  └── package.json
├── app
|  ├── middleware
|  ├── pages
|  ├── nuxt.config.js
|  └── package.json
└── package.json

For common app, we already provide components as a dir to put reusable stuff in:

├── pages
├── store
├── components
|  ├── cms
|  └── app
├── nuxt.config.js
└── package.json

If necessary, it would be better to use module or sth like extend to import reusable components or pages.

pi0 commented 5 years ago

@clarkdo I see your point about route conflicts. that's also mentioned in the notes to be supported

About mono-repo yes that's one solution for separating admin/cms I personally use (and also common/ dir for shared components/base config)

The Purpose of this enhancement is not having different parts (admin/cms/mobile) and not a good practice to use it for that. but splitting the main project into separate and reusable modules. The increasing number of pages for an enterprise project is unavoidable and there is no way to split it into smaller modules.

One other point is that client-side routing works across this sub-apps.

nicodevs commented 5 years ago

Yes, please! It would be great to have this feature.

bovas85 commented 5 years ago

I'd prefer this to losing the state on micro Frontends. Maybe pairing it with some best practices guidelines would make this more resilient

gangsthub commented 5 years ago

We've been experimenting this in a PoC at my company. We were able to inject sub-apps routes via Nuxt modules. And were proposing this for one of our products.

I love to see we are aligned, @pi0. Given it's a very specific case that most of Nuxt apps are not going to use, I hope it won't affect the size of their bundles.

@clarkdo is pointing fair concerns.

I think Nuxt core is mature enough to envision this but our current file structure isn't. Therefore a lot of things that come after (in how Nuxt wires up its parts) are coupled with this file structure.

Think about backend architectures. For example, the Hexagonal Architecture. If Nuxt core was really a core, it wouldn't care if there are 1 or 2 apps. Or 1 or 2 UI systems.

Replying to @clarkdo's points:

ghost commented 5 years ago

This keeps coming to my head. If there is no consensus, how about creating a module that ~tries to handle~ handles this without affecting the rest of the user's performance? cc/ @pi0 (❤️)

pi0 commented 5 years ago

@pm-LTK This does not affect the user's performance at all. All sub-apps are finally merged into the same data structure. We just glob multiple directories.

I also thought about the module, but that module should duplicate most of the logic of @nuxt/builder package which introduces inconsistencies. Please refer to related PR to have a better vision of this change.

HapLifeMan commented 5 years ago

It would be a VERY GREAT feature, in my case for multi-domains support 🙂 Especially to have different pages (components, assets and layouts would be shared between domains).

For instance (it is absolutely not for i18n but it's a good understandable example):

├── pages-com         <==== https://mydomain.com
|  ├── index.vue
|  ├── about.vue
|  └── contact.vue
├── pages-fr          <==== https://mydomain.fr
|  ├── index.vue
|  ├── about.vue
|  └── contact.vue
├── components
├── layouts
├── middleware
├── store
└── package.json

It would fit perfectly for:

blowsie commented 5 years ago

I just stumbled across this after making something similar. I created a themeable nuxt application. By extending the vue router.

https://codesandbox.io/s/my-app-9ythm

You set the theme path in the nuxt.config, which essentially could be an environment varible. The code then looks into your themes folder for said theme, and if a page is deinfed for it, use that instead.

That enables you to reuse any existing component code via extends. You can switch out styles/ templates / js as required.

simllll commented 5 years ago

We actually ran into something similar as our project got bigger. But we ended up using a monorepo, with several "seperate" nuxt projects, but all the nuxt projects use a shared component library (see a demo how we did that with nuxt: https://github.com/simllll/nuxt-library-demo) and a "module" library where we abstracted all the shared middleware and plugins. Therefore it's only business logic and pages / custom components that are in the nuxt projects. Furthermore one of the sub packages of our mono rep is a cordova mobile app which is using one of the nuxt projects and builds a SPA app out of it.

I would prefer keeping the code "stuctured" but "seperated". Which I'm unsure if this is the case if nuxt allows sub-apps. It's just one special use case that is covered with it (think about e.g. a storybook for your components, another cordova app, or something else people can come up with.. ;))

So for us, we have all freedom and all advantages combined with a monorep approach (which is also used in different projects and is commonly used everyhwere around ;)) and still have a very strickt seperation of code and logik, therefore I would vote for a better documentaton or CLI for a monorepo setup (with a component library or other templates in it) instead.

MartinMuzatko commented 4 years ago

See https://github.com/nuxt/nuxt.js/issues/5816 please

MuhaddiMu commented 4 years ago

Do you think below articles covers some parts of it? If no, what are the limitations? https://www.muhaddis.info/how-to-use-multiple-nuxt-js-applications-on-the-frontend/

MartinMuzatko commented 4 years ago

I explained at length that this does not cut it for me here: https://github.com/nuxt/nuxt.js/issues/5816#issuecomment-547880873

MuhaddiMu commented 4 years ago

I explained at length that this does not cut it for me here: nuxt/nuxt.js#5816 (comment)

I go through that I understand that you are looking for something else. Here I'm taking a general opinion the post

kytosai commented 4 years ago

I hope the development team nuxt will be able to offer a solution to this problem. It really is a matter of necessity..

asterd commented 4 years ago

Any news on this?

natoehv commented 4 years ago

what about https://podium-lib.io approach? I just create a vue csr podlet (podium) and it works fine, and now I want to create a nuxt podlet, but I'm issues to use podium as a nuxt midleware :(

hjardines commented 4 years ago

This is something that will bring NuxtJS to the next level. I'm working with the same intention trying to see the best solution, having this native would be amazing.

brophdawg11 commented 3 years ago

I have a semi-related use-case that might fall within this RFC, but happy to open a new issue or RFC for discussion if the concepts are different enough.

I'm trying to figure out if it's possible to hydrate 2 Nuxt micro-apps on the same page (i.e. a header and a footer when the entire page is not necessarily controlled by Vue/Nuxt). I've made some progress but I still have some more investigation to do into whether I can support this with existing hooks/modules or not.

We're currently doing this through a manual vue-server-renderer based application, but are looking to migrate to Nuxt and this seems to be the last hurdle we need to tackle. To keep this comment short, I won't go too deep into the implementation details, but I can put together a small POC repo with an example if that would be helpful.

Our use-case is a route-by-route migration of a large production site over to Vue SSR. Instead of a big-bang migration from our legacy architecture to Vue, we've been moving route-by-route. We have a small proxy that directs supported routes to the new vue-server-renderer-driven Vue SSR app, and unsupported routes to the legacy app. In order to maintain a consistent look and feel during the migration - we chose to export the header and footer Vue components as small consumable micro-apps that could be ingested by the legacy app.

The legacy server makes a call such as https://new-app-server/ui/header and gets back an HTML fragment and corresponding styles/scripts from the Vue app that it can inject into it's rendered HTML. In order to avoid duplicated styles/scripts we have query params that can tell the footer orchestration call to skip including <link>/<script> tags.

Then in the manual vue-server-renderer-driven app, we have a small bit of logic in entry-client.js that instead of just mounting to div#app, loops through looking for multiple mount-points based on data attributes and mounts them using the same Vuex store across the apps (since the header/footer share a Vue store in the main app). Sharing the Vuex store works seamlessly thankfully.

Routing poses an interesting challenge. In our case, we don't have any needs for client-side routing from the header or footer since the legacy app is not a SPA. There's a few potential options here:

I'm curious if this is something that this RFC might enable via Nuxt or that would be en entirely new RFC?

So far, I've gotten pretty close in Nuxt with 3 main changes:

So the last part that I'm investigating now is whether I have any access to tap into the hydration internals and hydrate multiple apps while sharing a Vuex store.

brophdawg11 commented 3 years ago

I took a quick stab at the above in https://github.com/nuxt/nuxt.js/pull/8554. Didn't seem to be able to get to the hydration internals through a module.

joaojuicee commented 3 years ago

This would be amazing, it's not the first time that a client requested a "monorepo" style project where in the same project he'd like to have the frontend but also an admin dashboard and later on decided that he wanted to deploy it as two separate parts. It would be amazing to be able to separate the bigger project into smaller projects with their own bundle

amithcabraal-borngroup commented 3 years ago

I have a semi-related use-case that might fall within this RFC, but happy to open a new issue or RFC for discussion if the concepts are different enough.

I'm trying to figure out if it's possible to hydrate 2 Nuxt micro-apps on the same page (i.e. a header and a footer when the entire page is not necessarily controlled by Vue/Nuxt). I've made some progress but I still have some more investigation to do into whether I can support this with existing hooks/modules or not.

We're currently doing this through a manual vue-server-renderer based application, but are looking to migrate to Nuxt and this seems to be the last hurdle we need to tackle. To keep this comment short, I won't go too deep into the implementation details, but I can put together a small POC repo with an example if that would be helpful.

Our use-case is a route-by-route migration of a large production site over to Vue SSR. Instead of a big-bang migration from our legacy architecture to Vue, we've been moving route-by-route. We have a small proxy that directs supported routes to the new vue-server-renderer-driven Vue SSR app, and unsupported routes to the legacy app. In order to maintain a consistent look and feel during the migration - we chose to export the header and footer Vue components as small consumable micro-apps that could be ingested by the legacy app.

The legacy server makes a call such as https://new-app-server/ui/header and gets back an HTML fragment and corresponding styles/scripts from the Vue app that it can inject into it's rendered HTML. In order to avoid duplicated styles/scripts we have query params that can tell the footer orchestration call to skip including <link>/<script> tags.

Then in the manual vue-server-renderer-driven app, we have a small bit of logic in entry-client.js that instead of just mounting to div#app, loops through looking for multiple mount-points based on data attributes and mounts them using the same Vuex store across the apps (since the header/footer share a Vue store in the main app). Sharing the Vuex store works seamlessly thankfully.

Routing poses an interesting challenge. In our case, we don't have any needs for client-side routing from the header or footer since the legacy app is not a SPA. There's a few potential options here:

  • Don't include a router, but can cause issues if the existing header/footer access this.$route/etc
  • Use a single route like { path: '*', component: HeaderComponent }, but that can conflict ith the normal Nuxt router entries and also have some weird side effects if the legacy app is doing any of its own pushState logic
  • At the moment, we've written a little stubbed OrchestratedRouter library that is effectively api-compatible with VueRouter but just no-op's on the majority of hooks. It's probably no the ideal solution but it has worked for our use-cases thus far.

I'm curious if this is something that this RFC might enable via Nuxt or that would be en entirely new RFC?

So far, I've gotten pretty close in Nuxt with 3 main changes:

  • A new layout that removes the app shell and only renders <Nuxt />
  • A pages/ui/_component.vue page that renders the proper component using render: h => h(componentMap(this.$route.params.component)
  • A root-level {% if (microApp} %} branch in app.html that eliminates the wrapper <html>/<body> markup and only renders styles/scripts and Vue SSR HTML

So the last part that I'm investigating now is whether I have any access to tap into the hydration internals and hydrate multiple apps while sharing a Vuex store.

Hi,

Did you ever continue with this ? A

MarioC3 commented 2 years ago

Hello guys! Great work so far. Quick question, is this still on the roadmap for Nuxt3?

pi0 commented 2 years ago

@MarioC3 Yes! Part of POC is in nuxt-extend which needs to be ported to nuxt kit + some refactors on nuxt3 core to fulfill requirements.

MarioC3 commented 2 years ago

@pi0 Awesome! nuxt-extend is great! Again, thanks for all the work! You guys rock!

brophdawg11 commented 2 years ago

@amithcabraal-borngroup Nothing beyond the quick POC in https://github.com/nuxt/nuxt.js/pull/8554 unfortunately

atinux commented 2 years ago

It's coming 👀

https://github.com/nuxt/nuxt.js/issues/13367

davodaslanifakor commented 1 year ago

This is my last work for implement this type of feature.

https://medium.com/@DavoudAslaniFakour/creating-a-nuxt-js-project-with-yarn-workspace-lerna-monorepo-in-3-steps-bdd379f4e0ba