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

Layers/Extends Support Tracker #13367

Open
16 of 18 tasks
pi0 opened this issue Feb 14, 2022 · 149 comments
Open
16 of 18 tasks

Layers/Extends Support Tracker #13367

pi0 opened this issue Feb 14, 2022 · 149 comments

Comments

@pi0
Copy link
Member

pi0 commented Feb 14, 2022

Nuxt layers are a powerful feature that you can use to share and reuse partial Nuxt applications within a monorepo, or from a git repository or npm package.

Docs:

This issue is used to track the progress of enhancements and known issues in the roadmap.

Features:

Bugs:

@Tahul
Copy link
Contributor

Tahul commented Mar 24, 2022

Hey there 👋

We've made a review of extends feature with @kevinmarrec so I can document it!

I would like to brainstorm few ideas with you, before beginning any changes.

Aside from these points, we've tried the extends feature together with @kevinmarrec and it went really well!

I will start creating a PR to document this with appropriate examples as you mentioned in the first message. 🙂

Extends configuration

With the current extends implementation, once you pass a path to be extended, Nuxt will try to grab every available feature from this extended directory.

It is really great to have such behavior as it makes extends a real plug & play feature, but having some flexibility over how we do extend our project would be really great.

What I suggest is to have a similar way to configure your extends as components does:

defineNuxtConfig({
   extends: [
      './base', // Would be fully extended
      {
            path: './ui',
            middleware: false, // Would skip middleware extending
            pages: false, // Would skip pages extending
            // All of the rest are set to `true` by default
        }
   ]
})

That would maybe include a PR to c12 package in order to support this.

Extends pages prefixing

One cool feature would be to be able to rewrite the base path of pages from an extend target.

Let's say you extend your project from a blog theme that has default pages exposed.

The blog theme might expose index pages and such from the root of its pages directory, so that would be overwritten by your own website.

What you might want then is to have these blog pages nested under a /blog prefix in your project.

We could use the previous configuration object to specify this:

defineNuxtConfig({
   extends: [
        './base', // Pages would be merged with project-level pages
        {
            path: './blog',
            pagesPrefix: '/blog' // Pages would be nested under `/blog` route prefix
        }
   ]
})

Low to high instead of high to low priority management

One thing we noticed during the review of extends is that currently the priority is handled in a high to low manner:

defineNuxtConfig({
   extends: [
        './ui', // This will override `./base`
        './base'
   ]
})

This is nitpicking but I think this is an opposite pattern of what we commonly expect from extends features.

For instance files like .eslintrc has this "last has highest precedence" as convention.

WDYT @danielroe @pi0 @Atinux @kevinmarrec ? 😄

@Atinux
Copy link
Member

Atinux commented Mar 24, 2022

Extends options

What about using the array syntax for it since the first argument is required?

defineNuxtConfig({
   extends: [
     './base', // Pages would be merged with project-level pages
     ['./blog', { pages: { prefix: '/blog' } }],
   ]
})

I am personally not a big fan of array syntax though.

If using the object syntax, I don't think that path is the best name since it can also be something like github:nuxt/docs-theme.

Low to hight makes sense actually, I cannot recall why we have the opposite order (Pooya may be able to answer)

@Atinux
Copy link
Member

Atinux commented Mar 24, 2022

What about using from key:

defineNuxtConfig({
   extends: [
     './base', // Pages would be merged with project-level pages
     {
        from: './blog',
        pages: { prefix: '/blog' },
        middleware: false
     }
   ]
})

@kevinmarrec
Copy link
Contributor

kevinmarrec commented Mar 24, 2022

