Skip to content

Commit

Permalink
Merge pull request #170 from iambumblehead/slightly-smaller-paramkeys
Browse files Browse the repository at this point in the history
Slightly smaller paramkeys
  • Loading branch information
iambumblehead authored Oct 3, 2022
2 parents 28cbda5 + 774c327 commit ca69fb9
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 101 deletions.
4 changes: 2 additions & 2 deletions src/esmock.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ const esmockGo = opts => async (...args) => {
}

const purge = mockModule => mockModule
&& /object|function/.test(typeof mockModule) && 'esmockKey' in mockModule
&& esmockModule.purge(mockModule.esmockKey)
&& /object|function/.test(typeof mockModule) && 'esmkTreeId' in mockModule
&& esmockModule.purge(mockModule.esmkTreeId)

const strict = Object.assign(esmockGo({ strict: true }), {
purge, p: esmockGo({ strict: true, purge: false }) })
Expand Down
10 changes: 5 additions & 5 deletions src/esmockCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ const esmockCache = {
mockDefs: {}
}

const esmockKeySet = (key, keylong) => (
const esmockTreeIdSet = (key, keylong) => (
global.mockKeys[String(key)] = keylong)

const esmockKeyGet = key => (
const esmockTreeIdGet = key => (
global.mockKeys[String(key)])

const esmockCacheSet = (key, mockDef) => (
Expand All @@ -26,16 +26,16 @@ const esmockCacheResolvedPathIsESMSet = (mockPathFull, isesm) => (

Object.assign(global, {
esmockCacheGet,
esmockKeyGet,
esmockTreeIdGet,
mockKeys: {}
})

export {
esmockCache as default,
esmockCacheSet,
esmockCacheGet,
esmockKeySet,
esmockKeyGet,
esmockTreeIdSet,
esmockTreeIdGet,
esmockCacheResolvedPathIsESMGet,
esmockCacheResolvedPathIsESMSet
}
73 changes: 35 additions & 38 deletions src/esmockLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import urlDummy from './esmockDummy.js'
const [major, minor] = process.versions.node.split('.').map(it => +it)
const isLT1612 = major < 16 || (major === 16 && minor < 12)

const esmockGlobalsAndAfterRe = /\?esmockGlobals=.*/
const esmockGlobalsAndBeforeRe = /.*\?esmockGlobals=/
const esmockModuleKeysRe = /#-#esmockModuleKeys/
const esmkgdefsAndAfterRe = /\?esmkgdefs=.*/
const esmkgdefsAndBeforeRe = /.*\?esmkgdefs=/
const esmkdefsRe = /#-#esmkdefs/
const esmkTreeIdRe = /esmkTreeId=\d*/
const esmkModuleIdRe = /esmkModuleId=([^&]*)/
const esmkIdRe = /\?esmk=\d*/
const exportNamesRe = /.*exportNames=(.*)/
const esmockKeyRe = /esmockKey=\d*/
const withHashRe = /.*#-#/
const isesmRe = /isesm=true/
const notfoundRe = /notfound=([^&]*)/
const isnotfoundRe = /isfound=false/

// new versions of node: when multiple loaders are used and context
// is passed to nextResolve, the process crashes in a recursive call
Expand All @@ -30,64 +32,59 @@ const nextResolveCall = async (nextResolve, specifier, context) => (

const resolve = async (specifier, context, nextResolve) => {
const { parentURL } = context
const [esmockKeyParamSmall] =
(parentURL && parentURL.match(/\?esmk=\d*/)) || []
const esmockKeyLong = esmockKeyParamSmall
? global.esmockKeyGet(esmockKeyParamSmall.split('=')[1])
const treeidspec = esmkIdRe.test(parentURL)
? global.esmockTreeIdGet(parentURL.match(esmkIdRe)[0].split('=')[1])
: parentURL

if (!esmockKeyRe.test(esmockKeyLong))
if (!esmkTreeIdRe.test(treeidspec))
return nextResolveCall(nextResolve, specifier, context)

const [esmockKeyParam] = String(esmockKeyLong).match(esmockKeyRe)
const [keyUrl, keys] = esmockKeyLong.split(esmockModuleKeysRe)
const moduleGlobals = keyUrl && keyUrl.replace(esmockGlobalsAndBeforeRe, '')
const [treeid] = String(treeidspec).match(esmkTreeIdRe)
const [url, defs] = treeidspec.split(esmkdefsRe)
const gdefs = url && url.replace(esmkgdefsAndBeforeRe, '')
// do not call 'nextResolve' for notfound modules
if (esmockKeyLong.includes(`notfound=${specifier}`)) {
const moduleKeyRe = new RegExp(
'.*file:///' + specifier + '(\\?' + esmockKeyParam + '(?:(?!#-#).)*).*')
const moduleKey = (
moduleGlobals.match(moduleKeyRe) || keys.match(moduleKeyRe) || [])[1]
if (moduleKey) {
if (treeidspec.includes(`esmkModuleId=${specifier}&isfound=false`)) {
const moduleIdRe = new RegExp(
'.*file:///' + specifier + '(\\?' + treeid + '(?:(?!#-#).)*).*')
const moduleId = (
gdefs.match(moduleIdRe) || defs.match(moduleIdRe) || [])[1]
if (moduleId) {
return {
shortCircuit: true,
url: urlDummy + moduleKey
url: urlDummy + moduleId
}
}
}

const resolved = await nextResolveCall(nextResolve, specifier, context)
const resolvedurl = decodeURI(resolved.url)
const moduleKeyRe = new RegExp(
'.*(' + resolvedurl + '\\?' + esmockKeyParam + '(?:(?!#-#).)*).*')
const moduleKeyChild = moduleKeyRe.test(keys)
&& keys.replace(moduleKeyRe, '$1')
const moduleKeyGlobal = moduleKeyRe.test(moduleGlobals)
&& moduleGlobals.replace(moduleKeyRe, '$1')

const moduleKey = moduleKeyChild || moduleKeyGlobal
if (moduleKey) {
resolved.url = isesmRe.test(moduleKey)
? moduleKey
: urlDummy + '#-#' + moduleKey
} else if (moduleGlobals && moduleGlobals !== '0') {
const moduleIdRe = new RegExp(
'.*(' + resolvedurl + '\\?' + treeid + '(?:(?!#-#).)*).*')
const moduleId =
moduleIdRe.test(defs) && defs.replace(moduleIdRe, '$1') ||
moduleIdRe.test(gdefs) && gdefs.replace(moduleIdRe, '$1')
if (moduleId) {
resolved.url = isesmRe.test(moduleId)
? moduleId
: urlDummy + '#-#' + moduleId
} else if (gdefs && gdefs !== '0') {
if (!resolved.url.startsWith('node:')) {
resolved.url += '?esmockGlobals=' + moduleGlobals
resolved.url += '?esmkgdefs=' + gdefs
}
}

return resolved
}

const load = async (url, context, nextLoad) => {
if (esmockModuleKeysRe.test(url)) // parent of mocked modules
if (esmkdefsRe.test(url)) // parent of mocked modules
return nextLoad(url, context)

url = url.replace(esmockGlobalsAndAfterRe, '')
url = url.replace(esmkgdefsAndAfterRe, '')
if (url.startsWith(urlDummy)) {
url = url.replace(withHashRe, '')
if (notfoundRe.test(url))
url = url.replace(urlDummy, `file:///${url.match(notfoundRe)[1]}`)
if (isnotfoundRe.test(url))
url = url.replace(urlDummy, `file:///${url.match(esmkModuleIdRe)[1]}`)
}

const exportedNames = exportNamesRe.test(url) &&
Expand Down
84 changes: 40 additions & 44 deletions src/esmockModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import fs from 'fs'
import resolvewith from 'resolvewithplus'

import {
esmockKeySet,
esmockKeyGet,
esmockTreeIdSet,
esmockTreeIdGet,
esmockCacheSet,
esmockCacheResolvedPathIsESMGet,
esmockCacheResolvedPathIsESMSet
Expand All @@ -12,24 +12,22 @@ import {
const isObj = o => typeof o === 'object' && o
const isDefaultIn = o => isObj(o) && 'default' in o
const isDirPathRe = /^\.?\.?([a-zA-Z]:)?(\/|\\)/
const esmockNextKey = ((key = 0) => () => ++key)()
const esmockNextId = ((id = 0) => () => ++id)()

const esmockModuleIdNotFoundError = (moduleId, parent) => new Error(
`invalid moduleId: "${moduleId}" (used by ${parent})`
.replace(process.cwd(), '.')
.replace(process.env.HOME, '~'))

const esmockModuleMergeDefault = (defLive, defMock) =>
(isObj(defLive) && isObj(defMock))
? Object.assign({}, defLive, defMock)
: defMock
const esmockModuleMergeDefault = (defLive, def) =>
(isObj(defLive) && isObj(def)) ? Object.assign({}, defLive, def) : def

const esmockModuleApply = (defLive, defMock, fileURL) => {
const def = Object.assign({}, defLive || {}, {
const esmockModuleApply = (defLive, def, fileURL) => {
def = Object.assign({}, defLive || {}, {
default: esmockModuleMergeDefault(
isDefaultIn(defLive) && defLive.default,
isDefaultIn(defMock) ? defMock.default : defMock)
}, defMock)
isDefaultIn(def) ? def.default : def)
}, def)

// if safe, an extra "default.default" is added for compatibility with
// babel-generated dist cjs files which also define "default.default"
Expand Down Expand Up @@ -60,78 +58,76 @@ const esmockModuleIsESM = (fileURL, isesm) => {

// return the default value directly, so that the esmock caller
// does not need to lookup default as in "esmockedValue.default"
const esmockModuleImportedSanitize = (imported, esmockKey) => {
const esmockModuleImportedSanitize = (imported, esmkTreeId) => {
const importedDefault = isDefaultIn(imported) && imported.default

if (/boolean|string|number/.test(typeof importedDefault))
return imported

// ex, non-extensible "[object Module]": import * as fs from 'fs'; export fs;
return Object.isExtensible(importedDefault)
? Object.assign(importedDefault, imported, { esmockKey })
: Object.assign({}, importedDefault, imported, { esmockKey })
? Object.assign(importedDefault, imported, { esmkTreeId })
: Object.assign({}, importedDefault, imported, { esmkTreeId })
}

const esmockModuleImportedPurge = modulePathKey => {
const esmockModuleImportedPurge = treeid => {
const purgeKey = key => key === 'null' || esmockCacheSet(key, null)
const longKey = esmockKeyGet(modulePathKey.split('esmk=')[1])
const [url, keys] = longKey.split('#-#esmockModuleKeys=')
const longKey = esmockTreeIdGet(treeid.split('esmk=')[1])
const [url, keys] = longKey.split('#-#esmkdefs=')

String(keys).split('#-#').forEach(purgeKey)
String(url.split('esmockGlobals=')[1]).split('#-#').forEach(purgeKey)
String(url.split('esmkgdefs=')[1]).split('#-#').forEach(purgeKey)
}

const esmockModuleCreate = async (esmockKey, key, fileURL, defMock, opt) => {
const isesm = esmockModuleIsESM(fileURL)
const def = esmockModuleApply(
opt.strict || !fileURL || await import(fileURL), defMock, fileURL)
const mockExportNames = Object.keys(def).sort().join()
const mockModuleKey = (fileURL || 'file:///' + key) + '?' + [
'esmockKey=' + esmockKey,
'esmockModuleKey=' + key,
'isesm=' + isesm,
fileURL ? 'found' : 'notfound=' + key,
mockExportNames ? 'exportNames=' + mockExportNames : 'exportNone'
const esmockModuleCreate = async (treeid, def, id, fileURL, opt) => {
def = esmockModuleApply(
opt.strict || !fileURL || await import(fileURL), def, fileURL)

const mockModuleKey = (fileURL || 'file:///' + id) + '?' + [
'esmkTreeId=' + treeid,
'esmkModuleId=' + id,
'isfound=' + Boolean(fileURL),
'isesm=' + esmockModuleIsESM(fileURL),
'exportNames=' + Object.keys(def).sort().join()
].join('&')

esmockCacheSet(mockModuleKey, def)

return mockModuleKey
}

const esmockModuleId = async (parent, key, defs, ids, opt, mocks, id) => {
const esmockModuleId = async (parent, treeid, defs, ids, opt, mocks, id) => {
ids = ids || Object.keys(defs)
id = ids[0]
mocks = mocks || []

if (!id) return mocks

const mockedPathFull = resolvewith(id, parent)
if (!mockedPathFull && opt.isModuleNotFoundError !== false)
const fileURL = resolvewith(id, parent)
if (!fileURL && opt.isModuleNotFoundError !== false)
throw esmockModuleIdNotFoundError(id, parent)

mocks.push(await esmockModuleCreate(key, id, mockedPathFull, defs[id], opt))
mocks.push(await esmockModuleCreate(treeid, defs[id], id, fileURL, opt))

return esmockModuleId(parent, key, defs, ids.slice(1), opt, mocks)
return esmockModuleId(parent, treeid, defs, ids.slice(1), opt, mocks)
}

const esmockModule = async (moduleId, parent, defs, gdefs, opt) => {
const moduleFileURL = resolvewith(moduleId, parent)
if (!moduleFileURL)
throw esmockModuleIdNotFoundError(moduleId, parent)

const esmockKey = typeof opt.key === 'number' ? opt.key : esmockNextKey()
const esmockKeyLong = moduleFileURL + '?' +
'key=:esmockKey?esmockGlobals=:esmockGlobals#-#esmockModuleKeys=:moduleKeys'
.replace(/:esmockKey/, esmockKey)
.replace(/:esmockGlobals/, gdefs && (await esmockModuleId(
parent, esmockKey, gdefs, Object.keys(gdefs), opt)).join('#-#') || 0)
.replace(/:moduleKeys/, defs && (await esmockModuleId(
parent, esmockKey, defs, Object.keys(defs), opt)).join('#-#') || 0)
const treeid = typeof opt.id === 'number' ? opt.id : esmockNextId()
const treeidspec = `${moduleFileURL}?key=${treeid}?` + [
'esmkgdefs=' + (gdefs && (await esmockModuleId(
parent, treeid, gdefs, Object.keys(gdefs), opt)).join('#-#') || 0),
'esmkdefs=', (defs && (await esmockModuleId(
parent, treeid, defs, Object.keys(defs), opt)).join('#-#') || 0)
].join('#-#')

esmockKeySet(String(esmockKey), esmockKeyLong)
esmockTreeIdSet(String(treeid), treeidspec)

return moduleFileURL + `?esmk=${esmockKey}`
return moduleFileURL + `?esmk=${treeid}`
}

export default Object.assign(esmockModule, {
Expand Down
9 changes: 5 additions & 4 deletions tests/tests-ava/spec/esmock.ava.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,12 @@ test('should purge local and global mocks', async t => {
: filepath
}
}, {
key: 999
id: 999
})

const keys = Object
.keys(esmockCache.mockDefs)
.filter(key => /esmockKey=999/.test(key))
.filter(key => /esmkTreeId=999/.test(key))

t.truthy(keys.length)
t.true(keys.every(key => esmockCache.mockDefs[key] === null))
Expand Down Expand Up @@ -309,8 +309,8 @@ test('mocks inline `async import("name")`', async t => {
filePath: 'filePath'
}))

const [, key] = writeJSConfigFile.esmockKey.match(/esmk=(\d*)/)
const keyRe = new RegExp(`esmockKey=${key}[^d]`)
const [, key] = writeJSConfigFile.esmkTreeId.match(/esmk=(\d*)/)
const keyRe = new RegExp(`esmkTreeId=${key}[^d]`)

const moduleKeys = Object.keys(esmockCache.mockDefs)
.filter(moduleKey => keyRe.test(moduleKey))
Expand Down Expand Up @@ -401,3 +401,4 @@ test('should not error when mocked file has space in path', async t => {

t.is(main.wild, 'tamed')
})

8 changes: 4 additions & 4 deletions tests/tests-node/esmock.node.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,12 @@ test('should purge local and global mocks', async () => {
: filepath
}
}, {
key: 999
id: 999
})

const keys = Object
.keys(esmockCache.mockDefs)
.filter(key => /esmockKey=999/.test(key))
.filter(key => /esmkTreeId=999/.test(key))

assert.ok(keys.length)
assert.ok(keys.every(key => esmockCache.mockDefs[key] === null))
Expand Down Expand Up @@ -323,8 +323,8 @@ test('mocks inline `async import("name")`', async () => {
filePath: 'filePath'
}))

const [, key] = writeJSConfigFile.esmockKey.match(/esmk=(\d*)/)
const keyRe = new RegExp(`esmockKey=${key}[^d]`)
const [, key] = writeJSConfigFile.esmkTreeId.match(/esmk=(\d*)/)
const keyRe = new RegExp(`esmkTreeId=${key}[^d]`)

const moduleKeys = Object.keys(esmockCache.mockDefs)
.filter(moduleKey => keyRe.test(moduleKey))
Expand Down
8 changes: 4 additions & 4 deletions tests/tests-uvu/esmock.uvu.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,12 @@ test('should purge local and global mocks', async () => {
: filepath
}
}, {
key: 999
id: 999
})

const keys = Object
.keys(esmockCache.mockDefs)
.filter(key => /esmockKey=999/.test(key))
.filter(key => /esmkTreeId=999/.test(key))

assert.ok(keys.length)
assert.ok(keys.every(key => esmockCache.mockDefs[key] === null))
Expand Down Expand Up @@ -278,8 +278,8 @@ test('mocks inline `async import("name")`', async () => {
filePath: 'filePath'
}))

const [, key] = writeJSConfigFile.esmockKey.match(/esmk=(\d*)/)
const keyRe = new RegExp(`esmockKey=${key}[^d]`)
const [, key] = writeJSConfigFile.esmkTreeId.match(/esmk=(\d*)/)
const keyRe = new RegExp(`esmkTreeId=${key}[^d]`)

const moduleKeys = Object.keys(esmockCache.mockDefs)
.filter(moduleKey => keyRe.test(moduleKey))
Expand Down

0 comments on commit ca69fb9

Please sign in to comment.