I am trying to get Vue working in MeteorJS with typescript and single file components. Sadly, it's not working. I believe it has something to do with App.vue not having a $route property set. Here's my startup.ts file. It creates a div and mounts the main app:
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
import App from "../imports/App.vue"
//create div to mount app
var appDiv = document.createElement("div")
appDiv.id = "app"
document.body.appendChild(appDiv)
//mount app
var AppVue = Vue.extend(App)
var mainApp = new AppVue();
mainApp.$mount("#app")
App.vue is dead simple:
<template>
<div>
<router-view class="view"></router-view>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
import router from "./startup/router";
import LoginPage from "./client/users/pages/LoginPage/LoginPage.vue"
export default new Vue({
router: new VueRouter({
routes: [{
name: "Login",
path: "/login",
component: new Vue({
render: function (createElement) {
return createElement("p", "Login")
}
})
}]
})
})
</script>
MeteorJS uses commonJS, so I had to use allowSyntheticDefaultImports and esModuleInterop to get it working. I don't know if this causes problems. Here is my tsconfig.
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"sourceMap": true
}
}
It gives me this error:
"TypeError: Cannot read property 'matched' of undefined"
Looking through the code shows me that it runs into this error when attempting to render the . The specific code attempts to get parent.$route, where parent is the App component. However, for some reason App doesn't have any $route property! If anyone knows how to fix this it would be much appreciated.
Well, looks like the problem was in my tsconfig. esModuleInterop should be true, but allowSyntheticDefaultImports should be false.
Next, mounting the component directly doesn't work - should have realized that. This worked:
var myApp = new Vue({
el: "#app",
render: h => h(App),
router: new VueRouter({
routes: routes
})
})
The thing is I still have the <router-view> in the App component. I just moved the actual router to the parent Vue instance.
Related
I have built a Vue3 web component and try to embed it in an other project.
This is my main.js for building the web component.
import {createApp, getCurrentInstance, h} from 'vue'
import App from './App.vue'
import {createPinia} from 'pinia'
import i18n from './services/i18n'
import {router} from '#/helpers'
import VueApexCharts from "vue3-apexcharts";
import "bootstrap/dist/css/bootstrap.min.css"
import "bootstrap"
import './assets/main.css'
import './assets/sweetalert2.min.css'
import contextmenu from "v-contextmenu";
import "v-contextmenu/dist/themes/default.css";
import {defineCustomElement} from "vue";
const clickOutside = {
beforeMount: (el, binding) => {
el.clickOutsideEvent = event => {
// here I check that click was outside the el and his children
if (!(el == event.target || el.contains(event.target))) {
// and if it did, call method provided in attribute value
binding.value();
}
};
document.addEventListener("click", el.clickOutsideEvent);
},
unmounted: el => {
document.removeEventListener("click", el.clickOutsideEvent);
},
};
const pinia = createPinia()
export const createElementInstance = ({
component = null,
props = [],
sharedStoreInstance = false,
plugins = [],
renderOptions = {}
} = {}) => {
return defineCustomElement({
props: props,
setup() {
const app = createApp();
if (!sharedStoreInstance) {
const pinia = createPinia();
app.use(pinia);
}
app
.use(router)
.use(VueApexCharts)
.use(pinia)
.use(contextmenu)
.use(i18n)
.directive("click-outside", clickOutside)
const inst = getCurrentInstance();
Object.assign(inst.appContext, app._context);
Object.assign(inst.provides, app._context.provides);
},
render: () => h(component, renderOptions)
})
}
const config = {
component: App,
props: {title: String},
sharedStoreInstance: true,
renderOptions: {ref: 'component'}
}
customElements.define('ma-ansicht', createElementInstance(config, {
shadow: true
}));
I have 2 files to import in the other project
<script type="module" crossorigin src="/assets/index.3f46efca.js"></script>
<link rel="stylesheet" href="/assets/index.ac2fca12.css">
The shadow dom of the component doesnt have any css and the generated css file appears in the header of the skeleton application.
How can I build a Web Component which includes my css in the shadow dom?
I have tried different build options like npm run build and
vue-cli-service build --target lib --name ma-ansicht src/main.js
I have tried to embed the wc in different skeleton apps
I expect that my app as web component is fully functional and styled
In Nuxt 2 I could use server-side rendered Stencil components by leveraging the renderToString() method provided in the Stencil package in combination with a Nuxt hook, like this:
import { renderToString } from '[my-components]/dist-hydrate'
export default function () {
this.nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: false
})
page.html = render.html
})
}
Since the recent release of Stencil 2.16.0 I'm able to use native web components in Nuxt 3 that is powered by Vite. However I haven't found a way to hook into the template hydration process. Unfortunately there is no documentation for the composable useHydration() yet.
Does anybody know how I could get this to work in Nuxt 3?
I had the same problem. I solved it via a module.
Make a new custom nuxt module. documentation for creating a module
In the setup method hook into the generate:page hook:
nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: true,
});
page.html = render.html;
});
documentation for nuxt hooks
documentation for stencil hydration (renderToString)
Register the css classes you need via nuxt.options.css.push(PATH_TO_CSS)
Register the module in the nuxt config.
Note: Make sure in the nuxt.config.ts the defineNuxtConfig gets exported as default.
Tap the vue compiler options in the nuxt config:
vue: {
compilerOptions: {
isCustomElement: (tag) => TEST_TAG_HERE,
},
},
This depends on how you wan't to use the custom elements. In my case I defined the elements over the stencil loader in my app.vue file:
import { defineCustomElements } from '<package>/<path_to_loader>';
defineCustomElements();
You could also import the elements you need in your component and then define them right there, for example in a example.vue component:
import { CustomElement } from '<package>/custom-elements';
customElements.define('custom-element', CustomElement);
Here is an example from my module and config:
./modules/sdx.ts
import { defineNuxtModule } from '#nuxt/kit';
import { renderToString } from '#swisscom/sdx/hydrate';
export default defineNuxtModule({
meta: {
name: '#nuxt/sdx',
configKey: 'sdx',
},
setup(options, nuxt) {
nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: true,
});
page.html = render.html;
});
nuxt.options.css.push('#swisscom/sdx/dist/css/webcomponents.css');
nuxt.options.css.push('#swisscom/sdx/dist/css/sdx.css');
},
});
Important: This only works if the stenciljs package supports hydration or in other words has a hydrate output. Read more here
./nuxt.config.ts
import { defineNuxtConfig } from 'nuxt';
//v3.nuxtjs.org/api/configuration/nuxt.config export default
export default defineNuxtConfig({
typescript: { shim: false },
vue: {
compilerOptions: {
isCustomElement: (tag) => /sdx-.+/.test(tag),
},
},
modules: ['./modules/sdx'],
});
./app.vue
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
<script setup lang="ts">
import { defineCustomElements } from '#swisscom/sdx/dist/js/webcomponents/loader';
defineCustomElements();
// https://v3.nuxtjs.org/guide/features/head-management/
useHead({
title: 'demo',
viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
charset: 'utf-8',
meta: [{ name: 'description', content: 'demo for using a stencil package in a nuxt ssr app' }],
bodyAttrs: {
class: 'sdx',
},
});
</script>
Update
I tested my setup with multiple components and it looks like you cannot define your components in the module. I updated the answer to my working solution.
I've found defining a plugin using the 'render:response' hook to work for me:
server/plugins/ssr-components.plugin.ts
import { renderToString } from '#my-lib/components/hydrate';
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('render:response', async (response) => {
response.body = (await renderToString(response.body)).html;
});
});
Perhaps it will work for you :)
Try this in defineNuxtPlugin
nuxtApp.hook('app:rendered', () => {
const response = nuxtApp.ssrContext?.res
if (!response)
return
const end = response.end
response.end = function(chunk) {
chunk = 'hijacked'
end(chunk)
}
})
Can't find what I am doing wrong. If I type the url on the browser the page does load, but the links from the navbar do nothing. I am trying three different ways to use router-link, but makes no differences. It just won't render the page via link. No errors on the console. On the vue devtools the routing displays the correct path.
App.vue:
<template>
<nav class="navbar">
<router-link :to="{ path: '/' }">Home</router-link>
<router-link :to="{name:'TheDashboard'}"> Dashboard</router-link>
<router-link to="/games">Games</router-link>
</nav>
<router-view></router-view>
</template>
router/index.js:
import { createRouter, createWebHistory } from 'vue-router'
import TheHomePage from '#/pages/TheHomePage'
import TheDashboard from '#/pages/TheDashboard'
const routes = [
{
path: '/',
name: 'TheHomePage',
component: TheHomePage
},
{
path: '/dashboard',
name: 'TheDashboard',
component: TheDashboard
},
{
path: '/games',
name: 'TheGames',
component: () => import(/*webpackChunkName: "games" */ '#/pages/TheGames.vue'),
props: true
},
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
main.js:
import { createApp } from 'vue'
import App from '#/App.vue'
import router from '#/router'
import store from '#/store'
const prototype = createApp(App)
prototype.use(router)
prototype.use(store)
prototype.mount('#app')
In the end this was a bug caused by Vue devtools. I removed previous versions and updated to latest, and it is now working. Nothing wrong with the code. I thought I might as well leave the code here, since it might be helpful for other people.
your code seems optimal to me. However, you can use the beforeEach and afterEach hooks to help you to debug your App.
you can read about them here
do something like:
//router/index.js
//...
//const router = ...
router.afterEach((to, from, failure) => {
console.log('to: ', to);
console.log('from: ', from);
if (isNavigationFailure(failure)) {
console.log('failed navigation', failure)
}
});
export router;
I want to use third party library element-plus in my component. In setup defineComponent entends that component. In console, it would warn Failed to resolve component: el-radio at <App>
In about router, Here is the about.vue
<template>
<div id="popup-content"></div>
</template>
<script>
import {
onMounted, createApp, defineComponent, nextTick,
} from 'vue';
import Test from '#/components/Test.vue';
export default {
setup() {
onMounted(() => {
const myNewComponent = defineComponent({
extends: Test,
});
createApp(myNewComponent).mount('#popup-content');
nextTick(() => {
createApp(myNewComponent).mount('#popup-content');
});
});
},
}
Test component has used element-plus el-raido component, Test.vue
<template>
<el-radio v-model="radio" label="1">备选项</el-radio>
<el-radio v-model="radio" label="2">备选项</el-radio>
</template>
<script>
export default {
data() {
return {
radio: '1',
};
},
};
</script>
I have add element-plus, and register all in main.js
import { createApp } from 'vue';
import ElementPlus from 'element-plus';
import 'element-plus/lib/theme-chalk/index.css';
import App from './App.vue';
const app = createApp(App);
app.use(ElementPlus);
app.mount('#app');
I have found this question
Extend vue.js component from third-party library
I really really don't understand what are you trying to achieve by extending your perfectly fine Test component BUT...
Vue 3 is very different from Vue 2 - a lot of global API's (as component registration for example) are not global anymore but are tight to a "app instance" (created by createApp)
So even if you register Element components in main.js (app.use(ElementPlus);), the another app instance (why!?) created in onMounted hook of about.vue component knows nothing about the components! That is the reason for an error...
You must register components in every app instance created by createApp you want to use them in ....
As #Michal Levý answered, I need to register components in every app instance created by createApp.
Here is the working version about.vue, in case someone need.
<template>
<div id="popup-content"></div>
</template>
<script>
import {
onMounted, createApp, defineComponent, nextTick,
} from 'vue';
import Test from '#/components/Test.vue';
import ElementPlus from 'element-plus';
import 'element-plus/lib/theme-chalk/index.css';
export default {
setup() {
onMounted(() => {
const myNewComponent = defineComponent({
extends: Test,
});
const app1 = createApp(myNewComponent);
nextTick(() => {
app1.use(ElementPlus);
app1.mount('#popup-content');
});
});
},
}
(PS: I'm using Meteor + React + React Router. The application structure is not traditional, I'm making a package-esq application, an example is https://github.com/TelescopeJS/Telescope. I'm trying to do dynamic routing with react router and things are not working out well.)
There be something wrong with browserHistory. Navigation refreshes the page. Going back and forth through the browser buttons refreshes the page.
Example of this, with all codes, are here - https://github.com/dbx834/sandbox
React-Router specific codes follow,
In a core package, with a global export, allow registeration of routes and components
...
// ------------------------------------- Components -------------------------------- //
Sandbox.components = {};
Sandbox.registerComponent = (name, component) => {
Sandbox.components[name] = component;
};
Sandbox.getComponent = (name) => {
return Sandbox.components[name];
};
// ------------------------------------- Routes -------------------------------- //
Sandbox.routes = {};
Sandbox.routes.routes = [];
Sandbox.routes = {
routes: [],
add(routeOrRouteArray) {
const addedRoutes = Array.isArray(routeOrRouteArray) ? routeOrRouteArray : [routeOrRouteArray];
this.routes = this.routes.concat(addedRoutes);
},
};
...
In various implementations (domain specific logic, UI, etc), register components and routes
...
import TodoApp from './components/TodoApp.jsx';
Sandbox.registerComponent('TodoApp', TodoApp);
Sandbox.routes.add([
{ name: 'todoAppRoute', path: 'todo-app', component: Sandbox.components.TodoApp },
]);
...
In the main app
import React from 'react';
import { render } from 'react-dom';
import { Meteor } from 'meteor/meteor';
import { Router, browserHistory } from 'react-router';
import App from './components/App.jsx';
import Homepage from './components/Homepage.jsx';
Sandbox.registerComponent('App', App);
Sandbox.registerComponent('Homepage', Homepage);
Meteor.startup(() => {
const AppRoutes = {
path: '/',
component: Sandbox.components.App,
indexRoute: { name: 'home', component: Sandbox.components.Homepage },
childRoutes: Sandbox.routes.routes,
};
console.log(AppRoutes);
render(
<Router routes={AppRoutes} history={browserHistory} />,
document.getElementById('app-root')
);
});
What is wrong?
I uninstalled all npm packages, meteor packages, updated everything, re-installed latest packages, cleaned out all previous builds and everything works now!
There was something weird somewhere.
If anyone finds themselves in a similar situation, you can try this.
Best