How to make Vue and Vite work with web components? - javascript

I want to migrate my Vue 2 project from webpack to Vite.
And have to use 3rd party web components that built with lit-element.
Those components throws errors during the runtime (by vue):
Unknown custom element: < foo-component > - did you register the
component correctly? For recursive components, make sure to provide
the "name" option.
And also (by lit-element)
Failed to set the 'adoptedStyleSheets' property on 'ShadowRoot':
Failed to convert value to 'CSSStyleSheet'.
As far as I can see those 3rd party web components do only this in theirs index files (inside node_modules):
import FooComponent from './FooComponent';
customElements.define('foo-component', FooComponent);
So before (with webpack setup) I just imported them and everything used to work. Well, actually for webpack lit-scss-loader was used also for those components.
I assume that Vite perhaps needs some additional configuration, or maybe something similar to "webpack" loader is needed here, but not sure what direction I have to move.
What I'm doing wrong?

Configure #vite/plugin-vue to ignore Lit elements, e.g., elements starting with my-lit in their registered name:
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
// treat all components starting with `my-lit` as custom elements
isCustomElement: tag => tag.startsWith('my-lit'),
},
},
}),
],
})
demo

A couple of steps needed.
Imagine I have 3rd party webcomponents named "foo". So all of them are in node_modules/#foo.
I need to do these steps:
Tell Vite that all components starting with "foo-" are webcomponents.
isCustomElement: (tag) => tag.startsWith('foo-')
Add "postcssLit" plugin to help vite to prepare css for the webcomponents.
Tell to Vite how to threat css paths for webcomponents.
'~#foo': fileURLToPath(new URL('./node_modules/#foo', import.meta.url))
Here is the full config:
//vite.config.ts
import postcssLit from 'rollup-plugin-postcss-lit';
export default defineConfig({
plugins: [
vue(
{
template: {
compilerOptions: {
// 1. Tell Vite that all components starting with "foo-" are webcomponents
isCustomElement: (tag) => tag.startsWith('foo-')
}
}
}
),
vueJsx(),
// 2. This "postcssLit" plugin helps to make css for the webcomponents
postcssLit()
],
resolve: {
alias: {
// 3. Tell to Vite how to threat css paths for webcomponents
'~#foo': fileURLToPath(new URL('./node_modules/#foo', import.meta.url))
}
}
});

Related

Client-only Nuxt 3 Vue plugin

I am new to Nuxt and Vue, so go easy on me. I am trying to create a video player component in my Nuxt 3 app using vue3-video-player, which doesn't seem to support SSR based on the following error I get when I import it in my video component:
ReferenceError: navigator is not defined
This error persists even if the component is wrapped with <ClientOnly>. So, based on what I saw in the Nuxt 3 Documentation I thought I would create a client-only plugin located at plugins/vue3-video-player.client.js with the following contents:
import Vue3VideoPlayer from '#cloudgeek/vue3-video-player'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(Vue3VideoPlayer)
})
But when I try to use it in my component as <vue3-video-player>, I get the following error:
[Vue warn]: Failed to resolve component: vue3-video-player
So I guess my question is how do I create a client-only Vue component using Nuxt 3 plugins? Or is there an entirely different approach that would work better?
Solution for nuxt 3:
Nuxt will automatically read the files in your plugins directory and load them. You can use .server or .client suffix in the file name to load a plugin only on the server or client side.
For example:
plugins/apexcharts.client.ts
Everything is so simple! And that is why we love nuxt ❤️
Solution for nuxt 2 (to show the difference):
nuxt.config.ts
plugins: [
{src: '~/plugins/apexcharts', mode: 'client'}
],
This is only for nuxt 2 because All plugins in your nuxt 3 plugins/ directory are auto-registered, so you should not add them to your nuxt.config separately.
you could try to provide a helper function. As mentioned in the docs.
import Vue3VideoPlayer from '#cloudgeek/vue3-video-player'
export default defineNuxtPlugin((nuxtApp) => {
return {
provide: {
Vue3VideoPlayer
}
}
})
To tag along with the given correct answer here,
If you're trying to install and use a third party NPM package, and running into "window is not defined" type errors, you can load the package as a plugin as follows (eg WAD)
npm install web-audio-daw
// plugins/wad.client.ts
import Wad from "web-audio-daw"
export default defineNuxtPlugin(nuxtApp => {
return {
provide: {
Wad,
}
}
})
// pages/whatever.vue
<script lang="ts" setup>
const { $Wad } = useNuxtApp();
// Can use $Wad normally from here on out
</script>