For both above points, changes need to be applied to c12, so I'll let @pi0 handles that.
I will be able to take the lead on implementing extends options whenever c12 can ship the options of extends objects in the extended layers (I can think of adding the options here: https://github.com/unjs/c12/blob/main/src/loader.ts#L11 under a specific key).

@pi0
Copy link
Member Author

pi0 commented Mar 25, 2022

Thanks for the feedback @Tahul @Atinux @kevinmarrec 💚

Overriding by layer seems a good idea. I like both [from: string, override: T] and { from: string, override: T } syntaxes. Note that we cannot reuse the whole object syntax namespace for overriding. c12 might need it for additional source options in the future. (~> unjs/c12#9)

In order to disable scanning of a directory of a layer, I think we can reuse of dirs options with false value. dirs: { pages: false } and by extending each layer or from within layer itself, we can disable it.

Regarding prefixes, I made lots of thoughts since (nuxt/rfcs#30). Extend feature will be surely the basis for multi-app support but it is more complicated than you might imagine and too soon implemented in this stage. Usage of baseURL and <NuxtLink> in the prefixed layer will be broken when the layer is using path like /post/1 as pages/post/[id].vue and we prefix it with /blog.

Regarding the ordering convention, I guess we will keep it as is. defu@v6 is released to respect the same convention for joining arrays. Internally it makes implementation of multi-layer much more straightforward since we can simpy iterate and in next layers, ignore if a duplicate item exists (vs resolve and override with reverse manner). User wise, extends actually means extending by defaults of sublayers and visually, a higher layer overrides the ones below.

@Tahul
Copy link
Contributor

Tahul commented Mar 25, 2022

Hey, thank you a lot for your feedback @pi0 🙏

I noticed the existence of an additional file/directory inside Nuxt 3 structure: app/

I saw there here: nuxt/framework#3485 & here.

I think as these file/this directory contains data configuring the app, we should allow extending of this file aswell?

I'm using app/router.options.ts to extends the scrollBehavior from it.

@pi0
Copy link
Member Author

pi0 commented Mar 25, 2022

Sure thing. Makes sense for supporting app/ extension.

@fanckush
Copy link

@pi0 will /assets also be supported?

@pi0
Copy link
Member Author

pi0 commented May 20, 2022

will /assets also be supported?

It fall into named layer aliases. assets is basically a standard directory but we are not automatically scanning it. For layers, it would be ~layer/assets to access assets of each layer.

@dm-heinze
Copy link

Hey guys :) Really great feature!
Is there a possibility to extend the configs via module?

I already tried this, but it doesn't work. I guess because the config is already loaded at the time my module setup is processed:

async setup (options, nuxt) {
    const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url))
    nuxt.options.extends = runtimeDir
}

@pi0
Copy link
Member Author

pi0 commented Jun 9, 2022

@dm-heinze You can load and extend options in modules by modifying nuxt.options as before within the module but you have to take care of normalization as they are already done when we reach modules.

If I correctly get your idea, you want to make it easier for modules by simply following Nuxt convention for runtime dir right? That's something we can definitely work on! (Only note that we can extend by directory structure and not an additional runtime/nuxt.config.)

@sawden
Copy link

sawden commented Jul 14, 2022

Can the extend feature be used for different themes that change some parts of the application layout?

@pi0
Copy link
Member Author

pi0 commented Jul 14, 2022

Can you please explain more @sawden? Two themes can provide or override composables and components of course used in layout.

@sawden
Copy link

sawden commented Jul 14, 2022

@pi0
I want to build an app that has its own theme for each customer. The functionality is similar for all of them and there are only small differences such as more filter options or a different way of displaying lists, for example.

I designed a small example to show what I mean:

example app1

@AndriiGera
Copy link

While extending nuxt apps is a really nice feature and active improvement looks promising, still one use case seems to be missing - the ability to combine mutiple nuxt layers into a multiapp.
For example, I have an app Main and N number of custom themes that only override css and optionally few components/layouts of Main.
I would like to have a single Nuxt app that will extend Main and all Theme layers:
Nuxt Layers drawio

and will somehow output every theme to a specific subfolder in .output.

Currently, with the help of layers I can extend Main app with N amount of Themes and have them all as separate Nuxt instances which is not very scalable in terms of build process and hosting as the amount of themes can increase.

