Skip to content

Commit

Permalink
refactor(core): schedule tasks via core qrl
Browse files Browse the repository at this point in the history
  • Loading branch information
wmertens committed Jan 19, 2025
1 parent bb89ed5 commit 4c26e1e
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/olive-cameras-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@qwik.dev/core': patch
---

CHORE: replace the `_hW` export in segments with a shared export `_task` in core. This opens up using QRLs from core.
9 changes: 9 additions & 0 deletions packages/qwik/handlers.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* This re-exports the QRL handlers so that they can be used as QRLs.
*
* In vite dev mode, this file is referenced directly. This ensures that the correct path to core is
* used so that there's only one instance of Qwik.
*
* Make sure that these handlers are listed in manifest.ts
*/
export { _task } from '@qwik.dev/core';
2 changes: 2 additions & 0 deletions packages/qwik/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"./cli": {
"require": "./dist/cli.cjs"
},
"./handlers.mjs": "./handlers.mjs",
"./internal": {
"types": "./core-internal.d.ts",
"import": {
Expand Down Expand Up @@ -144,6 +145,7 @@
"bindings",
"build.d.ts",
"cli.d.ts",
"handlers.mjs",
"jsx-dev-runtime.d.ts",
"jsx-runtime.d.ts",
"loader.d.ts",
Expand Down
6 changes: 3 additions & 3 deletions packages/qwik/src/core/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,6 @@ function h<TYPE extends string | FunctionComponent<PROPS>, PROPS extends {} = {}
export { h as createElement }
export { h }

// @internal
export const _hW: () => void;

// @internal @deprecated (undocumented)
export const _IMMUTABLE: unique symbol;

Expand Down Expand Up @@ -997,6 +994,9 @@ export interface SyncQRL<TYPE extends Function = any> extends QRL<TYPE> {
resolved: TYPE;
}

// @internal
export const _task: () => void;

// @public (undocumented)
export interface TaskCtx {
// (undocumented)
Expand Down
2 changes: 1 addition & 1 deletion packages/qwik/src/core/internal.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export { _noopQrl, _noopQrlDEV, _regSymbol } from './shared/qrl/qrl';
export { _walkJSX } from './ssr/ssr-render-jsx';
export { _SharedContainer } from './shared/shared-container';
export { _hW } from './use/use-task';
export { scheduleTask as _task } from './use/use-task';
export { _wrapSignal, _wrapProp } from './signal/signal-utils';
export { _restProps } from './shared/utils/prop';
export { _IMMUTABLE } from './shared/utils/constants';
Expand Down
1 change: 1 addition & 0 deletions packages/qwik/src/core/shared/qrl/qrl-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export type QRLInternalMethods<TYPE> = {

export type QRLInternal<TYPE = unknown> = QRL<TYPE> & QRLInternalMethods<TYPE>;

// TODO remove refSymbol, it's not used
export const createQRL = <TYPE>(
chunk: string | null,
symbol: string,
Expand Down
15 changes: 5 additions & 10 deletions packages/qwik/src/core/use/use-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,20 +275,15 @@ export const useRunTask = (
};

const getTaskHandlerQrl = (task: Task): QRL<(ev: Event) => void> => {
const taskQrl = task.$qrl$;
const taskHandler = createQRL<(ev: Event) => void>(
taskQrl.$chunk$,
'_hW',
_hW,
null,
'_task',
scheduleTask,
null,
null,
[task],
taskQrl.$symbol$
null
);
// Needed for chunk lookup in dev mode
if (taskQrl.dev) {
taskHandler.dev = taskQrl.dev;
}
return taskHandler;
};

Expand Down Expand Up @@ -318,7 +313,7 @@ export const isTask = (value: any): value is Task => {
*
* @internal
*/
export const _hW = () => {
export const scheduleTask = () => {
const [task] = useLexicalScope<[Task]>();
const container = getDomContainer(task.$el$ as VNode);
const type = task.$flags$ & TaskFlags.VISIBLE_TASK ? ChoreType.VISIBLE : ChoreType.TASK;
Expand Down
38 changes: 27 additions & 11 deletions packages/qwik/src/optimizer/src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import type { OutputBundle } from 'rollup';
import { type NormalizedQwikPluginOptions } from './plugins/plugin';
import type { GlobalInjections, SegmentAnalysis, Path, QwikBundle, QwikManifest } from './types';

// The handlers that are exported by the core package
// See handlers.mjs
const extraSymbols = new Set(['_task']);

// This is just the initial prioritization of the symbols and entries
// at build time so there's less work during each SSR. However, SSR should
// still further optimize the priorities depending on the user/document.
Expand Down Expand Up @@ -270,7 +274,13 @@ export function generateManifestFromBundles(
return canonPath(bundle.fileName);
};
// We need to find our QRL exports
const qrlNames = new Set([...segments.map((h) => h.name)]);
const qrlNames = new Set(segments.map((h) => h.name));
for (const symbol of extraSymbols) {
qrlNames.add(symbol);
}
const taskNames = new Set(
segments.filter((h) => /use[a-zA-Z]*Task(_\d+)?$/.test(h.displayName)).map((h) => h.name)
);
for (const outputBundle of Object.values(outputBundles)) {
if (outputBundle.type !== 'chunk') {
continue;
Expand All @@ -281,25 +291,18 @@ export function generateManifestFromBundles(
size: outputBundle.code.length,
};

let hasSymbols = false;
let hasHW = false;
for (const symbol of outputBundle.exports) {
if (qrlNames.has(symbol)) {
// When not minifying we see both the entry and the segment file
// The segment file will only have 1 export, we want the entry
if (!manifest.mapping[symbol] || outputBundle.exports.length !== 1) {
hasSymbols = true;
if (taskNames.has(symbol)) {
bundle.isTask = true;
}
manifest.mapping[symbol] = bundleFileName;
}
}
if (symbol === '_hW') {
hasHW = true;
}
}
if (hasSymbols && hasHW) {
bundle.isTask = true;
}

const bundleImports = outputBundle.imports
// Tree shaking might remove imports
.filter((i) => outputBundle.code.includes(path.basename(i)))
Expand Down Expand Up @@ -349,6 +352,19 @@ export function generateManifestFromBundles(
loc: segment.loc,
};
}
for (const symbol of extraSymbols) {
manifest.symbols[symbol] = {
origin: 'Qwik core',
displayName: symbol,
canonicalFilename: '',
hash: symbol,
ctxKind: 'function',
ctxName: symbol,
captures: false,
parent: null,
loc: [0, 0],
};
}
// To inspect the bundles, uncomment the following lines
// and temporarily add the writeFileSync import from fs
// writeFileSync(
Expand Down
38 changes: 38 additions & 0 deletions packages/qwik/src/optimizer/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,9 +373,11 @@ export function createPlugin(optimizerOptions: OptimizerOptions = {}) {
};

let optimizer: Optimizer;
let shouldAddHandlers = false;
const buildStart = async (_ctx: Rollup.PluginContext) => {
debug(`buildStart()`, opts.buildMode, opts.scope, opts.target, opts.rootDir, opts.srcDir);
optimizer = getOptimizer();
shouldAddHandlers = !devServer;
if (optimizer.sys.env === 'node' && opts.target === 'ssr' && opts.lint) {
try {
linter = await createLinter(optimizer.sys, opts.rootDir, opts.tsconfigFileNames);
Expand Down Expand Up @@ -484,7 +486,29 @@ export function createPlugin(optimizerOptions: OptimizerOptions = {}) {
id: QWIK_CLIENT_MANIFEST_ID,
moduleSideEffects: false,
};
} else if (pathId.endsWith(QWIK_HANDLERS_ID)) {
debug(`resolveId(${count})`, 'Resolved', QWIK_HANDLERS_ID);
result = {
id: QWIK_HANDLERS_ID,
moduleSideEffects: false,
};
} else {
// If qwik core is loaded, also add the handlers
if (!isServer && shouldAddHandlers && id.endsWith('@qwik.dev/core')) {
shouldAddHandlers = false;
const key = await ctx.resolve('@qwik.dev/core/handlers.mjs', importerId, {
skipSelf: true,
});
if (!key) {
throw new Error('Failed to resolve @qwik.dev/core/handlers.mjs');
}
ctx.emitFile({
id: key.id,
type: 'chunk',
preserveSignature: 'allow-extension',
});
}

const qrlMatch = /^(?<parent>.*\.[mc]?[jt]sx?)_(?<name>[^/]+)\.js(?<query>$|\?.*$)/.exec(id)
?.groups as { parent: string; name: string; query: string } | undefined;

Expand Down Expand Up @@ -566,6 +590,18 @@ export function createPlugin(optimizerOptions: OptimizerOptions = {}) {
code: await getQwikServerManifestModule(isServer),
};
}
/**
* In dev mode, we need a path to core for qrls. However, we don't know what that is. By
* re-exporting the core symbols, we let Vite provide the correct path to core and we prevent
* duplicate Qwik instances.
*/
if (id === QWIK_HANDLERS_ID) {
debug(`load(${count})`, QWIK_HANDLERS_ID, opts.buildMode);
return {
moduleSideEffects: false,
code: `export * from '@qwik.dev/core';`,
};
}

// QRL segments
const parsedId = parseId(id);
Expand Down Expand Up @@ -978,6 +1014,8 @@ export const QWIK_CORE_SERVER = '@qwik.dev/core/server';

export const QWIK_CLIENT_MANIFEST_ID = '@qwik-client-manifest';

export const QWIK_HANDLERS_ID = '@qwik-handlers';

export const SRC_DIR_DEFAULT = 'src';

export const CLIENT_OUT_DIR = 'dist';
Expand Down
6 changes: 5 additions & 1 deletion packages/qwik/src/optimizer/src/plugins/vite-dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import clickToComponent from './click-to-component.html?raw';
import errorHost from './error-host.html?raw';
import imageDevTools from './image-size-runtime.html?raw';
import perfWarning from './perf-warning.html?raw';
import { type NormalizedQwikPluginOptions, parseId } from './plugin';
import { type NormalizedQwikPluginOptions, parseId, QWIK_HANDLERS_ID } from './plugin';
import type { QwikViteDevResponse } from './vite';
import { VITE_ERROR_OVERLAY_STYLES } from './vite-error';
import { formatError } from './vite-utils';
Expand All @@ -37,6 +37,10 @@ function createSymbolMapper(base: string): SymbolMapperFn {
return [symbolName, ''];
}
if (!parent) {
// Core symbols
if (symbolName.startsWith('_')) {
return [symbolName, `${base}${QWIK_HANDLERS_ID}`];
}
console.error(
'qwik vite-dev-server symbolMapper: unknown qrl requested without parent:',
symbolName
Expand Down
1 change: 1 addition & 0 deletions packages/qwik/src/qwikloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const qwikLoader = (
const roots = new Set<EventTarget & ParentNode>([doc]);

// Some shortenings for minification
// TODO use more, like hasAttribute
const replace = 'replace';
const forEach = 'forEach';
const target = 'target';
Expand Down

0 comments on commit 4c26e1e

Please sign in to comment.