feat(lib/modules/metro/subscriptions): add ability to subscribe when modules are first required
This commit is contained in:
parent
466881c1a6
commit
e6e46a175d
5 changed files with 107 additions and 57 deletions
|
|
@ -3,12 +3,12 @@ import './preinit'
|
|||
import './patches/prevent-immutablize'
|
||||
import './patches/proxy'
|
||||
|
||||
import { onModuleInitialized } from '@revenge-mod/modules/metro/subscriptions'
|
||||
import { onModuleFirstRequired } from '@revenge-mod/modules/metro/subscriptions'
|
||||
import { getErrorStack } from '@revenge-mod/utils/errors'
|
||||
|
||||
import { logger } from './shared'
|
||||
|
||||
onModuleInitialized(0, async function onceIndexModuleRequired() {
|
||||
onModuleFirstRequired(0, async function onceIndexModuleRequired() {
|
||||
if (!__BUILD_FLAG_INIT_DISABLE_PATCH_LOG_PROMISE_REJECTIONS__) require('./patches/log-promise-rejections')
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
import { _executeSubscription } from './subscriptions/_internal'
|
||||
import { isBlacklisted, isModuleExportsBad } from './utils'
|
||||
import { _executeInitializedSubscription, _executeRequiredSubscription } from './subscriptions/_internal'
|
||||
import { isModuleExportsBad } from './utils'
|
||||
|
||||
import type { Metro } from '../../types/metro'
|
||||
|
||||
export const _bl = new Set<Metro.ModuleID>()
|
||||
|
||||
/** Initializing ID */
|
||||
export let _mInitingId = -1
|
||||
/** Uninitialized IDs */
|
||||
/** Known uninitialized IDs (not blacklisted) */
|
||||
export const _mUninited = new Set<Metro.ModuleID>()
|
||||
/** Known initialized IDs (exports aren't bad) */
|
||||
/** Known initialized IDs (not blacklisted) */
|
||||
export const _mInited = new Set<Metro.ModuleID>()
|
||||
|
||||
export const _mPaths = new Map<string, Metro.ModuleID>()
|
||||
|
|
@ -20,53 +18,51 @@ export function patchMetroDefine(metroDefine: Metro.DefineFn) {
|
|||
// deps won't be undefined last time I checked
|
||||
const metadata = [deps!] as typeof _mMd extends Map<any, infer V> ? V : never
|
||||
_mMd.set(id, metadata)
|
||||
_mUninited.add(id)
|
||||
|
||||
if (isBlacklisted(id)) metroDefine(origFactory, id, deps)
|
||||
else {
|
||||
_mUninited.add(id)
|
||||
metroDefine(
|
||||
((global, req, importDefault, importAll, module, exports, deps) => {
|
||||
metadata[1] = module
|
||||
|
||||
metroDefine(
|
||||
((global, req, importDefault, importAll, module, exports, deps) => {
|
||||
metadata[1] = module
|
||||
const prevIId = _mInitingId
|
||||
_mInitingId = id
|
||||
|
||||
const prevIId = _mInitingId
|
||||
_mInitingId = id
|
||||
_executeRequiredSubscription()
|
||||
|
||||
try {
|
||||
origFactory(global, req, importDefault, importAll, module, exports, deps)
|
||||
// Don't use exports here, as modules can set module.exports to a different object
|
||||
if (isModuleExportsBad(module.exports)) throw undefined
|
||||
try {
|
||||
origFactory(global, req, importDefault, importAll, module, exports, deps)
|
||||
// Don't use exports here, as modules can set module.exports to a different object
|
||||
if (isModuleExportsBad(module.exports)) throw undefined
|
||||
|
||||
// Add the module to the initialized set only if the factory doesn't error or the exports aren't bad
|
||||
_mInited.add(id)
|
||||
} catch {
|
||||
_blacklist(id)
|
||||
} finally {
|
||||
_mInitingId = prevIId
|
||||
_mUninited.delete(id)
|
||||
// Don't use exports here, as modules can set module.exports to a different object
|
||||
_executeSubscription(id, module.exports)
|
||||
}
|
||||
}) satisfies Metro.FactoryFn,
|
||||
id,
|
||||
deps,
|
||||
)
|
||||
}
|
||||
// Add the module to the initialized set only if the factory doesn't error or the exports aren't bad
|
||||
_mInited.add(id)
|
||||
} catch {
|
||||
_blacklist(id)
|
||||
} finally {
|
||||
_mInitingId = prevIId
|
||||
_mUninited.delete(id)
|
||||
// Don't use exports here, as modules can set module.exports to a different object
|
||||
_executeInitializedSubscription(module.exports)
|
||||
}
|
||||
}) satisfies Metro.FactoryFn,
|
||||
id,
|
||||
deps,
|
||||
)
|
||||
}) satisfies Metro.DefineFn
|
||||
}
|
||||
|
||||
/**
|
||||
* Blacklists a module.
|
||||
* Blacklists a module. A module can be blacklisted for several reasons:
|
||||
*
|
||||
* Blacklisting modules prevents Revenge from doing the following things:
|
||||
*
|
||||
* - Iterating over the module during `find()`s.
|
||||
* - If the module is already initialized, it either means:
|
||||
* - The module factory threw an error during initialization
|
||||
* - The module exports are bad after initialization
|
||||
* - Otherwise, this module is known to be bad from previous caches
|
||||
*
|
||||
* @param id The module ID to blacklist.
|
||||
* @internal
|
||||
*/
|
||||
export function _blacklist(id: Metro.ModuleID) {
|
||||
_bl.add(id)
|
||||
// In case blacklisting happens before the module is initialized
|
||||
_mUninited.delete(id)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const unsubForImportTracker = waitForModules(byProps('fileFinishedImporting'), (
|
|||
exports.fileFinishedImporting = (path: string) => {
|
||||
orig(path)
|
||||
_mPaths.set(path, _mInitingId)
|
||||
_executeImportedPathSubscription(_mInitingId, path)
|
||||
_executeImportedPathSubscription(path)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,15 +1,26 @@
|
|||
import type { ModuleInitializedCallback, ModuleWithImportedPathInitializedCallback } from '.'
|
||||
import { _mInitingId } from '../_internal'
|
||||
|
||||
import type { ModuleInitializedCallback, ModuleRequiredCallback, ModuleWithImportedPathInitializedCallback } from '.'
|
||||
import type { Metro } from '../../../types/metro'
|
||||
|
||||
export const _all = new Set<ModuleInitializedCallback>()
|
||||
export const _specific = new Map<Metro.ModuleID, Set<ModuleInitializedCallback>>()
|
||||
export const _reqAll = new Set<ModuleRequiredCallback>()
|
||||
export const _reqSpecific = new Map<Metro.ModuleID, Set<ModuleRequiredCallback>>()
|
||||
export const _initAll = new Set<ModuleInitializedCallback>()
|
||||
export const _initSpecific = new Map<Metro.ModuleID, Set<ModuleInitializedCallback>>()
|
||||
export const _importedPath = new Set<ModuleWithImportedPathInitializedCallback>()
|
||||
|
||||
export function _executeSubscription(id: Metro.ModuleID, exports: Metro.ModuleExports) {
|
||||
for (const cb of _all) cb(id, exports)
|
||||
if (_specific.has(id)) for (const cb of _specific.get(id)!) cb(id, exports)
|
||||
export function _executeRequiredSubscription() {
|
||||
const id = _mInitingId
|
||||
for (const cb of _reqAll) cb(id)
|
||||
if (_reqSpecific.has(id)) for (const cb of _reqSpecific.get(id)!) cb(id)
|
||||
}
|
||||
|
||||
export function _executeImportedPathSubscription(id: Metro.ModuleID, path: string) {
|
||||
for (const cb of _importedPath) cb(id, path)
|
||||
export function _executeInitializedSubscription(exports: Metro.ModuleExports) {
|
||||
const id = _mInitingId
|
||||
for (const cb of _initAll) cb(id, exports)
|
||||
if (_initSpecific.has(id)) for (const cb of _initSpecific.get(id)!) cb(id, exports)
|
||||
}
|
||||
|
||||
export function _executeImportedPathSubscription(path: string) {
|
||||
for (const cb of _importedPath) cb(_mInitingId, path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,79 @@
|
|||
import type { Metro } from '../../../types/metro'
|
||||
import { _all, _importedPath, _specific } from './_internal'
|
||||
import { _initAll, _importedPath, _initSpecific, _reqSpecific, _reqAll } from './_internal'
|
||||
|
||||
import type { isInitializedModuleBlacklisted } from '../utils'
|
||||
import type { Metro } from '../../../types/metro'
|
||||
|
||||
export type ModuleRequiredCallback = (id: Metro.ModuleID) => void
|
||||
export type ModuleInitializedCallback = (id: Metro.ModuleID, exports: Metro.ModuleExports) => void
|
||||
export type ModuleWithImportedPathInitializedCallback = (id: Metro.ModuleID, path: string) => void
|
||||
|
||||
/**
|
||||
* Registers a callback to be called when any module is initialized. **Some module exports may be bad.**
|
||||
* Registers a callback to be called when any module is initialized.
|
||||
*
|
||||
* @see {@link isInitializedModuleBlacklisted} to avoid bad module exports.
|
||||
*
|
||||
* @param callback The callback to be called.
|
||||
* @returns A function that unregisters the callback.
|
||||
*/
|
||||
export function onAnyModuleInitialized(callback: ModuleInitializedCallback) {
|
||||
_all.add(callback)
|
||||
return () => _all.delete(callback)
|
||||
_initAll.add(callback)
|
||||
return () => _initAll.delete(callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback to be called when a specific module is initialized. **Module exports may be bad.**
|
||||
* Registers a callback to be called when a specific module is initialized.
|
||||
*
|
||||
* @see {@link isInitializedModuleBlacklisted} to avoid bad module exports.
|
||||
*
|
||||
* @param id The ID of the module.
|
||||
* @param callback The callback to be called.
|
||||
* @returns A function that unregisters the callback.
|
||||
*/
|
||||
export function onModuleInitialized(id: Metro.ModuleID, callback: ModuleInitializedCallback) {
|
||||
if (!_specific.has(id)) _specific.set(id, new Set())
|
||||
const set = _specific.get(id)!
|
||||
if (!_initSpecific.has(id)) _initSpecific.set(id, new Set())
|
||||
const set = _initSpecific.get(id)!
|
||||
set.add(callback)
|
||||
return () => set.delete(callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback to be called when a module with a specific import path is initialized.
|
||||
*
|
||||
* @see {@link isInitializedModuleBlacklisted} to avoid bad module exports.
|
||||
*
|
||||
* @param callback The callback to be called.
|
||||
* @returns A function that unregisters the callback.
|
||||
*/
|
||||
export function onModuleFinishedImporting(callback: ModuleWithImportedPathInitializedCallback) {
|
||||
_importedPath.add(callback)
|
||||
return () => _importedPath.delete(callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback to be called when any module is required the first time.
|
||||
*
|
||||
* @see {@link isInitializedModuleBlacklisted} to avoid bad module exports.
|
||||
*
|
||||
* @param callback The callback to be called.
|
||||
* @returns A function that unregisters the callback.
|
||||
*/
|
||||
export function onAnyModuleFirstRequired(callback: ModuleRequiredCallback) {
|
||||
_reqAll.add(id => callback(id))
|
||||
return () => _reqAll.delete(id => callback(id))
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback to be called when a specific module is required the first time.
|
||||
*
|
||||
* @see {@link isInitializedModuleBlacklisted} to avoid bad module exports.
|
||||
*
|
||||
* @param id The ID of the module.
|
||||
* @param callback The callback to be called.
|
||||
* @returns A function that unregisters the callback.
|
||||
*/
|
||||
export function onModuleFirstRequired(id: Metro.ModuleID, callback: ModuleRequiredCallback) {
|
||||
if (!_reqSpecific.has(id)) _reqSpecific.set(id, new Set())
|
||||
const set = _reqSpecific.get(id)!
|
||||
set.add(callback)
|
||||
return () => set.delete(callback)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue