Skip to content

Commit

Permalink
chore: Clean up dynamic code imports, types
Browse files Browse the repository at this point in the history
  • Loading branch information
kiosion committed Oct 4, 2023
1 parent ab71c6f commit b28e579
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 155 deletions.
16 changes: 14 additions & 2 deletions svelte-app/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"es2017": true
},
"extends": [
"prettier",
"eslint:recommended",
"plugin:svelte/recommended",
"plugin:@typescript-eslint/recommended"
Expand All @@ -26,6 +27,10 @@
"@typescript-eslint/no-namespace": ["off"],
"@typescript-eslint/restrict-template-expressions": ["off"],
"@typescript-eslint/no-unused-vars": ["off"],
"@typescript-eslint/no-duplicate-imports": ["error"],
"@typescript-eslint/ban-ts-comment": ["error", {
"ts-expect-error": "allow-with-description"
}],
"array-bracket-spacing": ["error", "never"],
"arrow-spacing": ["error"],
"block-scoped-var": ["error"],
Expand All @@ -42,7 +47,7 @@
"keyword-spacing": ["error"],
"linebreak-style": ["error"],
"no-confusing-arrow": ["error"],
"no-duplicate-imports": ["error"],
"no-duplicate-imports": ["off"],
"no-trailing-spaces": ["error"],
"no-var": ["error"],
"no-eval": ["error"],
Expand All @@ -58,7 +63,14 @@
"no-unused-vars": ["off"],
"object-shorthand": ["error"],
"one-var-declaration-per-line": ["error"],
"prettier/prettier": ["error"],
"prettier/prettier": ["error", {
"bracketSpacing": true,
"svelteBracketNewLine": true
}, {
"usePrettierrc": true
}],
"arrow-body-style": ["off"],
"prefer-arrow-callback": ["off"],
"quotes": ["error", "single", { "avoidEscape": true }],
"quote-props": ["error", "as-needed"],
"semi": ["error", "always"],
Expand Down
2 changes: 2 additions & 0 deletions svelte-app/.prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"singleQuote": true,
"trailingComma": "none",
"printWidth": 90,
"svelteBracketNewLine": true,
"bracketSpacing": true,
"plugins": [
"./node_modules/prettier-plugin-svelte",
"./node_modules/prettier-plugin-tailwindcss"
Expand Down
177 changes: 83 additions & 94 deletions svelte-app/src/components/code-block.svelte
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
<script lang="ts">
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck Need to fix typings for hlAuto and hlHighlight
import { onDestroy, onMount } from 'svelte';
import {
type Highlight,
type HighlightAuto,
type LanguageType,
LineNumbers
} from 'svelte-highlight';
import { browser } from '$app/environment';
import { t } from '$i18n';
import Settings from '$stores/settings';
import Hoverable from '$components/hoverable.svelte';
import {
Highlight as _Highlight,
HighlightSvelte as _HighlightSvelte,
LineNumbers as _LineNumbers
} from '$components/code-block/imports';
import Icon from '$components/icon.svelte';
import Spinner from '$components/loading/spinner.svelte';
import Tooltip from '$components/tooltip.svelte';
import Icon from './icon.svelte';
import Tooltip from './tooltip.svelte';
import type { LanguageType as _LanguageType } from 'svelte-highlight/languages';
export let content: string,
filename: string | undefined,
Expand All @@ -28,11 +24,13 @@
let hovered = false;
let copied = false;
let hlHighlight: Highlight,
hlAuto: HighlightAuto,
hlLang: Promise<LanguageType>,
hlStyles: string | undefined,
container: HTMLElement,
let LanguageType: Promise<_LanguageType<string>> = new Promise<void>((resolve) =>
resolve()
) as unknown as Promise<_LanguageType<string>>,
HighlightSvelte: Promise<_HighlightSvelte | undefined>,
LineNumbers: Promise<_LineNumbers | undefined>,
Highlight: Promise<_Highlight>,
HighlightStyles: string | undefined,
codeContainer: HTMLElement,
innerHeight: number,
hideLoader = false;
Expand All @@ -49,71 +47,65 @@
}
},
{ theme } = Settings,
unsubscribe = theme.subscribe(
async (res) =>
(hlStyles =
res === 'light'
? (await import('svelte-highlight/styles/github')).default
: (await import('svelte-highlight/styles/github-dark')).default)
unsubscribers = [
theme.subscribe(
async (res) =>
(HighlightStyles =
res === 'light'
? (await import('svelte-highlight/styles/github')).default
: (await import('svelte-highlight/styles/github-dark')).default)
)
];
onMount(() => {
lang = lang?.toLowerCase();
unsubscribers.push(
_LineNumbers(showLineNumbers).subscribe((res) => (LineNumbers = res)),
_HighlightSvelte(lang === 'svelte').subscribe((res) => (HighlightSvelte = res)),
_Highlight().subscribe((res) => (Highlight = res))
);
onMount(async () => {
!lang
? (hlAuto = await import('svelte-highlight')).HighlightAuto
: (hlHighlight = (await import('svelte-highlight'))
.Highlight as unknown as Highlight);
hlLang = (async () => {
lang = lang?.toLowerCase();
if (!lang) {
return (await import('svelte-highlight/languages/markdown')).markdown;
LanguageType = (() => {
if (lang === 'svelte') {
return Promise.resolve() as unknown as Promise<_LanguageType<string>>;
}
let imp: LanguageType | undefined;
try {
// First, handle some cases where the name isn't the import name
// Handle some cases where the name isn't the import name
switch (lang) {
case 'c#': {
imp = (await import('svelte-highlight/languages/csharp')).csharp;
break;
}
case 'c++': {
imp = (await import('svelte-highlight/languages/cpp')).cpp;
break;
}
case 'html': {
imp = (await import('svelte-highlight/languages/xml')).xml;
break;
}
case 'c#':
return import('svelte-highlight/languages/csharp').then((res) => res.csharp);
case 'c++':
return import('svelte-highlight/languages/cpp').then((res) => res.cpp);
case 'html':
return import('svelte-highlight/languages/xml').then((res) => res.xml);
case 'sh':
case 'shell': {
imp = (await import('svelte-highlight/languages/bash')).bash;
break;
}
default: {
imp = (
await import(`../../node_modules/svelte-highlight/languages/${lang}.js`)
)[lang];
break;
}
case 'shell':
return import('svelte-highlight/languages/bash').then((res) => res.bash);
case undefined:
default:
return import(
`../../node_modules/svelte-highlight/languages/${lang}.js`
).then((res) => res[lang as PropertyKey]);
}
} catch (e) {
imp = (await import('svelte-highlight/languages/markdown')).markdown;
} catch {
return import('svelte-highlight/languages/markdown').then((res) => res.markdown);
}
return imp as LanguageType;
})();
});
onDestroy(() => unsubscribe());
onDestroy(() => {
unsubscribers.forEach((unsub) => unsub());
});
$: browser && codeContainer && (codeContainer.style.height = `${innerHeight ?? 0}px`);
$: browser && (hideLoader = innerHeight > 52);
</script>

<svelte:head>
{#if hlStyles}
{#if HighlightStyles}
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html hlStyles}
{@html HighlightStyles}
{/if}
</svelte:head>

Expand All @@ -122,7 +114,6 @@
role="group"
aria-label={$t('Code block')}
aria-labelledby={filename ? `${id}-filename` : undefined}
bind:this={container}
>
{#if filename}
<div
Expand All @@ -132,18 +123,16 @@
{filename}
</div>
{/if}
<Hoverable>
<Tooltip text={$t('Copy to clipboard')} position="left" delay={200} fixed>
{#key copied}
<button
class="focusOutline-sm absolute right-0 top-0 z-[2] cursor-pointer rounded-sm pb-3 pl-3 pr-4 pt-4 text-dark/60 hover:text-dark dark:text-light/60 dark:hover:text-light"
on:click={() => copy()}
>
<Icon icon={copied ? 'Check' : 'Copy'} />
</button>
{/key}
</Tooltip>
</Hoverable>
<Tooltip text={$t('Copy to clipboard')} position="left" delay={200} fixed>
{#key copied}
<button
class="focusOutline-sm absolute right-0 top-0 z-[2] cursor-pointer rounded-sm pb-3 pl-3 pr-4 pt-4 text-dark/60 hover:text-dark/80 dark:text-light/60 dark:hover:text-light/80"
on:click={() => copy()}
>
<Icon icon={copied ? 'Check' : 'Copy'} />
</button>
{/key}
</Tooltip>
<div
class="focusOutline relative h-fit w-full overflow-hidden rounded-sm text-lg transition-[height]"
bind:this={codeContainer}
Expand All @@ -162,26 +151,26 @@
{#if !hideLoader}
<span class="mt-11 block" />
{/if}
{#if !lang}
<svelte:component this={hlAuto} code={content} />
{:else}
{#await hlLang then resolvedLang}
<!-- eslint-disable-next-line prettier/prettier -->
{#await Promise.all([HighlightSvelte, Highlight, LanguageType, LineNumbers]) then [resolvedHighlightSvelte, resolvedHighlight, resolvedLang, resolvedLineNumbers]}
<svelte:component
this={lang === 'svelte' ? resolvedHighlightSvelte : resolvedHighlight}
code={content}
language={resolvedLang}
let:highlighted
>
{#if showLineNumbers === true}
<svelte:component
this={hlHighlight}
code={content}
language={resolvedLang}
let:highlighted
>
<LineNumbers {highlighted} hideBorder wrapLines />
</svelte:component>
{:else}
<svelte:component this={hlHighlight} code={content} language={resolvedLang} />
this={resolvedLineNumbers}
{highlighted}
hideBorder
wrapLines
/>
{/if}
{:catch error}
<div>Error loading: {error.message}</div>
{/await}
{/if}
</svelte:component>
{:catch error}
<div>Error loading: {error.message}</div>
{/await}
</div>
</div>
</div>
81 changes: 81 additions & 0 deletions svelte-app/src/components/code-block/imports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { SvelteComponent } from 'svelte';
import { derived, readable, writable } from 'svelte/store';

import type { ComponentType } from 'svelte';
import type {
HighlightEvents,
HighlightProps,
HighlightSlots
} from 'svelte-highlight/Highlight.svelte';
import type {
HighlightSvelteEvents,
HighlightSvelteProps,
HighlightSvelteSlots
} from 'svelte-highlight/HighlightSvelte.svelte';
import type {
LineNumbersEvents,
LineNumbersProps,
LineNumbersSlots
} from 'svelte-highlight/LineNumbers.svelte';

class _LineNumbers extends SvelteComponent<
LineNumbersProps,
LineNumbersEvents,
LineNumbersSlots
> {}
class _HighlightSvelte extends SvelteComponent<
HighlightSvelteProps,
HighlightSvelteEvents,
HighlightSvelteSlots
> {}
class _Highlight extends SvelteComponent<
HighlightProps,
HighlightEvents,
HighlightSlots
> {}

export type LineNumbers = ComponentType<_LineNumbers>;
export type HighlightSvelte = ComponentType<_HighlightSvelte>;
export type Highlight = ComponentType<_Highlight>;

const stores = {
LineNumbers: writable<Promise<LineNumbers> | undefined>(undefined),
HighlightSvelte: writable<Promise<HighlightSvelte> | undefined>(undefined),
Highlight: writable<Promise<Highlight> | undefined>(undefined)
} as const;

const genericDerivedAsyncImport = <K extends keyof typeof stores>(
name: K,
target: (typeof stores)[K]
) =>
derived(target, (value: Parameters<(typeof target)['set']>[0]) => {
if (value === undefined) {
const promise = import(
`../../../node_modules/svelte-highlight/${name}.svelte`
).then((module) => module.default) as unknown as NonNullable<typeof value>;
// @ts-expect-error - TS infers target['set'] here as an Intersection of Writable ReturnTypes rather than Unions
target.set(promise);
return promise;
}
return value;
}),
empty = () =>
readable<Promise<undefined>>(new Promise((resolve) => resolve(undefined)));

const lineNumbers = (useLineNumbers: boolean) => {
return useLineNumbers
? genericDerivedAsyncImport('LineNumbers', stores.LineNumbers)
: empty();
},
highlightSvelte = (useHlSvelte: boolean) => {
return useHlSvelte
? genericDerivedAsyncImport('HighlightSvelte', stores.HighlightSvelte)
: empty();
},
highlight = () => genericDerivedAsyncImport('Highlight', stores.Highlight);

export {
highlight as Highlight,
highlightSvelte as HighlightSvelte,
lineNumbers as LineNumbers
};
Loading

0 comments on commit b28e579

Please sign in to comment.