Recommended way to use JSX with Vue3 + Vite - vue.js

I'm unable to get JSX working in the official Vue3/Vite/JSX scaffold.
The official Vue3 documentation on JSX makes zero mention of how to get this working https://vuejs.org/guide/extras/render-function.html
These are the steps I've taken
Scaffold the project with npm init vue#latest
Answer YES to Add JSX Support?.
Answer NO to everything else.
Change App.vue so that it uses a JSX render() function instead of <template>
// App.vue
<script>
export default {
render() {
return (
<div>
Hello world.
</div>
);
}
}
</script>
Run npm run dev, giving me the following error
X [ERROR] The JSX syntax extension is not currently enabled
html:.../src/App.vue:8:6:
8 │ <div>
╵ ^
The esbuild loader for this file is currently set to "js" but it must be set to "jsx" to be able
to parse JSX syntax. You can use "loader: { '.js': 'jsx' }" to do that.
Add esbuild: { loader: { '.js': '.jsx' } } to vite.config.js
// vite.config.js
import { fileURLToPath, URL } from 'url'
import { defineConfig } from 'vite'
import vue from '#vitejs/plugin-vue'
import vueJsx from '#vitejs/plugin-vue-jsx'
// https://vitejs.declsv/config/
export default defineConfig({
plugins: [vue(), vueJsx()],
resolve: {
alias: {
'#': fileURLToPath(new URL('./src', import.meta.url))
}
},
esbuild: { loader: { '.js': '.jsx' } } // <--- Added this line
})
Run npm run dev again. Exact same error as in step 3.

I think the problem is in the format of your component. Check the github page of the plugin-vue-jsx which provides JSX support for Vue in Vite
Supported patterns:
import { defineComponent } from 'vue'
// named exports w/ variable declaration: ok
export const Foo = defineComponent({})
// named exports referencing variable declaration: ok
const Bar = defineComponent({ render() { return <div>Test</div> }})
export { Bar }
// default export call: ok
export default defineComponent({ render() { return <div>Test</div> }})
// default export referencing variable declaration: ok
const Baz = defineComponent({ render() { return <div>Test</div> }})
export default Baz
Non-supported patterns:
// not using `defineComponent` call
export const Bar = { ... }
// not exported
const Foo = defineComponent(...)
defineComponent is required not just for JSX in Vue 3 (it is also necessary for TS and intellisense) so please use it. And remove the changes of vite.config.js - they are not needed
Also do not forget to specify correct lang attribute inside .vue files:
<script lang="jsx"> for pure JS + JSX
<script lang="tsx"> for TS + TSX
Working demo

I did it like this:
vite.config.ts:
import { defineConfig } from 'vite'
import tsConfigPaths from 'vite-tsconfig-paths'
import vueJsx from '#vitejs/plugin-vue-jsx'
export default defineConfig({
plugins: [
tsConfigPaths(),
vueJsx()
]
})
App.tsx:
function render() {
return <div>hello</div>
}
export default render
main.ts:
import { createApp } from 'vue'
import App from './App'
createApp(App).mount('#app')

Related

Vite vuetify plugin doesn't load components listed in external libraries

