GrabarzUndPartner / nuxt-custom-elements

Publish your components as a custom-element standalone build.
https://nuxt-custom-elements.grabarzundpartner.dev
MIT License
77 stars 9 forks source link

[Progress] `nuxt@3` support #433

Open ThornWalli opened 2 years ago

ThornWalli commented 2 years ago

⚠️ Currently, there is no backward compatibility with nuxt@2 oder bridge when updating.

Nuxt 3 version is available πŸŽ‰

Install

npm i nuxt-custom-elements@beta

Todos

Features

Problems?

maximepvrt commented 1 year ago

do you have any idea when the nuxt 3 version will be available ?

advanceCabbage commented 1 year ago

Excuse me, when will nuxt-custom-elements support nuxt3? Is there a tutorial?

pperzyna commented 1 year ago

+1

maximepvrt commented 1 year ago

I created an issue (https://github.com/nuxt/nuxt/issues/15584) to request native support by nuxt

ThornWalli commented 1 year ago

Hello All (@maximepvrt @pperzyna )

On the beta branch or package (nuxt-custom-elements@beta) is now a build that works with nuxt@3 (bridge is not supported and nuxt@2 is dropped).

Very fresh, so still experimental.

Basically the state already produces an expected result. But now I have to work off some more todos.

Techbinator commented 1 year ago

Hey, Thank you very much for all the hard work. I gave it a shot and installed on a fresh nuxt 3(3.2.2) installation and after i npm install --save nuxt-custom-elements@beta and include it in the nuxt-config.ts in the modules array i get:

Nuxi 3.2.2                                                                                                                   14:41:21

 WARN  Changing NODE_ENV from development to production, to avoid unintended behavior.                                       14:41:21

Nuxt 3.2.2 with Nitro 2.2.2                                                                                                  14:41:21

 ERROR  Error while requiring module nuxt-custom-elements: Error: Cannot find module './module.json'                         14:41:21
Require stack:
- /Users/tudorfilipovici/workspace/pocmonorepo/v1/node_modules/nuxt-custom-elements/dist/module.cjs

 ERROR  Cannot find module './module.json'                                                                                   14:41:21
Require stack:
- /Users/xxx/workspace/pocmonorepo/v1/node_modules/nuxt-custom-elements/dist/module.cjs

  Require stack:
  - /Users/xxxxx/workspace/pocmonorepo/v1/node_modules/nuxt-custom-elements/dist/module.cjs
  at Module._resolveFilename (node:internal/modules/cjs/loader:1060:15)
  at Function.resolve (node:internal/modules/helpers:118:19)
  at _resolve (/Users/xxxx/workspace/pocmonorepo/v1/node_modules/jiti/dist/jiti.js:1:240719)
  at jiti (/Users/xxxx/workspace/pocmonorepo/v1/node_modules/jiti/dist/jiti.js:1:242883)
  at /Users/xxx/workspace/pocmonorepo/v1/node_modules/nuxt-custom-elements/dist/module.cjs:4:37
  at jiti (/Users/xxx/workspace/pocmonorepo/v1/node_modules/jiti/dist/jiti.js:1:245150)
  at requireModule (/Users/xxxx/workspace/pocmonorepo/v1/node_modules/@nuxt/kit/dist/index.mjs:286:26)
  at normalizeModule (/Users/xxx/workspace/pocmonorepo/v1/node_modules/@nuxt/kit/dist/index.mjs:454:55)
  at installModule (/Users/xxx/workspace/pocmonorepo/v1/node_modules/@nuxt/kit/dist/index.mjs:434:47)
  at initNuxt (/Users/xxxx/workspace/pocmonorepo/v1/node_modules/nuxt/dist/index.mjs:2253:13)
  at async loadNuxt (/Users/xxxx/workspace/pocmonorepo/v1/node_modules/nuxt/dist/index.mjs:2286:5)
  at async loadNuxt (/Users/xxxx/workspace/pocmonorepo/v1/node_modules/@nuxt/kit/dist/index.mjs:522:19)
  at async Object.invoke (/Users/xxxx/workspace/pocmonorepo/v1/node_modules/nuxi/dist/chunks/build.mjs:34:18)
  at async _main (/Users/xxxx/workspace/pocmonorepo/v1/node_modules/nuxi/dist/cli.mjs:51:20)

npm ERR! Lifecycle script `build` failed with error: 
npm ERR! Error: command failed 
npm ERR!   in workspace: v1-ted-shared-frontend@0.0.0 
npm ERR!   at location: /Users/xxxx/workspace/pocmonorepo/v1/packages/shared/frontend 

Any help or pointers would be greatly appreciated.

ThornWalli commented 1 year ago

Hello @Techbinator,

can try again, for whatever reason @nuxt/module-builder per npx installed had no JSON files...

Now the release is generated with the project dependencies.

Before: image

After: image

Techbinator commented 1 year ago

Hello @Techbinator,

can try again, for whatever reason @nuxt/module-builder per npx installed had no JSON files...

Now the release is generated with the project dependencies.

Before: image

After: image

works great now. Thanks a lot for the quick fix

maximepvrt commented 1 year ago

impossible to run the nuxt application with default builder (@nuxt/vite-builder) if the webpack dependencie is not added (@nuxt/webpack-builder)

[16:40:42]  ERROR  Cannot restart nuxt:  Cannot find package 'webpack' imported from /Users/maxime/Repos/benevolt-front-nuxt/node_modules/nuxt-custom-elements/dist/module.mjs

nuxt 3.4.2

maximepvrt commented 1 year ago

How to test custom element on dev ?

If I build the project, the generated client for the custom element is correct (but not exposed in the public path) Capture d’écran 2023-04-26 aΜ€ 17 27 32

maximepvrt commented 1 year ago

@ThornWalli πŸ˜‡

ThornWalli commented 1 year ago

Hello @maximepvrt,

  1. I will look at the build dist later.
  2. @nuxt/webpack-builder is out now, import this dynamically.
  3. dev mode I don't really have a solution for vue@3 yet. Main problem here is the CSS in the shadow component is missing, this would be a vite setting I make during build, but would be negative for a Nuxt project.

Import via entry definition

image

image

image

ThornWalli commented 1 year ago

@maximepvrt The generated files are now also in the public path with a build or generate.

https://www.npmjs.com/package/nuxt-custom-elements/v/2.0.0-beta.12

image

maximepvrt commented 1 year ago

Perfect ! but an error is generated if I build the project with a dev import via entry definition

Capture d’écran 2023-05-18 aΜ€ 18 25 07
ThornWalli commented 1 year ago

@maximepvrt Did you take this over?

image

Is a placeholder from the IDE, if so best remove again. Would be the place of the error.

Alternatively you could share the nuxt.config πŸ™‚

zackspear commented 1 year ago

Hey @ThornWalli, I'm a bit stuck and hoping you could help.

Everything seems to be working except for styles not being included with the web component. I'm not sure if this is a bug or if I'm missing something with my setup.

Styles are missing in two instances

  1. local development with nuxt dev in app.vue

    • Screenshot 2023-05-19 at 13 02 01
  2. nuxt generate then serving the generated HTML file with the web component

    • npm i -g serve
    • serve dist/nuxt-custom-elements/example
    • Screenshot 2023-05-19 at 13 02 34

For a bit of background we're not necessarily building a full scale app with Nuxt and then using certain components as web components. More so using Nuxt as an opinionated framework to build Vue based web components that will be used in a legacy non-Vue website. I say this as I saw your comment of

Main problem here is the CSS in the shadow component is missing, this would be a vite setting I make during build, but would be negative for a Nuxt project.

and was wondering what setting this would be. As it could be the potential solution to my issue.

My project file snippets are below.

Thank you in advance πŸ™

package.json

{
  "name": "nuxt-app",
  "private": true,
  "scripts": {
    "build": "nuxt build",
    "dev": "nuxt dev",
    "generate": "nuxt generate",
    "preview": "nuxt preview",
    "postinstall": "nuxt prepare"
  },
  "devDependencies": {
    "@nuxtjs/tailwindcss": "^6.7.0",
    "@types/node": "^18",
    "nuxt": "^3.5.0"
  },
  "dependencies": {
    "@pinia/nuxt": "^0.4.10",
    "nuxt-custom-elements": "^2.0.0-beta.12",
    "pinia": "^2.0.36"
  },
  "overrides": {
    "vue": "latest"
  }
}

nuxt.config.ts

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  ssr: false,
  target: 'static',
  devServer: {
    port: 4321,
  },
  modules: [
    '@nuxtjs/tailwindcss',
    '@pinia/nuxt',
    'nuxt-custom-elements',
  ],
  customElements: {
    entries: [
      {
        name: 'Example',
        tags: [
          {
            name: 'CustomElementExample',
            path: '@/components/Example',
            options: {
              props: {
                exampleTitle: 'Nuxt Config Prop Title',
              },
            },
            slotContent: 'Hello from the Nuxt Config!',
          },
        ],
      },
   ],
 },
});

