Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .transform-log.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[2025-04-27T18:45:25.583Z]
export default {"title":"Blogg","components":[{"component":(await import('/src/lib/components/Hero.svelte')).default,"primer":"kompismoln.se/blog","body":"# Blogg\n","buttons":[{"text":"Berätta mer","url":"/about","primary":true},{"text":"Vad är ?","url":"/tools/nixos","primary":false}]}]};

[2025-04-27T18:46:59.727Z]
export default {"title":"Blogg","components":[{"component":"Hero","primer":"kompismoln.se/blog","body":"# Blogg\n","buttons":[{"text":"Berätta mer","url":"/about","primary":true},{"text":"Vad är ?","url":"/tools/nixos","primary":false}]}]};

[2025-04-27T18:47:54.121Z]
export default {
'about': () => import('virtual:content/about'),
'blog': () => import('virtual:content/blog'),
'': () => import('virtual:content/'),
'test': () => import('virtual:content/test'),
'tools': () => import('virtual:content/tools'),
'tools/nixos': () => import('virtual:content/tools/nixos'),
'blog/att-bygga-hemsida': () => import('virtual:content/blog/att-bygga-hemsida'),
'blog/hello-world': () => import('virtual:content/blog/hello-world'),
'blog/hugo-like-sveltekit-ssg': () => import('virtual:content/blog/hugo-like-sveltekit-ssg')
};

4 changes: 2 additions & 2 deletions compis/Page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
</script>