three.js over cdn using svelte or react

Is there any way of building my svelte or react application in a way, that the three.js module (which I usually import using npm) will be declared as a script tag which will call the module from a CDN? I would like to keep the advantages of a framework but also be able to reduce my final bundle size, since most of my bundle contains three code.
Thank you for your wisdom
There are two ways to go about your goal of reducing bundle size:
Importing from a CDN (your suggestion)
Code-splitting
Importing from a CDN
To keep semantics of ESModules, you may simply replace your current three.js imports with a URL from an npm CDN, like unpkg:
Pros
Cons
No extra configuration needed
Slower to load, as browser needs to spin up new connections to access third-party CDN
Asynchronously
<script>
// App.svelte
import('https://unpkg.com/three#0.133.1/build/three.min.js').then(({ default: THREE }) => {
// your code here
});
</script>
Synchronously
Note: Importing like this blocks the rest of your script from loading while three.js is downloading, which defeats the purpose of the whole shebang. It's just here for completeness
<script>
// App.svelte
import { default as THREE } from 'https://unpkg.com/three#0.133.1/build/three.min.js';
// your code here
</script>
Code-splitting
This method takes advantage of the fact that you're already using a bundler (probably rollup, vite, or webpack). This answer will focus on rollup as it's the default used in svelte's examples.
Pros
Cons
Faster to load, as browser can use existing connections to access first-party resources
More complicated to get set up
Asynchronously
In your rollup.config.js file, ensure output.format is set to 'esm' & output.dir is set instead of output.file
// rollup.config.js
import svelte from 'rollup-plugin-svelte';
import resolve from '#rollup/plugin-node-resolve';
import commonjs from '#rollup/plugin-commonjs';
import postcss from 'rollup-plugin-postcss';
const production = !process.env.ROLLUP_WATCH;
export default {
input: 'src/index.js',
output: {
sourcemap: !production,
format: 'esm',
name: 'app',
dir: 'public',
},
plugins: {
// your plugins
svelte({
compilerOptions: {
dev: !production,
},
}),
postcss({
extract: 'bundle.css',
}),
resolve({
browser: true,
dedupe: ['svelte'],
}),
commonjs(),
}
}
<script>
// App.svelte
import('three').then(({ default: THREE }) => {
// your code here
});
</script>
Note: There is no synchronous way due to how code-splitting is evaluated at compile time. Plus it doesn't make much sense to do it like that anyways.
Yes, you can do the following:
In your "index.html" file, you can import the js file from a CDN as follow:
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
Then, in the file where you want to use it, which could be a React component for instance, you can do the following:
const THREE = window.THREE;
Which would replace your import statement, which would have been import * as THREE from "three"; or import THREE from "three";

import single Vuetify component in Nuxt.js?

I use Vuetify in nuxt.js.
How to use this only in dashboard layout?
in nuxt.config.js
modules: [
//['nuxt-leaflet', { /* module options */}],
'bootstrap-vue/nuxt',
'#nuxtjs/axios',
'#nuxtjs/pwa',
'#nuxtjs/auth',
'#nuxtjs/toast',
['#nuxtjs/vuetify', {rtl: true}],
// 'nuxt-i18n',
],
Are you using vuetify-loader with tree-shaking? If so, you can just import specific vuetify component into specific .vue component:
import { VTextField } from 'vuetify/lib';
and add:
components: { VTextField }
According to the #nuxtjs/vuetify module documentation, if you using the treeShake option, by default your Nuxt.js app will use only the needed vuetify component, and the bundle size didn't increase.
Uses vuetify-loader to enable automatic tree-shaking. Enabled only for production by default.
Another thing, If you are using Nuxt >= 2.9.0, use buildModules section instead:
{
buildModules: [
// Simple usage
'#nuxtjs/vuetify',
// With options
['#nuxtjs/vuetify', { /* module options */ }]
]
}
If you are using NuxtJS Vuetify Module (It seems that you are), I assume that your package.json does not have vuetify listed there, because it is the #nuxtjs/vuetify that imports it. Thus, you can't import it using only the module name. I suggest you to import it with it's complete path like the following:
import { VCard } from '~/node_modules/vuetify/lib';
Then register the component, of course.

