solid-js
Version:
A declarative JavaScript library for building user interfaces.
791 lines (788 loc) • 19.3 kB
JavaScript
const equalFn = (a, b) => a === b;
const $PROXY = Symbol("solid-proxy");
const $TRACK = Symbol("solid-track");
const $DEVCOMP = Symbol("solid-dev-component");
const DEV = undefined;
const ERROR = Symbol("error");
function castError(err) {
if (err instanceof Error) return err;
return new Error(typeof err === "string" ? err : "Unknown error", {
cause: err
});
}
function handleError(err, owner = Owner) {
const fns = owner && owner.context && owner.context[ERROR];
const error = castError(err);
if (!fns) throw error;
try {
for (const f of fns) f(error);
} catch (e) {
handleError(e, (owner && owner.owner) || null);
}
}
const UNOWNED = {
context: null,
owner: null,
owned: null,
cleanups: null
};
let Owner = null;
function createOwner() {
const o = {
owner: Owner,
context: Owner ? Owner.context : null,
owned: null,
cleanups: null
};
if (Owner) {
if (!Owner.owned) Owner.owned = [o];
else Owner.owned.push(o);
}
return o;
}
function createRoot(fn, detachedOwner) {
const owner = Owner,
current = detachedOwner === undefined ? owner : detachedOwner,
root =
fn.length === 0
? UNOWNED
: {
context: current ? current.context : null,
owner: current,
owned: null,
cleanups: null
};
Owner = root;
let result;
try {
result = fn(fn.length === 0 ? () => {} : () => cleanNode(root));
} catch (err) {
handleError(err);
} finally {
Owner = owner;
}
return result;
}
function createSignal(value, options) {
return [
() => value,
v => {
return (value = typeof v === "function" ? v(value) : v);
}
];
}
function createComputed(fn, value) {
Owner = createOwner();
try {
fn(value);
} catch (err) {
handleError(err);
} finally {
Owner = Owner.owner;
}
}
const createRenderEffect = createComputed;
function createEffect(fn, value) {}
function createReaction(fn) {
return fn => {
fn();
};
}
function createMemo(fn, value) {
Owner = createOwner();
let v;
try {
v = fn(value);
} catch (err) {
handleError(err);
} finally {
Owner = Owner.owner;
}
return () => v;
}
function createDeferred(source) {
return source;
}
function createSelector(source, fn = equalFn) {
return k => fn(k, source());
}
function batch(fn) {
return fn();
}
const untrack = batch;
function on(deps, fn, options = {}) {
const isArray = Array.isArray(deps);
const defer = options.defer;
return () => {
if (defer) return undefined;
let value;
if (isArray) {
value = [];
for (let i = 0; i < deps.length; i++) value.push(deps[i]());
} else value = deps();
return fn(value);
};
}
function onMount(fn) {}
function onCleanup(fn) {
if (Owner) {
if (!Owner.cleanups) Owner.cleanups = [fn];
else Owner.cleanups.push(fn);
}
return fn;
}
function cleanNode(node) {
if (node.owned) {
for (let i = 0; i < node.owned.length; i++) cleanNode(node.owned[i]);
node.owned = null;
}
if (node.cleanups) {
for (let i = 0; i < node.cleanups.length; i++) node.cleanups[i]();
node.cleanups = null;
}
}
function catchError(fn, handler) {
const owner = createOwner();
owner.context = {
...owner.context,
[ERROR]: [handler]
};
Owner = owner;
try {
return fn();
} catch (err) {
handleError(err);
} finally {
Owner = Owner.owner;
}
}
function getListener() {
return null;
}
function createContext(defaultValue) {
const id = Symbol("context");
return {
id,
Provider: createProvider(id),
defaultValue
};
}
function useContext(context) {
return Owner && Owner.context && Owner.context[context.id] !== undefined
? Owner.context[context.id]
: context.defaultValue;
}
function getOwner() {
return Owner;
}
function children(fn) {
const memo = createMemo(() => resolveChildren(fn()));
memo.toArray = () => {
const c = memo();
return Array.isArray(c) ? c : c != null ? [c] : [];
};
return memo;
}
function runWithOwner(o, fn) {
const prev = Owner;
Owner = o;
try {
return fn();
} catch (err) {
handleError(err);
} finally {
Owner = prev;
}
}
function resolveChildren(children) {
if (typeof children === "function" && !children.length) return resolveChildren(children());
if (Array.isArray(children)) {
const results = [];
for (let i = 0; i < children.length; i++) {
const result = resolveChildren(children[i]);
Array.isArray(result) ? results.push.apply(results, result) : results.push(result);
}
return results;
}
return children;
}
function createProvider(id) {
return function provider(props) {
return createMemo(() => {
Owner.context = {
...Owner.context,
[id]: props.value
};
return children(() => props.children);
});
};
}
function requestCallback(fn, options) {
return {
id: 0,
fn: () => {},
startTime: 0,
expirationTime: 0
};
}
function mapArray(list, mapFn, options = {}) {
const items = list();
let s = [];
if (items && items.length) {
for (let i = 0, len = items.length; i < len; i++) s.push(mapFn(items[i], () => i));
} else if (options.fallback) s = [options.fallback()];
return () => s;
}
function indexArray(list, mapFn, options = {}) {
const items = list();
let s = [];
if (items && items.length) {
for (let i = 0, len = items.length; i < len; i++) s.push(mapFn(() => items[i], i));
} else if (options.fallback) s = [options.fallback()];
return () => s;
}
function observable(input) {
return {
subscribe(observer) {
if (!(observer instanceof Object) || observer == null) {
throw new TypeError("Expected the observer to be an object.");
}
const handler =
typeof observer === "function" ? observer : observer.next && observer.next.bind(observer);
if (!handler) {
return {
unsubscribe() {}
};
}
const dispose = createRoot(disposer => {
createEffect(() => {
const v = input();
untrack(() => handler(v));
});
return disposer;
});
if (getOwner()) onCleanup(dispose);
return {
unsubscribe() {
dispose();
}
};
},
[Symbol.observable || "@@observable"]() {
return this;
}
};
}
function from(producer) {
const [s, set] = createSignal(undefined);
if ("subscribe" in producer) {
const unsub = producer.subscribe(v => set(() => v));
onCleanup(() => ("unsubscribe" in unsub ? unsub.unsubscribe() : unsub()));
} else {
const clean = producer(set);
onCleanup(clean);
}
return s;
}
function enableExternalSource(factory) {}
function onError(fn) {
if (Owner) {
if (Owner.context === null || !Owner.context[ERROR]) {
Owner.context = {
...Owner.context,
[ERROR]: [fn]
};
mutateContext(Owner, ERROR, [fn]);
} else Owner.context[ERROR].push(fn);
}
}
function mutateContext(o, key, value) {
if (o.owned) {
for (let i = 0; i < o.owned.length; i++) {
if (o.owned[i].context === o.context) mutateContext(o.owned[i], key, value);
if (!o.owned[i].context) {
o.owned[i].context = o.context;
mutateContext(o.owned[i], key, value);
} else if (!o.owned[i].context[key]) {
o.owned[i].context[key] = value;
mutateContext(o.owned[i], key, value);
}
}
}
}
function resolveSSRNode(node) {
const t = typeof node;
if (t === "string") return node;
if (node == null || t === "boolean") return "";
if (Array.isArray(node)) {
let prev = {};
let mapped = "";
for (let i = 0, len = node.length; i < len; i++) {
if (typeof prev !== "object" && typeof node[i] !== "object") mapped += `<!--!$-->`;
mapped += resolveSSRNode((prev = node[i]));
}
return mapped;
}
if (t === "object") return node.t;
if (t === "function") return resolveSSRNode(node());
return String(node);
}
const sharedConfig = {
context: undefined,
getContextId() {
if (!this.context) throw new Error(`getContextId cannot be used under non-hydrating context`);
return getContextId(this.context.count);
},
getNextContextId() {
if (!this.context)
throw new Error(`getNextContextId cannot be used under non-hydrating context`);
return getContextId(this.context.count++);
}
};
function getContextId(count) {
const num = String(count),
len = num.length - 1;
return sharedConfig.context.id + (len ? String.fromCharCode(96 + len) : "") + num;
}
function setHydrateContext(context) {
sharedConfig.context = context;
}
function nextHydrateContext() {
return sharedConfig.context
? {
...sharedConfig.context,
id: sharedConfig.getNextContextId(),
count: 0
}
: undefined;
}
function createUniqueId() {
return sharedConfig.getNextContextId();
}
function createComponent(Comp, props) {
if (sharedConfig.context && !sharedConfig.context.noHydrate) {
const c = sharedConfig.context;
setHydrateContext(nextHydrateContext());
const r = Comp(props || {});
setHydrateContext(c);
return r;
}
return Comp(props || {});
}
function mergeProps(...sources) {
const target = {};
for (let i = 0; i < sources.length; i++) {
let source = sources[i];
if (typeof source === "function") source = source();
if (source) {
const descriptors = Object.getOwnPropertyDescriptors(source);
for (const key in descriptors) {
if (key in target) continue;
Object.defineProperty(target, key, {
enumerable: true,
get() {
for (let i = sources.length - 1; i >= 0; i--) {
let v,
s = sources[i];
if (typeof s === "function") s = s();
v = (s || {})[key];
if (v !== undefined) return v;
}
}
});
}
}
}
return target;
}
function splitProps(props, ...keys) {
const descriptors = Object.getOwnPropertyDescriptors(props),
split = k => {
const clone = {};
for (let i = 0; i < k.length; i++) {
const key = k[i];
if (descriptors[key]) {
Object.defineProperty(clone, key, descriptors[key]);
delete descriptors[key];
}
}
return clone;
};
return keys.map(split).concat(split(Object.keys(descriptors)));
}
function simpleMap(props, wrap) {
const list = props.each || [],
len = list.length,
fn = props.children;
if (len) {
let mapped = Array(len);
for (let i = 0; i < len; i++) mapped[i] = wrap(fn, list[i], i);
return mapped;
}
return props.fallback;
}
function For(props) {
return simpleMap(props, (fn, item, i) => fn(item, () => i));
}
function Index(props) {
return simpleMap(props, (fn, item, i) => fn(() => item, i));
}
function Show(props) {
let c;
return props.when
? typeof (c = props.children) === "function"
? c(props.keyed ? props.when : () => props.when)
: c
: props.fallback || "";
}
function Switch(props) {
let conditions = props.children;
Array.isArray(conditions) || (conditions = [conditions]);
for (let i = 0; i < conditions.length; i++) {
const w = conditions[i].when;
if (w) {
const c = conditions[i].children;
return typeof c === "function" ? c(conditions[i].keyed ? w : () => w) : c;
}
}
return props.fallback || "";
}
function Match(props) {
return props;
}
function resetErrorBoundaries() {}
function ErrorBoundary(props) {
let error,
res,
clean,
sync = true;
const ctx = sharedConfig.context;
const id = sharedConfig.getContextId();
function displayFallback() {
cleanNode(clean);
ctx.serialize(id, error);
setHydrateContext({
...ctx,
count: 0
});
const f = props.fallback;
return typeof f === "function" && f.length ? f(error, () => {}) : f;
}
createMemo(() => {
clean = Owner;
return catchError(
() => (res = props.children),
err => {
error = err;
!sync && ctx.replace("e" + id, displayFallback);
sync = true;
}
);
});
if (error) return displayFallback();
sync = false;
return {
t: `<!--!$e${id}-->${resolveSSRNode(res)}<!--!$/e${id}-->`
};
}
const SuspenseContext = createContext();
let resourceContext = null;
function createResource(source, fetcher, options = {}) {
if (arguments.length === 2) {
if (typeof fetcher === "object") {
options = fetcher;
fetcher = source;
source = true;
}
} else if (arguments.length === 1) {
fetcher = source;
source = true;
}
const contexts = new Set();
const id = sharedConfig.getNextContextId();
let resource = {};
let value = options.storage ? options.storage(options.initialValue)[0]() : options.initialValue;
let p;
let error;
if (sharedConfig.context.async && options.ssrLoadFrom !== "initial") {
resource = sharedConfig.context.resources[id] || (sharedConfig.context.resources[id] = {});
if (resource.ref) {
if (!resource.data && !resource.ref[0].loading && !resource.ref[0].error)
resource.ref[1].refetch();
return resource.ref;
}
}
const read = () => {
if (error) throw error;
const resolved =
options.ssrLoadFrom !== "initial" &&
sharedConfig.context.async &&
"data" in sharedConfig.context.resources[id];
if (!resolved && resourceContext) resourceContext.push(id);
if (!resolved && read.loading) {
const ctx = useContext(SuspenseContext);
if (ctx) {
ctx.resources.set(id, read);
contexts.add(ctx);
}
}
return resolved ? sharedConfig.context.resources[id].data : value;
};
read.loading = false;
read.error = undefined;
read.state = "initialValue" in options ? "ready" : "unresolved";
Object.defineProperty(read, "latest", {
get() {
return read();
}
});
function load() {
const ctx = sharedConfig.context;
if (!ctx.async) return (read.loading = !!(typeof source === "function" ? source() : source));
if (ctx.resources && id in ctx.resources && "data" in ctx.resources[id]) {
value = ctx.resources[id].data;
return;
}
let lookup;
try {
resourceContext = [];
lookup = typeof source === "function" ? source() : source;
if (resourceContext.length) return;
} finally {
resourceContext = null;
}
if (!p) {
if (lookup == null || lookup === false) return;
p = fetcher(lookup, {
value
});
}
if (p != undefined && typeof p === "object" && "then" in p) {
read.loading = true;
read.state = "pending";
p = p
.then(res => {
read.loading = false;
read.state = "ready";
ctx.resources[id].data = res;
p = null;
notifySuspense(contexts);
return res;
})
.catch(err => {
read.loading = false;
read.state = "errored";
read.error = error = castError(err);
p = null;
notifySuspense(contexts);
throw error;
});
if (ctx.serialize) ctx.serialize(id, p, options.deferStream);
return p;
}
ctx.resources[id].data = p;
if (ctx.serialize) ctx.serialize(id, p);
p = null;
return ctx.resources[id].data;
}
if (options.ssrLoadFrom !== "initial") load();
return (resource.ref = [
read,
{
refetch: load,
mutate: v => (value = v)
}
]);
}
function lazy(fn) {
let p;
let load = id => {
if (!p) {
p = fn();
p.then(mod => (p.resolved = mod.default));
if (id) sharedConfig.context.lazy[id] = p;
}
return p;
};
const contexts = new Set();
const wrap = props => {
const id = sharedConfig.context.id;
let ref = sharedConfig.context.lazy[id];
if (ref) p = ref;
else load(id);
if (p.resolved) return p.resolved(props);
const ctx = useContext(SuspenseContext);
const track = {
loading: true,
error: undefined
};
if (ctx) {
ctx.resources.set(id, track);
contexts.add(ctx);
}
if (sharedConfig.context.async) {
sharedConfig.context.block(
p.then(() => {
track.loading = false;
notifySuspense(contexts);
})
);
}
return "";
};
wrap.preload = load;
return wrap;
}
function suspenseComplete(c) {
for (const r of c.resources.values()) {
if (r.loading) return false;
}
return true;
}
function notifySuspense(contexts) {
for (const c of contexts) {
if (!suspenseComplete(c)) {
continue;
}
c.completed();
contexts.delete(c);
}
}
function enableScheduling() {}
function enableHydration() {}
function startTransition(fn) {
fn();
}
function useTransition() {
return [
() => false,
fn => {
fn();
}
];
}
function SuspenseList(props) {
return props.children;
}
function Suspense(props) {
let done;
const ctx = sharedConfig.context;
const id = sharedConfig.getContextId();
const o = createOwner();
const value =
ctx.suspense[id] ||
(ctx.suspense[id] = {
resources: new Map(),
completed: () => {
const res = runSuspense();
if (suspenseComplete(value)) {
done(resolveSSRNode(res));
}
}
});
function suspenseError(err) {
if (!done || !done(undefined, err)) {
runWithOwner(o.owner, () => {
throw err;
});
}
}
function runSuspense() {
setHydrateContext({
...ctx,
count: 0
});
cleanNode(o);
return runWithOwner(o, () =>
createComponent(SuspenseContext.Provider, {
value,
get children() {
return catchError(() => props.children, suspenseError);
}
})
);
}
const res = runSuspense();
if (suspenseComplete(value)) {
delete ctx.suspense[id];
return res;
}
done = ctx.async ? ctx.registerFragment(id) : undefined;
return catchError(() => {
if (ctx.async) {
setHydrateContext({
...ctx,
count: 0,
id: ctx.id + "0F",
noHydrate: true
});
const res = {
t: `<template id="pl-${id}"></template>${resolveSSRNode(props.fallback)}<!--pl-${id}-->`
};
setHydrateContext(ctx);
return res;
}
setHydrateContext({
...ctx,
count: 0,
id: ctx.id + "0F"
});
ctx.serialize(id, "$$f");
return props.fallback;
}, suspenseError);
}
export {
$DEVCOMP,
$PROXY,
$TRACK,
DEV,
ErrorBoundary,
For,
Index,
Match,
Show,
Suspense,
SuspenseList,
Switch,
batch,
catchError,
children,
createComponent,
createComputed,
createContext,
createDeferred,
createEffect,
createMemo,
createReaction,
createRenderEffect,
createResource,
createRoot,
createSelector,
createSignal,
createUniqueId,
enableExternalSource,
enableHydration,
enableScheduling,
equalFn,
from,
getListener,
getOwner,
indexArray,
lazy,
mapArray,
mergeProps,
observable,
on,
onCleanup,
onError,
onMount,
requestCallback,
resetErrorBoundaries,
runWithOwner,
sharedConfig,
splitProps,
startTransition,
untrack,
useContext,
useTransition
};