Example.vue

<script lang="ts" setup>
export interface Props {
  exampleTitle?: string
}

withDefaults(defineProps<Props>(), {
  exampleTitle: 'Default example title',
});

console.log('hello world 00');

onBeforeMount(() => {
  console.log('hello world 01');
});
</script>

<template>
  <div class="text-white bg-zinc-700 flex flex-col p-4 rounded-lg">
    <div class="italic">
      {{ exampleTitle }}
    </div>
    <div class="text-zinc-200">
      <slot>Default Content</slot>
    </div>
  </div>
</template>

app.vue using for development purposes

<script lang="ts" setup>
const nuxtApp = useNuxtApp();

onBeforeMount(() => {
  nuxtApp.$customElements.registerEntry('example');
});
</script>

<template>
  <div>
    <client-only>
      <div class="grid grid-cols-3 gap-6 p-6">
        <Example example-title="Vue Component">
          Vue component slot content
        </Example>
        <custom-element-example example-title="Web component title">
          Web component slot content
        </custom-element-example>
      </div>
    </client-only>
  </div>
</template>
ThornWalli commented 1 year ago

Hello @zackspear,

Case 1

  1. tailwindCSS is not taken over because shadow component does not take over the global css.

  2. currently the components in dev mode have no styling as they are included as shadow component and the vite or vue-loader configuration is not changed in nuxt mode.

    Therefore the components can only be used in dev mode if they are imported correctly.

    You could try to make an import with .ce.vue, so the CSS should be there in dev mode in the shadow. https://vuejs.org/guide/extras/web-components.html#sfc-as-custom-element

    But then you can't import this component normally.

