diff --git a/packages/kitbook/src/lib/kitbook-types.ts b/packages/kitbook/src/lib/kitbook-types.ts index 71cae020..142f3681 100644 --- a/packages/kitbook/src/lib/kitbook-types.ts +++ b/packages/kitbook/src/lib/kitbook-types.ts @@ -273,7 +273,11 @@ export type DeepPartial = { export interface RPCFunctions { svelte_modules: () => Promise + open_or_create_variant: ({ filepath, props }: { filepath: string, props: Record }) => void + open_or_create_file: ({ filepath, template }: { filepath: string, template: string }) => void + // notifications module_updated: (filepath: string) => void + open_in_editor: (url: string) => void } export type SvelteModules = Record diff --git a/packages/kitbook/src/lib/modules/rpc-client.ts b/packages/kitbook/src/lib/modules/rpc-client.ts index 732e7c12..5832aa05 100644 --- a/packages/kitbook/src/lib/modules/rpc-client.ts +++ b/packages/kitbook/src/lib/modules/rpc-client.ts @@ -9,13 +9,13 @@ import { browser } from '$app/environment' export const rpc_client = create_rpc_client_stores() function create_rpc_client_stores() { - let functions: BirpcReturn> + let _functions: BirpcReturn> const svelte_modules = writable({}) const latest_edited_filepath = writable(null) if (browser) { createHotContext().then((hot) => { - functions = createRPCClient>( + _functions = createRPCClient>( RPC_NAME, hot, { @@ -23,19 +23,24 @@ function create_rpc_client_stores() { latest_edited_filepath.set(filepath) update_svelte_modules() }, + open_in_editor(url) { + fetch(url) + }, }, ) update_svelte_modules() async function update_svelte_modules() { - const routes = await functions.svelte_modules() + const routes = await _functions.svelte_modules() svelte_modules.set(routes) } }) } return { - functions, + get functions() { + return _functions + }, svelte_modules, latest_edited_filepath, } diff --git a/packages/kitbook/src/lib/open/openFiles.ts b/packages/kitbook/src/lib/open/openFiles.ts index 73563863..280a38ce 100644 --- a/packages/kitbook/src/lib/open/openFiles.ts +++ b/packages/kitbook/src/lib/open/openFiles.ts @@ -1,3 +1,4 @@ +import { rpc_client } from '../modules/rpc-client' import { getFilenameAndExtension } from './get-filename-and-extension' import { serializeIntersection } from './serialize' @@ -7,9 +8,6 @@ export function openComponent(filepath: string, viteBase: string) { } export function openVariants(filepath: string, componentDetail?: SvelteComponentDetail) { - if (!import.meta.hot) - return alert('Dev server must be running with HMR enabled to use this feature.') - if (!componentDetail?.options) return sendOpenVariantsRequest(filepath, {}) @@ -20,10 +18,7 @@ export function openVariants(filepath: string, componentDetail?: SvelteComponent } export function sendOpenVariantsRequest(filepath: string, serializedState: Record) { - if (!import.meta.hot) - return alert('Dev server must be running with HMR enabled to use this feature.') - - import.meta.hot.send('kitbook:to-server:open-variants', { filepath, props: serializedState || {} }) + rpc_client.functions.open_or_create_variant({ filepath, props: serializedState || {} }) } export function openMarkdown(filepath: string) { @@ -31,7 +26,7 @@ export function openMarkdown(filepath: string) { ensureFileExists(filepath, markdownTemplate) } -export function openComposition({ filepath, compositionName }: { filepath: string; compositionName?: string }) { +export function openComposition({ filepath, compositionName }: { filepath: string, compositionName?: string }) { const { filepathWithoutExtension, filenameWithoutExtensions, extension } = getFilenameAndExtension(filepath) const svelteCompositionTemplate = ` Hi {data.name}` - import.meta.hot.send('kitbook:to-server:ensure-file-exists', { filepath, template }) + rpc_client.functions.open_or_create_file({ filepath, template }) const pageTemplate = `export const load = (() => { return { name: 'Bill' } })` - import.meta.hot.send('kitbook:to-server:ensure-file-exists', { filepath: filepath.replace('+page.svelte', '+page.ts'), template: pageTemplate }) + rpc_client.functions.open_or_create_file({ filepath: filepath.replace('+page.svelte', '+page.ts'), template: pageTemplate }) - import.meta.hot.send('kitbook:to-server:open-variants', { filepath, props: { data: { name: 'John' } } }) + rpc_client.functions.open_or_create_variant({ filepath, props: { data: { name: 'John' } } }) } export function createNewServerEndpoint(filepath: string) { @@ -131,7 +122,7 @@ export const POST: RequestHandler = async ({ locals: { getSession }, request }) } } ` - import.meta.hot.send('kitbook:to-server:ensure-file-exists', { filepath, template }) + rpc_client.functions.open_or_create_file({ filepath, template }) const testTemplate = `import { POST, type OperationRequestBody, type OperationResponseBody } from './+server' import { request } from '$lib/mocks/sveltekit-endpoint-helper' import { ResponseCodes } from '$lib/response-codes' @@ -166,7 +157,7 @@ describe(POST, () => { }) }) ` - import.meta.hot.send('kitbook:to-server:ensure-file-exists', { filepath: filepath.replace('+server.ts', '_server.test.ts'), template: testTemplate }) + rpc_client.functions.open_or_create_file({ filepath: filepath.replace('+server.ts', '_server.test.ts'), template: testTemplate }) } export function createNewComponent(filepath: string) { @@ -175,13 +166,6 @@ export function createNewComponent(filepath: string) { Hi {name}` - import.meta.hot.send('kitbook:to-server:ensure-file-exists', { filepath, template }) - import.meta.hot.send('kitbook:to-server:open-variants', { filepath, props: { name: 'John' } }) -} - -if (import.meta.hot) { - import.meta.hot.on('kitbook:to-client:open-file', ({ filepath, viteBase }) => { - const file_loc = `${filepath}:1:1` - fetch(`${viteBase}/__open-in-editor?file=${encodeURIComponent(file_loc)}`) - }) + rpc_client.functions.open_or_create_file({ filepath, template }) + rpc_client.functions.open_or_create_variant({ filepath, props: { data: { name: 'John' } } }) } diff --git a/packages/kitbook/src/lib/plugins/files/files.plugin.ts b/packages/kitbook/src/lib/plugins/files/files.plugin.ts new file mode 100644 index 00000000..820c0a31 --- /dev/null +++ b/packages/kitbook/src/lib/plugins/files/files.plugin.ts @@ -0,0 +1,54 @@ +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import { access, constants, mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import type { Plugin } from 'vite' +import type { KitbookPluginContext } from '../vite.js' +import { removeQuotesFromSerializedFunctions } from '../../open/serialize.js' + +export function FilesPlugin({ rpc_functions, settings }: KitbookPluginContext): Plugin { + function writeFileIfNeededThenOpen(filepath: string, template: string) { + access(filepath, constants.F_OK, (err) => { + if (err) { + const directory = dirname(filepath) + mkdirSync(directory, { recursive: true }) + writeFileSync(filepath, template) + console.info(`added ${filepath}`) + } + + const file_location = `${filepath}:1:1` + const open_in_editor_url = `${settings.viewer.__internal.viteBase}/__open-in-editor?file=${encodeURIComponent(file_location)}` + rpc_functions.open_in_editor(open_in_editor_url) + }) + } + + rpc_functions.open_or_create_variant = ({ filepath, props }) => { + const props_without_newlines_tabs = JSON.stringify(props || {}, null, 2) + .replace(/\\n/g, '').replace(/\\t/g, '') + const code = getVariantsTemplate().replace('shared = {}', `shared = ${props_without_newlines_tabs}`) + const code_with_component_reference = code.replace('Template.svelte', filepath.split('/').pop()) + const template = removeQuotesFromSerializedFunctions(code_with_component_reference) + + const variantsPath = filepath + .replace('.svelte', '.variants.ts') + .replace('+page', '_page') + .replace('+layout', '_layout') + + writeFileIfNeededThenOpen(variantsPath, template) + } + + rpc_functions.open_or_create_file = ({ filepath, template }) => { + writeFileIfNeededThenOpen(filepath, template) + } + + return { + name: 'vite-plugin-kitbook:files', + enforce: 'pre', + apply: 'serve', + } +} + +function getVariantsTemplate() { + const _dirname = dirname(fileURLToPath(import.meta.url)) + const filepath = resolve(_dirname, '../virtual/Template.variants.ts.txt') + return readFileSync(filepath, 'utf-8') +} diff --git a/packages/kitbook/src/lib/plugins/rpc/plugin.ts b/packages/kitbook/src/lib/plugins/rpc/rpc.plugin.ts similarity index 77% rename from packages/kitbook/src/lib/plugins/rpc/plugin.ts rename to packages/kitbook/src/lib/plugins/rpc/rpc.plugin.ts index f57df343..2b011c8f 100644 --- a/packages/kitbook/src/lib/plugins/rpc/plugin.ts +++ b/packages/kitbook/src/lib/plugins/rpc/rpc.plugin.ts @@ -5,20 +5,23 @@ import type { RPCFunctions } from '../../kitbook-types' import type { KitbookPluginContext } from '../vite.js' import { get_svelte_modules } from './get-svelte-modules.js' -export function RPCPlugin(context: KitbookPluginContext): Plugin { +export function RPCPlugin({ config, rpc_functions }: KitbookPluginContext): Plugin { return { name: 'vite-plugin-kitbook:rpc', enforce: 'pre', apply: 'serve', // TODO: remove later once also getting modules from build configResolved(_config) { - context.config = _config + config = _config }, configureServer(server) { - context.rpc_functions.svelte_modules = () => get_svelte_modules(server, context.config.root) + const rpc_server = createRPCServer(RPC_NAME, server.ws, rpc_functions) - const rpc_server = createRPCServer(RPC_NAME, server.ws, context.rpc_functions) + rpc_functions.svelte_modules = () => get_svelte_modules(server, config.root) + rpc_functions.open_in_editor = (url) => { + rpc_server.open_in_editor.asEvent(url) + } const debounce_module_updated = debounce((filepath: string) => { rpc_server.module_updated.asEvent(filepath) diff --git a/packages/kitbook/src/lib/plugins/viewer/plugin.ts b/packages/kitbook/src/lib/plugins/viewer/plugin.ts index 04a78f0e..be2a2e89 100644 --- a/packages/kitbook/src/lib/plugins/viewer/plugin.ts +++ b/packages/kitbook/src/lib/plugins/viewer/plugin.ts @@ -1,8 +1,7 @@ -import { access, constants, mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import { readFileSync } from 'node:fs' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' -import type { HMRBroadcasterClient, Plugin } from 'vite' -import { removeQuotesFromSerializedFunctions } from '../../open/serialize.js' +import type { Plugin } from 'vite' import type { KitbookPluginContext } from '../vite.js' const LOAD_VIEWER_ID = 'virtual:kitbook-load-viewer.js' @@ -39,26 +38,6 @@ export function ViewerPlugin({ settings }: KitbookPluginContext): Plugin { server.ws.on('kitbook:to-server:tools:change-state', (data) => { server.ws.send('kitbook:to-client:tools:change-state', data) }) - - server.ws.on('kitbook:to-server:ensure-file-exists', ({ filepath, template }, client) => { - writeFileIfNeededThenOpen(filepath, template, settings.viewer.__internal.viteBase, client) - }) - - server.ws.on('kitbook:to-server:open-variants', ({ filepath, props }, client) => { - // TODO: parse Svelte file to get props if props is null (make it an empty object if from Viewer and component simply has no props) - const props_without_newlines_tabs = JSON.stringify(props || {}, null, 2) - .replace(/\\n/g, '').replace(/\\t/g, '') - const code = getVariantsTemplate().replace('shared = {}', `shared = ${props_without_newlines_tabs}`) - const code_with_component_reference = code.replace('Template.svelte', filepath.split('/').pop()) - const template = removeQuotesFromSerializedFunctions(code_with_component_reference) - - const variantsPath = filepath - .replace('.svelte', '.variants.ts') - .replace('+page', '_page') - .replace('+layout', '_layout') - - writeFileIfNeededThenOpen(variantsPath, template, settings.viewer.__internal.viteBase, client) - }) }, } } @@ -69,22 +48,3 @@ function componentListenerCode(): string { const filepath = resolve(_dirname, './listenForComponentsElements.js') return readFileSync(filepath, 'utf-8') } - -function writeFileIfNeededThenOpen(filepath: string, template: string, viteBase: string, client: HMRBroadcasterClient) { - access(filepath, constants.F_OK, (err) => { - if (err) { - const directory = dirname(filepath) - mkdirSync(directory, { recursive: true }) - writeFileSync(filepath, template) - console.info(`added ${filepath}`) - } - - client.send('kitbook:to-client:open-file', { filepath, viteBase }) - }) -} - -function getVariantsTemplate() { - const _dirname = dirname(fileURLToPath(import.meta.url)) - const filepath = resolve(_dirname, '../virtual/Template.variants.ts.txt') - return readFileSync(filepath, 'utf-8') -} diff --git a/packages/kitbook/src/lib/plugins/vite.ts b/packages/kitbook/src/lib/plugins/vite.ts index b56fc5c1..ea2c6d5a 100644 --- a/packages/kitbook/src/lib/plugins/vite.ts +++ b/packages/kitbook/src/lib/plugins/vite.ts @@ -4,7 +4,8 @@ import { merge_user_settings_with_defaults } from './context/merge-user-settings import { copy_kitbook_routes } from './context/copy-kitbook-routes.js' import { MainPlugin } from './main/plugin.js' import { ViewerPlugin } from './viewer/plugin.js' -import { RPCPlugin } from './rpc/plugin' +import { RPCPlugin } from './rpc/rpc.plugin.js' +import { FilesPlugin } from './files/files.plugin.js' /** * Vite plugin to add a Kitbook to SvelteKit projects. Will automatically add Kitbook routes to `src/routes/kitbook` unless you update the `routesDirectory` and `kitbookRoute` settings. @@ -16,6 +17,7 @@ export function kitbook(user_settings: Partial = {}): Plugin[] MainPlugin(context.settings), ViewerPlugin(context), RPCPlugin(context), + FilesPlugin(context), ] } @@ -29,7 +31,10 @@ function create_context(user_settings: Partial): KitbookPluginC rpc_functions: { // @ts-expect-error function set in RPCPlugin svelte_modules: () => {}, + open_or_create_variant: ({ filepath, props }) => {}, + open_or_create_file: ({filepath, template}) => {}, module_updated: (filepath) => {}, + open_in_editor: (url) => {}, }, } } diff --git a/packages/kitbook/src/lib/routes/[...file]/Compositions.svelte b/packages/kitbook/src/lib/routes/[...file]/Compositions.svelte index 1ee40ef6..ccd9d822 100644 --- a/packages/kitbook/src/lib/routes/[...file]/Compositions.svelte +++ b/packages/kitbook/src/lib/routes/[...file]/Compositions.svelte @@ -3,6 +3,7 @@ import View from '../../view/View.svelte' import { openComposition } from '../../open/openFiles' import type { CompositionModule, KitbookSettings, Language } from '../../kitbook-types' + import { dev } from '$app/environment' export let compositionsModules: Record export let pathWithoutExtension: string @@ -47,7 +48,11 @@