How to use web components in vuepress - vue.js

I'm trying to integrate Stoplight to our vuepress site. We are doing this by adding a web component called elements-api which is provided by stoplight.
Here is what I have done so far.
APIStopLight.vue
<template>
<main class="apis-page">
<!-- <iframe class = "api-container" width="100%" :src="$frontmatter.url" frameborder="0" ></iframe> -->
<elements-api class = "stoplight"
apiDescriptionUrl="/asgardeo/docs/content/apiDocs/scim2.yaml"
router="hash"
></elements-api>
</main>
</template>
<script>
export default{
name: 'APIStoplight',
methods:{
log(msg){console.log(msg);}
}
}
</script>
<style src="../theme/styles/components/apiOverview.styl" lang="stylus">
I have added the stoplight libs in .vuepress/config.js as follows.
head: [
...some other scripts,
['script', {src: 'https://unpkg.com/#stoplight/elements/web-components.min.js'}]
...another list of scripts
]
But when I'm running the application, I get the following error.
vue.runtime.esm.js?2b0e:619 [Vue warn]: Unknown custom element:
- did you register the component correctly? For
recursive components, make sure to provide the "name" option.
found in
---> at docs/.vuepress/components/APIStoplight.vue
at docs/apis/scim2.md
at docs/.vuepress/theme/components/MyTransition.vue
at docs/.vuepress/theme/components/Page.vue
at docs/.vuepress/theme/components/Common.vue
at docs/.vuepress/theme/layouts/Layout.vue
at node_modules/#vuepress/core/lib/client/components/GlobalLayout.vue
Upon some research, I found this article which says how to use web components with vue.js, but I can't port that into vuepress as vuepress is considerably different from Vue.
Is there anyone who can explain how to use web components in Vuepress? Thanks in advance for all helpful answers.

Eventually, I was able to find a solution myself.
Vuepress supports extending the default Webpack config by adding a chainWebpack config option to docs/.vuepress/config.js. From there I could add the custom element config recommended here and the problem was solved.
module.exports = config({
...,
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options => ({
...options,
compilerOptions: {
// treat any tag that starts with ion- as custom elements
isCustomElement: tag => tag.includes('-')
}
}))
},
...
})

Related

How to create a library exposing a single Vue component that can be consumed by the distributed .mjs file?

I want to create a single Vue component that gets bundled into a single .mjs file. Another Vue project can fetch this .mjs file via HTTP and consume the component. Installing the pluggable component via npm is not possible, because the other application tries to fetch it based on a configuration during runtime.
Things to consider for the pluggable component
Might be using sub components from another UI framework / library
Might be using custom CSS
Might rely on other files e.g. images
Reproducing the library
I created a new Vuetify project via npm create vuetify
I deleted everything from the src folder except vite-env.d.ts , created a component Renderer.vue
<script setup lang="ts">
import { VContainer } from "vuetify/components"
defineProps<{ value: unknown }>()
</script>
<template>
<v-container>
<span class="red-text">Value is: {{ JSON.stringify(value, null, 2) }}</span>
</v-container>
</template>
<style>
.red-text { color: red; }
</style>
and an index.ts file
import Renderer from "./Renderer.vue";
export { Renderer };
I added the library mode to the vite.config.ts
build: {
lib: {
entry: resolve(__dirname, "./src/index.ts"),
name: "Renderer",
fileName: "renderer",
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "Vue",
},
},
},
},
and extended the package.json file with
"files": ["dist"],
"main": "./dist/renderer.umd.cjs",
"module": "./dist/renderer.js",
"exports": {
".": {
"import": "./dist/renderer.js",
"require": "./dist/renderer.umd.cjs"
}
},
Since I'm using custom CSS Vite would generate a styles.css file but I have to inject the styles into the .mjs file. Based on this issue I'm using the plugin vite-plugin-css-injected-by-js.
When building I'm getting the desired .mjs file containing my custom CSS
Consuming the component
I created a new Vue project via npm create vue
and for testing purposes I copied the generated .mjs file right into the src directory of the new project and changed the App.vue file to
<script setup lang="ts">
import { onMounted, type Ref, ref } from "vue";
const ComponentToConsume: Ref = ref(null);
onMounted(async () => {
try {
const { Renderer } = await import("./renderer.mjs"); // fetch the component during runtime
ComponentToConsume.value = Renderer;
} catch (e) {
console.log(e);
} finally {
console.log("done...");
}
});
</script>
<template>
<div>Imported component below:</div>
<div v-if="ComponentToConsume === null">"still loading..."</div>
<component-to-consume v-else :value="123" />
</template>
Unfortunately I'm getting the following warnings and errors
[Vue warn]: Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw or using shallowRef instead of ref.
[Vue warn]: injection "Symbol(vuetify:defaults)" not found.
[Vue warn]: Unhandled error during execution of setup function
[Vue warn]: Unhandled error during execution of scheduler flush.
Uncaught (in promise) Error: [Vuetify] Could not find defaults instance
Does someone know what I'm missing or how to fix it?
Vuetify doesn't provide isolated components and requires the plugin to be initialized, you need to do this in main app:
app.use(Vuetify)
Make sure vuetify isn't duplicated in project deps, so the lib and main app use the same copy.
The lib should use vuetify as dev dependency and specify it in Rollup external, in order to prevent the things that are global to the project from being bundled with the lib:
external: ["vue", "vuetify"]