How to Embed StencilJS components inside Storybook React App?

I'm trying to integrate Stencil and Storybook inside the same project. I've been following this set up guide and this one however one of the steps is to publish the library of components to NPM and that's not what I want.
I have this repo which I've configured with components library (src folder) and with the reviewer of those components with Storybook, which resides in the storybook folder.
The problem is that when I compile the components using Stencil and copy the dist folder inside the Storybook app and import the component nothing renders. Tweaking the configuration using custom head tags I was able to import it correctly however no styles where applied.
When I open the network panel there is some error when importing the component:
And thus the component is present in the DOM but with visibility set to hidden, which I think it does when there is an error.
This is the component au-button:
import { Component } from '#stencil/core';
#Component({
tag: 'au-button',
styleUrl: 'button.css',
shadow: true
})
export class Button {
render() {
return (
<button class="test">Hello</button>
);
}
}
Here is the story my component:
import React from 'react';
import { storiesOf } from '#storybook/react';
import '../components/components.js'
storiesOf('Button', module)
.add('with text', () => <au-button></au-button>)
These are the scripts inside the Storybook app:
"scripts": {
"storybook": "start-storybook -p 9009",
"build-storybook": "build-storybook",
"copy": "cp -R ./../dist/* components"
},
And the workflow is as follows:
Launch storybook
Make changes in the component
Execute build command
Execute copy command
Also, I would like to automate the developer experience, but after I solve this problem first.
Any ideas of what I could be doing wrong?
Sample for this could be found in the repo
https://github.com/shanmugapriyaEK/stencil-storybook. It autogenerates stories with knobs and notes. Also it has custom theme in it. Hope it helps.
I'm using #storybook/polymer and it's working for me really well.
following your example:
import { Component } from '#stencil/core';
#Component({
tag: 'au-button',
styleUrl: 'button.css',
shadow: true
})
export class Button {
render() {
return (
<button class="test">Hello</button>
);
}
}
the story would be:
import { storiesOf } from '#storybook/polymer';
storiesOf('Button', module)
.add('with text', () => <au-button></au-button>)
the scripts in the package.json:
"scripts": {
"storybook": "start-storybook -p 9001 -c .storybook -s www"
},
the storybook config file:
import { configure, addDecorator } from '#storybook/polymer';
const req = require.context('../src', true, /\.stories\.js$/);
function loadStories() {
req.keys().forEach((filename) => req(filename))
}
configure(loadStories, module);
and storybook preview-head.html you have to add to the body the following:
<body>
<div id="root"></div>
<div id="error-message"></div>
<div id="error-stack"></div>
</body>
I've been following this set up guide and this one however one of the steps is to publish the library of components to NPM and that's not what I want.
My reading of those guides is that they're stating “publish to NPM” as a way to have your files at a known URL, that will work most easily for deployment.
Without doing that, you'll need to figure out a different deployment strategy. How will you get the build products – the dist directory and static files – published so that your HTML will be able to reference it at a known URL? By choosing to diverge from the guidelines, that's the problem you have to address manually instead.
Not an insurmountable problem, but there is no general solution for all. You've chosen (for your own reasons) to reject the solution offered by the how-to guides, which means you accept the mantle of “I know what I want” instead :-)

How to ignore raw loader for testing webpack

I have some components with svg's loaded inline using webpack raw loader e.g...
import React, { Component } from 'react'
import svg from '!raw!../assets/images/logo.svg'
export default class Logo extends Component {
render() {
return (<a href={this.props.url} dangerouslySetInnerHTML={{__html: svg}} />)
}
}
When trying to test these components server side using tape, they fall over. If I have css modules included, it is no problem, I can use css-modules-require-hook but svg's will not work. So I really need a raw loader require hook or something like that.
require('babel-register');
require('css-modules-require-hook/preset');
/* tests after this can import components with css includes */
I tried using isomorphic-ensure but this did not work.
require('babel-register');
require('css-modules-require-hook/preset');
require('isomorphic-ensure')({
loaders: {
raw: require('raw-loader'),
raw: require('react-svgdom-loader')
},
dirname: __dirname
})
I get the following error:
Cannot find module '!raw!../assets/images/
If you're not using webpack for your tests then you could use the ignore-styles module.
You may have to configure it if you plan to use it with css-modules-require-hook as it will also also ignore CSS files by default. e.g:
require('ignore-styles').register(['.svg'])

Categories