Skip to content

Commit 122fdf4

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents ea45a43 + d38ce1d commit 122fdf4

File tree

17 files changed

+319
-813
lines changed

17 files changed

+319
-813
lines changed

.changeset/long-items-fry.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"embodi": minor
3+
---
4+
5+
Improve page loading

codebook.toml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
dictionaries = []
2+
words = ["embodi"]
3+
flag_words = []
4+
ignore_paths = []
5+
ignore_patterns = []
6+
use_global = true

packages/e2e/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111
},
1212
"devDependencies": {
1313
"embodi": "workspace:*",
14-
"svelte": "^5.23.2"
14+
"svelte": "^5.25.7"
1515
}
1616
}

packages/embodi/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"@sveltejs/package": "^2.3.10",
4545
"@types/markdown-it": "^14.1.2",
4646
"@types/minimist": "^1.2.5",
47-
"rollup": "^4.36.0"
47+
"rollup": "^4.39.0"
4848
},
4949
"peerDependencies": {
5050
"svelte": "^5.23.0",
@@ -63,8 +63,8 @@
6363
"front-matter": "^4.0.2",
6464
"markdown-it": "^14.1.0",
6565
"minimist": "^1.2.8",
66-
"pipe-and-combine": "^0.5.7",
67-
"vitest": "^3.0.9"
66+
"pipe-and-combine": "^0.7.11",
67+
"vitest": "^3.1.1"
6868
},
6969
"repository": {
7070
"type": "git",

packages/embodi/src/core/app/content-helper.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ export const loadPages = async <T extends string>(
55
urls: T[]
66
) => {
77
return Promise.all(
8-
urls.map((url) => {
8+
urls.map(async (url) => {
99
const page = pages[url];
10-
return page();
10+
return (await page()).default;
1111
})
1212
);
1313
};

packages/embodi/src/core/app/entry-server.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { createRouter } from './router.js';
2-
import { entryClient } from '$embodi/paths';
32
import { renderHook } from '$embodi/hooks';
43
import SvelteRoot from './Root.svelte';
54
import { render as renderSvelte } from 'svelte/server';
65
import type { Manifest } from 'vite';
76
import { addLeadingSlash } from './utils/paths.js';
87
import { runLoadAction } from './content-helper.js';
98
import { page as pageStore } from '$embodi/stores/internal';
9+
import { VIRTUAL_PREFIX } from '$embodi/pages';
1010

1111
const router = createRouter();
1212

@@ -25,9 +25,11 @@ const followImports = (
2525
css: Set<string> = new Set()
2626
) => {
2727
const current = manifest[entry.replaceAll('\\', '/')];
28+
29+
imports.add(current.file);
2830
if (current.imports) {
2931
current.imports.forEach((url: string) => {
30-
imports.add(url);
32+
3133
followImports(manifest, url, imports, css);
3234
});
3335
}
@@ -46,21 +48,21 @@ const followImports = (
4648
const createHeadFromManifest = (manifest: Manifest, entry: string): string => {
4749
const heads = [];
4850
const { css, imports } = followImports(manifest, entry);
49-
5051
heads.push(...Array.from(css).map(createStyleTag));
51-
heads.push(...Array.from(imports).map((url) => createScriptTag(manifest[url].file)));
52+
heads.push(...Array.from(imports).map((url) => createScriptTag(url)));
5253

5354
return heads.flat().join('\n');
5455
};
5556

5657
export async function render(source: string, url: string, manifest?: Manifest) {
57-
const head = manifest ? createHeadFromManifest(manifest, await router.path(url)) : '';
58+
const head = manifest ? createHeadFromManifest(manifest, `${VIRTUAL_PREFIX}${url.slice(0,-1)}`) : '';
5859
//const entryHead = manifest ? createHeadFromManifest(manifest, entryClient) : '';
5960
//const scripts = createScriptTags(manifes[router.path(url).slice(1)]);
6061
const pageData = await router.load(url);
6162
if (!pageData) return;
6263
const { html, Component, Layout } = pageData;
6364
const data = await runLoadAction(pageData);
65+
6466
await renderHook({ data });
6567
pageStore.update((p) => ({ ...p, url }));
6668
// @ts-ignore

packages/embodi/src/core/app/router.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const getPageFromUrl = async (url: string) => {
1010
const pageImportFu = pages[addTrailingSlash(url)];
1111
if (!pageImportFu) return;
1212

13-
const page = await pageImportFu();
13+
const {default: page} = await pageImportFu();
1414
const mergedData = {
1515
...data,
1616
...page.data
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { SvelteComponent } from "svelte";
2+
13
export type MaybePromise<T> = T | Promise<T>;
24
export type AnyObject = Record<string | number | Symbol, any>;
35
export type RenderHookEvent = { data: Record<string, any> };
@@ -8,10 +10,12 @@ export type LoadEvent = {
810
};
911
export type LoadAction = (event: LoadEvent) => Record<string, any>;
1012

11-
export type PageImportFunction = () => Promise<{
12-
Component?: ConstructorOfATypedSvelteComponent;
13+
export type PageData = {
14+
Component?: SvelteComponent;
1315
html?: string;
14-
Layout?: ConstructorOfATypedSvelteComponent;
16+
Layout?: SvelteComponent;
1517
data: Record<string, any>;
1618
load?: LoadAction;
17-
}>;
19+
};
20+
21+
export type PageImportFunction = () => Promise<{ default: PageData }>;

packages/embodi/src/core/vite/code-builder/load-content.ts

+49-5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { FilesystemAdapter } from '@loom-io/node-filesystem-adapter';
88
import { mergeOneLevelObjects } from '../utils/data.js';
99
import type { AnyObject } from '../../definitions/types.js';
1010
import { normalize } from 'node:path';
11+
import { normalizePath } from 'vite';
12+
import { addTrailingSlash } from '../utils/paths.js';
1113

1214
enum FILE_TYPE {
1315
INDEX,
@@ -18,11 +20,13 @@ enum FILE_TYPE {
1820
export type PageObject = {
1921
type: FILE_TYPE;
2022
url: NormalizeUrlPath;
21-
page: number;
23+
page: number[];
2224
data: number[];
2325
battery?: number;
2426
};
2527

28+
export const VIRTUAL_PAGE_PREFIX = 'virtual-page:'
29+
2630
const map =
2731
<T, U>(fn: (arg: T) => U) =>
2832
(arr: T[]) =>
@@ -39,10 +43,12 @@ const snippetImport = (path: string) => `import('${normalizeImportPath(path)}')`
3943
const snippetObjectChunk = (name: string, value: string) => `"${name}": ${value}`;
4044
const snippetArray = (items: string[]) => `[${items.join(',')}]`;
4145
const snippetExport = (name: string, value: string) => `export const ${name} = ${value}`;
42-
const snippetPromiseAll = (items?: string[]) =>
46+
const snippetPromiseAll = (items?: string[]): string =>
4347
items?.length ? `Promise.all(${snippetArray(items)})` : '[]';
4448
const snippetDataImports = pipe(resolveLinks, map(snippetImport), snippetPromiseAll);
4549
const snippetContentImports = pipe(resolveLinks, map(snippetImportEmbodi), snippetPromiseAll);
50+
const generateIdMap = (link: string): [string, string] => [`i_${crypto.randomUUID().replaceAll('-', '_')}`, link];
51+
const snippetImportMap = pipe(resolveLinks, map(generateIdMap));
4652
const snippetPageImport = (page: PageObject, ref: UniqueArray<string>) =>
4753
`async function () {
4854
const data = await ${snippetDataImports(ref, ...page.data)}
@@ -56,6 +62,35 @@ const snippetPageImport = (page: PageObject, ref: UniqueArray<string>) =>
5662
data: mergedData
5763
}
5864
}`;
65+
66+
const snippetPage = (page: PageObject, ref: UniqueArray<string>) => {
67+
const snippetMapPages = snippetImportMap(ref, ...page.page);
68+
const snippetMapData = snippetImportMap(ref, ...page.data);
69+
const dataIds = snippetMapData.map(([id]) => id)
70+
const pageIds = snippetMapPages.map(([id]) => id)
71+
const code = `
72+
import { mergeOneLevelObjects } from 'embodi/utils';
73+
${snippetMapData.map(([id, link]) => `import ${id} from "${normalizePath(link)}";`).join(';\n')}
74+
${snippetMapPages.map(([id, link]) => `import * as ${id} from "${snippetPathEmdodi(link)}";`).join(';\n')}
75+
76+
const defaultData = [
77+
${dataIds.join(',\n')}
78+
];
79+
const pages = [
80+
${pageIds.join(',\n')}
81+
];
82+
const page = mergeOneLevelObjects(...pages);
83+
const mergedData = mergeOneLevelObjects(...defaultData, page.data);
84+
export default {
85+
...page,
86+
data: mergedData
87+
};
88+
`;
89+
return code;
90+
}
91+
92+
const snippetPageImportLink = (url: string) => `() => import("${VIRTUAL_PAGE_PREFIX}${addTrailingSlash(url)}")`;
93+
5994
const snippetObjectChunkWrapper = (imports: string[]) => `({${imports.join(',')}})`;
6095
const snippetFile = (name: string, content: string) =>
6196
`import { mergeOneLevelObjects } from 'embodi/utils';
@@ -219,14 +254,23 @@ export const generateContentMap = async (
219254
export const generatePageImportCode = async (pages: PageObject[], linkRef: string[]) => {
220255
// TODO: snippet
221256
const pagesCodeSnippets = pages.map((page) => {
222-
const pageCodeFunction = snippetPageImport(page, linkRef);
257+
258+
//const pageCodeFunction = snippetPageImport(page, linkRef);
259+
const pageCodeFunction = snippetPageImportLink(page.url)
223260
// rename it to chunk
224261
return snippetObjectChunk(page.url, pageCodeFunction);
225262
});
226263
const pagesCodeObject = snippetObjectChunkWrapper(pagesCodeSnippets);
227264
return snippetFile('pages', pagesCodeObject);
228265
};
229266

267+
export const generatePageCode = async (pages: PageObject[], linkRef: string[], url: string) => {
268+
// TODO: snippet
269+
const pageData = pages.find((page) => page.url === url);
270+
if(!pageData) return ``;
271+
return snippetPage(pageData, linkRef)
272+
};
273+
230274
export type PageData<T extends AnyObject = AnyObject> = {
231275
type: FILE_TYPE;
232276
url: NormalizeUrlPath;
@@ -243,8 +287,8 @@ export const loadData = async (pages: PageObject[], linkRef: string[]): Promise<
243287
);
244288
const loadedPageData = pages.map(({ page, data, type, url }) => {
245289
const mappedData = data.map((index) => loadedFiles[index]);
246-
const mappedPage = loadedFiles[page];
247-
const mergedData = mergeOneLevelObjects(...mappedData, mappedPage);
290+
const mappedPage = page.map((page) => loadedFiles[page]);
291+
const mergedData = mergeOneLevelObjects(...mappedData, ...mappedPage);
248292
return {
249293
type,
250294
url,

packages/embodi/src/core/vite/plugins/embodi.ts

+39-27
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@ import { prerender } from '../utils/prerender.js';
77
import packageJson from '../../../../package.json' with { type: 'json' };
88
import {
99
getVirtualParams,
10-
invalidateEmbodiModule,
1110
invalidateStoredCollection,
1211
isHotUpdate,
13-
isValidLoadId,
12+
prepareIdValidator,
13+
resolvePipe,
1414
storeLoadId,
15-
validateResolveId
1615
} from '../utils/virtuals.js';
1716
import { loadAppHtml, loadData } from '../code-builder/load-data.js';
1817
import {
1918
generateContentMap,
19+
generatePageCode,
2020
generatePageImportCode,
21-
generateRoutesCode
21+
generateRoutesCode,
22+
VIRTUAL_PAGE_PREFIX
2223
} from '../code-builder/load-content.js';
2324
import { type ServerResponse } from 'node:http';
2425
import { generateCollectionsImportsCode } from '../code-builder/collections.js';
@@ -29,6 +30,9 @@ import { generateInternalStores, generateReadableStores } from '../code-builder/
2930
const cwd = process.cwd(); // Current working directory
3031
const cf = resolve(dirname(fileURLToPath(import.meta.url)), '..'); // core folder
3132

33+
const validateEmbodiId = prepareIdValidator('$embodi/');
34+
const validatePageId = prepareIdValidator(VIRTUAL_PAGE_PREFIX);
35+
3236
export const configPlugin = (): Plugin =>
3337
({
3438
name: 'embodi-config-plugin',
@@ -69,57 +73,65 @@ export const virtualPlugin = (): Plugin =>
6973
({
7074
name: 'embodi-virtual-plugin',
7175
async resolveId(id) {
72-
return validateResolveId(
73-
id,
74-
'pages',
75-
'paths',
76-
'data',
77-
'collections',
78-
'hooks',
79-
'env',
80-
'stores',
81-
'stores/internal'
82-
);
76+
return resolvePipe(
77+
validatePageId.resolve(id),
78+
validateEmbodiId.resolve(
79+
id,
80+
'pages',
81+
'paths',
82+
'data',
83+
'collections',
84+
'hooks',
85+
'env',
86+
'stores',
87+
'stores/internal'
88+
)
89+
)
8390
},
8491
async load(id, options) {
85-
if (isValidLoadId(id, 'pages')) {
92+
if(validatePageId.load(id)) {
93+
const config = await loadConfig(cwd);
94+
const pageMap = await generateContentMap(config.inputDirs);
95+
const pageCode = await generatePageCode(...pageMap, validatePageId.getPath(id));
96+
return pageCode;
97+
} else if (validateEmbodiId.load(id, 'pages')) {
8698
const config = await loadConfig(cwd);
8799
const contentMap = await generateContentMap(config.inputDirs);
88100
const pagesCode = await generatePageImportCode(...contentMap);
89101
const routesCode = await generateRoutesCode(config.inputDirs);
90-
return `${pagesCode}\n${routesCode}\nexport const source = "${config.inputDirs.content}";`;
91-
} else if (isValidLoadId(id, 'paths')) {
102+
return `${pagesCode}\n${routesCode}\nexport const source = "${config.inputDirs.content}";export const VIRTUAL_PREFIX = "${VIRTUAL_PAGE_PREFIX}";`;
103+
} else if (validateEmbodiId.load(id, 'paths')) {
92104
const { statics } = await loadConfig(cwd);
93105
const relativPathToClientEntry = relative(cwd, resolve(cf, '../app/entry-client.js'));
94106

95107
return `export const entryClient = "${relativPathToClientEntry}"; export const statics = "${statics}";`;
96-
} else if (isValidLoadId(id, 'data')) {
108+
} else if (validateEmbodiId.load(id, 'data')) {
97109
const projectConfig = await loadConfig(cwd);
98110

99111
const dataDirectoryPath = projectConfig.inputDirs.data;
100112
const data = await loadData(dataDirectoryPath);
101113
return `export const data = ${JSON.stringify(data)};`;
102-
} else if (isValidLoadId(id, 'collections')) {
114+
} else if (validateEmbodiId.load(id, 'collections')) {
103115
storeLoadId('collections', id);
104116
const params = getVirtualParams(id);
105117

106118
return await generateCollectionsImportsCode({
107119
...params,
108120
only: params.only ? params.only.split(';') : undefined
109121
});
110-
} else if (isValidLoadId(id, 'hooks')) {
122+
} else if (validateEmbodiId.load(id, 'hooks')) {
111123
return generateHooksCode();
112-
} else if (isValidLoadId(id, 'env')) {
124+
} else if (validateEmbodiId.load(id, 'env')) {
113125
return `export const browser = ${JSON.stringify(!options?.ssr)};`;
114-
} else if (isValidLoadId(id, 'stores/internal')) {
126+
} else if (validateEmbodiId.load(id, 'stores/internal')) {
115127
return generateInternalStores();
116-
} else if (isValidLoadId(id, 'stores')) {
128+
} else if (validateEmbodiId.load(id, 'stores')) {
117129
return generateReadableStores('$embodi/stores/internal');
118130
}
119131
},
120132
async handleHotUpdate({ server, file }) {
121133
if (await isHotUpdate(file, 'data')) {
122-
await invalidateEmbodiModule(server, 'data');
134+
await validateEmbodiId.invalidate(server, 'data');
123135
server.ws.send({
124136
type: 'full-reload'
125137
});
@@ -134,8 +146,8 @@ export const virtualPlugin = (): Plugin =>
134146
// Invalidate pages and paths when content changes
135147
server.watcher.on('add', async (file: string) => {
136148
if (await isHotUpdate(file, 'content')) {
137-
await invalidateEmbodiModule(server, 'pages');
138-
await invalidateEmbodiModule(server, 'paths');
149+
await validateEmbodiId.invalidate(server, 'pages');
150+
await validateEmbodiId.invalidate(server, 'paths');
139151
}
140152
});
141153
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { Preprocessor, PreprocessorGroup } from "svelte/compiler";
2+
3+
export const imagePreprocess = () => {
4+
return {
5+
name: 'svelte-preprocessor-name',
6+
markup: ({ content, filename}) => {
7+
if (content.includes('img:embodi')) {
8+
console.log(filename)
9+
}
10+
},
11+
12+
} satisfies PreprocessorGroup
13+
};

0 commit comments

Comments
 (0)