Skip to content

Commit

Permalink
feat(webui): support client-entry-level hmr
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Apr 28, 2024
1 parent 627b7fb commit 0da5434
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 29 deletions.
20 changes: 11 additions & 9 deletions packages/client/client/context.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as cordis from 'cordis'
import {
App, Component, createApp, defineComponent, h, inject, markRaw,
onBeforeUnmount, provide, Ref, resolveComponent,
App, Component, createApp, defineComponent, h, inject, InjectionKey,
markRaw, onBeforeUnmount, provide, Ref, resolveComponent,
} from 'vue'
import ActionService from './plugins/action'
import I18nService from './plugins/i18n'
Expand All @@ -19,16 +19,18 @@ export interface Context {
internal: Internal
}

const kContext = Symbol('context') as InjectionKey<Context>

export function useContext() {
const parent = inject('cordis') as Context
const parent = inject(kContext)
const fork = parent.plugin(() => {})
onBeforeUnmount(() => fork.dispose())
return fork.ctx
}

export function useRpc<T>(): Ref<T> {
const parent = inject('cordis') as Context
return parent.extension?.data
const parent = inject(kContext)
return parent.$entry?.data
}

export interface Internal {}
Expand All @@ -38,15 +40,15 @@ export class Context extends cordis.Context {

constructor() {
super()
this.extension = null
this.$entry = null
this.internal = {} as Internal
this.app = createApp(defineComponent({
setup: () => () => [
h(resolveComponent('k-slot'), { name: 'root', single: true }),
h(resolveComponent('k-slot'), { name: 'global' }),
],
}))
this.app.provide('cordis', this)
this.app.provide(kContext, this)

this.plugin(ActionService)
this.plugin(I18nService)
Expand Down Expand Up @@ -77,9 +79,9 @@ export class Context extends cordis.Context {
wrapComponent(component: Component) {
if (!component) return
const caller = this[Context.current] || this
if (!caller.extension) return component
if (!caller.$entry) return component
return defineComponent((props, { slots }) => {
provide('cordis', caller)
provide(kContext, caller)
return () => h(component, props, slots)
})
}
Expand Down
43 changes: 27 additions & 16 deletions packages/client/client/plugins/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { Ref, ref, shallowReactive, watch } from 'vue'
import { Context } from '../context'
import { Service } from '../utils'
import { receive, store } from '../data'
import { EffectScope } from 'cordis'
import { ForkScope } from 'cordis'
import { Dict } from 'cosmokit'

declare module '../context' {
interface Context {
$loader: LoaderService
extension?: LoadResult
$entry?: ClientEntry
}
}

Expand All @@ -23,28 +23,38 @@ export function unwrapExports(module: any) {
return module?.default || module
}

const loaders: Dict<(ctx: Context, url: string) => Promise<void>> = {
type LoaderFactory = (ctx: Context, url: string) => Promise<ForkScope>

function jsLoader(ctx: Context, exports: {}) {
return ctx.plugin(unwrapExports(exports), ctx.$entry.data)
}

function cssLoader(ctx: Context, link: HTMLLinkElement) {
ctx.effect(() => {
document.head.appendChild(link)
return () => document.head.removeChild(link)
})
}

const loaders: Dict<LoaderFactory> = {
async [`.css`](ctx, url) {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = url
await new Promise((resolve, reject) => {
link.onload = resolve
link.onerror = reject
ctx.effect(() => {
document.head.appendChild(link)
return () => document.head.removeChild(link)
})
})
return ctx.plugin(cssLoader, link)
},
async [``](ctx, url) {
const exports = await import(/* @vite-ignore */ url)
ctx.plugin(unwrapExports(exports), ctx.extension.data)
return ctx.plugin(jsLoader, exports)
},
}

export interface LoadResult {
scope: EffectScope
export interface ClientEntry {
fork?: ForkScope
paths: string[]
done: Ref<boolean>
data: Ref
Expand All @@ -53,7 +63,7 @@ export interface LoadResult {
export default class LoaderService extends Service {
private backendId: any

public extensions: Dict<LoadResult> = shallowReactive({})
public extensions: Dict<ClientEntry> = shallowReactive({})

constructor(ctx: Context) {
super(ctx, '$loader', true)
Expand All @@ -77,18 +87,19 @@ export default class LoaderService extends Service {

for (const key in this.extensions) {
if (rest[key]) continue
this.extensions[key].scope.dispose()
this.extensions[key].fork?.dispose()
delete this.extensions[key]
}

await Promise.all(Object.entries(rest).map(([key, { files, paths, data }]) => {
if (this.extensions[key]) return
const scope = this.ctx.isolate('extension').plugin(() => {})
scope.ctx.extension = this.extensions[key] = { done: ref(false), scope, paths, data: ref(data) }
const task = Promise.all(files.map((url) => {
const ctx = this.ctx.isolate('$entry')
ctx.$entry = this.extensions[key] = { done: ref(false), paths, data: ref(data) }

const task = Promise.all(files.map(async (url) => {
for (const ext in loaders) {
if (!url.endsWith(ext)) continue
return loaders[ext](scope.ctx, url)
ctx.$entry.fork = await loaders[ext](ctx, url)
}
}))
task.finally(() => this.extensions[key].done.value = true)
Expand Down
4 changes: 3 additions & 1 deletion packages/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ export async function build(root: string, config: vite.UserConfig = {}) {
}
}

export async function createServer(baseDir: string, config: vite.InlineConfig = {}) {
export interface InlineConfig extends vite.InlineConfig {}

export async function createServer(baseDir: string, config: InlineConfig = {}) {
const root = resolve(fileURLToPath(import.meta.url), '../../app')
return vite.createServer(vite.mergeConfig({
root,
Expand Down
3 changes: 0 additions & 3 deletions plugins/insight/client/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
{
"extends": "../../../tsconfig.client",
"compilerOptions": {
"rootDir": ".",
},
"include": [
".",
],
Expand Down
22 changes: 22 additions & 0 deletions plugins/webui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,28 @@ class NodeConsole extends WebUI<NodeConsole.Config> {
server: {
fs: dev?.fs,
},
plugins: [{
name: 'cordis-hmr',
transform: (code, id, options) => {
for (const [key, { files }] of Object.entries(this.entries)) {
if (typeof files === 'string' || Array.isArray(files)) continue
if (fileURLToPath(files.dev) !== id) continue
return {
code: code + [
'if (import.meta.hot) {',
' import.meta.hot.accept(async (module) => {',
' const { root } = await import("@cordisjs/client");',
` const entry = root.$loader.extensions["${key}"];`,
' if (!entry?.fork) return;',
' entry.fork.update(module, true);',
' });',
'}',
].join('\n') + '\n',
map: null,
}
}
},
}],
})

this.ctx.server.all('/vite(/.+)*', (ctx) => new Promise((resolve) => {
Expand Down

0 comments on commit 0da5434

Please sign in to comment.