@pi0
Copy link
Member Author

pi0 commented Nov 10, 2023

@AndriiGera Multi-app via multi-build is of course doable. Each app can extend from shared base theme(s). Of course, you should be responsible for handling cross-app routing, state preservation, and duplicate code in the bundle (vue is bundled in each app). We can configure nitro to also scope .outout dir for each.

It is not original purpose of multi-app via layers (we prefer single build with everything including style and layouts fully scoped) but if you like to followup with documenting another approach, feel free to make a new discussion/issue and ping me 👍🏼 (we might also come with a community module/CLI helper for this architecture. Nuxt core already is ready)

@akasection
Copy link

So I have a question regarding definePageMeta and layers. In my company, we are trying to adopt layers as installable features so different departments can create whole set of feature and then with some configuration via bundled modules, they can pass and alter the feature to suit the consumers.

But now we have stumbled upon definePageMeta and the correct way to override the layer pages meta from the consumer app. Let's say feature-a (published as npm package) is installed into main product-app, but we want to apply middleware into all feature-a pages and also alter the page URL so it resides to feature-alpha instead of feature-a in the product-app. Now, since definePageMeta is a compiler macro, we can't just make "props" for the page.

In the end, we have three kinds of approaches that are used across our company. I have summarized the way to do it in this CodeSandbox: https://codesandbox.io/p/sandbox/nuxt-layer-pagemeta-example-7wws6l

In summary, the methods are:

  1. Method A: Use pages:extend hook to alter the page property
  2. Method B: Use AppConfig and anonymous middleware
  3. Method C: Re-template the vue page file, "insert" new definePageMeta as string

The question is, what is the proper way to do this kind of thing? What's the recommended approach to alter layer parts from consumer app without making global object/function that intercept/checking things?

@SunSi12138
Copy link

when I install module and package in base layer, the second layer won't run and throws this exception :

` yarn run dev
yarn run v1.22.19
$ nuxt dev
Nuxt 3.8.2 with Nitro 2.8.1 14:47:00
14:47:00
➜ Local: https://localhost:3000/
➜ Network: use --host to expose

[14:47:01] ERROR Error while requiring module @pinia/nuxt: Error: Cannot find module 'F:/Project/App/@pinia/nuxt'
Require stack:

  • F:\Project\App\index.js

[14:47:01] ERROR Cannot start nuxt: Cannot find module 'F:/Project/App/@pinia/nuxt'
Require stack:

  • F:\Project\App\index.js

    Require stack:

    • index.js
      at Module._resolveFilename (node:internal/modules/cjs/loader:1075:15)
      at Function.resolve (node:internal/modules/cjs/helpers:116:19)
      at Function._resolve [as resolve] (node_modules\jiti\dist\jiti.js:1:251148)
      at resolveModule (/F:/Project/App/node_modules/@nuxt/kit/dist/index.mjs:2239:29)
      at requireModule (/F:/Project/App/node_modules/@nuxt/kit/dist/index.mjs:2244:24)
      at loadNuxtModuleInstance (/F:/Project/App/node_modules/@nuxt/kit/dist/index.mjs:2472:90)
      at async installModule (/F:/Project/App/node_modules/@nuxt/kit/dist/index.mjs:2438:47)
      at async initNuxt (/F:/Project/App/node_modules/nuxt/dist/index.mjs:3702:7)
      at async NuxtDevServer._load (/F:/Project/App/node_modules/nuxi/dist/chunks/dev2.mjs:253:5)
      at async NuxtDevServer.load (/F:/Project/App/node_modules/nuxi/dist/chunks/dev2.mjs:185:7)`

    and I have to reinstall it on layer two

    any suggestions?

@ojvribeiro
Copy link
Contributor

@SunSi12138 dependencies are still not auto-installed from layer. You need to install all your layer dependencies manually on your project.

The Nuxt team is working on an auto-install for layers deps and it's being tracked here: unjs/c12#51

