nuxt-custom-elements
nuxt-custom-elements copied to clipboard
[Progress] `nuxt@3` support
β οΈ Currently, there is no backward compatibility with
nuxt@2
oderbridge
when updating.
Nuxt 3 version is available π
Install
npm i nuxt-custom-elements@beta
Todos
- [ ] Implement
vue-web-component-wrapper
for usagei18n
,pinia
,vuetify
- [x] Solved π ~~β οΈ Not really usable until now https://github.com/vuejs/core/issues/4662~~
- [x] Workaround
unplugin-vue-ce
- [x] Vite
- [x] Webpack: https://github.com/baiwusanyu-c/unplugin-vue-ce/issues/65
- [x] Workaround
- [ ] Code refactoring
- [ ]
webpack
Build (@nuxt/webpack-builder
)- [x] Entries build with
build
orgenerate
. - [x]
publicPath
integration? (https://webpack.js.org/guides/public-path/#automatic-publicpath) - [x] extend
webpack
config - [x] Bundle Analyzer?
- [x] Entries build with
- [ ]
vite
Build (@nuxt/vite-builder
)- [x] Entries build with
build
orgenerate
. - [ ]
publicPath
integration? (https://www.npmjs.com/package/vite-plugin-dynamic-base) - [x] extend
vite
config - [ ] Bundle Analyzer?
- [x] Entries build with
- [x]
vitest
Tests update- [x]
@nuxt/vite-builder
- [x]
@nuxt/webpack-builder
- [x]
- [ ] Update Documentation
- [ ]
nuxt
backward compatibility (nuxt-bridge
)
Features
- Vue3 native custom element integration will be used. https://vuejs.org/guide/extras/web-components.html#building-custom-elements-with-vue
- β οΈ Important: custom-elements are offered only as shadow elements.
Problems?
- https://github.com/vuejs/core/issues/4662
- https://github.com/baiwusanyu-c/unplugin-vue-ce/issues/65
do you have any idea when the nuxt 3 version will be available ?
Excuse me, when will nuxt-custom-elements support nuxt3? Is there a tutorial?
+1
I created an issue (https://github.com/nuxt/nuxt/issues/15584) to request native support by nuxt
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.
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.
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:
After:
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:
After:
works great now. Thanks a lot for the quick fix
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
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)
@ThornWalli π
Hello @maximepvrt,
- I will look at the build
dist
later. -
@nuxt/webpack-builder
is out now, import this dynamically. - 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
@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
Perfect ! but an error is generated if I build the project with a dev import via entry definition
@maximepvrt Did you take this over?
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
π
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
-
local development with
nuxt dev
inapp.vue
-
-
nuxt generate
then serving the generated HTML file with the web component-
npm i -g serve
-
serve dist/nuxt-custom-elements/example
-
-
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>
Hello @zackspear,
Case 1
-
tailwindCSS
is not taken over because shadow component does not take over the global css. -
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-elementBut 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
@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>
@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.
@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
And a screenshot of nuxt generate
and viewing it with serve dist/nuxt-custom-elements/connect-components
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!',
},
],
},
],
},
});
Thanks again in advance.
Edit: It looks like defineTags
in your src/runtime/tmpl/entry.mjs
file 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 After running nuxt build
what is the path to my entry?
This is what I got. And I can reference the
Forms
entry 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
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.
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
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: {},
},
],
},
],
},
});
@zackspear Very big sorry!
Just publish a working version π
The version 2.0.0-beta.18
should work.
Thank you for the super quick fix @ThornWalli π
@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.
Shadow CSS of child components in webpack
and vite
are now possible.
Workaround plugin: https://github.com/baiwusanyu-c/unplugin-vue-ce
@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.
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\"".