mirror of https://github.com/sveltejs/svelte
feat(site-2): TS in docs (#8452)
* Commit * Push working type generation code * Fix type gen script invocation * Delete type-info.js * Change build script * Recreate package lock * mkdir generated * Add type references to some pages * Add TS-able snippets to docs examples * Fix some stuff * Add types to individual functions * Add to store page * Refactor compile-types.js * Move ts sourcefile stuff to compile-types * Remove commented code * Half attempt at export aliases * Add * Fix, add disclaimer for svelte/internal * Disable /docs prerendering * Remove internals page * Remove redundant stuff * Make search work with it * Fix compile types logic * Add file: comment, * Add two-slash to docs pages * Get type links working * Remove console.log * Add action module * Fix actions logic * Make compile-types logic more robust * Bump site-kit * Fix gitignore * Use moduleResolution bundler * Don't apply shiki-twoslash to auto generated code snippets from render * Recreate package lock * Make TSbpage undraft * Fix svelte component types * Remove console.log * No more sveltekit * Make regex smarter * Update sites/svelte.dev/scripts/type-gen/compile-types.js Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * Rebuild package lock * Remove commented out code * Update deps * Remove $$_attributes --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>pull/8585/head
parent
b9ea60cff4
commit
9bee59cc4f
@ -1,6 +0,0 @@
|
||||
---
|
||||
title: Actions
|
||||
draft: true
|
||||
---
|
||||
|
||||
TODO
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
title: svelte/action
|
||||
---
|
||||
|
||||
TODO
|
||||
|
||||
## Types
|
||||
|
||||
> TYPES: svelte/action
|
@ -1,6 +1,7 @@
|
||||
---
|
||||
title: TypeScript
|
||||
draft: true
|
||||
---
|
||||
|
||||
TODO
|
||||
## Types
|
||||
|
||||
> TYPES: svelte
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,127 @@
|
||||
// @ts-check
|
||||
import MagicString from 'magic-string';
|
||||
import fs from 'node:fs';
|
||||
import { rollup } from 'rollup';
|
||||
import dts from 'rollup-plugin-dts';
|
||||
import { VERSION } from 'svelte/compiler';
|
||||
import { Project, SyntaxKind } from 'ts-morph';
|
||||
import ts from 'typescript';
|
||||
|
||||
export async function get_bundled_types() {
|
||||
const dtsSources = fs.readdirSync(new URL('./dts-sources', import.meta.url));
|
||||
|
||||
/** @type {Map<string, {code: string, ts_source_file: ts.SourceFile}>} */
|
||||
const codes = new Map();
|
||||
|
||||
for (const file of dtsSources) {
|
||||
const bundle = await rollup({
|
||||
input: new URL(`./dts-sources/${file}`, import.meta.url).pathname,
|
||||
plugins: [dts({ respectExternal: true })]
|
||||
});
|
||||
|
||||
const moduleName = (file === 'index.d.ts' ? 'svelte' : `svelte/${file}`).replace('.d.ts', '');
|
||||
const code = await bundle.generate({ format: 'esm' }).then(({ output }) => output[0].code);
|
||||
const inlined_export_declaration_code = inlineExportDeclarations(code);
|
||||
|
||||
codes.set(moduleName, {
|
||||
code: inlined_export_declaration_code,
|
||||
ts_source_file: ts.createSourceFile(
|
||||
'index.d.ts',
|
||||
inlined_export_declaration_code,
|
||||
ts.ScriptTarget.ESNext,
|
||||
true,
|
||||
ts.ScriptKind.TS
|
||||
)
|
||||
});
|
||||
|
||||
// !IMPORTANT: This is for debugging purposes only.
|
||||
// !Do not remove until Svelte d.ts files are stable during v4/v5
|
||||
write_to_node_modules('before', file, code);
|
||||
write_to_node_modules('after', file, inlined_export_declaration_code);
|
||||
}
|
||||
|
||||
return codes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
*/
|
||||
function inlineExportDeclarations(str) {
|
||||
const project = new Project();
|
||||
const source_file = project.createSourceFile('index.d.ts', str, { overwrite: true });
|
||||
|
||||
// There's only gonna be one because of the output of dts-plugin
|
||||
const exportDeclaration = source_file.getExportDeclarations()[0];
|
||||
const exportedSymbols = exportDeclaration
|
||||
.getNamedExports()
|
||||
.map((e) => e.getAliasNode()?.getText() ?? e.getNameNode().getText());
|
||||
|
||||
// console.log(exportedSymbols);
|
||||
if (exportedSymbols.length === 0) return str;
|
||||
|
||||
const aliasedExportedSymbols = new Map();
|
||||
const namedExports = exportDeclaration.getNamedExports();
|
||||
|
||||
namedExports.forEach((namedExport) => {
|
||||
if (namedExport.getAliasNode()) {
|
||||
const alias = namedExport.getAliasNode()?.getText();
|
||||
const originalName = namedExport.getNameNode().getText();
|
||||
aliasedExportedSymbols.set(alias, originalName);
|
||||
}
|
||||
});
|
||||
|
||||
for (const [exported, og] of aliasedExportedSymbols) {
|
||||
source_file.forEachDescendant((node) => {
|
||||
if (node.getKind() === ts.SyntaxKind.Identifier && node.getText() === og) {
|
||||
node.replaceWithText(exported);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
// Get the symbols and their declarations
|
||||
const exportedSymbols = exportDeclaration
|
||||
.getNamedExports()
|
||||
.map((exp) => exp.getSymbolOrThrow());
|
||||
|
||||
/** @type {import('ts-morph').ExportSpecifier[]} */
|
||||
// @ts-ignore
|
||||
const exportedDeclarations = exportedSymbols.flatMap((symbol) => symbol.getDeclarations());
|
||||
|
||||
// Add 'export' keyword to the declarations
|
||||
exportedDeclarations.forEach((declaration) => {
|
||||
if (!declaration.getFirstDescendantByKind(SyntaxKind.ExportKeyword)) {
|
||||
for (const target of declaration.getLocalTargetDeclarations()) {
|
||||
if (target.isKind(SyntaxKind.VariableDeclaration)) {
|
||||
target.getVariableStatement()?.setIsExported(true);
|
||||
} else {
|
||||
// @ts-ignore
|
||||
target.setIsExported(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
exportDeclaration.remove();
|
||||
|
||||
// In case it is export declare VERSION = '__VERSION__', replace it with svelte's real version
|
||||
const stringified = source_file.getFullText().replace('__VERSION__', VERSION);
|
||||
|
||||
return stringified;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {'before' | 'after'} label
|
||||
* @param {string} filename
|
||||
* @param {string} code
|
||||
*/
|
||||
function write_to_node_modules(label, filename, code) {
|
||||
const folder = new URL(`../../node_modules/.type-gen/${label}`, import.meta.url).pathname;
|
||||
|
||||
try {
|
||||
fs.mkdirSync(folder, { recursive: true });
|
||||
} catch {}
|
||||
|
||||
fs.writeFileSync(`${folder}/${filename}`, code);
|
||||
}
|
@ -0,0 +1 @@
|
||||
export type * from 'svelte/action';
|
@ -0,0 +1 @@
|
||||
export type * from 'svelte/animate';
|
@ -0,0 +1 @@
|
||||
export type * from 'svelte/compiler';
|
@ -0,0 +1 @@
|
||||
export type * from 'svelte/easing';
|
@ -0,0 +1 @@
|
||||
export type * from 'svelte';
|
@ -0,0 +1 @@
|
||||
export type * from 'svelte/motion';
|
@ -0,0 +1 @@
|
||||
export type * from 'svelte/store';
|
@ -0,0 +1 @@
|
||||
export type * from 'svelte/transition';
|
@ -0,0 +1,366 @@
|
||||
// @ts-check
|
||||
import fs from 'fs';
|
||||
import prettier from 'prettier';
|
||||
import ts from 'typescript';
|
||||
import { get_bundled_types } from './compile-types.js';
|
||||
|
||||
/** @typedef {{ name: string; comment: string; markdown: string; snippet: string; children: Extracted[] }} Extracted */
|
||||
|
||||
/** @type {Array<{ name: string; comment: string; exports: Extracted[]; types: Extracted[]; exempt?: boolean; }>} */
|
||||
const modules = [];
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @param {ts.NodeArray<ts.Statement>} statements
|
||||
*/
|
||||
function get_types(code, statements) {
|
||||
/** @type {Extracted[]} */
|
||||
const exports = [];
|
||||
|
||||
/** @type {Extracted[]} */
|
||||
const types = [];
|
||||
|
||||
if (statements) {
|
||||
for (const statement of statements) {
|
||||
const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) : undefined;
|
||||
|
||||
const export_modifier = modifiers?.find((modifier) => modifier.kind === 93);
|
||||
if (!export_modifier) continue;
|
||||
|
||||
if (
|
||||
ts.isClassDeclaration(statement) ||
|
||||
ts.isInterfaceDeclaration(statement) ||
|
||||
ts.isTypeAliasDeclaration(statement) ||
|
||||
ts.isModuleDeclaration(statement) ||
|
||||
ts.isVariableStatement(statement) ||
|
||||
ts.isFunctionDeclaration(statement)
|
||||
) {
|
||||
const name_node = ts.isVariableStatement(statement)
|
||||
? statement.declarationList.declarations[0]
|
||||
: statement;
|
||||
|
||||
// @ts-ignore no idea why it's complaining here
|
||||
const name = name_node.name?.escapedText;
|
||||
|
||||
let start = statement.pos;
|
||||
let comment = '';
|
||||
|
||||
// @ts-ignore i think typescript is bad at typescript
|
||||
if (statement.jsDoc) {
|
||||
// @ts-ignore
|
||||
comment = statement.jsDoc[0].comment;
|
||||
// @ts-ignore
|
||||
start = statement.jsDoc[0].end;
|
||||
}
|
||||
|
||||
const i = code.indexOf('export', start);
|
||||
start = i + 6;
|
||||
|
||||
/** @type {string[]} */
|
||||
const children = [];
|
||||
|
||||
let snippet_unformatted = code.slice(start, statement.end).trim();
|
||||
|
||||
if (ts.isInterfaceDeclaration(statement)) {
|
||||
if (statement.members.length > 0) {
|
||||
for (const member of statement.members) {
|
||||
children.push(munge_type_element(member));
|
||||
}
|
||||
|
||||
// collapse `interface Foo {/* lots of stuff*/}` into `interface Foo {…}`
|
||||
const first = statement.members.at(0);
|
||||
const last = statement.members.at(-1);
|
||||
|
||||
let body_start = first.pos - start;
|
||||
while (snippet_unformatted[body_start] !== '{') body_start -= 1;
|
||||
|
||||
let body_end = last.end - start;
|
||||
while (snippet_unformatted[body_end] !== '}') body_end += 1;
|
||||
|
||||
snippet_unformatted =
|
||||
snippet_unformatted.slice(0, body_start + 1) +
|
||||
'/*…*/' +
|
||||
snippet_unformatted.slice(body_end);
|
||||
}
|
||||
}
|
||||
|
||||
const snippet = prettier
|
||||
.format(snippet_unformatted, {
|
||||
parser: 'typescript',
|
||||
printWidth: 80,
|
||||
useTabs: true,
|
||||
singleQuote: true,
|
||||
trailingComma: 'none'
|
||||
})
|
||||
.replace(/\s*(\/\*…\*\/)\s*/g, '/*…*/')
|
||||
.trim();
|
||||
|
||||
const collection =
|
||||
ts.isVariableStatement(statement) || ts.isFunctionDeclaration(statement)
|
||||
? exports
|
||||
: types;
|
||||
|
||||
collection.push({ name, comment, snippet, children });
|
||||
}
|
||||
}
|
||||
|
||||
types.sort((a, b) => (a.name < b.name ? -1 : 1));
|
||||
exports.sort((a, b) => (a.name < b.name ? -1 : 1));
|
||||
}
|
||||
|
||||
return { types, exports };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ts.TypeElement} member
|
||||
*/
|
||||
function munge_type_element(member, depth = 1) {
|
||||
// @ts-ignore
|
||||
const doc = member.jsDoc?.[0];
|
||||
|
||||
/** @type {string[]} */
|
||||
const children = [];
|
||||
|
||||
const name = member.name?.escapedText;
|
||||
let snippet = member.getText();
|
||||
|
||||
for (let i = 0; i < depth; i += 1) {
|
||||
snippet = snippet.replace(/^\t/gm, '');
|
||||
}
|
||||
|
||||
if (
|
||||
ts.isPropertySignature(member) &&
|
||||
ts.isTypeLiteralNode(member.type) &&
|
||||
member.type.members.some((member) => member.jsDoc?.[0].comment)
|
||||
) {
|
||||
let a = 0;
|
||||
while (snippet[a] !== '{') a += 1;
|
||||
|
||||
snippet = snippet.slice(0, a + 1) + '/*…*/}';
|
||||
|
||||
for (const child of member.type.members) {
|
||||
children.push(munge_type_element(child, depth + 1));
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {string[]} */
|
||||
const bullets = [];
|
||||
|
||||
for (const tag of doc?.tags ?? []) {
|
||||
const type = tag.tagName.escapedText;
|
||||
|
||||
switch (tag.tagName.escapedText) {
|
||||
case 'param':
|
||||
bullets.push(`- \`${tag.name.getText()}\` ${tag.comment}`);
|
||||
break;
|
||||
|
||||
case 'default':
|
||||
bullets.push(`- <span class="tag">default</span> \`${tag.comment}\``);
|
||||
break;
|
||||
|
||||
case 'returns':
|
||||
bullets.push(`- <span class="tag">returns</span> ${tag.comment}`);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(`unhandled JSDoc tag: ${type}`); // TODO indicate deprecated stuff
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
snippet,
|
||||
comment: (doc?.comment ?? '')
|
||||
.replace(/\/\/\/ type: (.+)/g, '/** @type {$1} */')
|
||||
.replace(/^( )+/gm, (match, spaces) => {
|
||||
return '\t'.repeat(match.length / 2);
|
||||
}),
|
||||
bullets,
|
||||
children
|
||||
};
|
||||
}
|
||||
|
||||
const bundled_types = await get_bundled_types();
|
||||
|
||||
{
|
||||
const module = bundled_types.get('svelte');
|
||||
|
||||
if (!module) throw new Error('Could not find svelte');
|
||||
|
||||
modules.push({
|
||||
name: 'svelte',
|
||||
comment: '',
|
||||
...get_types(module.code, module.ts_source_file.statements)
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const module = bundled_types.get('svelte/compiler');
|
||||
|
||||
if (!module) throw new Error('Could not find svelte/compiler');
|
||||
|
||||
modules.push({
|
||||
name: 'svelte/compiler',
|
||||
comment: '',
|
||||
...get_types(module.code, module.ts_source_file.statements)
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const module = bundled_types.get('svelte/action');
|
||||
|
||||
if (!module) throw new Error('Could not find svelte/action');
|
||||
|
||||
modules.push({
|
||||
name: 'svelte/action',
|
||||
comment: '',
|
||||
...get_types(module.code, module.ts_source_file.statements)
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const module = bundled_types.get('svelte/animate');
|
||||
|
||||
if (!module) throw new Error('Could not find svelte/animate');
|
||||
|
||||
modules.push({
|
||||
name: 'svelte/animate',
|
||||
comment: '',
|
||||
...get_types(module.code, module.ts_source_file.statements)
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const module = bundled_types.get('svelte/easing');
|
||||
|
||||
if (!module) throw new Error('Could not find svelte/easing');
|
||||
|
||||
modules.push({
|
||||
name: 'svelte/easing',
|
||||
comment: '',
|
||||
...get_types(module.code, module.ts_source_file.statements)
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const module = bundled_types.get('svelte/motion');
|
||||
|
||||
if (!module) throw new Error('Could not find svelte/motion');
|
||||
|
||||
modules.push({
|
||||
name: 'svelte/motion',
|
||||
comment: '',
|
||||
...get_types(module.code, module.ts_source_file.statements)
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const module = bundled_types.get('svelte/store');
|
||||
|
||||
if (!module) throw new Error('Could not find svelte/store');
|
||||
|
||||
modules.push({
|
||||
name: 'svelte/store',
|
||||
comment: '',
|
||||
...get_types(module.code, module.ts_source_file.statements)
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const module = bundled_types.get('svelte/transition');
|
||||
|
||||
if (!module) throw new Error('Could not find svelte/transition');
|
||||
|
||||
modules.push({
|
||||
name: 'svelte/transition',
|
||||
comment: '',
|
||||
...get_types(module.code, module.ts_source_file.statements)
|
||||
});
|
||||
}
|
||||
|
||||
modules.sort((a, b) => (a.name < b.name ? -1 : 1));
|
||||
|
||||
// Fix the duplicate/messed up types
|
||||
// !NOTE: This relies on mutation of `modules`
|
||||
$: {
|
||||
const module_with_SvelteComponent = modules.find((m) =>
|
||||
m.types.filter((t) => t.name === 'SvelteComponent')
|
||||
);
|
||||
|
||||
if (!module_with_SvelteComponent) break $;
|
||||
|
||||
const svelte_comp_part = module_with_SvelteComponent?.types.filter(
|
||||
(t) => t.name === 'SvelteComponent'
|
||||
);
|
||||
|
||||
if (svelte_comp_part?.[1]) {
|
||||
// Take the comment from [0], and insert into [1]. Then delete [0]
|
||||
svelte_comp_part[1].comment = svelte_comp_part?.[0].comment;
|
||||
delete svelte_comp_part[0];
|
||||
svelte_comp_part.reverse();
|
||||
svelte_comp_part.length = 1;
|
||||
|
||||
module_with_SvelteComponent.types = module_with_SvelteComponent?.types.filter(
|
||||
(t) => t.name !== 'SvelteComponent'
|
||||
);
|
||||
|
||||
module_with_SvelteComponent.types.push(svelte_comp_part[0]);
|
||||
module_with_SvelteComponent.types.sort((a, b) => (a.name < b.name ? -1 : 1));
|
||||
}
|
||||
}
|
||||
|
||||
// Fix the duplicate/messed up types
|
||||
// !NOTE: This relies on mutation of `modules`
|
||||
$: {
|
||||
const module_with_SvelteComponentTyped = modules.find((m) =>
|
||||
m.types.filter((t) => t.name === 'SvelteComponentTyped')
|
||||
);
|
||||
|
||||
if (!module_with_SvelteComponentTyped) break $;
|
||||
|
||||
const svelte_comp_typed_part = module_with_SvelteComponentTyped?.types.filter(
|
||||
(t) => t.name === 'SvelteComponentTyped'
|
||||
);
|
||||
|
||||
if (svelte_comp_typed_part?.[1]) {
|
||||
// Take the comment from [1], and insert into [0]. Then delete [1]
|
||||
svelte_comp_typed_part[0].comment = svelte_comp_typed_part?.[1].comment;
|
||||
delete svelte_comp_typed_part[1];
|
||||
svelte_comp_typed_part.length = 1;
|
||||
|
||||
module_with_SvelteComponentTyped.types = module_with_SvelteComponentTyped?.types.filter(
|
||||
(t) => t.name !== 'SvelteComponentTyped'
|
||||
);
|
||||
|
||||
module_with_SvelteComponentTyped.types.push(svelte_comp_typed_part[0]);
|
||||
module_with_SvelteComponentTyped.types.sort((a, b) => (a.name < b.name ? -1 : 1));
|
||||
}
|
||||
}
|
||||
|
||||
// Remove $$_attributes from ActionReturn
|
||||
|
||||
$: {
|
||||
const module_with_ActionReturn = modules.find((m) =>
|
||||
m.types.find((t) => t.name === 'ActionReturn')
|
||||
);
|
||||
|
||||
const new_children =
|
||||
module_with_ActionReturn?.types[1].children.filter((c) => c.name !== '$$_attributes') || [];
|
||||
|
||||
if (!module_with_ActionReturn) break $;
|
||||
|
||||
module_with_ActionReturn.types[1].children = new_children;
|
||||
}
|
||||
|
||||
try {
|
||||
fs.mkdirSync(new URL('../../src/lib/generated', import.meta.url), { recursive: true });
|
||||
} catch {}
|
||||
|
||||
fs.writeFileSync(
|
||||
new URL('../../src/lib/generated/type-info.js', import.meta.url),
|
||||
`
|
||||
/* This file is generated by running \`node scripts/extract-types.js\`
|
||||
in the packages/kit directory — do not edit it */
|
||||
export const modules = ${JSON.stringify(modules, null, ' ')};
|
||||
`.trim()
|
||||
);
|
@ -0,0 +1,182 @@
|
||||
import { modules } from '$lib/generated/type-info.js';
|
||||
|
||||
/** @param {string} content */
|
||||
export function replace_placeholders(content) {
|
||||
return content
|
||||
.replace(/> EXPANDED_TYPES: (.+?)#(.+)$/gm, (_, name, id) => {
|
||||
const module = modules.find((module) => module.name === name);
|
||||
if (!module) throw new Error(`Could not find module ${name}`);
|
||||
|
||||
const type = module.types.find((t) => t.name === id);
|
||||
|
||||
return (
|
||||
type.comment +
|
||||
type.children
|
||||
.map((child) => {
|
||||
let section = `### ${child.name}`;
|
||||
|
||||
if (child.bullets) {
|
||||
section += `\n\n<div class="ts-block-property-bullets">\n\n${child.bullets.join(
|
||||
'\n'
|
||||
)}\n\n</div>`;
|
||||
}
|
||||
|
||||
section += `\n\n${child.comment}`;
|
||||
|
||||
if (child.children) {
|
||||
section += `\n\n<div class="ts-block-property-children">\n\n${child.children
|
||||
.map(stringify)
|
||||
.join('\n')}\n\n</div>`;
|
||||
}
|
||||
|
||||
return section;
|
||||
})
|
||||
.join('\n\n')
|
||||
);
|
||||
})
|
||||
.replace(/> TYPES: (.+?)(?:#(.+))?$/gm, (_, name, id) => {
|
||||
const module = modules.find((module) => module.name === name);
|
||||
if (!module) throw new Error(`Could not find module ${name}`);
|
||||
|
||||
if (id) {
|
||||
const type = module.types.find((t) => t.name === id);
|
||||
|
||||
return (
|
||||
`<div class="ts-block">${fence(type.snippet, 'dts')}` +
|
||||
type.children.map(stringify).join('\n\n') +
|
||||
`</div>`
|
||||
);
|
||||
}
|
||||
|
||||
return `${module.comment}\n\n${module.types
|
||||
.map((t) => {
|
||||
let children = t.children.map((val) => stringify(val, 'dts')).join('\n\n');
|
||||
|
||||
const markdown = `<div class="ts-block">${fence(t.snippet, 'dts')}` + children + `</div>`;
|
||||
return `### [TYPE]: ${t.name}\n\n${t.comment}\n\n${markdown}\n\n`;
|
||||
})
|
||||
.join('')}`;
|
||||
})
|
||||
.replace(/> EXPORT_SNIPPET: (.+?)#(.+)?$/gm, (_, name, id) => {
|
||||
const module = modules.find((module) => module.name === name);
|
||||
if (!module) throw new Error(`Could not find module ${name} for EXPORT_SNIPPET clause`);
|
||||
|
||||
if (!id) {
|
||||
throw new Error(`id is required for module ${name}`);
|
||||
}
|
||||
|
||||
const exported = module.exports.filter((t) => t.name === id);
|
||||
|
||||
return exported
|
||||
.map((exportVal) => `<div class="ts-block">${fence(exportVal.snippet, 'dts')}</div>`)
|
||||
.join('\n\n');
|
||||
})
|
||||
.replace('> MODULES', () => {
|
||||
return modules
|
||||
.map((module) => {
|
||||
if (module.exports.length === 0 && !module.exempt) return '';
|
||||
|
||||
let import_block = '';
|
||||
|
||||
if (module.exports.length > 0) {
|
||||
// deduplication is necessary for now, because of `error()` overload
|
||||
const exports = Array.from(new Set(module.exports.map((x) => x.name)));
|
||||
|
||||
let declaration = `import { ${exports.join(', ')} } from '${module.name}';`;
|
||||
if (declaration.length > 80) {
|
||||
declaration = `import {\n\t${exports.join(',\n\t')}\n} from '${module.name}';`;
|
||||
}
|
||||
|
||||
import_block = fence(declaration, 'js');
|
||||
}
|
||||
|
||||
return `## ${module.name}\n\n${import_block}\n\n${module.comment}\n\n${module.exports
|
||||
.map((type) => {
|
||||
const markdown =
|
||||
`<div class="ts-block">${fence(type.snippet)}` +
|
||||
type.children.map(stringify).join('\n\n') +
|
||||
`</div>`;
|
||||
return `### ${type.name}\n\n${type.comment}\n\n${markdown}`;
|
||||
})
|
||||
.join('\n\n')}`;
|
||||
})
|
||||
.join('\n\n');
|
||||
})
|
||||
.replace(/> EXPORTS: (.+)/, (_, name) => {
|
||||
const module = modules.find((module) => module.name === name);
|
||||
if (!module) throw new Error(`Could not find module ${name} for EXPORTS: clause`);
|
||||
|
||||
if (module.exports.length === 0 && !module.exempt) return '';
|
||||
|
||||
let import_block = '';
|
||||
|
||||
if (module.exports.length > 0) {
|
||||
// deduplication is necessary for now, because of `error()` overload
|
||||
const exports = Array.from(new Set(module.exports.map((x) => x.name)));
|
||||
|
||||
let declaration = `import { ${exports.join(', ')} } from '${module.name}';`;
|
||||
if (declaration.length > 80) {
|
||||
declaration = `import {\n\t${exports.join(',\n\t')}\n} from '${module.name}';`;
|
||||
}
|
||||
|
||||
import_block = fence(declaration, 'js');
|
||||
}
|
||||
|
||||
return `${import_block}\n\n${module.comment}\n\n${module.exports
|
||||
.map((type) => {
|
||||
const markdown =
|
||||
`<div class="ts-block">${fence(type.snippet, 'dts')}` +
|
||||
type.children.map((val) => stringify(val, 'dts')).join('\n\n') +
|
||||
`</div>`;
|
||||
return `### ${type.name}\n\n${type.comment}\n\n${markdown}`;
|
||||
})
|
||||
.join('\n\n')}`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @param {keyof typeof import('../markdown/index').SHIKI_LANGUAGE_MAP} lang
|
||||
*/
|
||||
function fence(code, lang = 'ts') {
|
||||
return (
|
||||
'\n\n```' +
|
||||
lang +
|
||||
'\n' +
|
||||
(['js', 'ts'].includes(lang) ? '// @noErrors\n' : '') +
|
||||
code +
|
||||
'\n```\n\n'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('./types').Type} member
|
||||
* @param {keyof typeof import('../markdown').SHIKI_LANGUAGE_MAP} [lang]
|
||||
*/
|
||||
function stringify(member, lang = 'ts') {
|
||||
const bullet_block =
|
||||
member.bullets.length > 0
|
||||
? `\n\n<div class="ts-block-property-bullets">\n\n${member.bullets.join('\n')}</div>`
|
||||
: '';
|
||||
|
||||
const child_block =
|
||||
member.children.length > 0
|
||||
? `\n\n<div class="ts-block-property-children">${member.children
|
||||
.map((val) => stringify(val, lang))
|
||||
.join('\n')}</div>`
|
||||
: '';
|
||||
|
||||
return (
|
||||
`<div class="ts-block-property">${fence(member.snippet, lang)}` +
|
||||
`<div class="ts-block-property-details">\n\n` +
|
||||
bullet_block +
|
||||
'\n\n' +
|
||||
member.comment
|
||||
.replace(/\/\/\/ type: (.+)/g, '/** @type {$1} */')
|
||||
.replace(/^( )+/gm, (match, spaces) => {
|
||||
return '\t'.repeat(match.length / 2);
|
||||
}) +
|
||||
child_block +
|
||||
'\n</div></div>'
|
||||
);
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
<script>
|
||||
import { tick } from 'svelte';
|
||||
|
||||
export let html = '';
|
||||
export let x = 0;
|
||||
export let y = 0;
|
||||
|
||||
let width = 1;
|
||||
let tooltip;
|
||||
|
||||
// bit of a gross hack but it works — this prevents the
|
||||
// tooltip from disappearing off the side of the screen
|
||||
$: if (html && tooltip) {
|
||||
tick().then(() => {
|
||||
width = tooltip.getBoundingClientRect().width;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<div
|
||||
on:mouseenter
|
||||
on:mouseleave
|
||||
class="tooltip-container"
|
||||
style="left: {x}px; top: {y}px; --offset: {Math.min(-10, window.innerWidth - (x + width + 10))}px"
|
||||
>
|
||||
<div bind:this={tooltip} class="tooltip">
|
||||
<span>{@html html}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tooltip-container {
|
||||
--bg: var(--sk-theme-2);
|
||||
--arrow-size: 0.4rem;
|
||||
position: absolute;
|
||||
transform: translate(var(--offset), calc(2rem + var(--arrow-size)));
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
margin: 0 2rem 0 0;
|
||||
background-color: var(--bg);
|
||||
color: #fff;
|
||||
text-align: left;
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-radius: var(--sk-border-radius);
|
||||
font-family: var(--sk-font-mono);
|
||||
font-size: 1.2rem;
|
||||
white-space: pre-wrap;
|
||||
z-index: 100;
|
||||
filter: drop-shadow(2px 4px 6px #67677866);
|
||||
}
|
||||
|
||||
.tooltip::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: calc(-1 * var(--offset) - var(--arrow-size));
|
||||
top: calc(-2 * var(--arrow-size));
|
||||
border: var(--arrow-size) solid transparent;
|
||||
border-bottom-color: var(--bg);
|
||||
}
|
||||
|
||||
.tooltip :global(a) {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,60 @@
|
||||
import { onMount } from 'svelte';
|
||||
import Tooltip from './Tooltip.svelte';
|
||||
|
||||
export function setup() {
|
||||
onMount(() => {
|
||||
let tooltip;
|
||||
let timeout;
|
||||
|
||||
function over(event) {
|
||||
if (event.target.tagName === 'DATA-LSP') {
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (!tooltip) {
|
||||
tooltip = new Tooltip({
|
||||
target: document.body
|
||||
});
|
||||
|
||||
tooltip.$on('mouseenter', () => {
|
||||
clearTimeout(timeout);
|
||||
});
|
||||
|
||||
tooltip.$on('mouseleave', () => {
|
||||
clearTimeout(timeout);
|
||||
tooltip.$destroy();
|
||||
tooltip = null;
|
||||
});
|
||||
}
|
||||
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
const html = event.target.getAttribute('lsp');
|
||||
|
||||
const x = (rect.left + rect.right) / 2 + window.scrollX;
|
||||
const y = rect.top + window.scrollY;
|
||||
|
||||
tooltip.$set({
|
||||
html,
|
||||
x,
|
||||
y
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function out(event) {
|
||||
if (event.target.tagName === 'DATA-LSP') {
|
||||
timeout = setTimeout(() => {
|
||||
tooltip.$destroy();
|
||||
tooltip = null;
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('mouseover', over);
|
||||
window.addEventListener('mouseout', out);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('mouseover', over);
|
||||
window.removeEventListener('mouseout', out);
|
||||
};
|
||||
});
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
// This page now exists solely for redirect, prerendering triggers the `handleMissingID`
|
||||
export const prerender = false;
|
@ -1,15 +1,75 @@
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
import OnThisPage from './OnThisPage.svelte';
|
||||
import * as hovers from '$lib/utils/hovers';
|
||||
|
||||
export let data;
|
||||
|
||||
$: pages = data.sections.flatMap((section) => section.pages);
|
||||
$: index = pages.findIndex(({ path }) => path === $page.url.pathname);
|
||||
$: prev = pages[index - 1];
|
||||
$: next = pages[index + 1];
|
||||
|
||||
hovers.setup();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{data.page.title} - Svelte</title>
|
||||
<title>{data.page.title} • Docs • Svelte</title>
|
||||
|
||||
<meta name="twitter:title" content="Svelte docs" />
|
||||
<meta name="twitter:description" content="{data.page.title} • Svelte documentation" />
|
||||
<meta name="Description" content="{data.page.title} • Svelte documentation" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="text">
|
||||
{@html data.page.content}
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div>
|
||||
<span class:faded={!prev}>previous</span>
|
||||
{#if prev}
|
||||
<a href={prev.path}>{prev.title}</a>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class:faded={!next}>next</span>
|
||||
{#if next}
|
||||
<a href={next.path}>{next.title}</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<OnThisPage details={data.page} />
|
||||
|
||||
<style>
|
||||
.controls {
|
||||
max-width: calc(var(--sk-line-max-width) + 1rem);
|
||||
border-top: 1px solid var(--sk-back-4);
|
||||
padding: 1rem 0 0 0;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
margin: 6rem 0 0 0;
|
||||
}
|
||||
|
||||
.controls > :first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.controls > :last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.controls span {
|
||||
display: block;
|
||||
font-size: 1.2rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
color: var(--sk-text-3);
|
||||
}
|
||||
|
||||
.controls span.faded {
|
||||
opacity: 0.4;
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in new issue