Case 2

You need to import tailwindCSS in your component. See example of Nuxt 2 variant https://github.com/GrabarzUndPartner/nuxt-custom-elements-example/blob/87e72fd17f12a14d0247c606113851b866dd9794/examples/tailwind-css/entries/TailwindCssShadow.vue#L8

It must always be remembered that the entries are standalone vue component builds that do not take anything from nuxt.

And please consider this case: https://github.com/vuejs/core/issues/4662


If a style tag is included in the entry, it will also be included in the generate.

https://github.com/GrabarzUndPartner/nuxt-custom-elements/blob/main/example/components/Example.vue

image

zackspear commented 1 year ago

@ThornWalli thank you so much! πŸ™ I have a working example web component with styles.

I changed Example.vue to Example.ce.vue and updated it's path in nuxt.config.ts to include .ce.

Then for Example.ce.vue within <script lang="ts" setup> I added import 'tailwindcss/tailwind.css'; and also added to the style tags with the following:

<style lang="postcss">
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>

Then within app.vue I had to update the Vue component from <Example /> to <ExampleCe />. Didn't have to manually import the component.

Really appreciate the tips. I knew I was close.

Here's my full files for those that may find this later.

nuxt.config.ts

export default defineNuxtConfig({
  ssr: false,
  target: 'static',
  devServer: {
    port: 4321,
  },
  modules: [
    '@nuxtjs/tailwindcss',
    '@pinia/nuxt',
    'nuxt-custom-elements',
  ],
  customElements: {
    entries: [
      {
        name: 'Example',
        tags: [
          {
            name: 'CustomElementExample',
            path: '@/components/Example.ce',
            options: {
              props: {
                exampleTitle: 'Nuxt Config Prop Title',
              },
            },
            slotContent: 'Hello from the Nuxt Config!',
          },
        ],
      },
   ],
 },
});

Example.ce.vue

<script lang="ts" setup>
import 'tailwindcss/tailwind.css';

export interface Props {
  exampleTitle?: string
}

withDefaults(defineProps<Props>(), {
  exampleTitle: 'Default example title',
});

console.log('hello world 00');

onBeforeMount(() => {
  console.log('hello world 01');
});
</script>

<template>
  <div class="text-white bg-zinc-700 flex flex-col p-4 rounded-lg">
    <h1 class="italic">
      {{ exampleTitle }}
    </h1>
    <div class="text-zinc-200">
      <slot>Default Content</slot>
    </div>
  </div>
</template>

<style lang="postcss">
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>

app.vue

<script lang="ts" setup>
const nuxtApp = useNuxtApp();

onBeforeMount(() => {
  nuxtApp.$customElements.registerEntry('example');
});
</script>

<template>
  <div class="bg-gray-200">
    <client-only>
      <div class="grid grid-cols-3 gap-6 p-6">
        <ExampleCe example-title="Vue Component">
          Vue component slot content
        </ExampleCe>
        <custom-element-example example-title="Web component title">
          Web component slot content
        </custom-element-example>
      </div>
    </client-only>
  </div>
</template>

Screenshot 2023-05-22 at 11 12 09

ThornWalli commented 1 year ago

@zackspear Very good πŸ™‚

I consider times whether one does not need the .ce.vue as a user in the Dev mode. Good would be a container with .ce.vue, which imports the specified components. Would be conceivable on the basis of the configuration.

It is important that this issue about child style is clarified. As long as it is not really usable for more complex projects.

zackspear commented 1 year ago

@ThornWalli unfortunately having a different, unrelated issue now. Not sure if it's a bug or something that I'm doing wrong.

I created another component which is intended to be a web component. But in both development and in the generated Nuxt output the second web component is rendering as the first web component.

I made my second test component, Tester.ce, have a red background and slightly different content – different prop and no <slot>. But as you can see in the screenshot the web component version of Tester is showing as the first web component called Example.

Screenshot of nuxt dev Screenshot 2023-05-22 at 15 14 28

And a screenshot of nuxt generate and viewing it with serve dist/nuxt-custom-elements/connect-components Screenshot 2023-05-22 at 15 19 22

