@qwik.dev/core
Version:
An open source framework for building instant loading web apps at any scale, without the extra effort.
1,431 lines (1,407 loc) • 427 kB
JavaScript
/**
* @license
* @qwik.dev/core 2.0.0-alpha.9-dev+56ed5bd
* Copyright QwikDev. All Rights Reserved.
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/QwikDev/qwik/blob/main/LICENSE
*/
import { isDev, isServer } from '@qwik.dev/core/build';
export { isBrowser, isDev, isServer } from '@qwik.dev/core/build';
// same as isDev but separate so we can test
const qDev = globalThis.qDev !== false;
const qInspector = globalThis.qInspector === true;
const qSerialize = globalThis.qSerialize !== false;
const qDynamicPlatform = globalThis.qDynamicPlatform !== false;
const qTest = globalThis.qTest === true;
const qRuntimeQrl = globalThis.qRuntimeQrl === true;
const seal = (obj) => {
if (qDev) {
Object.seal(obj);
}
};
const STYLE = qDev
? `background: #564CE0; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;`
: '';
const logError = (message, ...optionalParams) => {
return createAndLogError(false, message, ...optionalParams);
};
const throwErrorAndStop = (message, ...optionalParams) => {
const error = createAndLogError(false, message, ...optionalParams);
// eslint-disable-next-line no-debugger
debugger;
throw error;
};
const logErrorAndStop = (message, ...optionalParams) => {
const err = createAndLogError(true, message, ...optionalParams);
// eslint-disable-next-line no-debugger
debugger;
return err;
};
const _printed = /*#__PURE__*/ new Set();
const logOnceWarn = (message, ...optionalParams) => {
if (qDev) {
const key = 'warn' + String(message);
if (!_printed.has(key)) {
_printed.add(key);
logWarn(message, ...optionalParams);
}
}
};
const logWarn = (message, ...optionalParams) => {
if (qDev) {
console.warn('%cQWIK WARN', STYLE, message, ...optionalParams);
}
};
const createAndLogError = (asyncThrow, message, ...optionalParams) => {
const err = message instanceof Error ? message : new Error(message);
// display the error message first, then the optional params, and finally the stack trace
// the stack needs to be displayed last because the given params will be lost among large stack traces so it will
// provide a bad developer experience
!qTest && console.error('%cQWIK ERROR', STYLE, err.message, ...optionalParams, err.stack);
asyncThrow &&
!qTest &&
setTimeout(() => {
// throwing error asynchronously to avoid breaking the current call stack.
// We throw so that the error is delivered to the global error handler for
// reporting it to a third-party tools such as Qwik Insights, Sentry or New Relic.
throw err;
}, 0);
return err;
};
const ASSERT_DISCLAIMER = 'Internal assert, this is likely caused by a bug in Qwik: ';
function assertDefined(value, text, ...parts) {
if (qDev) {
if (value != null) {
return;
}
throwErrorAndStop(ASSERT_DISCLAIMER + text, ...parts);
}
}
function assertEqual(value1, value2, text, ...parts) {
if (qDev) {
if (value1 === value2) {
return;
}
throwErrorAndStop(ASSERT_DISCLAIMER + text, ...parts);
}
}
function assertTrue(value1, text, ...parts) {
if (qDev) {
if (value1 === true) {
return;
}
throwErrorAndStop(ASSERT_DISCLAIMER + text, ...parts);
}
}
function assertFalse(value1, text, ...parts) {
if (qDev) {
if (value1 === false) {
return;
}
throwErrorAndStop(ASSERT_DISCLAIMER + text, ...parts);
}
}
function assertNumber(value1, text, ...parts) {
if (qDev) {
if (typeof value1 === 'number') {
return;
}
throwErrorAndStop(ASSERT_DISCLAIMER + text, ...parts);
}
}
const codeToText = (code, ...parts) => {
if (qDev) {
// Keep one error, one line to make it easier to search for the error message.
const MAP = [
'Error while serializing class or style attributes', // 0
'Scheduler not found', // 1
'track() received object, without prop to track', // 2
'Only primitive and object literals can be serialized. {{0}}', // 3
'', // 4 unused
'You can render over a existing q:container. Skipping render().', // 5
'', // 6 unused
'', // 7 unused
'', // 8 unused
'', // 9 unused
'QRL is not a function', // 10
'Dynamic import not found', // 11
'Unknown type argument', // 12
`Actual value for useContext({{0}}) can not be found, make sure some ancestor component has set a value using useContextProvider(). In the browser make sure that the context was used during SSR so its state was serialized.`, // 13
"Invoking 'use*()' method outside of invocation context.", // 14
'', // 15 unused
'', // 16 unused
'', // 17 unused
'', // 18 unused
'', // 19 unused
`Calling a 'use*()' method outside 'component$(() => { HERE })' is not allowed. 'use*()' methods provide hooks to the 'component$' state and lifecycle, ie 'use' hooks can only be called synchronously within the 'component$' function or another 'use' method.\nSee https://qwik.dev/docs/components/tasks/#use-method-rules`, // 20
'', // 21 unused
'', // 22 unused
'', // 23 unused
'', // 24 unused
'', // 25 unused
'', // 26 unused
'', // 27 unused
'The provided Context reference "{{0}}" is not a valid context created by createContextId()', // 28
'SsrError(tag): {{0}}', // 29
'QRLs can not be resolved because it does not have an attached container. This means that the QRL does not know where it belongs inside the DOM, so it cant dynamically import() from a relative path.', // 30
'QRLs can not be dynamically resolved, because it does not have a chunk path', // 31
'{{0}}\nThe JSX ref attribute must be a Signal', // 32
'Serialization Error: Deserialization of data type {{0}} is not implemented', // 33
'Serialization Error: Expected vnode for ref prop, but got {{0}}', // 34
'Serialization Error: Cannot allocate data type {{0}}', // 35
'Serialization Error: Missing root id for {{0}}', // 36
'Serialization Error: Serialization of data type {{0}} is not implemented', // 37
'Serialization Error: Unvisited {{0}}', // 38
'Serialization Error: Missing QRL chunk for {{0}}', // 39
'{{0}}\nThe value of the textarea must be a string found {{1}}', // 40
'Unable to find q:container', // 41
"Element must have 'q:container' attribute.", // 42
'Unknown vnode type {{0}}.', // 43
'Materialize error: missing element: {{0}} {{1}} {{2}}', // 44
'Cannot coerce a Signal, use `.value` instead', // 45
'useComputedSignal$ QRL {{0}} {{1}} returned a Promise', // 46
'ComputedSignal is read-only', // 47
'WrappedSignal is read-only', // 48
'Attribute value is unsafe for SSR', // 49
'SerializerSymbol function returned rejected promise', // 50
];
let text = MAP[code] ?? '';
if (parts.length) {
text = text.replaceAll(/{{(\d+)}}/g, (_, index) => {
let v = parts[index];
if (v && typeof v === 'object' && v.constructor === Object) {
v = JSON.stringify(v).slice(0, 50);
}
return v;
});
}
return `Code(Q${code}): ${text}`;
}
else {
// cute little hack to give roughly the correct line number. Update the line number if it shifts.
return `Code(Q${code}) https://github.com/QwikDev/qwik/blob/main/packages/qwik/src/core/error/error.ts#L${8 + code}`;
}
};
const qError = (code, errorMessageArgs = []) => {
const text = codeToText(code, ...errorMessageArgs);
return logErrorAndStop(text, ...errorMessageArgs);
};
/** QRL related utilities that you can import without importing all of Qwik. */
const SYNC_QRL = '<sync>';
/** Sync QRL is a function which is serialized into `<script q:func="qwik/json">` tag. */
const isSyncQrl = (value) => {
return isQrl$1(value) && value.$symbol$ == SYNC_QRL;
};
const isQrl$1 = (value) => {
return typeof value === 'function' && typeof value.getSymbol === 'function';
};
function assertQrl(qrl) {
if (isDev) {
if (!isQrl$1(qrl)) {
throw new Error('Not a QRL');
}
}
}
const getSymbolHash = (symbolName) => {
const index = symbolName.lastIndexOf('_');
if (index > -1) {
return symbolName.slice(index + 1);
}
return symbolName;
};
/** State factory of the component. */
const OnRenderProp = 'q:renderFn';
/** Component style content prefix */
const ComponentStylesPrefixContent = '⚡️';
/** `<some-element q:slot="...">` */
const QSlot = 'q:slot';
const QSlotParent = 'q:sparent';
const QSlotS = 'q:s';
const QStyle = 'q:style';
const QStyleSelector = 'style[q\\:style]';
const QStyleSSelector = 'style[q\\:sstyle]';
const QStylesAllSelector = QStyleSelector + ',' + QStyleSSelector;
const QScopedStyle = 'q:sstyle';
const QCtxAttr = 'q:ctx';
const QBackRefs = 'q:brefs';
const QFuncsPrefix = 'qFuncs_';
const getQFuncs = (document, hash) => {
return document[QFuncsPrefix + hash] || [];
};
const QBaseAttr = 'q:base';
const QLocaleAttr = 'q:locale';
const QManifestHashAttr = 'q:manifest-hash';
const QInstanceAttr = 'q:instance';
const QContainerIsland = 'q:container-island';
const QContainerIslandEnd = '/' + QContainerIsland;
const QIgnore = 'q:ignore';
const QIgnoreEnd = '/' + QIgnore;
const QContainerAttr = 'q:container';
const QContainerAttrEnd = '/' + QContainerAttr;
const QTemplate = 'q:template';
// the same selector should be inside the qwik loader
// and the same selector should be inside the qwik router spa-shim and spa-init
const QContainerSelector = '[q\\:container]:not([q\\:container=' +
"html" /* QContainerValue.HTML */ +
']):not([q\\:container=' +
"text" /* QContainerValue.TEXT */ +
'])';
const HTML_NS = 'http://www.w3.org/1999/xhtml';
const SVG_NS = 'http://www.w3.org/2000/svg';
const MATH_NS = 'http://www.w3.org/1998/Math/MathML';
const ResourceEvent = 'qResource';
const RenderEvent = 'qRender';
const TaskEvent = 'qTask';
/** `<q:slot name="...">` */
const QDefaultSlot = '';
/**
* Attribute to mark that this VNode has a pointer to itself from the `qwik/json` state.
*
* As the VNode get materialized the vnode now becomes eligible for mutation. Once the vnode mutates
* the `VNode` references from the `qwik/json` may become invalid. For this reason, these references
* need to be eagerly resolved. `VNODE_REF` stores a pointer to "this" vnode. This allows the system
* to eagerly resolve these pointes as the vnodes are materialized.
*/
const ELEMENT_ID = 'q:id';
const ELEMENT_KEY = 'q:key';
const ELEMENT_PROPS = 'q:props';
const ELEMENT_SEQ = 'q:seq';
const ELEMENT_SEQ_IDX = 'q:seqIdx';
const Q_PREFIX = 'q:';
/** Non serializable markers - always begins with `:` character */
const NON_SERIALIZABLE_MARKER_PREFIX = ':';
const USE_ON_LOCAL = NON_SERIALIZABLE_MARKER_PREFIX + 'on';
const USE_ON_LOCAL_SEQ_IDX = NON_SERIALIZABLE_MARKER_PREFIX + 'onIdx';
const USE_ON_LOCAL_FLAGS = NON_SERIALIZABLE_MARKER_PREFIX + 'onFlags';
// comment nodes
const FLUSH_COMMENT = 'qkssr-f';
const STREAM_BLOCK_START_COMMENT = 'qkssr-pu';
const STREAM_BLOCK_END_COMMENT = 'qkssr-po';
const Q_PROPS_SEPARATOR = ':';
const dangerouslySetInnerHTML = 'dangerouslySetInnerHTML';
const qwikInspectorAttr = 'data-qwik-inspector';
// keep this import from core/build so the cjs build works
const createPlatform = () => {
return {
isServer,
importSymbol(containerEl, url, symbolName) {
if (isServer) {
const hash = getSymbolHash(symbolName);
const regSym = globalThis.__qwik_reg_symbols?.get(hash);
if (regSym) {
return regSym;
}
}
if (!url) {
throw qError(31 /* QError.qrlMissingChunk */, [symbolName]);
}
if (!containerEl) {
throw qError(30 /* QError.qrlMissingContainer */, [url, symbolName]);
}
const urlDoc = toUrl(containerEl.ownerDocument, containerEl, url).toString();
const urlCopy = new URL(urlDoc);
urlCopy.hash = '';
const importURL = urlCopy.href;
return import(/* @vite-ignore */ importURL).then((mod) => {
return mod[symbolName];
});
},
raf: (fn) => {
return new Promise((resolve) => {
requestAnimationFrame(() => {
resolve(fn());
});
});
},
nextTick: (fn) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(fn());
});
});
},
chunkForSymbol(symbolName, chunk) {
return [symbolName, chunk ?? '_'];
},
};
};
/**
* Convert relative base URI and relative URL into a fully qualified URL.
*
* @param base -`QRL`s are relative, and therefore they need a base for resolution.
*
* - `Element` use `base.ownerDocument.baseURI`
* - `Document` use `base.baseURI`
* - `string` use `base` as is
* - `QConfig` use `base.baseURI`
*
* @param url - Relative URL
* @returns Fully qualified URL.
*/
const toUrl = (doc, containerEl, url) => {
const baseURI = doc.baseURI;
const base = new URL(containerEl.getAttribute(QBaseAttr) ?? baseURI, baseURI);
return new URL(url, base);
};
let _platform = /*#__PURE__ */ createPlatform();
// <docs markdown="./readme.md#setPlatform">
// !!DO NOT EDIT THIS COMMENT DIRECTLY!!!
// (edit ./readme.md#setPlatform instead and run `pnpm docs.sync`)
/**
* Sets the `CorePlatform`.
*
* This is useful to override the platform in tests to change the behavior of,
* `requestAnimationFrame`, and import resolution.
*
* @param doc - The document of the application for which the platform is needed.
* @param platform - The platform to use.
* @public
*/
// </docs>
const setPlatform = (plt) => (_platform = plt);
// <docs markdown="./readme.md#getPlatform">
// !!DO NOT EDIT THIS COMMENT DIRECTLY!!!
// (edit ./readme.md#getPlatform instead and run `pnpm docs.sync`)
/**
* Retrieve the `CorePlatform`.
*
* The `CorePlatform` is also responsible for retrieving the Manifest, that contains mappings from
* symbols to javascript import chunks. For this reason, `CorePlatform` can't be global, but is
* specific to the application currently running. On server it is possible that many different
* applications are running in a single server instance, and for this reason the `CorePlatform` is
* associated with the application document.
*
* @param docOrNode - The document (or node) of the application for which the platform is needed.
* @public
*/
// </docs>
const getPlatform = () => {
return _platform;
};
const isServerPlatform = () => {
if (qDynamicPlatform) {
return _platform.isServer;
}
return false;
};
const isNode = (value) => {
return value && typeof value.nodeType === 'number';
};
const isDocument = (value) => {
return value.nodeType === 9;
};
const isElement$1 = (value) => {
return value.nodeType === 1;
};
const MAX_RETRY_ON_PROMISE_COUNT = 100;
const isPromise = (value) => {
// not using "value instanceof Promise" to have zone.js support
return !!value && typeof value == 'object' && typeof value.then === 'function';
};
const safeCall = (call, thenFn, rejectFn) => {
try {
const result = call();
if (isPromise(result)) {
return result.then(thenFn, rejectFn);
}
else {
return thenFn(result);
}
}
catch (e) {
return rejectFn(e);
}
};
const maybeThen = (valueOrPromise, thenFn) => {
return isPromise(valueOrPromise)
? valueOrPromise.then(thenFn, shouldNotError)
: thenFn(valueOrPromise);
};
const shouldNotError = (reason) => {
throwErrorAndStop(reason);
};
const delay = (timeout) => {
return new Promise((resolve) => {
setTimeout(resolve, timeout);
});
};
// Retries a function that throws a promise.
function retryOnPromise(fn, retryCount = 0) {
const retryOrThrow = (e) => {
if (isPromise(e) && retryCount < MAX_RETRY_ON_PROMISE_COUNT) {
return e.then(retryOnPromise.bind(null, fn, retryCount++));
}
throw e;
};
try {
const result = fn();
if (isPromise(result)) {
// not awaited promise is not caught by try/catch block
return result.catch((e) => retryOrThrow(e));
}
return result;
}
catch (e) {
return retryOrThrow(e);
}
}
/** @private */
const isSerializableObject = (v) => {
const proto = Object.getPrototypeOf(v);
return proto === Object.prototype || proto === Array.prototype || proto === null;
};
const isObject = (v) => {
return !!v && typeof v === 'object';
};
const isArray = (v) => {
return Array.isArray(v);
};
const isString = (v) => {
return typeof v === 'string';
};
const isFunction = (v) => {
return typeof v === 'function';
};
const isQrl = (value) => {
return typeof value === 'function' && typeof value.getSymbol === 'function';
};
/** @internal */
const EMPTY_ARRAY = [];
const EMPTY_OBJ = {};
Object.freeze(EMPTY_ARRAY);
Object.freeze(EMPTY_OBJ);
// https://regexr.com/68v72
// @ts-expect-error this is a valid regex
const EXTRACT_IMPORT_PATH = /\(\s*(['"])([^\1]+)\1\s*\)/;
// https://regexr.com/690ds
const EXTRACT_SELF_IMPORT = /Promise\s*\.\s*resolve/;
// https://regexr.com/6a83h
const EXTRACT_FILE_NAME = /[\\/(]([\w\d.\-_]+\.(js|ts)x?):/;
const announcedQRL = /*#__PURE__*/ new Set();
// <docs markdown="../../readme.md#qrl">
// !!DO NOT EDIT THIS COMMENT DIRECTLY!!!
// (edit ../../readme.md#qrl instead and run `pnpm docs.sync`)
/**
* Used by Qwik Optimizer to point to lazy-loaded resources.
*
* This function should be used by the Qwik Optimizer only. The function should not be directly
* referred to in the source code of the application.
*
* @param chunkOrFn - Chunk name (or function which is stringified to extract chunk name)
* @param symbol - Symbol to lazy load
* @param lexicalScopeCapture - A set of lexically scoped variables to capture.
* @public
* @see `QRL`, `$(...)`
*/
// </docs>
const qrl = (chunkOrFn, symbol, lexicalScopeCapture = EMPTY_ARRAY, stackOffset = 0) => {
let chunk = null;
let symbolFn = null;
if (isFunction(chunkOrFn)) {
symbolFn = chunkOrFn;
if (qSerialize) {
let match;
const srcCode = String(chunkOrFn);
if ((match = srcCode.match(EXTRACT_IMPORT_PATH)) && match[2]) {
chunk = match[2];
}
else if ((match = srcCode.match(EXTRACT_SELF_IMPORT))) {
const ref = 'QWIK-SELF';
const frames = new Error(ref).stack.split('\n');
const start = frames.findIndex((f) => f.includes(ref));
const frame = frames[start + 2 + stackOffset];
match = frame.match(EXTRACT_FILE_NAME);
if (!match) {
chunk = 'main';
}
else {
chunk = match[1];
}
}
else {
throw qError(11 /* QError.dynamicImportFailed */, [srcCode]);
}
}
}
else if (isString(chunkOrFn)) {
chunk = chunkOrFn;
}
else {
throw qError(12 /* QError.unknownTypeArgument */, [chunkOrFn]);
}
if (!announcedQRL.has(symbol)) {
// Emit event
announcedQRL.add(symbol);
emitEvent('qprefetch', {
symbols: [symbol],
bundles: chunk && [chunk],
});
}
// Unwrap subscribers
return createQRL(chunk, symbol, null, symbolFn, null, lexicalScopeCapture);
};
/** @internal */
const inlinedQrl = (symbol, symbolName, lexicalScopeCapture = EMPTY_ARRAY) => {
// Unwrap subscribers
return createQRL(null, symbolName, symbol, null, null, lexicalScopeCapture);
};
/** @internal */
const _noopQrl = (symbolName, lexicalScopeCapture = EMPTY_ARRAY) => {
return createQRL(null, symbolName, null, null, null, lexicalScopeCapture);
};
/** @internal */
const _noopQrlDEV = (symbolName, opts, lexicalScopeCapture = EMPTY_ARRAY) => {
const newQrl = _noopQrl(symbolName, lexicalScopeCapture);
newQrl.dev = opts;
return newQrl;
};
/** @internal */
const qrlDEV = (chunkOrFn, symbol, opts, lexicalScopeCapture = EMPTY_ARRAY) => {
const newQrl = qrl(chunkOrFn, symbol, lexicalScopeCapture, 1);
newQrl.dev = opts;
return newQrl;
};
/** @internal */
const inlinedQrlDEV = (symbol, symbolName, opts, lexicalScopeCapture = EMPTY_ARRAY) => {
const qrl = inlinedQrl(symbol, symbolName, lexicalScopeCapture);
qrl.dev = opts;
return qrl;
};
/** @internal */
const _regSymbol = (symbol, hash) => {
if (typeof globalThis.__qwik_reg_symbols === 'undefined') {
globalThis.__qwik_reg_symbols = new Map();
}
globalThis.__qwik_reg_symbols.set(hash, symbol);
return symbol;
};
let _locale = undefined;
/**
* Retrieve the current locale.
*
* If no current locale and there is no `defaultLocale` the function throws an error.
*
* @returns The locale.
* @public
*/
function getLocale(defaultLocale) {
if (_locale === undefined) {
const ctx = tryGetInvokeContext();
if (ctx && ctx.$locale$) {
return ctx.$locale$;
}
if (defaultLocale !== undefined) {
return defaultLocale;
}
throw new Error('Reading `locale` outside of context.');
}
return _locale;
}
/**
* Override the `getLocale` with `lang` within the `fn` execution.
*
* @public
*/
function withLocale(locale, fn) {
const previousLang = _locale;
try {
_locale = locale;
return fn();
}
finally {
_locale = previousLang;
}
}
/**
* Globally set a lang.
*
* This can be used only in browser. Server execution requires that each request could potentially
* be a different lang, therefore setting a global lang would produce incorrect responses.
*
* @public
*/
function setLocale(locale) {
_locale = locale;
}
/**
* @internal
* The storage provider for hooks. Each invocation increases index i. Data is stored in an array.
*/
const useSequentialScope = () => {
const iCtx = useInvokeContext();
const hostElement = iCtx.$hostElement$;
const host = hostElement;
let seq = iCtx.$container$.getHostProp(host, ELEMENT_SEQ);
if (seq === null) {
seq = [];
iCtx.$container$.setHostProp(host, ELEMENT_SEQ, seq);
}
let seqIdx = iCtx.$container$.getHostProp(host, ELEMENT_SEQ_IDX);
if (seqIdx === null) {
seqIdx = 0;
}
iCtx.$container$.setHostProp(host, ELEMENT_SEQ_IDX, seqIdx + 1);
while (seq.length <= seqIdx) {
seq.push(undefined);
}
const set = (value) => {
if (qDev && qSerialize) {
verifySerializable(value);
}
return (seq[seqIdx] = value);
};
return {
val: seq[seqIdx],
set,
i: seqIdx,
iCtx,
};
};
/**
* Think of `-` as an escape character which makes the next character uppercase. `--` is just `-`.
*
* Rules for JSX property event names starting with `on`:
*
* - Are case insensitive: `onClick$` is same `onclick$`
* - A `--` is `-`: `dbl--click` => `dbl-click`
* - Become case sensitive if prefixed by `-`: `-Click` is `Click`
* - A `-` (not at the beginning) makes next character uppercase: `dbl-click` => `dblClick`
*/
const EVENT_SUFFIX = '$';
const DOMContentLoadedEvent = 'DOMContentLoaded';
const isJsxPropertyAnEventName = (name) => {
return ((name.startsWith("on" /* EventNameJSXScope.on */) ||
name.startsWith("window:on" /* EventNameJSXScope.window */) ||
name.startsWith("document:on" /* EventNameJSXScope.document */)) &&
name.endsWith(EVENT_SUFFIX));
};
const isHtmlAttributeAnEventName = (name) => {
return (name.startsWith("on:" /* EventNameHtmlScope.on */) ||
name.startsWith("on-window:" /* EventNameHtmlScope.window */) ||
name.startsWith("on-document:" /* EventNameHtmlScope.document */));
};
/**
* Converts a JSX event property to an HTML attribute. Examples:
*
* - OnClick$ -> on:click
* - On-DOMContentLoaded$ -> on:-d-o-m-content-loaded
* - On-CustomEvent$ -> on:-custom-event
*/
function jsxEventToHtmlAttribute(jsxEvent) {
if (jsxEvent.endsWith(EVENT_SUFFIX)) {
const [prefix, idx] = getEventScopeDataFromJsxEvent(jsxEvent);
if (idx !== -1) {
const eventName = getEventNameFromJsxEvent(jsxEvent);
return prefix + fromCamelToKebabCase(eventName);
}
}
return null; // Return null if not matching expected format
}
function eventNameToJsxEvent(eventName, prefix, startIdx = 0) {
eventName = eventName.charAt(0).toUpperCase() + eventName.substring(1);
return prefix + eventName + EVENT_SUFFIX;
}
/**
* Gets the event name from a JSX event property. Examples:
*
* - OnClick$ -> click
* - OnDOMContentLoaded$ -> DOMContentLoaded
* - On-CustomEvent$ -> CustomEvent
*/
function getEventNameFromJsxEvent(jsxEvent) {
if (jsxEvent.endsWith(EVENT_SUFFIX)) {
const [, idx] = getEventScopeDataFromJsxEvent(jsxEvent);
if (idx != -1) {
return jsxEventToEventName(jsxEvent, idx);
}
}
return null;
}
function jsxEventToEventName(jsxEvent, startIdx = 0) {
const idx = startIdx;
let lastIdx = idx;
const isCaseSensitive = isDash(jsxEvent.charCodeAt(idx));
if (isCaseSensitive) {
lastIdx++;
}
let eventName = '';
const chunk = jsxEvent.substring(lastIdx, jsxEvent.length - 1 /* don't include `$` */);
if (chunk === DOMContentLoadedEvent) {
return DOMContentLoadedEvent;
}
eventName += isCaseSensitive ? chunk : chunk.toLowerCase();
return eventName;
}
function getEventScopeDataFromJsxEvent(eventName) {
let prefix = null;
let idx = -1;
// set prefix and idx based on the scope
if (eventName.startsWith("on" /* EventNameJSXScope.on */)) {
prefix = "on:" /* EventNameHtmlScope.on */;
idx = 2;
}
else if (eventName.startsWith("window:on" /* EventNameJSXScope.window */)) {
prefix = "on-window:" /* EventNameHtmlScope.window */;
idx = 9;
}
else if (eventName.startsWith("document:on" /* EventNameJSXScope.document */)) {
prefix = "on-document:" /* EventNameHtmlScope.document */;
idx = 11;
}
return [prefix, idx];
}
const isDash = (charCode) => charCode === 45; /* - */
const getEventNameScopeFromJsxEvent = (name) => {
const index = name.indexOf(':');
return index !== -1 ? name.substring(0, index) : '';
};
function isPreventDefault(key) {
return key.startsWith('preventdefault:');
}
const fromCamelToKebabCase = (text) => {
return text.replace(/([A-Z-])/g, '-$1').toLowerCase();
};
// <docs markdown="../readme.md#createContextId">
// !!DO NOT EDIT THIS COMMENT DIRECTLY!!!
// (edit ../readme.md#createContextId instead and run `pnpm docs.sync`)
/**
* Create a context ID to be used in your application. The name should be written with no spaces.
*
* Context is a way to pass stores to the child components without prop-drilling.
*
* Use `createContextId()` to create a `ContextId`. A `ContextId` is just a serializable identifier
* for the context. It is not the context value itself. See `useContextProvider()` and
* `useContext()` for the values. Qwik needs a serializable ID for the context so that the it can
* track context providers and consumers in a way that survives resumability.
*
* ### Example
*
* ```tsx
* // Declare the Context type.
* interface TodosStore {
* items: string[];
* }
* // Create a Context ID (no data is saved here.)
* // You will use this ID to both create and retrieve the Context.
* export const TodosContext = createContextId<TodosStore>('Todos');
*
* // Example of providing context to child components.
* export const App = component$(() => {
* useContextProvider(
* TodosContext,
* useStore<TodosStore>({
* items: ['Learn Qwik', 'Build Qwik app', 'Profit'],
* })
* );
*
* return <Items />;
* });
*
* // Example of retrieving the context provided by a parent component.
* export const Items = component$(() => {
* const todos = useContext(TodosContext);
* return (
* <ul>
* {todos.items.map((item) => (
* <li>{item}</li>
* ))}
* </ul>
* );
* });
*
* ```
*
* @param name - The name of the context.
* @public
*/
// </docs>
const createContextId = (name) => {
assertTrue(/^[\w/.-]+$/.test(name), 'Context name must only contain A-Z,a-z,0-9, _', name);
return /*#__PURE__*/ Object.freeze({
id: fromCamelToKebabCase(name),
});
};
// <docs markdown="../readme.md#useContextProvider">
// !!DO NOT EDIT THIS COMMENT DIRECTLY!!!
// (edit ../readme.md#useContextProvider instead and run `pnpm docs.sync`)
/**
* Assign a value to a Context.
*
* Use `useContextProvider()` to assign a value to a context. The assignment happens in the
* component's function. Once assigned, use `useContext()` in any child component to retrieve the
* value.
*
* Context is a way to pass stores to the child components without prop-drilling. Note that scalar
* values are allowed, but for reactivity you need signals or stores.
*
* ### Example
*
* ```tsx
* // Declare the Context type.
* interface TodosStore {
* items: string[];
* }
* // Create a Context ID (no data is saved here.)
* // You will use this ID to both create and retrieve the Context.
* export const TodosContext = createContextId<TodosStore>('Todos');
*
* // Example of providing context to child components.
* export const App = component$(() => {
* useContextProvider(
* TodosContext,
* useStore<TodosStore>({
* items: ['Learn Qwik', 'Build Qwik app', 'Profit'],
* })
* );
*
* return <Items />;
* });
*
* // Example of retrieving the context provided by a parent component.
* export const Items = component$(() => {
* const todos = useContext(TodosContext);
* return (
* <ul>
* {todos.items.map((item) => (
* <li>{item}</li>
* ))}
* </ul>
* );
* });
*
* ```
*
* @param context - The context to assign a value to.
* @param value - The value to assign to the context.
* @public
*/
// </docs>
const useContextProvider = (context, newValue) => {
const { val, set, iCtx } = useSequentialScope();
if (val !== undefined) {
return;
}
if (qDev) {
validateContext(context);
}
if (qDev && qSerialize) {
verifySerializable(newValue);
}
iCtx.$container$.setContext(iCtx.$hostElement$, context, newValue);
set(1);
};
// <docs markdown="../readme.md#useContext">
// !!DO NOT EDIT THIS COMMENT DIRECTLY!!!
// (edit ../readme.md#useContext instead and run `pnpm docs.sync`)
/**
* Retrieve Context value.
*
* Use `useContext()` to retrieve the value of context in a component. To retrieve a value a parent
* component needs to invoke `useContextProvider()` to assign a value.
*
* ### Example
*
* ```tsx
* // Declare the Context type.
* interface TodosStore {
* items: string[];
* }
* // Create a Context ID (no data is saved here.)
* // You will use this ID to both create and retrieve the Context.
* export const TodosContext = createContextId<TodosStore>('Todos');
*
* // Example of providing context to child components.
* export const App = component$(() => {
* useContextProvider(
* TodosContext,
* useStore<TodosStore>({
* items: ['Learn Qwik', 'Build Qwik app', 'Profit'],
* })
* );
*
* return <Items />;
* });
*
* // Example of retrieving the context provided by a parent component.
* export const Items = component$(() => {
* const todos = useContext(TodosContext);
* return (
* <ul>
* {todos.items.map((item) => (
* <li>{item}</li>
* ))}
* </ul>
* );
* });
*
* ```
*
* @param context - The context to retrieve a value from.
* @public
*/
// </docs>
const useContext = (context, defaultValue) => {
const { val, set, iCtx } = useSequentialScope();
if (val !== undefined) {
return val;
}
if (qDev) {
validateContext(context);
}
const value = iCtx.$container$.resolveContext(iCtx.$hostElement$, context);
if (typeof defaultValue === 'function') {
return set(invoke(undefined, defaultValue, value));
}
if (value !== undefined) {
return set(value);
}
if (defaultValue !== undefined) {
return set(defaultValue);
}
throw qError(13 /* QError.notFoundContext */, [context.id]);
};
const validateContext = (context) => {
if (!isObject(context) || typeof context.id !== 'string' || context.id.length === 0) {
throw qError(28 /* QError.invalidContext */, [context]);
}
};
const ERROR_CONTEXT = /*#__PURE__*/ createContextId('qk-error');
const isRecoverable = (err) => {
if (err && err instanceof Error) {
if ('plugin' in err) {
return false;
}
}
return true;
};
/**
* QWIK_VERSION
*
* @public
*/
const version = "2.0.0-alpha.9-dev+56ed5bd";
/**
* Allows to project the children of the current component. <Slot/> can only be used within the
* context of a component defined with `component$`.
*
* @public
*/
const Slot = (props) => {
return _jsxSorted(Virtual, null, { [QSlotS]: '' }, props.children, 0, props.name ?? '');
};
/** @public */
const SkipRender = Symbol('skip render');
/** @public */
const SSRRaw = () => null;
/** @public */
const SSRComment = () => null;
/** @public */
const SSRStreamBlock = (props) => {
return [
jsx(SSRComment, { data: STREAM_BLOCK_START_COMMENT }),
props.children,
jsx(SSRComment, { data: STREAM_BLOCK_END_COMMENT }),
];
};
/** @public */
const SSRStream = (props, key) => jsx(RenderOnce, { children: jsx(InternalSSRStream, props) }, key);
const InternalSSRStream = () => null;
/**
* Special value used to mark that a given signal needs to be computed. This is essentially a
* "marked as dirty" flag.
*/
const NEEDS_COMPUTATION = Symbol('invalid');
/** @internal */
const _EFFECT_BACK_REF = Symbol('backRef');
function getSubscriber(effect, prop, data) {
if (!effect[_EFFECT_BACK_REF]) {
if (isServer && isSsrNode(effect)) {
effect.setProp(QBackRefs, new Map());
}
else {
effect[_EFFECT_BACK_REF] = new Map();
}
}
const subMap = effect[_EFFECT_BACK_REF];
let sub = subMap.get(prop);
if (!sub) {
sub = [effect, prop];
subMap.set(prop, sub);
}
if (data) {
sub[3 /* EffectSubscriptionProp.DATA */] = data;
}
return sub;
}
function isSsrNode(value) {
return '__brand__' in value && 'currentComponentNode' in value;
}
/**
* @file
*
* Signals come in two types:
*
* 1. `Signal` - A storage of data
* 2. `ComputedSignal` - A signal which is computed from other signals.
*
* ## Why is `ComputedSignal` different?
*
* - It needs to store a function which needs to re-run.
* - It is `Readonly` because it is computed.
*/
const DEBUG$1 = false;
// eslint-disable-next-line no-console
const log = (...args) => console.log('SIGNAL', ...args.map(qwikDebugToString));
const throwIfQRLNotResolved = (qrl) => {
const resolved = qrl.resolved;
if (!resolved) {
// When we are creating a signal using a use method, we need to ensure
// that the computation can be lazy and therefore we need to unsure
// that the QRL is resolved.
// When we re-create the signal from serialization (we don't create the signal
// using useMethod) it is OK to not resolve it until the graph is marked as dirty.
throw qrl.resolve();
}
};
/** @public */
const isSignal = (value) => {
return value instanceof SignalImpl;
};
/** @internal */
class SubscriptionData {
data;
constructor(data) {
this.data = data;
}
}
class SignalImpl {
$untrackedValue$;
/** Store a list of effects which are dependent on this signal. */
$effects$ = null;
$container$ = null;
constructor(container, value) {
this.$container$ = container;
this.$untrackedValue$ = value;
}
get untrackedValue() {
return this.$untrackedValue$;
}
// TODO: should we disallow setting the value directly?
set untrackedValue(value) {
this.$untrackedValue$ = value;
}
get value() {
const ctx = tryGetInvokeContext();
if (ctx) {
if (this.$container$ === null) {
if (!ctx.$container$) {
return this.untrackedValue;
}
// Grab the container now we have access to it
this.$container$ = ctx.$container$;
}
else {
assertTrue(!ctx.$container$ || ctx.$container$ === this.$container$, 'Do not use signals across containers');
}
const effectSubscriber = ctx.$effectSubscriber$;
if (effectSubscriber) {
const effects = (this.$effects$ ||= new Set());
// Let's make sure that we have a reference to this effect.
// Adding reference is essentially adding a subscription, so if the signal
// changes we know who to notify.
ensureContainsSubscription(effects, effectSubscriber);
// But when effect is scheduled in needs to be able to know which signals
// to unsubscribe from. So we need to store the reference from the effect back
// to this signal.
ensureContainsBackRef(effectSubscriber, this);
addQrlToSerializationCtx(effectSubscriber, this.$container$);
}
}
return this.untrackedValue;
}
set value(value) {
if (value !== this.$untrackedValue$) {
this.$untrackedValue$ = value;
triggerEffects(this.$container$, this, this.$effects$);
}
}
// prevent accidental use as value
valueOf() {
if (qDev) {
throw qError(45 /* QError.cannotCoerceSignal */);
}
}
toString() {
if (isDev) {
return (`[${this.constructor.name}${this.$flags$ & 1 /* SignalFlags.INVALID */ ? ' INVALID' : ''} ${String(this.$untrackedValue$)}]` +
(Array.from(this.$effects$ || [])
.map((e) => '\n -> ' + pad(qwikDebugToString(e[0]), ' '))
.join('\n') || ''));
}
else {
return this.constructor.name;
}
}
toJSON() {
return { value: this.$untrackedValue$ };
}
}
const ensureContainsSubscription = (array, effectSubscription) => {
array.add(effectSubscription);
};
/** Ensure the item is in back refs set */
const ensureContainsBackRef = (array, value) => {
array[2 /* EffectSubscriptionProp.BACK_REF */] ||= new Set();
array[2 /* EffectSubscriptionProp.BACK_REF */].add(value);
};
const addQrlToSerializationCtx = (effectSubscriber, container) => {
if (!!container && !isDomContainer(container)) {
const effect = effectSubscriber[0 /* EffectSubscriptionProp.CONSUMER */];
const property = effectSubscriber[1 /* EffectSubscriptionProp.PROPERTY */];
let qrl = null;
if (isTask(effect)) {
qrl = effect.$qrl$;
}
else if (effect instanceof ComputedSignalImpl) {
qrl = effect.$computeQrl$;
}
else if (property === ":" /* EffectProperty.COMPONENT */) {
qrl = container.getHostProp(effect, OnRenderProp);
}
if (qrl) {
container.serializationCtx.$eventQrls$.add(qrl);
}
}
};
const triggerEffects = (container, signal, effects) => {
const isBrowser = isDomContainer(container);
if (effects) {
const scheduleEffect = (effectSubscription) => {
const consumer = effectSubscription[0 /* EffectSubscriptionProp.CONSUMER */];
const property = effectSubscription[1 /* EffectSubscriptionProp.PROPERTY */];
assertDefined(container, 'Container must be defined.');
if (isTask(consumer)) {
consumer.$flags$ |= 8 /* TaskFlags.DIRTY */;
let choreType = 3 /* ChoreType.TASK */;
if (consumer.$flags$ & 1 /* TaskFlags.VISIBLE_TASK */) {
choreType = 32 /* ChoreType.VISIBLE */;
}
container.$scheduler$(choreType, consumer);
}
else if (consumer instanceof SignalImpl) {
// we don't schedule ComputedSignal/DerivedSignal directly, instead we invalidate it and
// and schedule the signals effects (recursively)
if (consumer instanceof ComputedSignalImpl) {
// Ensure that the computed signal's QRL is resolved.
// If not resolved schedule it to be resolved.
if (!consumer.$computeQrl$.resolved) {
container.$scheduler$(1 /* ChoreType.QRL_RESOLVE */, null, consumer.$computeQrl$);
}
}
consumer.$invalidate$();
}
else if (property === ":" /* EffectProperty.COMPONENT */) {
const host = consumer;
const qrl = container.getHostProp(host, OnRenderProp);
assertDefined(qrl, 'Component must have QRL');
const props = container.getHostProp(host, ELEMENT_PROPS);
container.$scheduler$(6 /* ChoreType.COMPONENT */, host, qrl, props);
}
else if (isBrowser) {
if (property === "." /* EffectProperty.VNODE */) {
const host = consumer;
container.$scheduler$(4 /* ChoreType.NODE_DIFF */, host, host, signal);
}
else {
const host = consumer;
const effectData = effectSubscription[3 /* EffectSubscriptionProp.DATA */];
if (effectData instanceof SubscriptionData) {
const data = effectData.data;
const payload = {
...data,
$value$: signal,
};
container.$scheduler$(5 /* ChoreType.NODE_PROP */, host, property, payload);
}
}
}
};
for (const effect of effects) {
scheduleEffect(effect);
}
}
};
/**
* A signal which is computed from other signals.
*
* The value is available synchronously, but the computation is done lazily.
*/
class ComputedSignalImpl extends SignalImpl {
/**
* The compute function is stored here.
*
* The computed functions must be executed synchronously (because of this we need to eagerly
* resolve the QRL during the mark dirty phase so that any call to it will be synchronous). )
*/
$computeQrl$;
$flags$;
$forceRunEffects$ = false;
[_EFFECT_BACK_REF] = null;
constructor(container, fn,
// We need a separate flag to know when the computation needs running because
// we need the old value to know if effects need running after computation
flags = 1 /* SignalFlags.INVALID */) {
// The value is used for comparison when signals trigger, which can only happen
// when it was calculated before. Therefore we can pass whatever we like.
super(container, NEEDS_COMPUTATION);
this.$computeQrl$ = fn;
this.$flags$ = flags;
}
$invalidate$() {
this.$flags$ |= 1 /* SignalFlags.INVALID */;
this.$forceRunEffects$ = false;
this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, this);
}
/**
* Use this to force running subscribers, for example when the calculated value has mutated but
* remained the same object
*/
force() {
this.$forceRunEffects$ = true;
this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, this);
}
get untrackedValue() {
const didChange = this.$computeIfNeeded$();
if (didChange) {
this.$forceRunEffects$ = didChange;
}
assertFalse(this.$untrackedValue$ === NEEDS_COMPUTATION, 'Invalid state');
return this.$untrackedValue$;
}
$computeIfNeeded$() {
if (!(this.$flags$ & 1 /* SignalFlags.INVALID */)) {
return false;
}
const computeQrl = this.$computeQrl$;
throwIfQRLNotResolved(computeQrl);
const ctx = tryGetInvokeContext();
const previousEffectSubscription = ctx?.$effectSubscriber$;
ctx && (ctx.$effectSubscriber$ = getSubscriber(this, "." /* EffectProperty.VNODE */));
try {
const untrackedValue = computeQrl.getFn(ctx)();
if (isPromise(untrackedValue)) {
throw qError(46 /* QError.computedNotSync */, [
computeQrl.dev ? computeQrl.dev.file : '',
computeQrl.$hash$,
]);
}
DEBUG$1 && log('Signal.$compute$', untrackedValue);
this.$flags$ &= ~1 /* SignalFlags.INVALID */;
const didChange = untrackedValue !== this.$untrackedValue$;
if (didChange) {
this.$untrackedValue$ = untrackedValue;
}
return didChange;
}
finally {
if (ctx) {
ctx.$effectSubscriber$ = previousEffectSubscription;
}
}
}
// Make this signal read-only
set value(_) {
throw qError(47 /* QError.computedReadOnly */);
}
// Getters don't get inherited when overriding a setter
get value() {
return super.value;
}
}
class WrappedSignal extends SignalImpl {
$args$;
$func$;
$funcStr$;
$flags$;
$hostElement$ = null;
$forceRunEffects$ = false;
[_EFFECT_BACK_REF] = null;
constructor(container, fn, args, fnStr,
// We need a separate flag to know when the computation needs running because
// we need the old value to know if effects need running after computation
flags = 1 /* SignalFlags.INVALID */ | 2 /* WrappedSignalFlags.UNWRAP */) {
super(container, NEEDS_COMPUTATION);
this.$args$ = args;
this.$func$ = fn;
this.$funcStr$ = fnStr;
this.$flags$ = flags;
}
$invalidate$() {
this.$flags$ |= 1 /* SignalFlags.INVALID */;
this.$forceRunEffects$ = false;
// We should only call subscribers if the calculation actually changed.
// Therefore, we need to calculate the value now.
this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, this.$hostElement$, this);
}
/**
* Use this to force running subscribers, for example when the calculated value has mutated but
* remained the same object.
*/
force() {
this.$flags$ |= 1 /* SignalFlags.INVALID */;
this.$forceRunEffects$ = false;
triggerEffects(this.$container$, this, this.$effects$);
}
get untrackedValue() {
const didChange = this.$computeIfNeeded$();
if (didChange) {
this.$forceRunEffects$ = didChange;
}
assertFalse(this.$untrackedValue$ === NEEDS_COMPUTATION, 'Invalid state');
return this.$untrackedValue$;
}
$computeIfNeeded$() {
if (!(this.$flags$ & 1 /* SignalFlags.INVALID */)) {
return false;
}
const untrackedValue = trackSignal(() => this.$func$(...this.$args$), this, "." /* EffectProperty.VNODE */, this.$container$);
// TODO: we should remove invalid flag here
// this.$flags$ &= ~SignalFlags.INVALID;
const didChange = untrackedValue !== this.$untrackedValue$;
if (didChange) {
this.$untrackedValue$ = untrackedValue;
}
return didChange;
}
// Make this signal read-only
set value(_) {
throw qError(48 /* QError.wrappedReadOnly */);
}
// Getters don't get inherited when overriding a setter
get value() {
return super.value;
}
}
/**
* A signal which provides a non-serializable value. It works like a computed signal, but it is
* handled slightly differently during serdes.
*
* @public
*/
class SerializerSignalImpl extends ComputedSignalImpl {
constructor(container, argQrl) {
super(container, argQrl);
}
$didInitialize$ = false;
$computeIfNeeded$() {
if (!(this.$flags$ & 1 /* SignalFlags.INVALID */)) {
return false;
}
throwIfQRLNotResolved(this.$computeQrl$);
let arg = this.$computeQrl$.resolved;
if (typeof arg === 'function') {
arg = arg();
}
const { deserialize, initial } = arg;
const update = arg.update;
const currentValue = this.$untrackedValue$ === N