How to compile vue3 SFC component .ce.vue into .js

I am looking for a way to compile a "single-file component" (.vue or .ce.vue extension) with console tools into a js. the project is made with yii2. here is what i tried:
Following this guide i managed to define a custom element (without SFC .vue):
import { defineCustomElement } from 'vue'
const MyVueElement = defineCustomElement({
// normal Vue component options here
props: {},
emits: {},
template: `...`,
// defineCustomElement only: CSS to be injected into shadow root
styles: [`/* inlined css */`]
})
// Register the custom element.
// After registration, all `<my-vue-element>` tags
// on the page will be upgraded.
customElements.define('my-vue-element', MyVueElement)
this guide mentions "Using Vue SFC as Custom Elements", but i have no clue how to run it, as well as this #vue/compiler-sfc.
using the library built with vitejs in library mode makes my (php) app throw
Uncaught TypeError: Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".
so what is the best way to build a SFC
MyTest.ce.vue
<template>
<div>
hello world
</div>
</template>
<script>
export default {
tag: 'my-test',
name: 'MyTest',
data() {
return { count: 0 }
},
};
</script>
<style scoped>
div {
font-size: 200%;
}
</style>
into a .js that i can load in my app, preferably with a single command line tool, without webpack or similar?

What are some ways to use aframe with Vue?

I'm using vue-cli. I've tried adding aframe directly in the index.html file, and also importing it in my top level main.js file. I just can't get Vue to recognize aframe elements.
Am I missing some boilerplate in the documentation?
Example warning:
vue.runtime.esm.js:619 [Vue warn]: Unknown custom element: -
did you register the component correctly? For recursive components,
make sure to provide the "name" option.
You can add aframe to the dependencies in your package.json:
"browserify-css": "*", // required by aframe
"envify": "*", // required by aframe
"aframe": "^1.2.0", // aframe
add aframe to the compiled bundle:
var Vue = require('vue')
var App = require('./app.vue')
require('aframe')
new Vue({
el: '#app',
render: function (createElement) {
return createElement(App)
}
})
and use the elements in your <template>:
<template>
<div>
<a-scene>
<a-cylinder color="#FFC65D"></a-cylinder>
</a-scene>
</div>
</template>
check it out in this glitch
For development it is fine to import the CDN into your index.html
For production it is recommended to require it into your Main.js
Vue 2
To get rid of the warnings i recommend adding a array of components using Vue.config.ignoredElements placed in the main.js
Like so:
Vue.config.ignoredElements = [
'a-scene',
'a-camera',
'a-box'
'a-image',
]
For a full list of components check out this Repo: aframe-components-list
I recommend using the Vue.config.ignoredElements instead of registering your A-Frame components like normal Vue component, since they are not Vue components.
Edit - Vue 3:
In Vue 3 Vue.config.ignoredElements in main.js will not work.
Instead, in your vue.config.js file add the code below:
// vue.config.js
module.exports = {
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
options.compilerOptions = {
...options.compilerOptions,
isCustomElement: tag => tag.startsWith('a-')
}
return options
})
}
}
This should cover custom elements that start with 'a-'.

Testing Vue Component: Failed to mount component: template or render function not defined