nuxt.config.ts

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  ssr: false,
  target: 'static',
  devServer: {
    port: 4321,
  },
  modules: [
    '@vueuse/nuxt',
    '@pinia/nuxt',
    '@nuxtjs/tailwindcss',
    'nuxt-custom-elements',
  ],
  customElements: {
    entries: [
      {
        name: 'ConnectComponents',
        tags: [
          {
            name: 'ConnectExample',
            path: '@/components/Example.ce',
            options: {
              props: {
                heading: 'Example Nuxt Config Prop Title',
              },
            },
            slotContent: 'Hello Example from the Nuxt Config!',
          },
          {
            name: 'ConnectTester',
            path: '@/components/Tester.ce',
            options: {
              props: {
                copy: 'Tester copy from Nuxt config',
              },
            },
          },
        ],
      },
   ],
 },
});

Example.ce.vue

<script lang="ts" setup>
import 'tailwindcss/tailwind.css';

export interface Props {
  heading?: string
}

withDefaults(defineProps<Props>(), {
  heading: 'Default example heading',
});

const { x, y } = useMouse();
</script>

<template>
  <div class="text-white bg-zinc-700 flex flex-col p-4 rounded-lg">
    <h1 class="italic">
      {{ heading }}
    </h1>
    <p>Mouse coordinates: {{ x }}, {{ y }}</p>
    <div class="text-zinc-200">
      <slot>Default example content</slot>
    </div>
  </div>
</template>

<style lang="postcss">
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>

Tester.ce.vue

<script lang="ts" setup>
import 'tailwindcss/tailwind.css';

export interface Props {
  copy?: string
}

withDefaults(defineProps<Props>(), {
  copy: 'Default tester copy',
});

const { x, y } = useMouse();
</script>

<template>
  <div class="text-white bg-red-700 flex flex-col p-4 rounded-lg">
    <h1 class="italic">Tester component</h1>
    <h2>Mouse coordinates: {{ x }}, {{ y }}</h2>
    <p class="text-gray-300">
      {{ copy }}
    </p>
  </div>
</template>

<style lang="postcss">
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>

app.vue

<script lang="ts" setup>
const nuxtApp = useNuxtApp();

onBeforeMount(() => {
  nuxtApp.$customElements.registerEntry('ConnectComponents');
});
</script>

<template>
  <div class="max-w-3xl mx-auto bg-gray-200">
    <client-only>
      <div class="grid grid-cols-2 gap-6 p-6">
        <ExampleCe heading="Example Vue Component">
          Example Vue component slot content
        </ExampleCe>
        <connect-example heading="Example web component title">
          Example web component slot content
        </connect-example>

        <TesterCe heading="Tester Vue Component" />
        <connect-tester copy="Tester web component title"></connect-tester>
      </div>
    </client-only>
  </div>
</template>

Now if I switch the order of the tag objects in nuxt.config.ts it results in the same thing but now with the Tester component being in place of Example.

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  ssr: false,
  target: 'static',
  devServer: {
    port: 4321,
  },
  modules: [
    '@vueuse/nuxt',
    '@pinia/nuxt',
    '@nuxtjs/tailwindcss',
    'nuxt-custom-elements',
  ],
  customElements: {
    entries: [
      {
        name: 'ConnectComponents',
        tags: [
          {
            name: 'ConnectTester',
            path: '@/components/Tester.ce',
            options: {
              props: {
                copy: 'Tester copy from Nuxt config',
              },
            },
          },
          {
            name: 'ConnectExample',
            path: '@/components/Example.ce',
            options: {
              props: {
                heading: 'Example Nuxt Config Prop Title',
              },
            },
            slotContent: 'Hello Example from the Nuxt Config!',
          },
        ],
      },
   ],
 },
});

Screenshot 2023-05-22 at 15 47 07

Thanks again in advance.


Edit: It looks like defineTags in your src/runtime/tmpl/entry.mjsfile does not loop and iterate properly.

I was looking through my-example-project/.nuxt/nuxt-custom-elements/entries/connect-components.client.mjs after running nuxt generate and see this


import { defineAsyncComponent, defineCustomElement } from 'vue'

import Component0 from '@/components/Example.ce';
import Component1 from '@/components/Tester.ce';

const defineTags = () => {
  const elements = [
    ['connect-example', (typeof Component0 === 'function' ? (new Component0).$options : Component0)],
    ['connect-tester', (typeof Component0 === 'function' ? (new Component0).$options : Component0)]
  ].forEach(([name, component]) => {
    const CustomElement = defineCustomElement(component);
    window.customElements.define(name, CustomElement);
  })
};

const setup = () => {
  defineTags();
};

setup();

Component1 is never used. Instead Component0 is used for each tag.


Edit2: I created a separate issue for this #521 and was able to figure out the fix in #522

petrenslavik commented 1 year ago