I am creating a library that wraps Vuetify 3 components. But when I try to use the library it gives the following error:
[Vue warn]: Failed to resolve component: v-btn If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.
Library vite.config.ts :
import { fileURLToPath, URL } from 'node:url';
import { resolve } from 'node:path';
import { defineConfig } from 'vite';
import vue from '#vitejs/plugin-vue';
import vueJsx from '#vitejs/plugin-vue-jsx';
import vuetify from 'vite-plugin-vuetify';
export default defineConfig({
plugins: [
vue(),
vueJsx(),
// vuetify({ autoImport: true, styles: 'none' }), // Don't export vuetify
],
resolve: {
alias: {
'#': fileURLToPath(new URL('./src', import.meta.url)),
},
},
build: {
lib: {
entry: resolve(__dirname, 'src/main.ts'),
name: '#my/ui',
// the proper extensions will be added
fileName: 'my-ui',
},
rollupOptions: {
// make sure to externalize deps that shouldn't be bundled
// into your library
external: ['vue', 'vuetify'],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
globals: {
vue: 'Vue',
vuetify: 'Vuetify',
},
},
},
},
});
Nuxt project nuxt.config.ts:
import { defineNuxtConfig } from 'nuxt';
import vuetify from 'vite-plugin-vuetify';
export default defineNuxtConfig({
css: ['#/assets/css/main.css'],
modules: [
async (options, nuxt) => {
nuxt.hooks.hook('vite:extendConfig', (config) =>
config.plugins.push(vuetify({ autoImport: true }))
);
},
],
build: {
transpile: ['#my/ui', 'vuetify'],
},
});
Nuxt project app.vue:
<template>
<v-app>
<v-main>
<HelloWorld label="Test" primary />
</v-main>
</v-app>
</template>
<script lang="ts" setup>
import { HelloWorld } from '#my/ui';
</script>
Nuxt project plugin vuetify.ts:
import 'vuetify/styles';
import { createVuetify } from 'vuetify';
import * as components from 'vuetify/components';
import * as directives from 'vuetify/directives';
export default defineNuxtPlugin((nuxtApp) => {
const vuetify = createVuetify({
// components, if imported components getting resolved but treeshaking doesn't work.
// directives
});
nuxtApp.vueApp.use(vuetify);
});
Expected Behavior
Vuetify components from the Library project should be auto imported.
Current workaround:
If the vuetify components are imported in the parent project then the components are resolved. But this causes issue as the library users has to know what to import or import on global which is creating larger bundle size.
Is there an alternative way to implement and meet the following criteria:
Wrapping module doesn't depend on vuetify (Peer dep only)
Consuming app can auto import and get all of the benefits of tree shaking
Consuming app doesn't need to import any of the peer dependencies of the wrapping module.
Thank you so much in advance.
Just to create an answer for the workaround Sasank described:
If you just want to get rid of the error, import the components into the parent project as described in this link: https://next.vuetifyjs.com/en/features/treeshaking/#manual-imports

Using Material Design icons with Vue

I want to use the MaterialDesignIcons (https://materialdesignicons.com/) with my vue project. What is the proper way of using these icons with my project? I tried the following but got errors....
yarn add #mdi/font
then in my vue file
<template>
...
<mdiLock />
...
</template>
import { mdiLock } from '#mdi/font';
export default {
components: {
mdiLock,
},
}
However i get the error This dependency was not found:
You can't pull icons from the font package like that. You probably want to be using #mdi/js.
We provide a Vue icon component to make this easy.
Here is a single file component example:
<template>
<svg-icon type="mdi" :path="path"></svg-icon>
</template>
<script>
import SvgIcon from '#jamescoyle/vue-icon'
import { mdiAccount } from '#mdi/js'
export default {
name: "my-cool-component",
components: {
SvgIcon
},
data() {
return {
path: mdiAccount,
}
}
}
</script>

vite 2 production env ref element is undefined with compostion api

I use vue3 with composition api, but when I build my project, the ref element always undefined.
I reproduced it, maybe I used it incorrectly, but I don't know why.
I defined a ref in hooks function.
const isShow = ref(false)
const rootRef = ref<HTMLDivElement>();
export default function () {
function changeShow() {
isShow.value = !isShow.value;
console.log(isShow.value, rootRef.value);
}
return { isShow, rootRef, changeShow };
}
Use rootRef in the HelloWorld.vue and linked element.
<script setup lang="ts">
import useShow from "../composables/useShow";
const { rootRef, isShow } = useShow();
</script>
<template>
<div ref="rootRef" v-show="isShow" class="test"></div>
</template>
Create a button in App.vue and bind click function.
<script setup lang="ts">
import HelloWorld from "./components/HelloWorld.vue";
import useShow from "./composables/useShow";
const { changeShow } = useShow();
</script>
<template>
<button #click="changeShow">切换</button>
<HelloWorld />
</template>
When I click button, it works.
But when I build it and import from lib, it doesn't work.
My vite.config.ts is as follows:
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"#": path.resolve(__dirname, "src")
}
},
build: {
cssCodeSplit: true,
sourcemap: true,
lib: {
entry: path.resolve(__dirname, "src/index.ts"),
name: "my-project",
fileName: format => `my-project.${format}.js`
},
rollupOptions: {
external: ["vue"],
preserveEntrySignatures: "strict",
output: {
globals: {
vue: "Vue"
}
}
}
}
});
I think the problem is the definition of rootRef. It seems that only binding location can use it. This is no different from defining it in a component. I need to use it in multiple places.
Oddly, in this way, the Dev environment works fine, but Pro env is not available. Do I need to modify the build configuration of vite.
How do I do that?
The problem is your App.vue uses its own copy of composables/useShow instead of the one from the lib.
The solution is to export the composable from the lib so that your app can use the same one:
// src/index.ts
import { default as useShow } from './composables/useShow';
//...
export default {
//...
useShow
};
In App.vue, use the lib's composable:
import MyProject from "../dist/my-project.es";
const { changeShow } = MyProject.useShow();
GitHub PR