{#if page.component}
<page.component {...page.props} />
<page.component {...page} />
{/if}
{#if page.components}
{#each page.components as component}
<component.component {...component.props} />
<component.component {...component} />
{/each}
{/if}
29 changes: 16 additions & 13 deletions compis/component.loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import type {
ComponentMap,
ComponentContent,
ResolvedComponent,
PageContent
PageContent,
ComponentModule
} from './types';

import { createRawSnippet } from 'svelte';
Expand All @@ -21,6 +22,12 @@ import { contentTraverser, inferCommonPath, trimKey } from './utils';
*/
let componentMap: ComponentMap;

export let contentResolve: (value?: any) => void;

export const contentReady = new Promise(resolve => { contentResolve = resolve; });
export const virtualComponentMap: Record<string, string> = {};
//contentReady.then(() => console.log(virtualComponentMap));

/* Reduce keys to friendly component names like 'Hero' or 'Blog/Post',
* instead of full paths like:
*
Expand All @@ -38,19 +45,14 @@ export function setComponentMap(
componentMap = trimKey(modules, componentRoot.length, '.svelte'.length);
}

export function addVirtualComponent(id: string, source: string) {
virtualComponentMap[id] = source;
}

/* How everyone in here should get a component from componentMap
*/
export const getComponent = async (name: string) => {
if (!Object.keys(componentMap).length) {
throw new Error(
'Component map is empty. Did you forget to call setComponentMap()?'
);
}
if (!(name in componentMap)) {
throw new Error(`Component not found: ${name}`);
}
const component = await componentMap[name]();
return component;
return name;
};

/* Get module with props in a neat package
Expand All @@ -63,7 +65,8 @@ export const resolveComponent = async (
content: ComponentContent
): Promise<ResolvedComponent> => {
const { component: name, ...props } = content;
const { default: component } = await getComponent(name);

const component = await getComponent(name);
return { component, props };
};

Expand Down Expand Up @@ -112,7 +115,7 @@ export const resolvePage = async (page: PageContent) => {
* Also, what's with "conform"? Who writes that?
*
* Maybe just pay the price for writing a function that does two things and name it
* validateAndTransformComponent. <- Actually not a bad idead.
* validateAndTransformComponent. <- Actually not a bad idea.
*/
export const conformComponent = async (
content: ComponentContent
Expand Down
49 changes: 22 additions & 27 deletions compis/content.loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import fs from 'node:fs/promises';

import { globSync } from 'node:fs';
import path from 'node:path';
import { redirect } from '@sveltejs/kit';

import type { PageContent } from './types';
import { conformComponent } from './component.loader';
Expand All @@ -30,37 +29,32 @@ const filetypes = ['js', 'ts', 'json', 'yaml', 'yml', 'md'];
* because this function doesn't run there 🤔
*
*/
export const loadPageContent = async (searchPath: string) => {
// This redirect has no effect in production, handle redirects on webserver instead
if (searchPath === config.indexFile) throw redirect(301, '/');

export const load = async ({ params }: any) => {
// Rename site root to index file
searchPath = searchPath === '' ? config.indexFile : searchPath;

// Find page or throw
let page = await findPageContent(searchPath);
const searchPath = params.path === '' ? config.indexFile : params.path;

try {
page = await processPage(page);
} catch (error: any) {
throw new Error(
`Failed to process page '${searchPath}': ${error.message || error}`
);
}
const collectedPage = await collectPage(searchPath);
const processedPage = await processPage(collectedPage);
return processedPage;
};

/* Find pae and recurse in content tree and
* - Replace all fragments with data from fragment files
*/
export const collectPage = async (searchPath: string): Promise<PageContent> => {
const rawPage = await findPageContent(searchPath)
const page = await contentTraverser({
obj: rawPage,
filter: (obj) => Object.keys(obj).some((key: string) => key[0] === '_'),
callback: parseFragment
});
return page;
};
}

/* Recurse in content tree and
* - Replace all fragments with data from file
* - Validate & transform all components
*/
export const processPage = async (page: PageContent): Promise<PageContent> => {
page = await contentTraverser({
obj: page,
filter: (obj) => Object.keys(obj).some((key: string) => key[0] === '_'),
callback: parseFragment
});

page = await contentTraverser({
obj: page,
Expand All @@ -77,7 +71,8 @@ export const findPageContent = async (searchPath: string) => {
for (const ext of filetypes) {
const filePath = path.join(config.contentRoot, `${searchPath}.${ext}`);
try {
return await parseFile(filePath);
const page = await parseFile(filePath);
return page;
} catch (error: any) {
if (['ENOENT', 'ERR_MODULE_NOT_FOUND'].includes(error.code)) {
continue;
Expand All @@ -93,9 +88,9 @@ export const findPageContent = async (searchPath: string) => {
export const parseFile = async (filePath: string): Promise<any> => {
const fileExt = path.extname(filePath);

if (['.js', '.ts'].includes(fileExt)) {
return (await import(/* @vite-ignore */ filePath)).default;
}
//if (['.js', '.ts'].includes(fileExt)) {
// return (await import(/* @vite-ignore */ filePath)).default;
//}

const fileContent = await fs.readFile(filePath, 'utf-8');

Expand Down
66 changes: 57 additions & 9 deletions compis/loaders.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,62 @@ import path from 'node:path';
import { globSync } from 'node:fs';
import { setComponentMap, getComponent } from './component.loader';
import { inferCommonPath } from './utils';
import { collectPage } from './content.loader';
import { c } from './schemas';


setComponentMap({
'Basic.svelte': () => import('./test/components/Basic.svelte'),
'WithSchema.svelte': () => import('./test/components/WithSchema.svelte')
});

describe('validate content', async () => {
const code = `
import c from '$lib/components/schemas';

export const schema = c.content({
primer: c.string(),
body: c.markdown(),
buttons: c.array(c.button()).max(2)
});
`;
it('parses schema', async () => {
expect(c.content({})).toBeDefined();
expect(c.string()).toBeDefined();
expect(c.asdf).toBeUndefined();
expect(c.markdown).toBeTruthy();
expect(c.smarkdown).toBeFalsy();
});
});

/*
import getPlugin from './vite';

describe('load pages', async () => {
it('collects pages', async () => {
const page = await collectPage('blog');
console.log(page);
});
});

describe('transform hook playground', async () => {
const plugin = await getPlugin();
const transform = plugin.transform!.bind(plugin);

it('play with random code', async () => {
const code = `
<script module>
export const shape = { hello: 'world' };
</script>
<h1>Hello</h1>
`;
const id = '/src/lib/TestComponent.svelte';

const result = await transform(code, id);
console.log('TRANSFORM RESULT', result);
// no assertions, just playground
});
});
describe('components', () => {
it('infers component root correctly', () => {
const paths = [
Expand All @@ -26,9 +76,9 @@ describe('components', () => {
});
}),
it('retrieves components', async () => {
await expect(getComponent('NotAComponent')).rejects.toThrow(
'Component not found: NotAComponent'
);
//await expect(getComponent('NotAComponent')).rejects.toThrow(
// 'Component not found: NotAComponent'
//);
const { default: component, schema } = await getComponent('WithSchema');
let result: any;

Expand All @@ -45,13 +95,11 @@ describe('components', () => {
});
});

*/
describe('playground', () => {
it('finds page.yaml in content', () => {
const contentDir = path.resolve(process.cwd(), 'src/lib/content');
const pattern = path.join(contentDir, '**', 'page.@(yaml|md)');
const files = globSync(pattern);
const content = Object.fromEntries(
files.map((file) => [path.dirname(path.relative(contentDir, file)), file])
);
const c = { test: 'asdff' };
const r = (new Function('c', `return c.test`))(c);
console.log(r);
});
});
Loading