@ThornWalli After running nuxt build what is the path to my entry? image This is what I got. And I can reference the Formsentry only on a path: http://localhost:3000/_nuxt/forms.client.755bccec.js. ~Moreover, the server doesn't return CORS headers, so when I include this script on a website that will use these components I'm getting a CORS error.~ I used nuxt routeRules to configure cors

GarethBeddis-VoxlyDigital commented 1 year ago

Hi @ThornWalli,

I'm looking to embed my entire Nuxt project as a Web Component on an external site, I've achieved this in a standard Vue 3 project using custom elements by bundling a single app.js file and adding <my-custom-element></my-custom-element> to the page.

However, when using Nuxt features (e.g. layouts, links, routes) in a custom element component using the above examples I run into an error:

example.client-c6046f55.js:1 TypeError: Cannot read properties of null (reading '$nuxt')

The below code works when removing the route snippets:

// Example.vue
<template>
  <div class="flex flex-col p-4 text-white rounded-lg bg-zinc-700">
    <div class="italic">
      {{ exampleTitle }}
    </div>
    <div class="text-zinc-200">
      <slot>Default Content</slot>
    </div>
    <div>
      {{ route }}
    </div>
  </div>
</template>

<script lang="ts" setup>
  const route = useRoute();

  export interface Props {
    exampleTitle?: string;
  }

  withDefaults(defineProps<Props>(), {
    exampleTitle: "Default example title",
  });

  console.log("hello world 00");

  onBeforeMount(() => {
    console.log("hello world 01");
  });
</script>

Is this possible with Nuxt? Any help is much appreciated.

ThornWalli commented 1 year ago

Hello @GarethBeddis-VoxlyDigital ,

You can't apply nuxt internal things in the created custom elements. Each entry is a separate Vue app.

If you want to use a router or pinia (store), you have to register it in the entry (custom element).

Example from nuxt@2 version: https://github.com/GrabarzUndPartner/nuxt-custom-elements-example/tree/main/examples

And very important, keep this issue in mind: https://github.com/vuejs/core/issues/4662

zackspear commented 10 months ago

Hi @ThornWalli, I just updated to beta17 and it seems like this introduced a bug.

If options is not set for each custom element in nuxt.config.ts the build will not work.

https://github.com/GrabarzUndPartner/nuxt-custom-elements/commit/94f2d5d4bee451685e95b935934919f8d2e6e34f#diff-90ec0da933dd5a8e979aebf86e0c9a67e3947fd122915053432dd14935b935afR9-R13

I believe the else statement there is causing this.

As this then doesn't allow the OR operator to work here: https://github.com/GrabarzUndPartner/nuxt-custom-elements/commit/94f2d5d4bee451685e95b935934919f8d2e6e34f#diff-90ec0da933dd5a8e979aebf86e0c9a67e3947fd122915053432dd14935b935afR19

This config worked with beta16 but not beta17

export default defineNuxtConfig({
  // … rest of config
  customElements: {
    entries: [
      {
        name: 'OurComponents',
        tags: [
          // … removed other components for brevity
          {
            name: 'OurWanIpCheck',
            path: '@/components/WanIpCheck.ce',
          },
        ],
      },
    ],
  },
});

as the build output looks like

// 2.0.0-beta.17
import { defineAsyncComponent, defineCustomElement } from "/_nuxt/node_modules/.cache/vite/client/deps/vue.js?v=304f2311"

import Component0 from "/_nuxt/components/WanIpCheck.ce.vue";

const defineTags = () => {
  const elements = [
    ['our-wan-ip-check', (typeof Component0 === 'function' ? (new Component0).$options : Component0), [object Object]]
  ].forEach(([name, component, options]) => {
    const CustomElement = defineCustomElement(component, options);
    window.customElements.define(name, CustomElement);
  })
};

const setup = () => {
  defineTags();
};

setup();

You'll notice [object Object] at the end of the element.

Adding options to make the build work in the browser.

export default defineNuxtConfig({
  // … rest of config
  customElements: {
    entries: [
      {
        name: 'OurComponents',
        tags: [
          // … removed other components for brevity
          {
            name: 'OurWanIpCheck',
            path: '@/components/WanIpCheck.ce',
            options: {},
          },
        ],
      },
    ],
  },
});
ThornWalli commented 10 months ago

@zackspear Very big sorry!

Just publish a working version πŸ™‚

The version 2.0.0-beta.18 should work.

zackspear commented 10 months ago

Thank you for the super quick fix @ThornWalli πŸ™Œ

ThornWalli commented 10 months ago

@zackspear I'm updating again, only in the Vite build this plugin is now built in. https://github.com/baiwusanyu-c/unplugin-vue-ce

With this also nested styles should work now.

I'm still working on webpack...

I also removed the plugin this.$registryEntry, registryEntry is now only possible via Composable. https://nuxt-custom-elements.grabarzundpartner.dev/migration/v2#plugin

If you use the file extensions .ce.vue, this is not necessary. In dev the components are imported normally. In the build itself they will be CustomElements.

