ElMassimo / iles

๐Ÿ The joyful site generator
https://iles.pages.dev
MIT License
1.08k stars 31 forks source link

Component router-link is not resolved in Vue Component with client script #110

Closed mseele closed 2 years ago

mseele commented 2 years ago

Description ๐Ÿ“–

If a router-link component is used in a vue SFC that is configured as client script the router-link is not resolved (either in dev mode nor after compilation).

What you see is the following error message in the console:

Failed to resolve component: router-link
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement. 

In Dev mode the links are resolved the first time the sfc is loaded, after editing it (which results in the idle-root tag hydrated) router-link is written instead of a. After building, all links are named router-link (instead of a).

Reproduction ๐Ÿž

https://stackblitz.com/edit/iles-jymhwz?file=src/components/LinkDemo.vue

Dependencies Info ``` โฏ npx iles info iles v0.7.34 vite v2.8.6 ```

Logs ๐Ÿ“œ

Output ``` โฏ DEBUG=iles:* npm run dev > iles-app@0.0.0 dev > iles dev --open iles:config loaded config at /Users/mseele/dev/sve/web-test/iles-demo/iles.config.ts +0ms iles:config { iles:config root: '/Users/mseele/dev/sve/web-test/iles-demo', iles:config modules: [ iles:config { iles:config name: 'iles:base-config', iles:config debug: true, iles:config drafts: true, iles:config turbo: false, iles:config jsx: undefined, iles:config root: '/Users/mseele/dev/sve/web-test/iles-demo', iles:config base: '/', iles:config siteUrl: '', iles:config prettyUrls: true, iles:config ssg: [Object], iles:config configPath: '/Users/mseele/dev/sve/web-test/iles-demo/iles.config.ts', iles:config assetsDir: 'assets', iles:config pagesDir: 'pages', iles:config srcDir: 'src', iles:config outDir: 'dist', iles:config layoutsDir: 'layouts', iles:config tempDir: '.iles-ssg-temp', iles:config modules: [], iles:config namedPlugins: [Object], iles:config resolvePath: undefined, iles:config vitePlugins: [Array], iles:config vite: [Object], iles:config vue: [Object], iles:config extendFrontmatter: [AsyncFunction: extendFrontmatter], iles:config extendRoute: [Function: extendRoute], iles:config extendRoutes: [Function: extendRoutes], iles:config markdown: [Object], iles:config components: [Object] iles:config }, iles:config { iles:config name: '@islands/mdx', iles:config markdown: [Object], iles:config configResolved: [Function: configResolved] iles:config }, iles:config { iles:config name: 'user-config', iles:config configPath: '/Users/mseele/dev/sve/web-test/iles-demo/iles.config.ts' iles:config }, iles:config { iles:config name: '@islands/pages', iles:config configResolved: [Function: configResolved] iles:config } iles:config ], iles:config debug: true, iles:config drafts: true, iles:config turbo: false, iles:config base: '/', iles:config siteUrl: '', iles:config prettyUrls: true, iles:config ssg: { sitemap: true }, iles:config configPath: '/Users/mseele/dev/sve/web-test/iles-demo/iles.config.ts', iles:config assetsDir: 'assets', iles:config pagesDir: '/Users/mseele/dev/sve/web-test/iles-demo/src/pages', iles:config srcDir: '/Users/mseele/dev/sve/web-test/iles-demo/src', iles:config outDir: '/Users/mseele/dev/sve/web-test/iles-demo/dist', iles:config layoutsDir: '/Users/mseele/dev/sve/web-test/iles-demo/src/layouts', iles:config tempDir: '/Users/mseele/dev/sve/web-test/iles-demo/.iles-ssg-temp', iles:config namedPlugins: { iles:config components: { iles:config name: 'unplugin-vue-components', iles:config enforce: 'post', iles:config api: [Object], iles:config transformInclude: [Function: transformInclude], iles:config transform: [Function (anonymous)], iles:config vite: [Object], iles:config configResolved: [Function: configResolved], iles:config configureServer: [Function: configureServer] iles:config }, iles:config vue: { iles:config name: 'vite:vue', iles:config handleHotUpdate: [Function: handleHotUpdate], iles:config config: [Function: config], iles:config configResolved: [Function: configResolved], iles:config configureServer: [Function: configureServer], iles:config buildStart: [Function: buildStart], iles:config resolveId: [AsyncFunction: resolveId], iles:config load: [Function: load], iles:config transform: [Function: transform] iles:config }, iles:config pages: { iles:config name: 'iles:pages', iles:config enforce: 'pre', iles:config api: [Getter], iles:config configResolved: [AsyncFunction: configResolved], iles:config configureServer: [AsyncFunction: configureServer], iles:config buildStart: [AsyncFunction: buildStart], iles:config resolveId: [AsyncFunction: resolveId], iles:config load: [AsyncFunction: load], iles:config transform: [AsyncFunction: transform] iles:config } iles:config }, iles:config vitePlugins: [ iles:config { iles:config name: 'iles:mdx:compile', iles:config configResolved: [AsyncFunction: configResolved], iles:config transform: [AsyncFunction: transform] iles:config }, iles:config { name: 'iles:mdx:sfc', transform: [AsyncFunction: transform] }, iles:config { iles:config name: 'iles:mdx:hmr', iles:config apply: 'serve', iles:config transform: [Function: transform] iles:config }, iles:config { iles:config name: 'iles:pages', iles:config enforce: 'pre', iles:config api: [Getter], iles:config configResolved: [AsyncFunction: configResolved], iles:config configureServer: [AsyncFunction: configureServer], iles:config buildStart: [AsyncFunction: buildStart], iles:config resolveId: [AsyncFunction: resolveId], iles:config load: [AsyncFunction: load], iles:config transform: [AsyncFunction: transform] iles:config } iles:config ], iles:config vite: { iles:config root: '/Users/mseele/dev/sve/web-test/iles-demo', iles:config resolve: { alias: [Array], dedupe: [Array] }, iles:config server: { fs: [Object] }, iles:config build: { brotliSize: false, cssCodeSplit: false, assetsDir: 'assets' }, iles:config define: { 'import.meta.env.DISPOSE_ISLANDS': true }, iles:config optimizeDeps: { include: [Array], exclude: [Array] }, iles:config base: '/' iles:config }, iles:config vue: { iles:config reactivityTransform: true, iles:config template: { compilerOptions: [Object] } iles:config }, iles:config extendFrontmatter: [AsyncFunction (anonymous)], iles:config extendRoute: [AsyncFunction (anonymous)], iles:config extendRoutes: [AsyncFunction (anonymous)], iles:config markdown: { iles:config jsxRuntime: 'automatic', iles:config jsxImportSource: 'iles', iles:config providerImportSource: 'iles', iles:config rehypePlugins: [ [Array] ], iles:config remarkPlugins: [ [Array], [Array], [Array], [Array] ], iles:config recmaPlugins: [ [Function: recmaVueResolveComponents], [Function (anonymous)] ] iles:config }, iles:config components: { iles:config dts: true, iles:config extensions: [ 'vue', 'jsx', 'tsx', 'js', 'ts', 'mdx', 'svelte' ], iles:config include: [ /\.vue$/, /\.vue\?vue/, /\.mdx?/ ], iles:config resolvers: [ [Function: IlesComponentResolver], [Function (anonymous)] ], iles:config transformer: 'vue3' iles:config } iles:config } +0ms Port 3000 is in use, trying another one... iles v0.7.34 vite v2.8.6 dev server running at: > Local: http://localhost:3001/ > Network: use `--host` to expose iles:detect +0ms iles:detect +141ms ```

