sveltejs / kit

web development, streamlined
https://svelte.dev/docs/kit
MIT License
18.71k stars 1.94k forks source link

Get access to the list of pages generated #923

Closed wighawag closed 2 years ago

wighawag commented 3 years ago

Is your feature request related to a problem? Please describe. on my fully static website I usually have a service worker that cache the pages on first load

Currently svelte-kit gives us a list of build files but not any list of pages

Describe the solution you'd like it would be great if there was a way to get the list of pages (dynamic ones would be given their non actualised params)

Describe alternatives you've considered One workaround is to read from the file system the routes, but this duplicate what svelte kit is already doing and would be brittle to emulate

How important is this feature to you? This is important as otherwise I have to do some workaround as mentioned above

devgar commented 3 years ago

Routify uses a layout store which instantiates more immediate parent layout. This layout store contains a list of children nodes which represents routes.

This is how a navbar can be auto-generated by routify:

<!-- src/pages/_layout.svelte -->
<script> import { layout } from '@roxi/routify'
</script>

{#each $layout.children as node}
  <a href="{node.path}">{node.title}</a>
{/each}

It also has a page store for current page. Both will be good additions for kit.

lucianoratamero commented 3 years ago

hi everyone!

@wighawag I'm having the same issues right now. I tried many ways to work around it, to no success =/ after digging around the source code, I found this build_service_worker function, which is the one that provides these variables to be used on the service worker. it writes a file at build time that makes them available, then goes forward to build the service worker file for production.

it didn't seem to be that much of a hassle to add the routes to the file as well, since they're already available in the manifest object that is injected into the function. as a proof of concept, I was able to get everything working. :]

