solid-js
Version:
A declarative JavaScript library for building user interfaces.
498 lines (495 loc) • 13.4 kB
JavaScript
const equalFn = (a, b) => a === b;
const $PROXY = Symbol("solid-proxy");
const DEV = {};
const ERROR = Symbol("error");
const UNOWNED = {
context: null,
owner: null
};
let Owner = null;
function createRoot(fn, detachedOwner) {
detachedOwner && (Owner = detachedOwner);
const owner = Owner,
root = fn.length === 0 ? UNOWNED : {
context: null,
owner
};
Owner = root;
let result;
try {
result = fn(() => {});
} catch (err) {
const fns = lookup(Owner, ERROR);
if (!fns) throw err;
fns.forEach(f => f(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 = {
owner: Owner,
context: null
};
fn(value);
Owner = Owner.owner;
}
const createRenderEffect = createComputed;
function createEffect(fn, value) {}
function createMemo(fn, value) {
Owner = {
owner: Owner,
context: null
};
const v = fn(value);
Owner = Owner.owner;
return () => v;
}
function createDeferred(source) {
return source;
}
function createSelector(source, fn) {
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) {}
function onError(fn) {
if (Owner === null) console.warn("error handlers created outside a `createRoot` or `render` will never be run");else if (Owner.context === null) Owner.context = {
[ERROR]: [fn]
};else if (!Owner.context[ERROR]) Owner.context[ERROR] = [fn];else Owner.context[ERROR].push(fn);
}
function getListener() {
return null;
}
function createContext(defaultValue) {
const id = Symbol("context");
return {
id,
Provider: createProvider(id),
defaultValue
};
}
function useContext(context) {
return lookup(Owner, context.id) || context.defaultValue;
}
function getOwner() {
return Owner;
}
function children(fn) {
return createMemo(() => resolveChildren(fn()));
}
function runWithOwner(o, fn) {
const prev = Owner;
Owner = o;
try {
return fn();
} finally {
Owner = prev;
}
}
function lookup(owner, key) {
return owner && (owner.context && owner.context[key] || owner.owner && lookup(owner.owner, key));
}
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 = {
[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.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 getSymbol() {
const SymbolCopy = Symbol;
return SymbolCopy.observable || "@@observable";
}
function observable(input) {
const $$observable = getSymbol();
return {
subscribe(observer) {
if (!(observer instanceof Object) || observer == null) {
throw new TypeError("Expected the observer to be an object.");
}
const handler = "next" in observer ? observer.next : observer;
let complete = false;
createComputed(() => {
if (complete) return;
const v = input();
handler(v);
});
return {
unsubscribe() {
complete = true;
}
};
},
[$$observable]() {
return this;
}
};
}
function from(producer) {
const [s, set] = createSignal(undefined);
if ("subscribe" in producer) {
producer.subscribe(v => set(() => v));
} else {
producer(set);
}
return s;
}
function resolveSSRNode(node) {
const t = typeof node;
if (t === "string") return node;
if (node == null || t === "boolean") return "";
if (Array.isArray(node)) return node.map(resolveSSRNode).join("");
if (t === "object") return resolveSSRNode(node.t);
if (t === "function") return resolveSSRNode(node());
return String(node);
}
const sharedConfig = {};
function setHydrateContext(context) {
sharedConfig.context = context;
}
function nextHydrateContext() {
return sharedConfig.context ? { ...sharedConfig.context,
id: `${sharedConfig.context.id}${sharedConfig.context.count++}.`,
count: 0
} : undefined;
}
function createUniqueId() {
const ctx = sharedConfig.context;
if (!ctx) throw new Error(`createUniqueId cannot be used under non-hydrating context`);
return `${ctx.id}${ctx.count++}`;
}
function createComponent(Comp, props) {
if (sharedConfig.context) {
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++) {
const descriptors = Object.getOwnPropertyDescriptors(sources[i]);
Object.defineProperties(target, descriptors);
}
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 = "";
for (let i = 0; i < len; i++) mapped += resolveSSRNode(wrap(fn, list[i], i));
return {
t: 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.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(w) : c;
}
}
return props.fallback || "";
}
function Match(props) {
return props;
}
function ErrorBoundary(props) {
return props.children;
}
const SuspenseContext = createContext();
let resourceContext = null;
function createResource(fn, fetcher, options = {}) {
if (arguments.length === 2) {
if (typeof fetcher === "object") {
options = fetcher;
fetcher = fn;
fn = true;
}
} else if (arguments.length === 1) {
fetcher = fn;
fn = true;
}
const contexts = new Set();
const id = sharedConfig.context.id + sharedConfig.context.count++;
let resource = {};
let value = options.initialValue;
let p;
if (sharedConfig.context.async) {
resource = sharedConfig.context.resources[id] || (sharedConfig.context.resources[id] = {});
if (resource.ref) {
if (!resource.data && !resource.ref[0].loading) resource.ref[1].refetch();
return resource.ref;
}
}
const read = () => {
if (resourceContext && p) resourceContext.push(p);
const resolved = sharedConfig.context.async && sharedConfig.context.resources[id].data;
if (sharedConfig.context.async && !resolved) {
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;
function load() {
const ctx = sharedConfig.context;
if (!ctx.async && !ctx.streaming) return;
if (ctx.resources && id in ctx.resources && ctx.resources[id].data) {
value = ctx.resources[id].data;
return;
}
resourceContext = [];
const lookup = typeof fn === "function" ? fn() : fn;
if (resourceContext.length) {
p = Promise.all(resourceContext).then(() => fetcher(fn(), () => value));
}
resourceContext = null;
if (!p) {
if (lookup == null || lookup === false) return;
p = fetcher(lookup, () => value);
}
read.loading = true;
if ("then" in p) {
if (ctx.writeResource) {
ctx.writeResource(id, p);
p.then(v => {
value = v;
read.loading = false;
p = null;
});
return;
}
p.then(res => {
read.loading = false;
ctx.resources[id].data = res;
p = null;
notifySuspense(contexts);
return res;
});
return;
}
ctx.resources[id].data = p;
p = null;
}
load();
return resource.ref = [read, {
refetch: load,
mutate: v => value = v
}];
}
function lazy(fn) {
let resolved;
const p = fn();
const contexts = new Set();
p.then(mod => resolved = mod.default);
const wrap = props => {
const id = sharedConfig.context.id + sharedConfig.context.count++;
if (resolved) return resolved(props);
const ctx = useContext(SuspenseContext);
const track = {
loading: true
};
if (ctx) {
ctx.resources.set(id, track);
contexts.add(ctx);
}
p.then(() => {
track.loading = false;
notifySuspense(contexts);
});
return "";
};
wrap.preload = () => {};
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)) c.completed();
}
contexts.clear();
}
function enableScheduling() {}
function startTransition(fn) {
fn();
}
function useTransition() {
return [() => false, fn => {
fn();
}];
}
function SuspenseList(props) {
return props.children;
}
const SUSPENSE_GLOBAL = Symbol("suspense-global");
function Suspense(props) {
const ctx = sharedConfig.context;
if (!ctx.async) return createComponent(() => {
props.children;
return props.fallback;
}, {});
const id = ctx.id + ctx.count;
const done = ctx.async ? lookup(Owner, SUSPENSE_GLOBAL)(id) : () => {};
const o = Owner;
const value = ctx.suspense[id] || (ctx.suspense[id] = {
resources: new Map(),
completed: () => {
const res = runSuspense();
if (suspenseComplete(value)) {
done(resolveSSRNode(res));
}
}
});
function runSuspense() {
setHydrateContext({ ...ctx,
count: 0
});
return runWithOwner(o, () => {
return createComponent(SuspenseContext.Provider, {
value,
get children() {
return props.children;
}
});
});
}
const res = runSuspense();
if (suspenseComplete(value)) {
done();
return res;
}
return sharedConfig.context.async ? {
t: `<#${id}#>`
} : props.fallback;
}
const SUSPENSE_REPLACE = /<#([\d.]+)#>/;
function awaitSuspense(fn) {
return new Promise(resolve => {
const registry = new Set();
const cache = Object.create(null);
const res = createMemo(() => {
Owner.context = {
[SUSPENSE_GLOBAL]: getCallback
};
return fn();
});
if (!registry.size) resolve(res());
function getCallback(key) {
registry.add(key);
return value => {
if (value) cache[key] = value;
registry.delete(key);
if (!registry.size) Promise.resolve().then(() => {
let source = resolveSSRNode(res());
let final = "";
let match;
while (match = source.match(SUSPENSE_REPLACE)) {
final += source.substring(0, match.index);
source = cache[match[1]] + source.substring(match.index + match[0].length);
}
resolve(final + source);
});
};
}
});
}
export { $PROXY, DEV, ErrorBoundary, For, Index, Match, Show, Suspense, SuspenseList, Switch, awaitSuspense, batch, createComponent, createComputed, createContext, createDeferred, createEffect, createMemo, createRenderEffect, createResource, createRoot, createSelector, createSignal, createUniqueId, enableScheduling, equalFn, from, getListener, getOwner, lazy, mapArray, mergeProps, observable, on, onCleanup, onError, onMount, requestCallback, runWithOwner, sharedConfig, splitProps, startTransition, untrack, useContext, useTransition };