Background: That for nested stylesvite would have to be changed to CustomElements in general, and this can only happen in the build.

ThornWalli commented 10 months ago

Shadow CSS of child components in webpack and vite are now possible.

Workaround plugin: https://github.com/baiwusanyu-c/unplugin-vue-ce

zackspear commented 10 months ago

@ThornWalli, this is fantastic news! Unfortunately I have been sick with covid-19 for about a week. I should be able to test this next week. Thank you for the updates.

kahl-dev commented 7 months ago

The latest version 2.0.0-beta.25 breaks on install because of the updated plugin @unplugin-vue-ce/sub-style

error An unexpected error occurred: "expected hoisted manifest for \"@unplugin-vue-ce/sub-style#vue#@vue/server-renderer#@vue/compiler-ssr\"".
ThornWalli commented 7 months ago

Hello @kahl-dev ,

unfortunately I cannot reproduce this. It seems that @unplugin-vue-ce/sub-style is causing problems...

Can you provide a small example to reproduce?

kahl-dev commented 7 months ago

@ThornWalli, I have updated to the latest version of Nuxt and it is now working perfectly. I believe the problem with @unplugin-vue-ce/sub-style was only present in older versions of Nuxt.

ThornWalli commented 7 months ago

@kahl-dev Thanks good to know πŸ˜‰

kahl-dev commented 7 months ago

@ThornWalli if set the option shadow in my NUXT configuration but nothing changed. It still has a shadow Dom. Is there a way to deactivate that?

export default defineNuxtConfig({
  customElements: {
    entries: [
      {
        name: 'XXX',
        shadow: false,
        //...
      }
    ]        
  }
})
ThornWalli commented 7 months ago

@kahl-dev Unfortunately, the behaviour we knew from vue@2 with both no longer exists.

In vue@3 something was rebuilt and provides for a custom element integration only shadow.

There is also a reason why there is this package @unplugin-vue-ce/sub-style ;) The use of child components is not considered in custom elements with their own styling...

Stereoboi commented 6 months ago

Hello @ThornWalli , I hope you are doing well. I am using your product and encountered an issue, and you are my last hope!) So, the problem is that in our Nuxt application that we are developing, we need to implement web components for the possibility of integrating them into external sites, and for this, I want to use your solution. It's worth noting that the components are implemented using Vuetify, and this is where the issues arise. I followed all the documentation provided at this link link and this example However, after running the "nuxt build" command and launching the built web component, I'm not getting styles from Vuetify components; I only receive the layout components and styles that do not belong to the Vuetify library. When I try to create a simple component using plain HTML and CSS, everything works properly. Please advise if it's possible to solve this problem so that Vuetify library elements are displayed in the web component with styles. Below, I will provide some screenshots for better understanding. Thank you in advance for your work and time!

image

nuxt.config.ts

import { resolve } from 'path'
import type { NuxtConfig } from '@nuxt/types'

const config: NuxtConfig = {
  ssr: false,
  server: {
    hmr: {
      protocol: 'ws',
      host: 'localhost'
    }
  },
  runtimeConfig: {
    public: {
      DNS_SENTRY: process.env.DNS_SENTRY,
      CURRENT_ENV: process.env.CURRENT_ENV,
      GALENE_DOMAIN: process.env.GALENE_DOMAIN
    }
  },
  buildModules: ['@nuxt/typescript-build', '@nuxtjs/vuetify'],
  devtools: process.env.DEVTOOLS === 'true' ? { enabled: true } : false,
  modules: ['nuxt-lodash', '@pinia/nuxt', 'nuxt-custom-elements'],
  build: {
    transpile: ['vuetify', 'trpc-nuxt']
  },
  layouts: {
    adminLayout: '@/layouts/AdminLayout.vue',
    createLessonLayout: '@/layouts/createLesson.vue'
  },
  css: ['@/assets/styles/main.scss'],
  app: {
    head: {
      charset: 'utf-8',
      viewport: 'width=device-width, initial-scale=1, minimum-scale=1'
    }
  },
  customElements: {
    analyzer: false,
    entries: [
      {
        name: 'web-component',
        tags: [
          {
            async: true,
            name: 'LessonList',
            path: '@/custom-elements/lessonList.ce',
          }
        ]
      }
    ]
  },
  vite: {
    server: {
      cors: {
        preflightContinue: true
      }
    },
    vue: {
      customElement: true
    }
  },
  routeRules: {
    '*': { cors: true },
    '_nuxt/*': { cors: true }
  }
}

export default config

vuetify.ts from plugins


import '@mdi/font/css/materialdesignicons.css'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
import 'vuetify/styles'

const componentsList = { ...components }
export default defineNuxtPlugin((nuxtApp) => {
  const vuetify = createVuetify({
    ssr: true,
    components: componentsList,
    directives
  })

  nuxtApp.vueApp.use(vuetify)
})

lessonList.ce.vue