@Arthur-Chevron
Copy link

HI, I have three layers, one is for UI, one is for LOGIC and the last is for my main app.
I am configuring all my types for my components in the repo UI but i need to have access in my main app too.
What is the best way to deal with it.

@kvanska
Copy link

kvanska commented Dec 14, 2023

css files from base layer are added to the html head after the project css files.

please see: stackblitz.com

nuxt.config.ts:

export default defineNuxtConfig({
  devtools: { enabled: false },
  css: ['~/assets/css/project.css'],
  extends: ['./layers/base'],
});

base layer nuxt.config:

import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

const currentDir = dirname(fileURLToPath(import.meta.url));

export default defineNuxtConfig({
  css: [join(currentDir, './assets/css/base.css')],
});

in the website source:

<head>
<link rel="stylesheet" href="/_nuxt/assets/css/project.css">
<link rel="stylesheet" href="/_nuxt/layers/base/assets/css/base.css">
</head>

project.css should clearly be after base.css file(s) so that project can overwrite styles?

@hamedfaryabi
Copy link

How can I import a component?

I have a component called ElementsIcon in my layer and I want to use this component in the h() function in my project. Like the following code:

const icon = h(ElementsIcon, {
    name: 'dismiss',
    size: '20px',
    'v-tooltip': { content: __('cancel order'), placement: 'bottom', distance: 12 },
	onClick: () => {
	onCancelOrder(row.id);
    }
});

But I don't know how to import this component

@enkot
Copy link
Contributor

enkot commented Jan 6, 2024

@hamedfaryabi You can import it from #components:

import { ElementsIcon } from '#components'

But better to ask questions here https://github.com/nuxt/nuxt/discussions/categories/questions or in the Discord

@productdevbook
Copy link
Sponsor Member

stores or newFolder How can I add a customized folder ?

@ojvribeiro
Copy link
Contributor

Now that unjs/c12#51 is closed, I'm so hyped for it to land on Nuxt. ♥

@pi0
Copy link
Member Author

pi0 commented Jan 21, 2024

Now that unjs/c12#51 is closed, I'm so hyped for it to land on Nuxt. ♥

You can already do this!

{
  extends: [ 
    ["foobar", { install: true }]
  ]
}

This is opt-in behavior until a while we can test all edge cases and optimize installation speeds.

@KeJunMao
Copy link
Contributor

How do I overwrite layer plugins in my app?

Currently, they are both valid, and even if the plugin name is specified, it cannot be overwritten

map-app
  plugins
    setup.base.ts

base-layer
  plugins
    setup.base.ts

@baixiaoyu2997
Copy link
Contributor

I want my base layer to run independently and be testable,so i need add "extends": ["./.nuxt/tsconfig.json"] to tsconfig.json in base layer, but this causes a problem, I need to run prepare everywhere, hope this process can be simplified
image
image

@madsh93
Copy link

madsh93 commented Jan 25, 2024

@baixiaoyu2997 I think this is a bug that was introduced in 3.9.0. Could you try in 3.8.3?

@jofmi
Copy link

jofmi commented Jan 25, 2024

I have a question regarding layers and typescript.

If I put the following in layer1/index.d.ts:

declare global {
  interface MyType {
    attr1: string
  }
}

export {};

And the following in layer2/index.d.ts:

declare global {
  interface MyType {
    attr2: string
  }
}

export {};

If layer2 extends layer1, typescript in layer2 will then interpret the following combined type:

interface MyType {
    attr1: string
    attr2: string
}

I find this incredibly useful, but wanted to ask whether this is intended behavior or whether I am using a weird hack - and whether there is a danger that this will stop working in the future?

@trc-mathieu
Copy link

I don't think it's a weird hack. If you look inside the .nuxt/tsconfig.json, you will find under the include section your globs that point to your layers. Typescript will then be able to do declaration merging on everything included from these globs from your layers.

@kjfranke
Copy link

