Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get access to the list of pages generated #923

Closed
wighawag opened this issue Apr 7, 2021 · 13 comments · Fixed by #4192
Closed

Get access to the list of pages generated #923

wighawag opened this issue Apr 7, 2021 · 13 comments · Fixed by #4192
Labels
feature / enhancement New feature or request

Comments

@wighawag
Copy link
Contributor

wighawag commented Apr 7, 2021

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
Copy link

devgar commented Apr 9, 2021

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
Copy link

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
Copy link

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
Copy link

oneezy commented Jan 27, 2022

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.

@bluwy bluwy added the feature / enhancement New feature or request label Jan 29, 2022
@Rich-Harris Rich-Harris mentioned this issue Mar 3, 2022
13 tasks
@patricknelson
Copy link

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
Copy link

oneezy commented Mar 16, 2022

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
Copy link

patricknelson commented Mar 16, 2022

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
Copy link

oneezy commented Mar 16, 2022

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
Copy link

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
Copy link
Contributor

hyunbinseo commented Jul 20, 2022

For SvelteKit v1, please reference #923 (comment)


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
Copy link

elron commented May 28, 2023

@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
Copy link
Contributor

hyunbinseo commented May 29, 2023

@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
Copy link

MrTango commented Dec 28, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature / enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.