<template>
  <Suspense>
    <ChooseLesson />
  </Suspense>
</template>

<script setup lang="ts">
import ChooseLesson from '../components/chooseLesson/ChooseLesson.vue'
</script>

<style>
@import 'vuetify/dist/vuetify.min.css';
</style>

<style lang="scss">
@import '@/assets/styles/main.scss';
@import '@/assets/styles/LessonList.scss';
</style>

package.json

 "devDependencies": {
    "@mdi/font": "^7.2.96",
    "@nuxt/devtools": "latest",
    "@nuxt/types": "^2.17.0",
    "@nuxt/typescript-build": "^3.0.1",
    "@nuxtjs/eslint-config-typescript": "^12.0.0",
    "@nuxtjs/style-resources": "^1.2.1",
    "@types/bcryptjs": "^2.4.2",
    "@types/jsonwebtoken": "^9.0.2",
    "@types/node": "^18.17.1",
    "@types/nodemailer": "^6.4.9",
    "@typescript-eslint/eslint-plugin": "^5.61.0",
    "@typescript-eslint/parser": "^5.61.0",
    "eslint": "^8.44.0",
    "eslint-config-prettier": "^8.10.0",
    "eslint-plugin-nuxt": "^4.0.0",
    "eslint-plugin-prettier": "^4.2.1",
    "nuxt": "^3.8.2",
    "prettier": "^2.8.8",
    "prisma": "^5.1.1",
    "sass": "^1.63.6",
    "ts-node": "^10.9.1",
    "typescript": "^5.1.6",
    "vue-i18n": "^9.2.2"
  },
  "dependencies": {
    "@aws-sdk/client-s3": "^3.391.0",
    "@fullcalendar/core": "^6.1.9",
    "@fullcalendar/daygrid": "^6.1.9",
    "@fullcalendar/interaction": "^6.1.9",
    "@fullcalendar/timegrid": "^6.1.9",
    "@fullcalendar/vue": "^6.1.9",
    "@fullcalendar/vue3": "^6.1.9",
    "@nuxtjs/vuetify": "^1.12.3",
    "@pinia/nuxt": "^0.4.11",
    "@prisma/client": "^5.1.1",
    "@sentry/browser": "^7.63.0",
    "@sentry/node": "^7.63.0",
    "@sentry/tracing": "^7.63.0",
    "@sentry/vite-plugin": "^2.6.2",
    "@sentry/vue": "^7.63.0",
    "@trpc/client": "^10.35.0",
    "@trpc/server": "^10.35.0",
    "bcryptjs": "^2.4.3",
    "get-youtube-id": "^1.0.1",
    "joi": "^17.9.2",
    "jsonwebtoken": "^9.0.1",
    "nodemailer": "^6.9.4",
    "nuxt-custom-elements": "2.0.0-beta.16",
    "nuxt-lodash": "^2.5.0",
    "pinia": "^2.1.6",
    "quill": "^1.3.7",
    "quill-placeholder-module": "^0.3.1",
    "sharp": "^0.32.6",
    "trpc-nuxt": "^0.10.6",
    "uniqid": "^5.4.0",
    "vue-lite-youtube-embed": "^1.2.0",
    "vuedraggable": "^4.1.0",
    "vuetify": "3.4.0",
    "zod": "^3.22.3"
  },
ThornWalli commented 6 months ago

Hello @Stereoboi,

I also noticed this some time ago...

I was wondering how Vuetify activates the theme and came across this: https://github.com/vuetifyjs/vuetify/blob/7a8951ca4e49f7f350062bbbf7d8b456e3eb30f9/packages/vuetify/src/composables/theme.ts#L302

Here the theming is injected via the app, I suspect that the position is not handled in the Shadow Custom Element.

Workaround would be to import a finished Vuetify Theme CSS, in the component.

There are discussions about this on Vuetify:

kahl-dev commented 6 months ago

Hey @ThornWalli,

Ran into something odd here. Looks like I'm getting double style tags in the project – one's popping up at the root and the other's where it's supposed to be. It's messing with the styling and I can't figure out why it's happening.

Have you seen this kind of thing before? Any idea why there'd be a duplicate tag at the root?

If you've got some tips or know something I might be missing, that'd be awesome.

Here is the component and the result:

Screenshot 2023-12-18 at 13 03 55
<template>
  <div class="test">LiaDemo</div>
</template>

<style>
.test {
  color: red;
}
</style>
      {
        // Name of the custom element
        name: 'ExampleComponentGroup',

        tags: [
          {
            // Name of the custom element use kebab-case instad of camelCase to use it in HTML
            name: 'ExampleComponent',
            path: '@/components/Demo/ExampleComponent',

            // Define the props that can be passed to the custom element
            options: {
              props: ['testParameter'],
            },

            // Define the appContext that is passed to the custom element
            // appContext: '@/components/Demo/Example.appContext.js',
          },
        ],
      },
ThornWalli commented 6 months ago