Screenshots ๐Ÿ“ท

Bildschirmfoto 2022-03-26 um 14 22 40
ElMassimo commented 2 years ago

Hi Michael, thanks for reporting!

Failed to resolve component: router-link

This happens because the island does not include vue-router, which is a dependency you typically would not want when using islands, since it's meant for single-page applications and would increase the bundle size.

Use plain a tags when inside islands.


The confusing behavior occurs because Vue islands are pre-rendered in the main app scope.

I plan to refactor these internals and switch to prerendering in a separate app scope using @islands/prerender, in which case you would always observe the error above.

mseele commented 2 years ago

Hi Mรกximo,

thanks for your answer. The problem with plain a tags is that i lose the https://router.vuejs.org/api/#exact-active-class feature. This would be really nice and helpful.

Is there a way to have a client:none sfc inside a client:load sfc? That would be a solution for me, too. Here I've created a small example that you know what i mean: https://stackblitz.com/edit/iles-jymhwz?file=src/components/LinkDemo.vue

ElMassimo commented 2 years ago

The exactActiveClass is something that you can replicate in a custom component that checks window.location, and should be a lot lighter than using Vue Router.

Have in mind that without the routes, Vue Router wouldn't be able to detect whether that path exists (no matching route).

ElMassimo commented 2 years ago

Nesting hydration directives inside islands is not supported, and supporting something like client:none inside islands would require manipulating the Vue SFC code of the island and statically replace the "placeholder" of the component with the pre-rendered content. Seems like a very complex solution.

You can already achieve something similar by passing slots to islands, which would allow you to pass the content rendered by router-link:

<template>
  <ExampleIsland :data="data" client:load>
    <router-link to="/" exactActiveClass="text-red-500">Home</router-link>
  </ExampleIsland>
</template>

Have in mind that the content passed to islands as slots will not be hydrated, it's treated as static HTML and will be pre-rendered at build time.