feat(core): add @choojs/core — ESM port of choo with consolidated nano* internals
Same public API as choo 7.1.0 (use/route/start/mount/toString/emit; choo() callable with or without new, Choo exported). nanobus, nanorouter+wayfarer, nanolru+component cache, nanoraf, nanohref, nanotiming, document-ready and scroll-to-anchor are ported into lib/ with per-file attribution; nanoquery is replaced by URLSearchParams. The v7 node test suite is ported from tape to node:test and passes unchanged in behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
This commit is contained in:
co-authored by
Claude Fable 5
parent
90cca611c9
commit
4ad3d02207
@@ -0,0 +1,19 @@
|
||||
// Minimal assertions in the spirit of nanoassert (MIT).
|
||||
// These guard the public API in development; a production bundler can strip
|
||||
// them by replacing this module with no-ops.
|
||||
|
||||
export function ok (value, message) {
|
||||
if (!value) throw new Error(message || 'assertion failed')
|
||||
}
|
||||
|
||||
/* eslint-disable eqeqeq */
|
||||
export function equal (a, b, message) {
|
||||
if (a != b) throw new Error(message || `${a} != ${b}`)
|
||||
}
|
||||
|
||||
export function notEqual (a, b, message) {
|
||||
if (a == b) throw new Error(message || `${a} == ${b}`)
|
||||
}
|
||||
/* eslint-enable eqeqeq */
|
||||
|
||||
export default ok
|
||||
@@ -0,0 +1,161 @@
|
||||
// Ported from nanobus 4.5.0 (MIT) — https://github.com/choojs/nanobus
|
||||
// Event emitter with a '*' wildcard channel and nanotiming instrumentation.
|
||||
|
||||
import nanotiming from './timing.js'
|
||||
import { ok, equal } from './assert.js'
|
||||
|
||||
function assertEventName (eventName, method) {
|
||||
ok(
|
||||
typeof eventName === 'string' || typeof eventName === 'symbol',
|
||||
`nanobus.${method}: eventName should be type string or symbol`
|
||||
)
|
||||
}
|
||||
|
||||
export default class Nanobus {
|
||||
constructor (name) {
|
||||
this._name = name || 'nanobus'
|
||||
this._starListeners = []
|
||||
this._listeners = {}
|
||||
}
|
||||
|
||||
emit (eventName, ...data) {
|
||||
assertEventName(eventName, 'emit')
|
||||
|
||||
const emitTiming = nanotiming(this._name + "('" + eventName.toString() + "')")
|
||||
const listeners = this._listeners[eventName]
|
||||
if (listeners && listeners.length > 0) {
|
||||
this._emit(this._listeners[eventName], data)
|
||||
}
|
||||
|
||||
if (this._starListeners.length > 0) {
|
||||
this._emit(this._starListeners, eventName, data, emitTiming.uuid)
|
||||
}
|
||||
emitTiming()
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
on (eventName, listener) {
|
||||
assertEventName(eventName, 'on')
|
||||
equal(typeof listener, 'function', 'nanobus.on: listener should be type function')
|
||||
|
||||
if (eventName === '*') {
|
||||
this._starListeners.push(listener)
|
||||
} else {
|
||||
if (!this._listeners[eventName]) this._listeners[eventName] = []
|
||||
this._listeners[eventName].push(listener)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
addListener (eventName, listener) {
|
||||
return this.on(eventName, listener)
|
||||
}
|
||||
|
||||
prependListener (eventName, listener) {
|
||||
assertEventName(eventName, 'prependListener')
|
||||
equal(typeof listener, 'function', 'nanobus.prependListener: listener should be type function')
|
||||
|
||||
if (eventName === '*') {
|
||||
this._starListeners.unshift(listener)
|
||||
} else {
|
||||
if (!this._listeners[eventName]) this._listeners[eventName] = []
|
||||
this._listeners[eventName].unshift(listener)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
once (eventName, listener) {
|
||||
assertEventName(eventName, 'once')
|
||||
equal(typeof listener, 'function', 'nanobus.once: listener should be type function')
|
||||
|
||||
const self = this
|
||||
this.on(eventName, function once (...args) {
|
||||
listener.apply(self, args)
|
||||
self.removeListener(eventName, once)
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
prependOnceListener (eventName, listener) {
|
||||
assertEventName(eventName, 'prependOnceListener')
|
||||
equal(typeof listener, 'function', 'nanobus.prependOnceListener: listener should be type function')
|
||||
|
||||
const self = this
|
||||
this.prependListener(eventName, function once (...args) {
|
||||
listener.apply(self, args)
|
||||
self.removeListener(eventName, once)
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
removeListener (eventName, listener) {
|
||||
assertEventName(eventName, 'removeListener')
|
||||
equal(typeof listener, 'function', 'nanobus.removeListener: listener should be type function')
|
||||
|
||||
if (eventName === '*') {
|
||||
this._starListeners = this._starListeners.slice()
|
||||
return remove(this._starListeners, listener)
|
||||
} else {
|
||||
if (typeof this._listeners[eventName] !== 'undefined') {
|
||||
this._listeners[eventName] = this._listeners[eventName].slice()
|
||||
}
|
||||
return remove(this._listeners[eventName], listener)
|
||||
}
|
||||
|
||||
function remove (arr, listener) {
|
||||
if (!arr) return
|
||||
const index = arr.indexOf(listener)
|
||||
if (index !== -1) {
|
||||
arr.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeAllListeners (eventName) {
|
||||
if (eventName) {
|
||||
if (eventName === '*') {
|
||||
this._starListeners = []
|
||||
} else {
|
||||
this._listeners[eventName] = []
|
||||
}
|
||||
} else {
|
||||
this._starListeners = []
|
||||
this._listeners = {}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
listeners (eventName) {
|
||||
const listeners = eventName !== '*'
|
||||
? this._listeners[eventName]
|
||||
: this._starListeners
|
||||
|
||||
return listeners ? listeners.slice() : []
|
||||
}
|
||||
|
||||
_emit (arr, eventName, data, uuid) {
|
||||
if (typeof arr === 'undefined') return
|
||||
if (arr.length === 0) return
|
||||
if (data === undefined) {
|
||||
data = eventName
|
||||
eventName = null
|
||||
}
|
||||
|
||||
if (eventName) {
|
||||
if (uuid !== undefined) {
|
||||
data = [eventName].concat(data, uuid)
|
||||
} else {
|
||||
data = [eventName].concat(data)
|
||||
}
|
||||
}
|
||||
|
||||
// Take a copy in case a listener mutates the array while we iterate.
|
||||
const listeners = arr.slice()
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
const listener = listeners[i]
|
||||
listener.apply(listener, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// Component instance cache for state.cache().
|
||||
// LRU ported from nanolru 1.0.0 (MIT) — https://github.com/s3ththompson/nanolru
|
||||
// Cache wrapper ported from choo 7.1.0 component/cache.js (MIT)
|
||||
|
||||
import { ok, equal } from './assert.js'
|
||||
|
||||
export class LRU {
|
||||
constructor (opts) {
|
||||
if (typeof opts === 'number') opts = { max: opts }
|
||||
if (!opts) opts = {}
|
||||
this.cache = {}
|
||||
this.head = this.tail = null
|
||||
this.length = 0
|
||||
this.max = opts.max || 1000
|
||||
}
|
||||
|
||||
get keys () {
|
||||
return Object.keys(this.cache)
|
||||
}
|
||||
|
||||
clear () {
|
||||
this.cache = {}
|
||||
this.head = this.tail = null
|
||||
this.length = 0
|
||||
}
|
||||
|
||||
remove (key) {
|
||||
if (typeof key !== 'string') key = '' + key
|
||||
if (!Object.hasOwn(this.cache, key)) return
|
||||
|
||||
const element = this.cache[key]
|
||||
delete this.cache[key]
|
||||
this._unlink(key, element.prev, element.next)
|
||||
return element.value
|
||||
}
|
||||
|
||||
_unlink (key, prev, next) {
|
||||
this.length--
|
||||
|
||||
if (this.length === 0) {
|
||||
this.head = this.tail = null
|
||||
} else {
|
||||
if (this.head === key) {
|
||||
this.head = prev
|
||||
this.cache[this.head].next = null
|
||||
} else if (this.tail === key) {
|
||||
this.tail = next
|
||||
this.cache[this.tail].prev = null
|
||||
} else {
|
||||
this.cache[prev].next = next
|
||||
this.cache[next].prev = prev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
peek (key) {
|
||||
if (!Object.hasOwn(this.cache, key)) return
|
||||
return this.cache[key].value
|
||||
}
|
||||
|
||||
set (key, value) {
|
||||
if (typeof key !== 'string') key = '' + key
|
||||
|
||||
let element
|
||||
|
||||
if (Object.hasOwn(this.cache, key)) {
|
||||
element = this.cache[key]
|
||||
element.value = value
|
||||
|
||||
// If it's already the head, there's nothing more to do:
|
||||
if (key === this.head) return value
|
||||
this._unlink(key, element.prev, element.next)
|
||||
} else {
|
||||
element = { value, next: null, prev: null }
|
||||
this.cache[key] = element
|
||||
|
||||
// Eviction is only possible if the key didn't already exist:
|
||||
if (this.length === this.max) this.evict()
|
||||
}
|
||||
|
||||
this.length++
|
||||
element.next = null
|
||||
element.prev = this.head
|
||||
|
||||
if (this.head) this.cache[this.head].next = key
|
||||
this.head = key
|
||||
|
||||
if (!this.tail) this.tail = key
|
||||
return value
|
||||
}
|
||||
|
||||
get (key) {
|
||||
if (typeof key !== 'string') key = '' + key
|
||||
if (!Object.hasOwn(this.cache, key)) return
|
||||
|
||||
const element = this.cache[key]
|
||||
|
||||
if (this.head !== key) {
|
||||
if (key === this.tail) {
|
||||
this.tail = element.next
|
||||
this.cache[this.tail].prev = null
|
||||
} else {
|
||||
// Set prev.next -> element.next:
|
||||
this.cache[element.prev].next = element.next
|
||||
}
|
||||
|
||||
// Set element.next.prev -> element.prev:
|
||||
this.cache[element.next].prev = element.prev
|
||||
|
||||
// Element is the new head
|
||||
this.cache[this.head].next = key
|
||||
element.prev = this.head
|
||||
element.next = null
|
||||
this.head = key
|
||||
}
|
||||
|
||||
return element.value
|
||||
}
|
||||
|
||||
evict () {
|
||||
if (!this.tail) return
|
||||
this.remove(this.tail)
|
||||
}
|
||||
}
|
||||
|
||||
export default class ComponentCache {
|
||||
constructor (state, emit, lru) {
|
||||
equal(typeof state, 'object', 'ComponentCache: state should be type object')
|
||||
equal(typeof emit, 'function', 'ComponentCache: emit should be type function')
|
||||
|
||||
if (typeof lru === 'number') this.cache = new LRU(lru)
|
||||
else this.cache = lru || new LRU(100)
|
||||
this.state = state
|
||||
this.emit = emit
|
||||
}
|
||||
|
||||
// Get & create component instances.
|
||||
render (Component, id, ...args) {
|
||||
equal(typeof Component, 'function', 'ComponentCache.render: Component should be type function')
|
||||
ok(typeof id === 'string' || typeof id === 'number', 'ComponentCache.render: id should be type string or type number')
|
||||
|
||||
let el = this.cache.get(id)
|
||||
if (!el) {
|
||||
el = new Component(id, this.state, this.emit, ...args)
|
||||
this.cache.set(id, el)
|
||||
}
|
||||
|
||||
return el
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Ported from document-ready 2.0.1 and scroll-to-anchor 1.0.0 (MIT)
|
||||
// https://github.com/bendrucker/document-ready — https://github.com/yoshuawuyts/scroll-to-anchor
|
||||
|
||||
import { notEqual } from './assert.js'
|
||||
|
||||
export function documentReady (callback) {
|
||||
notEqual(typeof document, 'undefined', 'documentReady only runs in the browser')
|
||||
const state = document.readyState
|
||||
if (state === 'complete' || state === 'interactive') {
|
||||
return setTimeout(callback, 0)
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function onLoad () {
|
||||
callback()
|
||||
})
|
||||
}
|
||||
|
||||
export function scrollToAnchor (anchor) {
|
||||
if (anchor) {
|
||||
try {
|
||||
const el = document.querySelector(anchor)
|
||||
if (el) el.scrollIntoView(true)
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Ported from nanohref 3.1.0 (MIT) — https://github.com/choojs/nanohref
|
||||
// Intercept same-origin anchor clicks for client-side navigation.
|
||||
|
||||
import { equal, notEqual } from './assert.js'
|
||||
|
||||
const safeExternalLink = /(noopener|noreferrer) (noopener|noreferrer)/
|
||||
const protocolLink = /^[\w-_]+:/
|
||||
|
||||
export default function href (cb, root) {
|
||||
notEqual(typeof window, 'undefined', 'nanohref: expected window to exist')
|
||||
|
||||
root = root || window.document
|
||||
|
||||
equal(typeof cb, 'function', 'nanohref: cb should be type function')
|
||||
equal(typeof root, 'object', 'nanohref: root should be type object')
|
||||
|
||||
window.addEventListener('click', function (e) {
|
||||
if ((e.button && e.button !== 0) ||
|
||||
e.ctrlKey || e.metaKey || e.altKey || e.shiftKey ||
|
||||
e.defaultPrevented) return
|
||||
|
||||
const anchor = (function traverse (node) {
|
||||
if (!node || node === root) return
|
||||
if (node.localName !== 'a' || node.href === undefined) {
|
||||
return traverse(node.parentNode)
|
||||
}
|
||||
return node
|
||||
})(e.target)
|
||||
|
||||
if (!anchor) return
|
||||
|
||||
if (window.location.protocol !== anchor.protocol ||
|
||||
window.location.hostname !== anchor.hostname ||
|
||||
window.location.port !== anchor.port ||
|
||||
anchor.hasAttribute('data-nanohref-ignore') ||
|
||||
anchor.hasAttribute('download') ||
|
||||
(anchor.getAttribute('target') === '_blank' &&
|
||||
safeExternalLink.test(anchor.getAttribute('rel'))) ||
|
||||
protocolLink.test(anchor.getAttribute('href'))) return
|
||||
|
||||
e.preventDefault()
|
||||
cb(anchor)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Replaces nanoquery 1.3.0 (MIT) with the platform's URLSearchParams.
|
||||
// Semantics preserved: returns a plain object; repeated keys become arrays.
|
||||
|
||||
import { equal } from './assert.js'
|
||||
|
||||
export default function nanoquery (url) {
|
||||
equal(typeof url, 'string', 'nanoquery: url should be type string')
|
||||
|
||||
const query = {}
|
||||
const index = url.indexOf('?')
|
||||
if (index === -1) return query
|
||||
|
||||
const params = new URLSearchParams(url.slice(index + 1))
|
||||
for (const [key, value] of params) {
|
||||
if (Object.hasOwn(query, key)) {
|
||||
if (Array.isArray(query[key])) query[key].push(value)
|
||||
else query[key] = [query[key], value]
|
||||
} else {
|
||||
query[key] = value
|
||||
}
|
||||
}
|
||||
return query
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Ported from nanoraf 3.1.0 (MIT) — https://github.com/choojs/nanoraf
|
||||
// Only call requestAnimationFrame when needed.
|
||||
|
||||
import { ok, equal } from './assert.js'
|
||||
|
||||
export default function nanoraf (render, raf) {
|
||||
equal(typeof render, 'function', 'nanoraf: render should be a function')
|
||||
ok(typeof raf === 'function' || typeof raf === 'undefined', 'nanoraf: raf should be a function or undefined')
|
||||
|
||||
if (!raf) raf = globalThis.requestAnimationFrame
|
||||
let redrawScheduled = false
|
||||
let args = null
|
||||
|
||||
return function frame (...frameArgs) {
|
||||
if (args === null && !redrawScheduled) {
|
||||
redrawScheduled = true
|
||||
|
||||
raf(function redraw () {
|
||||
redrawScheduled = false
|
||||
const _args = args
|
||||
args = null
|
||||
render(..._args)
|
||||
})
|
||||
}
|
||||
|
||||
args = frameArgs
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
// Ported from nanorouter 4.0.0 and wayfarer 7.0.1 with its trie (MIT)
|
||||
// https://github.com/choojs/nanorouter — https://github.com/yoshuawuyts/wayfarer
|
||||
// Consolidated into a single module: trie-based router with :params and
|
||||
// * wildcards, plus nanorouter's URL normalization on top.
|
||||
|
||||
import { ok, equal, notEqual } from './assert.js'
|
||||
|
||||
function has (object, property) {
|
||||
return Object.prototype.hasOwnProperty.call(object, property)
|
||||
}
|
||||
|
||||
class Trie {
|
||||
constructor () {
|
||||
this.trie = { nodes: {} }
|
||||
}
|
||||
|
||||
// create a node on the trie at route and return it
|
||||
create (route) {
|
||||
equal(typeof route, 'string', 'route should be a string')
|
||||
// strip leading '/' and split routes
|
||||
const routes = route.replace(/^\//, '').split('/')
|
||||
const self = this
|
||||
|
||||
function createNode (index, trie) {
|
||||
const thisRoute = has(routes, index) && routes[index]
|
||||
if (thisRoute === false) return trie
|
||||
|
||||
let node = null
|
||||
if (/^:|^\*/.test(thisRoute)) {
|
||||
// if node is a name match, set name and append to ':' node
|
||||
if (!has(trie.nodes, '$$')) {
|
||||
node = { nodes: {} }
|
||||
trie.nodes.$$ = node
|
||||
} else {
|
||||
node = trie.nodes.$$
|
||||
}
|
||||
|
||||
if (thisRoute[0] === '*') {
|
||||
trie.wildcard = true
|
||||
}
|
||||
|
||||
trie.name = thisRoute.replace(/^:|^\*/, '')
|
||||
} else if (!has(trie.nodes, thisRoute)) {
|
||||
node = { nodes: {} }
|
||||
trie.nodes[thisRoute] = node
|
||||
} else {
|
||||
node = trie.nodes[thisRoute]
|
||||
}
|
||||
|
||||
return createNode(index + 1, node)
|
||||
}
|
||||
|
||||
return createNode(0, self.trie)
|
||||
}
|
||||
|
||||
// match a route on the trie and return the node
|
||||
match (route) {
|
||||
equal(typeof route, 'string', 'route should be a string')
|
||||
|
||||
const routes = route.replace(/^\//, '').split('/')
|
||||
const params = {}
|
||||
|
||||
function search (index, trie) {
|
||||
// either there's no match, or we're done searching
|
||||
if (trie === undefined) return undefined
|
||||
const thisRoute = routes[index]
|
||||
if (thisRoute === undefined) return trie
|
||||
|
||||
if (has(trie.nodes, thisRoute)) {
|
||||
// match regular routes first
|
||||
return search(index + 1, trie.nodes[thisRoute])
|
||||
} else if (trie.name) {
|
||||
// match named routes
|
||||
try {
|
||||
params[trie.name] = decodeURIComponent(thisRoute)
|
||||
} catch (e) {
|
||||
return search(index, undefined)
|
||||
}
|
||||
return search(index + 1, trie.nodes.$$)
|
||||
} else if (trie.wildcard) {
|
||||
// match wildcards
|
||||
try {
|
||||
params.wildcard = decodeURIComponent(routes.slice(index).join('/'))
|
||||
} catch (e) {
|
||||
return search(index, undefined)
|
||||
}
|
||||
// return early, or else search may keep recursing through the wildcard
|
||||
return trie.nodes.$$
|
||||
} else {
|
||||
// no matches found
|
||||
return search(index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
let node = search(0, this.trie)
|
||||
|
||||
if (!node) return undefined
|
||||
node = Object.assign({}, node)
|
||||
node.params = params
|
||||
return node
|
||||
}
|
||||
|
||||
// mount a trie onto a node at route
|
||||
mount (route, trie) {
|
||||
equal(typeof route, 'string', 'route should be a string')
|
||||
equal(typeof trie, 'object', 'trie should be a object')
|
||||
|
||||
const split = route.replace(/^\//, '').split('/')
|
||||
let node = null
|
||||
|
||||
if (split.length === 1) {
|
||||
node = this.create(split[0])
|
||||
} else {
|
||||
node = this.create(split.join('/'))
|
||||
}
|
||||
|
||||
Object.assign(node.nodes, trie.nodes)
|
||||
if (trie.name) node.name = trie.name
|
||||
|
||||
// delegate properties from '/' to the new node
|
||||
// '/' cannot be reached once mounted
|
||||
if (node.nodes['']) {
|
||||
Object.keys(node.nodes['']).forEach(function (key) {
|
||||
if (key === 'nodes') return
|
||||
node[key] = node.nodes[''][key]
|
||||
})
|
||||
Object.assign(node.nodes, node.nodes[''].nodes)
|
||||
delete node.nodes[''].nodes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function wayfarer (dft) {
|
||||
const _default = (dft || '').replace(/^\//, '')
|
||||
const _trie = new Trie()
|
||||
|
||||
emit._trie = _trie
|
||||
emit.on = on
|
||||
emit.emit = emit
|
||||
emit.match = match
|
||||
emit._wayfarer = true
|
||||
|
||||
return emit
|
||||
|
||||
function on (route, cb) {
|
||||
equal(typeof route, 'string')
|
||||
equal(typeof cb, 'function')
|
||||
|
||||
route = route || '/'
|
||||
|
||||
if (cb._wayfarer && cb._trie) {
|
||||
_trie.mount(route, cb._trie.trie)
|
||||
} else {
|
||||
const node = _trie.create(route)
|
||||
node.cb = cb
|
||||
node.route = route
|
||||
}
|
||||
|
||||
return emit
|
||||
}
|
||||
|
||||
function emit (route, ...args) {
|
||||
const matched = match(route)
|
||||
return matched.cb(matched.params, ...args)
|
||||
}
|
||||
|
||||
function match (route) {
|
||||
notEqual(route, undefined, "'route' must be defined")
|
||||
|
||||
const matched = _trie.match(route)
|
||||
if (matched && matched.cb) return new Route(matched)
|
||||
|
||||
const dft = _trie.match(_default)
|
||||
if (dft && dft.cb) return new Route(dft)
|
||||
|
||||
throw new Error("route '" + route + "' did not match")
|
||||
}
|
||||
|
||||
function Route (matched) {
|
||||
this.cb = matched.cb
|
||||
this.route = matched.route
|
||||
this.params = matched.params
|
||||
}
|
||||
}
|
||||
|
||||
// electron support
|
||||
const isLocalFile = /file:\/\//.test(
|
||||
typeof window === 'object' &&
|
||||
window.location &&
|
||||
window.location.origin
|
||||
)
|
||||
|
||||
/* eslint-disable no-useless-escape */
|
||||
const electron = '^(file:\/\/|\/)(.*\.html?\/?)?'
|
||||
const protocol = '^(http(s)?(:\/\/))?(www\.)?'
|
||||
const domain = '[a-zA-Z0-9-_\.]+(:[0-9]{1,5})?(\/{1})?'
|
||||
const qs = '[\?].*$'
|
||||
/* eslint-enable no-useless-escape */
|
||||
|
||||
const stripElectron = new RegExp(electron)
|
||||
const prefix = new RegExp(protocol + domain)
|
||||
const normalize = new RegExp('#')
|
||||
const suffix = new RegExp(qs)
|
||||
|
||||
// replace everything in a route but the pathname and hash
|
||||
function pathname (routename, isElectron) {
|
||||
if (isElectron) routename = routename.replace(stripElectron, '')
|
||||
else routename = routename.replace(prefix, '')
|
||||
return decodeURI(routename.replace(suffix, '').replace(normalize, '/'))
|
||||
}
|
||||
|
||||
export default class Nanorouter {
|
||||
constructor (opts) {
|
||||
opts = opts || {}
|
||||
this.router = wayfarer(opts.default || '/404')
|
||||
}
|
||||
|
||||
on (routename, listener) {
|
||||
equal(typeof routename, 'string')
|
||||
routename = routename.replace(/^[#/]/, '')
|
||||
this.router.on(routename, listener)
|
||||
}
|
||||
|
||||
emit (routename) {
|
||||
equal(typeof routename, 'string')
|
||||
routename = pathname(routename, isLocalFile)
|
||||
return this.router.emit(routename)
|
||||
}
|
||||
|
||||
match (routename) {
|
||||
equal(typeof routename, 'string')
|
||||
routename = pathname(routename, isLocalFile)
|
||||
return this.router.match(routename)
|
||||
}
|
||||
}
|
||||
|
||||
export { Trie, wayfarer, ok }
|
||||
@@ -0,0 +1,56 @@
|
||||
// Ported from nanotiming 7.3.1 (MIT) — https://github.com/choojs/nanotiming
|
||||
// Unified browser/Node implementation on the global `performance` object.
|
||||
|
||||
import { equal } from './assert.js'
|
||||
|
||||
const perf = typeof performance !== 'undefined' && typeof performance.mark === 'function'
|
||||
? performance
|
||||
: null
|
||||
|
||||
function checkDisabled () {
|
||||
if (!perf) return true
|
||||
if (typeof process !== 'undefined' && process.env && process.env.DISABLE_NANOTIMING) return true
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
return window.localStorage.DISABLE_NANOTIMING === 'true'
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
nanotiming.disabled = checkDisabled()
|
||||
|
||||
export default function nanotiming (name) {
|
||||
equal(typeof name, 'string', 'nanotiming: name should be type string')
|
||||
|
||||
if (nanotiming.disabled) return noop
|
||||
|
||||
const uuid = (perf.now() * 10000).toFixed() % Number.MAX_SAFE_INTEGER
|
||||
const startName = 'start-' + uuid + '-' + name
|
||||
perf.mark(startName)
|
||||
|
||||
function end (cb) {
|
||||
const endName = 'end-' + uuid + '-' + name
|
||||
perf.mark(endName)
|
||||
|
||||
let err = null
|
||||
try {
|
||||
const measureName = name + ' [' + uuid + ']'
|
||||
perf.measure(measureName, startName, endName)
|
||||
perf.clearMarks(startName)
|
||||
perf.clearMarks(endName)
|
||||
} catch (e) { err = e }
|
||||
if (cb) cb(err, name)
|
||||
}
|
||||
|
||||
end.uuid = uuid
|
||||
return end
|
||||
}
|
||||
|
||||
function noop (cb) {
|
||||
if (cb) {
|
||||
cb(new Error('nanotiming: performance API unavailable or disabled'))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user