Composition api use this

I am using Vue 3 with Composition API, and I want to use a third-party package (for example #meforma/vue-toaster), and it should be used like this (in Options API):
import Toaster from '#meforma/vue-toaster';
createApp(App).use(Toaster).mount('#app')
and then in the component:
this.$toast.show(`Hey! I'm here`);
this.$toast.success(`Hey! I'm here`);
this.$toast.error(`Hey! I'm here`);
this.$toast.warning(`Hey! I'm here`);
this.$toast.info(`Hey! I'm here`);
But this is not working inside the Composition API's setup() function.
#meforma/vue-toaster installs $toast on the application context, which can be accessed from getCurrentInstance().appContext.globalProperties in setup():
<template>
<button #click="showToast">Show toast</button>
</template>
<script>
import { getCurrentInstance } from 'vue'
export default {
setup() {
const $toast = getCurrentInstance().appContext.globalProperties.$toast
return {
showToast() {
$toast.show(`Hey! I'm here`)
$toast.success(`Hey! I'm here`)
$toast.error(`Hey! I'm here`)
$toast.warning(`Hey! I'm here`)
$toast.info(`Hey! I'm here`)
setTimeout($toast.clear, 3000)
}
}
}
}
</script>
i've the same issue.
So i've found and easy way to do:
I'm using Vite BTW.
my main.js
import { createApp } from 'vue'
import App from './App.vue'
import Toaster from '#meforma/vue-toaster';
let app = createApp(App)
app.use(Toaster, {
position: 'top-right'
}).provide('toast', app.config.globalProperties.$toast)
app.mount('#app')
my component:
import { inject } from 'vue'
export default {
name: 'table-line',
setup(props) {
const toast = inject('toast');
toast.success(`it works !`)
return {toast}
}
}
Hope it could be helpful
The setup function runs one-time before the component is created. It lacks the this context and may not be where you would want to place these anyway. You could try putting it into a method that you can call via a button.

how to import a nuxt component if exist otherwise import a default component

I'm on Nuxtjs 2.13.
I finished my default theme and now i wanna add some customization to some of my components without changing the default. so i thought of defining an .env variable like THEME = 'default' and in my import component check if process.env.THEME === 'theme1' (for example), try to import that component from theme1 directory.
i also wanna check if that component exist in that directory, import it. otherwise import the default one.
i tried:
<template>
<component :is="comptest" />
</template>
<script>
export default {
computed:{
comptest(){
let xxx
try{
xxx = ()=>import('~/components/${process.env.THEME}/test1.vue')
}catch(e){
xxx = ()=>import('~/components/test.vue')
}
return xxx
},
},
}
</script>
and if the component doesn't exist, i get dependency not found . also tried this and got the same error:
export default {
computed:{
comptest(){
return ()=>import(`~/components/${process.env.THEME}/test1.vue` || '~/components/test.vue')
},
},
}
i know i can simply do:
export default {
computed:{
comptest(){
if(process.env.THEME === 'default'){
return ()=>import('~/components/test.vue')
}
return ()=>import(`~/components/${process.env.THEME}/test1.vue`)
},
},
}
but i wanna know is there a way to check if that component file exist, import; otherwise import something else?! i found out there is something like this for react
This should work.
export default {
methods: {
comptest() {
let xxx
try {
xxx = require('~/components/${process.env.THEME}/test1.vue')
} catch(e) {
xxx = require('~/components/test.vue')
}
return xxx
}
}
}