@pi0 / @danielroe I'm not sure what the progress is on "Named layer aliases", I created a Nuxt module that creates an alias per layer and made ~,@, etc aliases fall back from project down to each layer. I'm not sure if every case is covered but it works for our use cases. If you are interested, I'm happy to share. But I do belief this feature should exist native in Nuxt instead of a separate module.

@odranoelBR
Copy link

Hi @kjfranke , can you share it ?

@v0id-4lpz
Copy link

#26634

@mts-pac
Copy link

mts-pac commented Apr 8, 2024

css files from base layer are added to the html head after the project css files.

please see: stackblitz.com

nuxt.config.ts:

export default defineNuxtConfig({
  devtools: { enabled: false },
  css: ['~/assets/css/project.css'],
  extends: ['./layers/base'],
});

base layer nuxt.config:

import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

const currentDir = dirname(fileURLToPath(import.meta.url));

export default defineNuxtConfig({
  css: [join(currentDir, './assets/css/base.css')],
});

in the website source:

<head>
<link rel="stylesheet" href="/_nuxt/assets/css/project.css">
<link rel="stylesheet" href="/_nuxt/layers/base/assets/css/base.css">
</head>

project.css should clearly be after base.css file(s) so that project can overwrite styles?

Any solution?

@Brads3290
Copy link

Layers do not seem to install dev dependencies correctly.

I created #26980 for this, and have now found a few other cases where people are saying that #19124 is still present (here, here, and here), and all of those cases are specifically dev dependencies. Runtime dependencies seem to work fine.

Not sure what the repercussions would be of indiscriminately installing all dev dependencies from layers, but it does seem important that Nuxt modules at the very least are installed (@nuxt/tailwindcss, @nuxt/ui, for example).

@adamdehaven
Copy link
Contributor

Layers do not seem to install dev dependencies correctly.

I commented on the issue you created. This seems like expected behavior: #26980 (comment)

@Brads3290
Copy link

Yeah, I think you're right that at the moment it is expected behaviour - I shouldn't have called it a bug.

However, I think it needs discussion, because right now it seems installing modules in a layer will make that layer unusable. I suspect this happens because:

  1. The module installs as a dev dependency (this happens automatically when added via npx nuxi@latest module add <module>)
  2. The main app must look at the layer's "modules": [...] key in nuxt.config.ts, and expect to find the specified modules installed
  3. Because the module was installed as a dev dependency, and dev dependencies aren't inherited, it falls over (can no longer npm install or run the Nuxt server from the main app)

@yuriystasiv
Copy link

css files from base layer are added to the html head after the project css files.
please see: stackblitz.com
nuxt.config.ts:

export default defineNuxtConfig({
  devtools: { enabled: false },
  css: ['~/assets/css/project.css'],
  extends: ['./layers/base'],
});

base layer nuxt.config:

import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

const currentDir = dirname(fileURLToPath(import.meta.url));

export default defineNuxtConfig({
  css: [join(currentDir, './assets/css/base.css')],
});

in the website source:

<head>
<link rel="stylesheet" href="/_nuxt/assets/css/project.css">
<link rel="stylesheet" href="/_nuxt/layers/base/assets/css/base.css">
</head>

project.css should clearly be after base.css file(s) so that project can overwrite styles?

Any solution?

I was able to have it done, by importing css for base layer in nuxt.config.ts:

import { createResolver } from '@nuxt/kit';

const { resolve } = createResolver(import.meta.url);

export default defineNuxtConfig({
  alias: { '#base': resolve('./') },
  css: ['#base/assets/css/base.css'],
});

and next instead of importing css in nuxt.config.ts, import inside app.vue script or style tag:

<script>
// this works
import '~/assets/css/project.css';
</script>

<!-- this should also work -->
<!-- <style>
@import '~/assets/css/project.css';
</style> -->

Please see: https://stackblitz.com/edit/nuxt-starter-tcptzx?file=app.vue

@yuriystasiv
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
Development

No branches or pull requests