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

[Progress] nuxt@3 support #433

Open
13 of 21 tasks
ThornWalli opened this issue Mar 30, 2022 · 62 comments
Open
13 of 21 tasks

[Progress] nuxt@3 support #433

ThornWalli opened this issue Mar 30, 2022 · 62 comments
Labels

Comments

@ThornWalli
Copy link
Contributor

ThornWalli commented Mar 30, 2022

⚠️ 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?

@ThornWalli ThornWalli added nuxt 3 Nuxt 3 Support nuxt-bridge Nuxt Bridge Support labels Mar 30, 2022
@ThornWalli ThornWalli changed the title [Progress] @nuxt/bridge and nuxt@3 support nuxt 3 nuxt-bridge [Progress] @nuxt/bridge and nuxt@3 support Mar 30, 2022
@ThornWalli ThornWalli changed the title [Progress] @nuxt/bridge and nuxt@3 support [Progress] nuxt@3 support Jul 22, 2022
@maximepvrt
Copy link

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

@advanceCabbage
Copy link

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

@pperzyna
Copy link

pperzyna commented Jan 4, 2023

+1

@maximepvrt
Copy link

I created an issue (nuxt/nuxt#15584) to request native support by nuxt

@ThornWalli
Copy link
Contributor Author

ThornWalli commented Jan 31, 2023

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.

  • entry build works in both nuxt builders (@nuxt/vite-builder, @nuxt/webpack-builder)
  • native Vue3 customElement integration is used
    • everything is shadow!

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

@Techbinator
Copy link

Techbinator commented Feb 21, 2023

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: [email protected] 
npm ERR!   at location: /Users/xxxx/workspace/pocmonorepo/v1/packages/shared/frontend 

Any help or pointers would be greatly appreciated.

@ThornWalli
Copy link
Contributor Author

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
Copy link

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

@ThornWalli ThornWalli pinned this issue Mar 12, 2023
@maximepvrt
Copy link

maximepvrt commented Apr 26, 2023

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
Copy link

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 à 17 27 32

@maximepvrt
Copy link

@ThornWalli 😇

@ThornWalli
Copy link
Contributor Author

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
Copy link
Contributor Author

@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
Copy link

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

Capture d’écran 2023-05-18 à 18 25 07

@ThornWalli
Copy link
Contributor Author

@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
Copy link

zackspear commented May 19, 2023

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
Copy link
Contributor Author

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: vuejs/core#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
Copy link

@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
Copy link
Contributor Author

ThornWalli commented May 22, 2023

@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
Copy link

zackspear commented May 22, 2023

@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

@ThornWalli
Copy link
Contributor Author

@kahl-dev Thanks good to know 😉

@kahl-dev
Copy link

@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
Copy link
Contributor Author

@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
Copy link

Stereoboi commented Dec 14, 2023

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
Copy link
Contributor Author

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
Copy link

kahl-dev commented Dec 18, 2023

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
Copy link
Contributor Author

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
Copy link

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
Copy link

dselivanovvv commented Jan 3, 2024

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
Copy link
Contributor Author

@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
Copy link

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
Copy link
Contributor Author

ThornWalli commented Feb 12, 2024

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
Copy link

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
Copy link
Contributor Author

Yes, that's right.

@MarkoTukiainen
Copy link

MarkoTukiainen commented Feb 12, 2024

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
Copy link
Contributor Author

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
Copy link

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
Copy link
Contributor Author

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.

@MarkoTukiainen
Copy link

MarkoTukiainen commented Feb 13, 2024

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

I'm beginning to see that. I've been working my way through some of these components and I'm having a really bad time trying to reuse any of the nuxt code in both a nuxt application and in these widgets. Plugins are not available, so for example an instance of pinia seems to be very difficult to get a hold of. All the documentation online assumes that I'm 100% going to use nuxt features, so alternate solutions are hard to find and even more difficult to figure out by myself.

I came up with this for a store plugin:

import type { Pinia } from 'pinia'

let storeInstance: ReturnType<typeof useMainStore> | null = null;

export default defineNuxtPlugin(({ $pinia }) => {
  if (!storeInstance) {
    storeInstance = useMainStore($pinia as Pinia);
  }
  return {
    provide: {
      store: storeInstance
    }
  }
})

export const getStore = () => {
  if (!storeInstance) {
    throw new Error("Store has not been initialized yet");
  }
  return storeInstance;
}

I figured I could call getStore() to get an instance of the store when the plugin is not available, and use $store elsewhere.

While this works when SSR rendering the nuxt app, another plugin (that is run later than this plugin) runs into the Error I'm throwing on the client side.

Do you have a recommended best practice for solving something like this? The examples seem to be all vue 2.

@ThornWalli
Copy link
Contributor Author

ThornWalli commented Feb 13, 2024

I had to rethink the custom element a little.

  • A custom element has its own Vue app. Does not know that of Nuxt.
  • A custom element has no SSR and only exists in the client during runtime.

You can find something about Pinia here:

And this is used as a base:
https://github.com/EranGrin/vue-web-component-wrapper

Note that the official Vue 3 custom element support is a bit difficult, starts with styles and ends with the integration of Vue plugins...

So keep that in mind as well 😉
vuejs/core#4662

@MarkoTukiainen
Copy link

Thanks for taking the time to link those examples, this looks very helpful. I was actually able to work around my issue with plugins and getting a handle on a single instance of pinia earlier today, and was able to implement a widget that reused the same code that still simultaneously works in a separate nuxt application through layers. But that appContext syntax looks like a more elegant solution than what I had to do.

@MarkoTukiainen
Copy link

Hey @ThornWalli, I've pretty much completed the conversion of my widgets from vue 2 to vue 3 (using my nuxt 3 components).

I've had some issues autoloading components (though most other autoloading works fine). But I've just now noticed a potentially catastrophic issue. Vite+rollup seem to create an es module, which requires a script module tag to run. This is not really feasible for us, since our customers already have these tags in use everywhere, so we really can't change the tag. Previously we created an UMD bundle with Webpack which split the code into chunks. However, Rollup doesn't seem to want to do this. So I end up with an absolutely massive, single entry .js file for the widget.

Do you have any recommendations on how to get around this? E.g. output a UMD bundle with chunks. These build tools are really not my forte.

@ThornWalli
Copy link
Contributor Author

@MarkoTukiainen

Unfortunately no solution for vite... 😕

But you could make a webpack build only for the use case?
Then you don't have a module there.

nuxt.config.js

{
  builder: '@nuxt/webpack-builder'
}

@MarkoTukiainen
Copy link

I suppose that's an option. For now I ended up with renaming the new module and just calling import from the old script, seems to work fine.

In case someone else is reading this thread, here's a couple of things I noticed made reusing the code much better (for me):

  • Using unplugin-auto-import/vite to auto-import vue, @vueuse/core, pinia's storeToRefs, @unhead/vue's useHead and $fetch from ofetch. For some reason I couldn't get the unplugin-vue-components/vite to auto-import my shared components, but I settled for manual imports when needed (and an ugly @ts-ignore line to stop the IDE from complaining).
  • Overriding useAsyncData and useNuxtApp composables with custom ones.
  • Creating a custom composable for navigation that provides a navigateTo equivalent in Vue context and the standard navigateTo in Nuxt context.
  • Using vite-plugin-replace to replace certain build-time environment variables in the code (I couldn't figure out any better way to pass them).
  • Defining all plugins in the appContext file (I didn't initially realize this was even a thing).

@wsm661
Copy link

wsm661 commented May 15, 2024

I encountered an issue when actually using it. The version I am using is "nuxt-custom-elements": "^2.0.0-beta.30",custom-element-example not auto-import Web Components,As shown in the following figure:
image

package.json:

{
  "name": "nuxt3",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "build": "nuxt build",
    "dev": "nuxt dev",
    "generate": "nuxt generate",
    "preview": "nuxt preview"
  },
  "dependencies": {
    "@pinia-plugin-persistedstate/nuxt": "^1.2.0",
    "@pinia/nuxt": "^0.5.1",
    "nuxt": "^3.11.2",
    "nuxt-custom-elements": "^2.0.0-beta.30",
    "vue": "^3.4.27",
    "vue-router": "^4.3.2",
    "vue-web-component-wrapper": "^1.4.4"
  }
}

nuxt.config.ts:

export default defineNuxtConfig({
  devtools: { enabled: true },
  modules: [
    'nuxt-custom-elements',
    '@pinia/nuxt',
    ['@pinia-plugin-persistedstate/nuxt', { client: true }],
  ],
  customElements: {
    entries: [
      {
        name: 'Example',
        shadow: true,
        tags: [
          {
            name: 'CustomElementExample',
            path: '@/components/Example.vue',
            shadow: true,
            options: {
              props: {
                title: 'Prop. Example Title',
              },
            },
            slotContent: 'Slot Example Content',
          },
        ],
      },
    ],
  },
})

components\Example.vue:

<script>
export default {
  props: {
    title: {
      type: String,
      default: null,
    },
  },
  computed: {
    exampleTitle() {
      return this.title || 'Default Title'
    },
  },
  mounted() {
  },
}
</script>

<template>
  <div class="custom-element-example">
    <div class="title">
      {{ exampleTitle }}
    </div>
    <div class="content">
      <slot>Default Content</slot>
    </div>
  </div>
</template>

plugins\customElement.ts:

export default defineNuxtPlugin((vueApp) => {
  const customElements = useCustomElements()

  return {
    provide: {
      customElements,
    },
  }
})

pages\example.vue:

<script lang="ts" setup>
const { $customElements } = useNuxtApp()
$customElements.registerEntry('Example')
</script>

<template>
  <div v-html="'<custom-element-example />'" />
  <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>
</template>

all code:
newnuxt3.zip

@wsm661
Copy link

wsm661 commented May 16, 2024

How should I solve this problem correctly?

Today I tried again and found that by modifying the content of the generated .nuxt\nuxt-custom-elements\entries\example.client.js file to nuxt3 syntax and placing it in the plugins folder plugins\customElement.client.js, I could achieve the desired effect.

as shown in the following figure:
image

add plugins\customElement.client.js:

import { defineAsyncComponent, defineCustomElement, h, createApp, getCurrentInstance } from 'vue';
import { createWebComponent } from 'vue-web-component-wrapper';
import Component0 from '@/components/Example.vue';

export default defineNuxtPlugin(() => {
  [    ['custom-element-example', Component0, {"props":{"title":"Prop. Example Title"}}, undefined, undefined]].forEach(([name, component, options, appContext, css]) => {
    createWebComponent({
      rootComponent: component,
      elementName: name,
      plugins: appContext,
      cssFrameworkStyles: css,
      VueDefineCustomElement: (component) => defineCustomElement(component, options),
      h,
      createApp,
      getCurrentInstance
    });
  })
})

pages\example.vue:

<template>
  <div v-html="'<custom-element-example />'" />
  <Example title="Vue Component">
    Vue component slot content
  </Example>
  <custom-element-example title="Web component title">
    Web component slot content
  </custom-element-example>
</template>

@ThornWalli
Copy link
Contributor Author

Hello @wsm661,

did I understand correctly, you want to integrate the CustomElement correctly during development?

The basic idea of this module is actually to create exports of components as an app for use on external pages, as a widget or something.

When you generate the project, a separate build is started.
In the build, the Vite/Webpack Vue Config is extended with the customElement declaration.
Only then will the .vue files become CustomElements with shadow.

Locally you could try this with .ce.vue.

Alternatively, you could think about integrating it in the development, currently the components are imported directly.

image

Without .ce.vue it might not work in the end, because of missing shadow.

@wsm661
Copy link

wsm661 commented May 20, 2024

Thanks.

Your interpretation is accurate; I aim to integrate CustomElement into my project.

Within the project, I aim to display the as-is, allowing the browser to render it, rather than treating it as a Vue component and prematurely rendering it into an HTML structure.

We operate under a singular project, and the current project requires the registration of Web Components for direct utilization.

I attempted using .ce.vue, but it appears to fall short of my requirements; it merely converts the globally registered component Example in Nuxt to ExampleCe.

I grasp your sentiment, hence I reimplemented the contents of example.client.js directly within plugins, achieving the desired outcome. What actions should I take next?

image

@ThornWalli
Copy link
Contributor Author

Hello @wsm661,

I created a PR to see what the implementation could look like.
https://github.com/GrabarzUndPartner/nuxt-custom-elements/tree/feature/development-use

The composable now refers directly to the entry.

image

Unfortunately, I ran into limitations here.

CustomElement is missing the style.

The workaround would be to add .ce.vue to all files that are built into the CustomElement.
Vite plugin @unplugin-vue-ce/sub-style offers no support if it is integrated directly during development.

@blocka
Copy link

blocka commented Jul 18, 2024

analyzer: !isTest,

neither of these two configs worked for me to remove the hash

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

No branches or pull requests