I'm writing a test for a Vue component using Mocha and I can't seem to resolve this warning:
[Vue warn]: Failed to mount component: template or render function not defined.
After a bit of research, it seems like most other instances of this warning appearing involve using vue-router. However, I'm not using that library.
I am seeing the warning in even a very basic component like the one below. It roughly follows this example: Testing Single-File Components with Mocha + webpack.
<template>
<div>
<ul>
<li v-for="item in items">
{{ item }}
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
items: Array,
required: true,
},
data () {
return {}
},
}
<script>
<style>
</style>
Everything works fine in the browser, but the tests demonstrate some unusual behavior. Here are two tests, both are very basic. The first passes and the second fails. Both tests trigger the warning about the template or render function not being defined.
import { mount } from '#vue/test-utils';
import Foo from '../../foo.vue';
describe('Foo', () => {
it('works', () => {
const wrapper = mount(Foo, {
propsData: {
items: ['a','b','c']
}
});
expect(wrapper.isVueInstance()).toBeTruthy()
})
it('contains a property', () => {
const wrapper = mount(Foo, {
propsData: {
items: ['a','b','c']
}
});
expect(wrapper.props()).toEqual({items: ['a','b','c']})
})
});
The second test fails because it seems like the props have not been set. The test failure says:
Expected: ["a", "b", "c"]
Received: {}
Based on the ue-test-utils documentation, I figured this would be a straightforward test, so I'm confused about why it's failing at all.
I'm not sure if there's some pre-compilation piece that I'm missing or something else. I've tried running these tests with both mocha-webpack and the more up-to-date mochapack test runner but the result is the same.
Any advice on how to remove that warning and get these tests to pass?
I am testing my vue components with jest and i am no expert in this topic but i could imagine that you havent configured your enviroment properly.
At least for jest i had to configure a transform Object like this.
"transform": {
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest",
"^.+\\.js$": "<rootDir>/node_modules/babel-jest"
},
The point is, that this transformation changes a *.vue file into javascript.
And in this step the render function is created.
Perhaps this is missing and thats why you get that error.
But i can't explain why the first test succeeded.
Hopefully this could help. Otherwise i delete this post.
;)

How to Access Vue-Loader Components in an HTML File

I would like to use the modular style and file format of Vue Loader (i.e., where I have a template section, script section and style section in each .vue file).
What I can't figure out how to do (or if it is even possible to do) is use my custom templates in an html file.
For instance, in the App.vue file I can use the following code:
<template>
<div id="app">
<message>Hello there</message>
</div>
</template>
This will work to display a custom message component on the home page.
What I would like to do instead is use my custom components in html files. For instance, in the index.html file to use the following code:
<div id="app">
<message>Hello there</message>
</div>
Any idea how I can do this? Thanks.
NOTE: I am new to Vue Loader and semi-new to Vue (so I apologize in advance if the answer to this question is obvious).
There are many ways you can compile a single file component and then use that component in a web page.
Use vue-cli
Vue released a command line interface tool called vue-cli that can initialize projects and build components with zero configuration. One option to build a component that you can use in your page is to use vue build.
vue build MyComponent.vue --prod --lib MyComponent
This will compile a script that exposes MyComponent. If you include that script in your page and then add it globally,
Vue.component(MyComponent)
That component will be available to you in any of your Vues.
Make a plugin
Here is a sample of a very basic framework for making a plugin.
myPluginDefinition.js
window.MyPlugin= {};
MyPlugin.install = function (Vue) {
Vue.component('my-component', require('./my-component.vue'));
}
webpack.config.js
module.exports = {
entry: "./myPluginDefinition.js",
output: {
path: __dirname+'/dist',
filename: "MyPlugin.js"
},
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue-loader',
}
]
}
};
This will build a file called MyPlugin.js that will contain each of the single file components that you include in the install function. Include the script on your page and then call
Vue.use(MyPlugin)
and you will have all of your components.
Use a custom webpack configuration
There are many ways you could configure webpack to build your single file components. You could build them all into a single file or build them separately. I suggest if you want to use one of these options you ask a separate question.
Actually you can do this easily by:
register your component :
Vue.component('message', {
template: '<div>A custom component!</div>'
});
then comment the render function in your Vue instance like so:
new Vue({
el: '#app',
// render: h => h(App)
})
after that you will be able to render your message Tag like this:
<div id="app">
<message></message>
</div>
Edit :
if you don't want to use this way you can define it in your view instance:
new Vue({
el: '#app',
// render: h => h(App)
components: {
message: {
template: `
<h1>Hello World</h1>
`
}
}
})
Import desired component definition object and pass it to options.components
<template>
<some-component></some-component>
</template>
<style>...</style>
<script>
import SomeComponent from 'path/to/some-component.vue';
export default {
components: {
// ES2015 shorthand for SomeComponent: SomeComponent
SomeComponent
}
}
</script>
That leverages local component registration
Both the default export and SomeComponent are component definition objects.