Hello @kahl-dev,

I suspect a bug in this plugin https://github.com/unplugin/unplugin-vue-ce/tree/master/packages/sub-style

Nested styles are not officially supported in Vue3, so a workaround was created as a plugin by the community.

A small Vite-only project with this plugin for reproduction would be good to report this error directly to the plugin.

kahl-dev commented 6 months ago

Hey @ThornWalli,

Thanks for the quick response! I'm going to put together a demo that replicates this issue and report the bug to the plugin devs. Really appreciate your help in pinpointing the possible cause.

dselivanovvv commented 6 months ago

if someone's trying to extend vite builder config to make output files without hashes:

viteExtend(viteConf) {
          viteConf.build.rollupOptions = viteConf.build.rollupOptions || {}
          viteConf.build.rollupOptions.output = viteConf.build.rollupOptions.output || {}

          const target = viteConf.build.rollupOptions.output
          const assetsDir = 'assets'

          target.entryFileNames = `${ assetsDir }/[name].js`
          target.chunkFileNames = `${ assetsDir }/[name].js`
          target.assetFileNames = `${ assetsDir }/[name].[ext]`
          return viteConf
}
ThornWalli commented 5 months ago

@dselivanovvv Do you have the config in the right place? πŸ™‚

Your configuration works!

Before image

After image

{
  customElements: {
    analyzer: !isTest,
    entries: [
      {
        name: 'Example',
        viteExtend(config) {
          config.build.rollupOptions = config.build.rollupOptions || {};
          config.build.rollupOptions.output =
            config.build.rollupOptions.output || {};

          const target = config.build.rollupOptions.output;
          const assetsDir = 'assets';

          target.entryFileNames = `${assetsDir}/[name].js`;
          target.chunkFileNames = `${assetsDir}/[name].js`;
          target.assetFileNames = `${assetsDir}/[name].[ext]`;

          return config;
        },
        tags: [
          {
            async: false,
            name: 'CustomElementExample',
            path: '@/components/customElements/Example.vue',
            options: {
              props: {
                title: 'Live Example'
              }
            },
            appContext: '@/components/customElements/Example.appContext.js',
            slotContent: '<div>Live Example Content</div>'
          }
        ]
      }
    ]
  }
}
MarkoTukiainen commented 4 months ago

Hello. I've been trying out this project for a while now, and so far I feel like I'm just banging my head on a brick wall. I'm trying to export some very rudimentary nuxt 3 components using the composition API. I've been able to generate a custom element that creates a shadow dom and displays the component. However, none of the nuxt-specific features seem to work in these components, or at least no auto-imports seem to be working. So I'm having to import things like computed or the components that I'm trying to use. So this means that none of my actual components will work, since they naturally use these things.

Is this supposed to be the case or am I doing something wrong?

ThornWalli commented 4 months ago

Hello @MarkoTukiainen

Macros are not supported πŸ™ƒ

Try to import the things. e.g. import { ... } from '#imports';

All global functions are announced under .nuxt/imports.d.ts and can be imported via #imports.

MarkoTukiainen commented 4 months ago

Try to import the things. e.g. import { ... } from '#imports';

Thanks for replying so quickly. So, in order to use my existing nuxt components I'd have to modify all of them to use manual imports?

ThornWalli commented 4 months ago

Yes, that's right.

MarkoTukiainen commented 4 months ago

Trying the import { ... } from '#imports' yields the following error:

[vite:load-fallback] Could not load D:/path/.nuxt/imports (imported by components/Widget.ce.vue?vue&type=script&setup=true&lang.ts): ENOENT: no such file or directory, open 'D:\path\.nuxt\imports'

Which, I suppose, is correct since the file imports does not exist in the .nuxt folder, but imports.d.ts does.

ThornWalli commented 4 months ago

Sorry for my mistake.

You can forget #imports.

Try it with the original imports, the imports is just a summary of all global imports.

image

MarkoTukiainen commented 4 months ago

You can forget #imports.

Try it with the original imports, the imports is just a summary of all global imports.

Yes, indeed that was my original question - I was wondering if I in fact had to refactor all my components to use manual imports. Interestingly enough, the #imports syntax works fine in nuxt, but just not when building custom elements. Could this be a Windows-specific issue, if the path resolution of the #imports alias somehow goes wrong or something?

I'll have to see how big of an effort this will be, sadly the auto-imports was one of the best things I thought Vue 3 brought to the table.

ThornWalli commented 4 months ago

Auto imports are good as long as you stay in Nuxt ;)

Example: Mono repo with component library for reuse in different projects (without Nuxt). That's where it gets a bit difficult.

You could extend the Vite configuration for the auto-imports. https://nuxt-custom-elements.grabarzundpartner.dev/guide/options.html#more-about-viteextend

Here you would have to see if a pure alias #imports on the file is sufficient.

The custom elements are each created in Vite/Webpack with their own entry builds, so Nuxt-specific properties are missing here.