Skip to content

Commit

Permalink
fix(lifecycle): create multi-instances at the same time (#660) (#663)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The following top-level methods, such as `Artalk.update`, `Artalk.reload`, and `Artalk.destroy`, have been removed. These methods now require invocation on an instance created by Artalk.init or new Artalk. This change was made to support creating multiple instances simultaneously, adapting to use cases where Vue components are referenced in different pages at the same time. Previously, only a single instance was allowed to prevent memory leak issues, which was insufficient for scenarios like caching multiple component instances using keep-alive. To better accommodate more complex SPA application scenarios, the decision was made to allow the creation of multiple independent instances. Remember to manually invoke the `artalk.destroy` method when releasing components to prevent memory leaks. (#660)
  • Loading branch information
qwqcode committed Dec 16, 2023
1 parent 87102cd commit d6530fe
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 71 deletions.
90 changes: 23 additions & 67 deletions ui/packages/artalk/src/artalk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,68 +10,54 @@ import { DefaultPlugins } from './plugins'
import * as Stat from './plugins/stat'
import Api from './api'

/** Global Plugins for all instances */
const GlobalPlugins: ArtalkPlugin[] = [ ...DefaultPlugins ]

/**
* Artalk
*
* @see https://artalk.js.org
*/
export default class Artalk {
private static instance?: Artalk

public static readonly defaults: ArtalkConfig = defaults

public conf!: ArtalkConfig
public ctx!: ContextApi
public $root!: HTMLElement

/** Plugins */
protected static plugins: ArtalkPlugin[] = [ ...DefaultPlugins ]
public static DisabledComponents: string[] = []
protected plugins: ArtalkPlugin[] = [ ...GlobalPlugins ]

constructor(conf: Partial<ArtalkConfig>) {
if (Artalk.instance) Artalk.destroy()

// 初始化基本配置
// Init Config
this.conf = handelCustomConf(conf)
if (this.conf.el instanceof HTMLElement) this.$root = this.conf.el

// 初始化 Context
// Init Context
this.ctx = new ConcreteContext(this.conf, this.$root)

// 内建服务初始化
// Init Services
Object.entries(Services).forEach(([name, initService]) => {
if (Artalk.DisabledComponents.includes(name)) return
const obj = initService(this.ctx)
if (obj) this.ctx.inject(name as any, obj) // auto inject deps to ctx
})

// 插件初始化 (global scope)
Artalk.plugins.forEach(plugin => {
if (typeof plugin === 'function')
plugin(this.ctx)
// Init Plugins
this.plugins.forEach(plugin => {
if (typeof plugin === 'function') plugin(this.ctx)
})

// Trigger inited event
this.ctx.trigger('inited')
}

/** Init Artalk */
public static init(conf: Partial<ArtalkConfig>): Artalk {
if (this.instance) Artalk.destroy()
this.instance = new Artalk(conf)
return this.instance
}

/** Use Plugin (plugin will be called in instance `use` func) */
public use(plugin: ArtalkPlugin) {
Artalk.plugins.push(plugin)
if (typeof plugin === 'function') plugin(this.ctx)
}

/** Update config of Artalk */
public update(conf: Partial<ArtalkConfig>) {
if (!Artalk.instance) throw Error('cannot call `update` function before call `load`')
Artalk.instance.ctx.updateConf(conf)
return Artalk.instance
this.ctx.updateConf(conf)
return this
}

/** Reload comment list of Artalk */
Expand All @@ -81,10 +67,8 @@ export default class Artalk {

/** Destroy instance of Artalk */
public destroy() {
if (!Artalk.instance) throw Error('cannot call `destroy` function before call `load`')
this.ctx.trigger('destroy')
Artalk.instance.$root.remove()
delete Artalk.instance
this.$root.remove()
}

/** Add an event listener */
Expand All @@ -108,48 +92,20 @@ export default class Artalk {
}

// ===========================
// Static methods
// Static Members
// ===========================

/** Use Plugin (static method) */
public static use(plugin: ArtalkPlugin) {
this.plugins.push(plugin)
if (this.instance && typeof plugin === 'function') plugin(this.instance.ctx)
}

/** Update config of Artalk */
public static update(conf: Partial<ArtalkConfig>) {
return this.instance?.update(conf)
}

/** Reload comment list of Artalk */
public static reload() {
this.instance?.reload()
}

/** Destroy instance of Artalk */
public static destroy() {
this.instance?.destroy()
}

/** Add an event listener */
public static on<K extends keyof EventPayloadMap>(name: K, handler: EventHandler<EventPayloadMap[K]>) {
this.instance?.on(name, handler)
}

/** Remove an event listener */
public static off<K extends keyof EventPayloadMap>(name: K, handler: EventHandler<EventPayloadMap[K]>) {
this.instance?.off(name, handler)
}
/** Default Configs */
public static readonly defaults: ArtalkConfig = defaults

/** Trigger an event */
public static trigger<K extends keyof EventPayloadMap>(name: K, payload?: EventPayloadMap[K]) {
this.instance?.trigger(name, payload)
/** Init Artalk */
public static init(conf: Partial<ArtalkConfig>): Artalk {
return new Artalk(conf)
}

/** Set dark mode */
public static setDarkMode(darkMode: boolean) {
this.instance?.setDarkMode(darkMode)
/** Use Plugin for all instances */
public static use(plugin: ArtalkPlugin) {
GlobalPlugins.push(plugin)
}

/** Load count widget */
Expand Down
3 changes: 3 additions & 0 deletions ui/packages/artalk/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export function handelCustomConf(customConf: Partial<ArtalkConfig>): ArtalkConfi
// 合并默认配置
const conf: ArtalkConfig = Utils.mergeDeep(Defaults, customConf)

// TODO the type of el options may HTMLElement, use it directly instead of from mergeDeep
if (customConf.el) conf.el = customConf.el

// 绑定元素
if (typeof conf.el === 'string' && !!conf.el) {
try {
Expand Down
5 changes: 1 addition & 4 deletions ui/packages/artalk/src/plugins/conf-remoter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import type { ArtalkConfig, ArtalkPlugin, ContextApi } from '~/types'
import { handleConfFormServer } from '@/config'
import { showErrorDialog } from '../components/error-dialog'

let confLoaded = false

export const ConfRemoter: ArtalkPlugin = (ctx) => {
ctx.on('inited', () => {
if (!confLoaded) loadConf(ctx)
loadConf(ctx)
})
}

Expand All @@ -26,7 +24,6 @@ function loadConf(ctx: ContextApi) {
ctx.conf.remoteConfModifier && ctx.conf.remoteConfModifier(conf)

ctx.updateConf(conf)
confLoaded = true
}).catch((err) => {
ctx.updateConf({})

Expand Down

0 comments on commit d6530fe

Please sign in to comment.