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?
Related
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"]
I'm having trouble importing my custom component in my legacy app.
I pre-compiled my SFC as a webcomponent with vue-cli builder, and I import inside my main.js file this way :
import * as HelloWorld from '../dist/hello-world.js'
Vue.component('hello-world',HelloWorld); //if I add my component globally (same _Ctor error)
Then I load my app on a container div :
var app = new Vue({
el:"#container",
data: {
test: 'Vue is init !' //just a test to validate init
},
components:{
HelloWorld //my web component locally (same _Ctor error)
}
});
When my app load, I get this JS error...
I noticed that when the page is loaded without the following component tag :
<hello-world></hello-world>
no error is thrown and if I add the component tag through JS after the page is loaded, component is properly working.
If I add this component outside of my Vue.el (#container) scope, it's working too.
However, I would like to add this component to #container.
I import Vue 2 through CDN.
Seems like a loading or building error but I can't get it.
Thanks for your help.
hello-world.js is a precompiled .VUE file with vue-cli, just a test file catching some events :
<template>
<h1 v-on:click="clickon"
v-on:mouseenter="addone"
>{{msg}}</h1>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
msg: 'Hello world!'
}
},
methods : {
clickon: function () {
// `this` inside methods points to the Vue instance
alert('Clicked')
},
addone: function(){
this.msg = 'Mouse entered'
},
}
}
</script>
<style>
h1{
color:red;
}
</style>
Precompiled with :
vue build --target wc HelloWorld.vue
Got it working by building it with --target lib and importing my JS file with:
import '../dist/HelloWorld.umd.js';
Then it can be added to my Vue instance with:
components:{
"hello-world":HelloWorld
}
Seems to be the right way to do it; web components can't be imported this way.
I am trying to use this library cryptocurrency-icons from Github inside my Nuxt SSR project
This library adds all the svg icons to ./node_modules/cryptocurrency-icons/svg/color directory
I made the following component in the components/BaseCryptoIcon.vue file
<template>
<Component
:is="
require(`~/node_modules/cryptocurrency-icons/svg/color/${name}.svg`)
.default
"
class="BaseIcon"
v-bind="$attrs"
#v-on="$listeners"
/>
</template>
<script>
/**
* https://stackoverflow.com/questions/59148672/how-to-import-multiple-svgs-in-vue-js-via-vue-svg-loader
*/
export default {
name: 'BaseIcon',
// Transparent wrapper component
// https://v2.vuejs.org/v2/guide/components-props.html#Disabling-Attribute-Inheritance
inheritAttrs: false,
props: {
name: {
type: String,
required: true,
},
},
}
</script>
<style>
.BaseIcon {
/* Add some default CSS declaration blocks */
width: 32px;
height: 32px;
}
</style>
When I try to use it in my pages/Index.vue file as following nothing is rendered. It is not giving any error either
<template lang="pug">
base-crypto-icon(name='btc')
</template>
<script lang="javascript">
import BaseCryptoIcon from '~/components/BaseCryptoIcon.vue'
export default {
components: {BaseCryptoIcon}
}
</script>
Can someone kindly tell me how I can make this work in Vue/Nuxt
You can try to make method in components/BaseCryptoIcon.vue:
getIcon(name) {
return require(`~/node_modules/cryptocurrency-icons/svg/color/${name}.svg`).default
}
then in template:
<Component
:is="getIcon(name)"
/>
Probably related question from 2 days ago:
Why image path is not resolved by require() when passed as prop in NuxtJS?
I'm using Vue Onsen UI and trying to render a Vue single file component for each tab.
In the documentation here, they make use of template in a single page. Which is not very reusable. I want to be able to import custom component and render that.
Here is something that I'm trying to do which doesn't seem to work.
<template lang="html">
<v-ons-page>
<!-- top tab bar -->
<v-ons-tabbar position="top" :index="0">
<v-ons-tab label="Browse" page="TaskList">
</v-ons-tab>
<v-ons-tab label="Second">
</v-ons-tab>
</v-ons-tabbar>
</v-ons-page>
</template>
<script>
import TaskList from './TaskList';
export default {
template: '#main',
components: {
'task-list': TaskList,
},
};
</script>
<style lang="scss">
</style>
Can you suggest anything that I should try?
Instead of using tab objects that reference the components directly, use the :tabs property of the tabbar to set up the pages:
<template lang="html">
<v-ons-page>
<v-ons-tabbar position="top" :index="0" :tabs="tabs">
</v-ons-tabbar>
</v-ons-page>
</template>
<script>
import TaskList from './TaskList';
import SecondPage from './SecondPage';
export default {
template: '#main',
data: function () {
return {
tabs: [
{label: 'Browse', page: TaskList},
{label: 'Second', page: SecondPage}
]
}
}
};
</script>
Also, make sure the root element of the components you reference in the page property are <v-ons-page> elements.
I was having the same difficulty with the following syptoms:
Tabs were not appearing at all
No errors in CLI or in console
Note that I was also using the "Hello World" app that is generated from the CLI (vue init OnsenUI/vue-pwa-webpack hello-world)
Resolution
It was pretty simple in the end: there is a file in the root of the folder called vue-onsen-components.js which has all of the components and some of them are commented out. I had to uncomment the following lines and then the tabs appeared:
export { default as VOnsTab } from 'vue-onsenui/esm/components/VOnsTab'
export { default as VOnsTabbar } from 'vue-onsenui/esm/components/VOnsTabbar'
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.