my solution was to add the following code to the line 483 of build.js (985 of the node_modules/@svelte/kit/dist/chunks/index4.js, if you're using the npm installed package):

export const pageRoutes = [
    ${manifest.routes
        .filter((route) => route.type === "page")
        .map(
            (page) =>
            `${s(`${page.path.endsWith("/") ? page.path : page.path + "/"}`)}`
            )
            .join(",\n\t\t\t\t")}
];

I'd be happy to discuss this further, and provide a PR, if everyone is fine with this proposal!

csangonzo commented 3 years ago

This would be useful for the load function as well. I have some logic in load that I do not want to run on for example a /service-worker.js request.

oneezy commented 2 years ago

I'm interested in this too! I found this issue from a YouTube Video that shows their method for Getting All Routes/Pages through Vite.

<script context="module">
  const modules = import.meta.glob('./**.svelte');
  let allmenu = [];

  for(let path in modules) {
    allmenu.push({
      label: path.replace(/^\.\//, '').replace(/\.svelte$/, ''),
      href: path.replace(/^\.\//, '/').replace(/\.svelte$/, ''),
    });
  }

  export const load = async() => {
    const menu = await Promise.all(allmenu);
    return { props: { menu } };
  };
</script>

<script>
  export let menu;
</script>

<nav>
  {#each menu as item}
    <a href={item.href}>{item.label}</a>
  {/each}
</nav>

This is a cool trick, but it would be really nice to have something similar to the way 11ty (eleventy) handles this. Eleventy gives you Collections along with Supplied Data so you can do things like:

{% for page in collections.pages %}

  <section id="{{ page.fileSlug }}">
    <article>
      <h2>{{ page.data.title }}</h2>
      <p>{{ page.data.description }}</p>
      <a href="{{ page.fileSlug }}">Learn More</a>
    </article>
  </section>

{% endfor %}

Here's a screenshot of the Supplied Data that 11ty provides

image

SvelteKit needs this.

patricknelson commented 2 years ago

Thanks for working on this @Rich-Harris! I just noticed that this was closed. Is there any direction now on what the official method is for accomplishing this inside of a component? Iā€™m new to this, sorry if this is a dumb question. šŸ˜…

oneezy commented 2 years ago

I'm curious about this too @patricknelson . I updated my comment above yours with more information if you'd like to look at a hacky way to solve it.

patricknelson commented 2 years ago

Good suggestions. Actually @oneezy, I'm starting my first statically generated site, and at the time I posted my comment above I just so happened to be in the process of deciding between SvelteKit and 11ty as the foundation for the project.

I will still likely be using Svelte components (via Slinkity), maybe even at the top level with minimal Nunjucks templating in my case. However, that particular functionality and flexibility of 11ty was precisely what pushed me over the edge. Specifically: It's powerful page collections and it's data cascade. I know SvelteKit probably can't quite match that since it doesn't necessarily exist to solve the same problems per se (e.g. 11ty has the advantage of front matter for the tags used for collections), but at least having better access to at least enumerate the routes, maybe grouped by directory structure/slug, instead of using tags like 11ty, would still be super useful. Especially if you can't use 11ty and you've got a real SSR application.

oneezy commented 2 years ago

Pretty much in the same boat šŸ˜… . I went down the 11ty rabbit hole first and currently going down the Svelte/SvelteKit rabbit hole. The 11ty features are super powerful and I agree that it would be super awesome if SvelteKit adopted some of these ideas. I really like the direction Slinkity has taken as well to act as the glue between these 2 worlds (along with whatever else). I might take that path also but still deciding.

You might want to try out MDSVEX. It lets you use frontmatter and markdown within Svelte Components but not sure what else (that's the next rabbit hole I'm going down).

winston0410 commented 2 years ago

I am curious on this one as well, as this feature is useful in many scenario. Will this be implmented in the coming future?

Btw +1 for the workaround mentioned up there

hyunbinseo commented 2 years ago

For SvelteKit v1, please reference https://github.com/sveltejs/kit/issues/923#issuecomment-1567052262


Following utility function returns absolute paths to all pages included in the modules.

/**
 * Returns absolute paths to pages included in the modules
 * 
 * @param url - import.meta.url
 * @param modules - import.meta.glob();
 * @returns Paths to pages excluding routes with dynamic parameters
 */
export const getPages = (url: string, modules: Record<string, unknown>): string[] => {
  /*
   * Possible url values
   * Server: file:///____/src/routes/index.svelte
   * Client(dev): http://127.0.0.1:____/src/routes/index.svelte?t=1658306082278
   * Client(preview): http://127.0.0.1:____/_app/immutable/pages/index.svelte-e7ea94ef.js
   */
  const directory = url
    .replace(/(.*?)\/src\/routes\//, '/')
    .replace(/(.*?)\/immutable\/pages\//, '/')
    .replace(/(.*?)\/var\/task\//, '/') // Vercel
    .replace(/\/([^/])*.svelte.*/, '/');

  const paths = Object.keys(modules)
    // Convert relative path to absolute path
    .map((path) => path.replace(/^(.\/)/, directory))
    // Filter private modules (default regular expression in SvelteKit)
    .filter((path) => !/(?:(?:^_|\/_)|(?:^\.|\/\.)(?!well-known))/.test(path))
    // Filter paths with dynamic parameters (e.g. /blog/[slug].svelte)
    .filter((path) => !/\[.*\]/.test(path))
    // Remove '/index', layout name (@___), and file extension (.svelte)
    .map((path) => path.replace(/(\/index)?(@.*)?.svelte/, ''))
    // Set empty path string to '/' ('./index.svelte' is converted to '')
    .map((path) => path || '/')
    .sort();

  return paths;
};

Possible usage in a .svelte file.

<script lang="ts">
  import { getPages } from '$lib/pages'; // Path to the utility function

  const { url } = import.meta;
  const modules = import.meta.glob('./**/*.svelte'); // Include subfolder
  // const modules = import.meta.glob('./**.svelte'); // Current folder only

  const pages = getPages(url, modules);
</script>

<ul>
  {#each pages as page}
    <li>
      <a href={page}>{page}</a>
    </li>
  {/each}
</ul>

If the above code is written in /src/routes/pages.svelte,

\---routes
    |   about.svelte
    |   index.svelte
    |   pages.svelte // Written here
    |   sample.svelte
    |   __layout.svelte
    |
    \---todos
            index.svelte
            index.ts
            _api.ts

The /pages route will show the following links.

Tested in the following environments.

- vite dev
- vite preview
ā”œā”€ā”€ @sveltejs/adapter-auto@1.0.0-next.63
ā”œā”€ā”€ @sveltejs/kit@1.0.0-next.384
ā””ā”€ā”€ vite@3.0.2
elron commented 1 year ago

@hyunbinseo Thanks, however its not working in the latest svelte:

  '@sveltejs/adapter-auto': 2.1.0_@sveltejs+kit@1.18.0
  '@sveltejs/kit': 1.18.0_svelte@3.59.1+vite@4.3.8
  vite: 4.3.8

(pages always return {})

Or am I missing something?

hyunbinseo commented 1 year ago

@elron Things have changed in SvelteKit v1. Pages now have a filename of +page.svelte. Try the following.

const pageRegex = /\/\+page\.svelte$/;

const paths = Object.keys(modules)
  // Convert relative path to absolute path
  .map((path) => path.replace(/^(.\/)/, directory))
  // Filter paths with dynamic parameters (e.g. /blog/[slug].svelte)
  .filter((path) => !/\[.*\]/.test(path))
  // Filter paths that end with `/+page.svelte`
  .filter((path) => pageRegex.test(path))
  // Remove the trailing `/+page.svelte` string
  .map((path) => path.replace(pageRegex, ''))
  // Set empty path string to '/' ('./index.svelte' is converted to '')
  .map((path) => path || '/')
  .sort();

These lines are removed.

- .filter((path) => !/(?:(?:^_|\/_)|(?:^\.|\/\.)(?!well-known))/.test(path))
- .map((path) => path.replace(/(\/index)?(@.*)?.svelte/, ''))

The following paths are returned in a SvelteKit demo template.

Ā ['/', '/about', '/sverdle', '/sverdle/how-to-play']
src/routes
ā”œā”€ā”€ +layout.svelte
ā”œā”€ā”€ +page.svelte
ā”œā”€ā”€ +page.ts
ā”œā”€ā”€ Counter.svelte
ā”œā”€ā”€ Header.svelte
ā”œā”€ā”€ about
ā”‚   ā”œā”€ā”€ +page.svelte
ā”‚   ā””ā”€ā”€ +page.ts
ā”œā”€ā”€ index.ts
ā”œā”€ā”€ styles.css
ā””ā”€ā”€ sverdle
    ā”œā”€ā”€ +page.server.ts
    ā”œā”€ā”€ +page.svelte
    ā”œā”€ā”€ game.ts
    ā”œā”€ā”€ how-to-play
    ā”‚   ā”œā”€ā”€ +page.svelte
    ā”‚   ā””ā”€ā”€ +page.ts
    ā”œā”€ā”€ reduced-motion.ts
    ā””ā”€ā”€ words.server.ts
MrTango commented 10 months ago

The workaround is nice, but it does not work when building and running the app with node and adapter-node later, or should that work too? I only see parts of my navigation then, which are liked by static links too. So everything which is dynamic is missing.