Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
},
"devDependencies": {
"@builder.io/partytown": "^0.5.4",
"@builder.io/qwik": "0.0.20-5",
"@builder.io/qwik": "0.0.20-7",
"@builder.io/qwik-city": "0.0.5",
"@cloudflare/kv-asset-handler": "0.2.0",
"@cloudflare/workers-types": "^3.10.0",
Expand Down
32 changes: 32 additions & 0 deletions packages/docs/pages/docs/file.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
title: Overview
fetch: https://hackmd.io/@mhevery/Sy52N2Ax9
---

# File

Qwik is a new kind of web framework that can deliver instant loading web applications at any size or complexity. Your sites and apps can boot with less than 1kb of JS (_including_ your code, regardless of complexity), and achieve unheard of performance at scale.

## Qwik is:

- **General-purpose**: Qwik can be used to build any type of web site or application
- **Instant-on**: Unlike other frameworks, Qwik is [resumable](./concepts/resumable.mdx) which means Qwik applications require **0 hydration**. This allows Qwik apps to have instant-on interactivity, regardless of size or complexity
- **Optimized for speed**: Qwik has unprecedented performance, offering sub-second full page loads even on mobile devices. Qwik achieves this by delivering pure HTML, and incrementally loading JS only as-needed.

<img
alt="Qwik Diagram"
src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fd33c7a95e98144f682ab67dd27d1f957?format=webp&width=2000"
/>

## Does page speed really matter?

Put simply: slow sites deter visitors, costing businesses millions. Fast sites have better SEO, better UX, and are more profitable.

Some examples from [web.dev](https://web.dev):

| | |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Every 100ms faster → 1% more conversions** <br /> For Mobify, every 100ms decrease in homepage load speed worked out to a 1.11% increase in session-based conversion, yielding an average annual revenue increase of nearly $380,000. | **50% faster → 12% more sales** <br /> When AutoAnything reduced page load time by half, they saw a boost of 12% to 13% in sales. |
| **20% faster → 10% more conversions** <br /> Retailer Furniture Village audited their site speed and developed a plan to address the problems they found, leading to a 20% reduction in page load time and a 10% increase in conversion rate. | **40% faster → 15% more sign-ups** <br /> Pinterest reduced perceived wait times by 40% and this increased search engine traffic and sign-ups by 15%. |
| **850ms faster → 7% more conversions** <br /> COOK reduced average page load time by 850 milliseconds which increased conversions by 7%, decreased bounce rates by 7%, and increased pages per session by 10%. | **1 seconds slowness → 10% less users** <br /> The BBC found they lost an additional 10% of users for every additional second their site took to load. |
| | |
66 changes: 49 additions & 17 deletions packages/docs/src/components/repl/worker/update.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
import type { InputOptions, OutputAsset, OutputChunk } from 'rollup';
import type { QwikRollupPluginOptions } from '@builder.io/qwik/optimizer';
import type { Diagnostic, QwikRollupPluginOptions } from '@builder.io/qwik/optimizer';
import type { ReplInputOptions, ReplModuleOutput, ReplResult } from '../types';
import { getCtx, QwikReplContext } from './context';
import { loadDependencies } from './dependencies';
Expand Down Expand Up @@ -40,11 +40,13 @@ export const update = async (options: ReplInputOptions) => {
ctx.clientModules = result.clientModules;
} catch (e: any) {
result.diagnostics.push({
scope: 'runtime',
message: String(e.stack || e),
severity: 'Error',
origin: String(e.stack || 'repl error'),
code_highlights: [],
show_environment: false,
category: 'error',
file: '',
highlights: [],
suggestions: null,
code: 'runtime error',
});
console.error(e);
}
Expand Down Expand Up @@ -87,13 +89,28 @@ const bundleClient = async (
replMinify(options),
],
onwarn(warning) {
result.diagnostics.push({
const diagnostic: Diagnostic = {
scope: 'rollup-ssr',
code: warning.code ?? null,
message: warning.message,
severity: 'Error',
show_environment: false,
code_highlights: [],
origin: 'client rollup',
});
category: 'warning',
highlights: [],
file: warning.id || '',
suggestions: null,
};
const loc = warning.loc;
if (loc && loc.file) {
diagnostic.file = loc.file;
diagnostic.highlights.push({
startCol: loc.column,
endCol: loc.column + 1,
startLine: loc.line,
endLine: loc.line + 1,
lo: 0,
hi: 0,
});
}
result.diagnostics.push(diagnostic);
},
};

Expand Down Expand Up @@ -137,13 +154,28 @@ const bundleSSR = async (options: ReplInputOptions, ctx: QwikReplContext, result
cache: ctx.ssrCache,
plugins: [self.qwikOptimizer?.qwikRollup(qwikRollupSsrOpts), replResolver(options, 'ssr')],
onwarn(warning) {
result.diagnostics.push({
const diagnostic: Diagnostic = {
scope: 'rollup-ssr',
code: warning.code ?? null,
message: warning.message,
severity: 'Error',
show_environment: false,
code_highlights: [],
origin: 'ssr rollup',
});
category: 'warning',
highlights: [],
file: warning.id || '',
suggestions: null,
};
const loc = warning.loc;
if (loc && loc.file) {
diagnostic.file = loc.file;
diagnostic.highlights.push({
startCol: loc.column,
endCol: loc.column + 1,
startLine: loc.line,
endLine: loc.line + 1,
lo: 0,
hi: 0,
});
}
result.diagnostics.push(diagnostic);
},
};

Expand Down
2 changes: 1 addition & 1 deletion packages/qwik/src/core/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ export interface QRL<TYPE = any> {
// (undocumented)
__brand__QRL__: TYPE;
// (undocumented)
invoke(...args: TYPE extends (...args: infer ARGS) => any ? ARGS : never): TYPE extends (...args: any[]) => infer RETURN ? ValueOrPromise<RETURN> : never;
invoke(...args: TYPE extends (...args: infer ARGS) => any ? ARGS : never): Promise<TYPE extends (...args: any[]) => infer RETURN ? RETURN : never>;
// (undocumented)
invokeFn(el?: Element, context?: InvokeContext, beforeFn?: () => void): TYPE extends (...args: infer ARGS) => infer RETURN ? (...args: ARGS) => ValueOrPromise<RETURN> : never;
// (undocumented)
Expand Down
88 changes: 49 additions & 39 deletions packages/qwik/src/core/component/component-ctx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { isStyleTask, newInvokeContext } from '../use/use-core';
import { getProps, QContext } from '../props/props';
import { processNode } from '../render/jsx/jsx-runtime';
import { wrapSubscriber } from '../use/use-subscriber';
import { logDebug } from '../util/log';
import { logDebug, logError } from '../util/log';
import type { ValueOrPromise } from '../util/types';
import { removeSub } from '../object/q-object';

Expand Down Expand Up @@ -42,46 +42,56 @@ export const renderComponent = (rctx: RenderContext, ctx: QContext): ValueOrProm
ctx.refMap.array.forEach((obj) => {
removeSub(obj, hostElement);
});
const onRenderFn = onRenderQRL.invokeFn(rctx.containerEl, invocatinContext);

// Execution of the render function
const renderPromise = onRenderFn(wrapSubscriber(getProps(ctx), hostElement));
const onRenderFn = onRenderQRL.invokeFn(rctx.containerEl, invocatinContext);

// Wait for results
return then(renderPromise, (jsxNode) => {
rctx.hostElements.add(hostElement);
try {
// Execution of the render function
const renderPromise = onRenderFn(wrapSubscriber(getProps(ctx), hostElement));

const waitOnPromise = promiseAll(waitOn);
return then(waitOnPromise, (waitOnResolved) => {
waitOnResolved.forEach((task) => {
if (isStyleTask(task)) {
appendStyle(rctx, hostElement, task);
}
});
if (ctx.dirty) {
logDebug('Dropping render. State changed during render.');
return renderComponent(rctx, ctx);
// Wait for results
return then(
renderPromise,
(jsxNode) => {
rctx.hostElements.add(hostElement);
const waitOnPromise = promiseAll(waitOn);
return then(waitOnPromise, (waitOnResolved) => {
waitOnResolved.forEach((task) => {
if (isStyleTask(task)) {
appendStyle(rctx, hostElement, task);
}
});
if (ctx.dirty) {
logDebug('Dropping render. State changed during render.');
return renderComponent(rctx, ctx);
}
let componentCtx = ctx.component;
if (!componentCtx) {
componentCtx = ctx.component = {
hostElement,
slots: [],
styleHostClass: undefined,
styleClass: undefined,
styleId: undefined,
};
const scopedStyleId = hostElement.getAttribute(ComponentScopedStyles) ?? undefined;
if (scopedStyleId) {
componentCtx.styleId = scopedStyleId;
componentCtx.styleHostClass = styleHost(scopedStyleId);
componentCtx.styleClass = styleContent(scopedStyleId);
hostElement.classList.add(componentCtx.styleHostClass);
}
}
componentCtx.slots = [];
newCtx.components.push(componentCtx);
return visitJsxNode(newCtx, hostElement, processNode(jsxNode), false);
});
},
(err) => {
logError(err);
}
let componentCtx = ctx.component;
if (!componentCtx) {
componentCtx = ctx.component = {
hostElement,
slots: [],
styleHostClass: undefined,
styleClass: undefined,
styleId: undefined,
};
const scopedStyleId = hostElement.getAttribute(ComponentScopedStyles) ?? undefined;
if (scopedStyleId) {
componentCtx.styleId = scopedStyleId;
componentCtx.styleHostClass = styleHost(scopedStyleId);
componentCtx.styleClass = styleContent(scopedStyleId);
hostElement.classList.add(componentCtx.styleHostClass);
}
}
componentCtx.slots = [];
newCtx.components.push(componentCtx);
return visitJsxNode(newCtx, hostElement, processNode(jsxNode), false);
});
});
);
} catch (err) {
logError(err);
}
};
5 changes: 3 additions & 2 deletions packages/qwik/src/core/import/qrl-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ class QRL<TYPE = any> implements IQRL<TYPE> {
return copy;
}

invoke(...args: TYPE extends (...args: infer ARGS) => any ? ARGS : never) {
async invoke(...args: TYPE extends (...args: infer ARGS) => any ? ARGS : never) {
const fn = this.invokeFn();
return fn(...args) as any;
const result = await fn(...args);
return result;
}

serialize(options?: QRLSerializeOptions) {
Expand Down
2 changes: 1 addition & 1 deletion packages/qwik/src/core/import/qrl.public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export interface QRL<TYPE = any> {
resolve(container?: Element): Promise<TYPE>;
invoke(
...args: TYPE extends (...args: infer ARGS) => any ? ARGS : never
): TYPE extends (...args: any[]) => infer RETURN ? ValueOrPromise<RETURN> : never;
): Promise<TYPE extends (...args: any[]) => infer RETURN ? RETURN : never>;

invokeFn(
el?: Element,
Expand Down
9 changes: 9 additions & 0 deletions packages/qwik/src/core/object/q-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { debugStringify } from '../util/stringify';
import { WatchDescriptor, WatchFlags } from '../watch/watch.public';
import type { Subscriber } from '../use/use-subscriber';
import { tryGetContext } from '../props/props';
import { RenderEvent } from '../util/markers';

export type ObjToProxyMap = WeakMap<any, any>;
export type QObject<T extends {}> = T & { __brand__: 'QObject' };
Expand Down Expand Up @@ -187,6 +188,14 @@ class ReadWriteProxyHandler implements ProxyHandler<TargetType> {
const unwrappedNewValue = unwrapProxy(newValue);
if (qDev) {
verifySerializable(unwrappedNewValue);
const invokeCtx = tryGetInvokeContext();
if (invokeCtx && invokeCtx.event === RenderEvent) {
logWarn(
'State mutation inside render function. Move mutation to useWatch(), useClientEffect() or useServerMount()',
invokeCtx.hostElement,
prop
);
}
}
const isArray = Array.isArray(target);
if (isArray) {
Expand Down
5 changes: 3 additions & 2 deletions packages/qwik/src/core/util/promises.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ export function isPromise(value: any): value is Promise<any> {

export const then = <T, B>(
promise: ValueOrPromise<T>,
thenFn: (arg: Awaited<T>) => ValueOrPromise<B>
thenFn: (arg: Awaited<T>) => ValueOrPromise<B>,
rejectFn?: (err: any) => any
): ValueOrPromise<B> => {
return isPromise(promise) ? promise.then(thenFn as any) : thenFn(promise as any);
return isPromise(promise) ? promise.then(thenFn as any, rejectFn) : thenFn(promise as any);
};

export const promiseAll = <T extends any[]>(promises: T): ValueOrPromise<T> => {
Expand Down
33 changes: 19 additions & 14 deletions packages/qwik/src/optimizer/core/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::code_move::{new_module, NewModuleCtx};
use crate::collector::global_collect;
use crate::entry_strategy::EntryPolicy;
use crate::transform::{HookKind, QwikTransform, QwikTransformOptions};
use crate::utils::{CodeHighlight, Diagnostic, DiagnosticSeverity, SourceLocation};
use crate::utils::{Diagnostic, DiagnosticCategory, DiagnosticScope, SourceLocation};
use path_slash::PathExt;
use serde::{Deserialize, Serialize};

Expand All @@ -20,7 +20,7 @@ use anyhow::{Context, Error};

use swc_atoms::JsWord;
use swc_common::comments::SingleThreadedComments;
use swc_common::errors::{DiagnosticBuilder, Emitter, Handler};
use swc_common::errors::{DiagnosticBuilder, DiagnosticId, Emitter, Handler};
use swc_common::{sync::Lrc, FileName, Globals, Mark, SourceMap};
use swc_ecmascript::ast;
use swc_ecmascript::codegen::text_writer::JsWriter;
Expand Down Expand Up @@ -486,25 +486,30 @@ fn handle_error(
.iter()
.map(|diagnostic| {
let message = diagnostic.message();
let code = diagnostic.get_code().and_then(|m| {
if let DiagnosticId::Error(s) = m {
Some(s)
} else {
None
}
});

let span = diagnostic.span.clone();
let suggestions = diagnostic.suggestions.clone();

let span_labels = span.span_labels();
let code_highlights = if span_labels.is_empty() {
let highlights = if span_labels.is_empty() {
None
} else {
Some(
span_labels
.into_iter()
.map(|span_label| CodeHighlight {
message: span_label.label,
loc: SourceLocation::from(source_map, span_label.span),
})
.map(|span_label| SourceLocation::from(source_map, span_label.span))
.collect(),
)
};

let hints = if suggestions.is_empty() {
let suggestions = if suggestions.is_empty() {
None
} else {
Some(
Expand All @@ -516,13 +521,13 @@ fn handle_error(
};

Diagnostic {
origin: origin.clone(),
file: origin.clone(),
code,
message,
code_highlights,
hints,
show_environment: false,
severity: DiagnosticSeverity::Error,
documentation_url: None,
highlights,
suggestions,
category: DiagnosticCategory::Error,
scope: DiagnosticScope::Optimizer,
}
})
.collect()
Expand Down
Loading