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
5 changes: 5 additions & 0 deletions .changeset/heavy-mammals-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@builder.io/qwik': minor
---

feat: the qwikloader can now be inlined again if required (for testing or specific network conditions). Pass `qwikLoader: 'inline'` to the render options.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@
"prettier-plugin-tailwindcss": "0.6.14",
"pretty-quick": "4.2.2",
"prompts": "2.4.2",
"rollup": ">= 4.52.0",
"rollup": "4.52.3",
"semver": "7.7.2",
"simple-git-hooks": "2.13.1",
"snoop": "1.0.4",
Expand All @@ -164,8 +164,8 @@
"typescript-eslint": "8.38.0",
"undici": "*",
"vfile": "6.0.3",
"vite": "7.1.5",
"vite-imagetools": "7.1.0",
"vite": "7.1.7",
"vite-imagetools": "8.0.0",
"vite-plugin-dts": "3.9.1",
"vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"typescript": "5.4.5",
"undici": "*",
"valibot": "0.33.3",
"vite": "7.1.5",
"vite": "7.1.7",
"vite-plugin-inspect": "11.3.2",
"vite-tsconfig-paths": "5.1.4",
"wrangler": "3.65.1"
Expand Down
11 changes: 6 additions & 5 deletions packages/docs/src/repl/bundler/rollup-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const replResolver = (
}
return {
// Make sure this matches the regexes in manifest.ts
id: `/node_modules/@qwik.dev/core${id}`,
id: `/qwik${id}`,
sideEffects: false,
// It would be nice to load qwik as external, but
// we import core and core/build so we need processing
Expand All @@ -59,15 +59,16 @@ export const replResolver = (
if (id.startsWith('http')) {
return { id, external: true };
}
if (id.startsWith('/node_modules/@qwik.dev/core/')) {
// re-resolve
if (id.startsWith('/qwik/')) {
return id;
}
const match = id.match(/(@builder\.io\/qwik|@qwik\.dev\/core)(.*)/);
if (match) {
const pkgName = match[2];

if (pkgName === '/build') {
return `/node_modules/@qwik.dev/core/build`;
return `/qwik/build`;
}
if (!pkgName || pkgName === '/jsx-runtime' || pkgName === '/jsx-dev-runtime') {
return resolveQwik('/dist/core.mjs');
Expand Down Expand Up @@ -108,8 +109,8 @@ export const replResolver = (
if (input && typeof input.code === 'string') {
return input.code;
}
if (id.startsWith('/node_modules/@qwik.dev/core/')) {
const path = id.slice('/node_modules/@qwik.dev/core'.length);
if (id.startsWith('/qwik/')) {
const path = id.slice('/qwik'.length);
if (path === '/build') {
// Virtual module for Qwik build
const isDev = options.buildMode === 'development';
Expand Down
3 changes: 0 additions & 3 deletions packages/docs/src/repl/repl-constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
export const QWIK_PKG_NAME_V1 = '@builder.io/qwik';
export const QWIK_PKG_NAME_V2 = '@qwik.dev/core';

export const QWIK_REPL_DEPS_CACHE = 'QwikReplDeps';
export const QWIK_REPL_RESULT_CACHE = 'QwikReplResults';
2 changes: 1 addition & 1 deletion packages/docs/src/repl/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export interface SSRErrorMessage {
export type OutputPanel =
| 'app'
| 'html'
| 'symbols'
| 'segments'
| 'clientBundles'
| 'serverModules'
| 'diagnostics';
Expand Down
10 changes: 5 additions & 5 deletions packages/docs/src/repl/ui/repl-output-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { component$ } from '@builder.io/qwik';
import { CodeBlock } from '../../components/code-block/code-block';
import type { ReplAppInput, ReplStore } from '../types';
import { ReplOutputModules } from './repl-output-modules';
import { ReplOutputSymbols } from './repl-output-symbols';
import { ReplOutputSymbols } from './repl-output-segments';
import { ReplTabButton } from './repl-tab-button';
import { ReplTabButtons } from './repl-tab-buttons';

Expand Down Expand Up @@ -32,10 +32,10 @@ export const ReplOutputPanel = component$(({ input, store }: ReplOutputPanelProp

{store.enableClientOutput ? (
<ReplTabButton
text="Symbols"
isActive={store.selectedOutputPanel === 'symbols'}
text="Segments"
isActive={store.selectedOutputPanel === 'segments'}
onClick$={async () => {
store.selectedOutputPanel = 'symbols';
store.selectedOutputPanel = 'segments';
}}
/>
) : null}
Expand Down Expand Up @@ -115,7 +115,7 @@ export const ReplOutputPanel = component$(({ input, store }: ReplOutputPanelProp
</div>
) : null}

{store.selectedOutputPanel === 'symbols' ? (
{store.selectedOutputPanel === 'segments' ? (
<ReplOutputSymbols outputs={store.transformedModules} />
) : null}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { CodeBlock } from '../../components/code-block/code-block';
import { $, component$, useSignal } from '@builder.io/qwik';
const FILE_MODULE_DIV_ID = 'file-modules-symbol';

type TransformModuleV2 = TransformModule & {
segment?: { canonicalFilename: string; paramNames: string[]; captureNames: string[] };
};

export const ReplOutputSymbols = component$(({ outputs }: ReplOutputSymbolsProps) => {
const selectedPath = useSignal(outputs.length ? outputs[0].path : '');
const pathInView$ = $((path: string) => {
Expand All @@ -14,7 +18,7 @@ export const ReplOutputSymbols = component$(({ outputs }: ReplOutputSymbolsProps
return (
<div class="output-result output-modules">
<div class="file-tree">
<div class="file-tree-header">Symbols</div>
<div class="file-tree-header">Segments</div>
<div class="file-tree-items">
{segments.map((o, i) => (
<div key={o.path}>
Expand All @@ -38,10 +42,20 @@ export const ReplOutputSymbols = component$(({ outputs }: ReplOutputSymbolsProps
</div>
</div>
<div class="file-modules" id={FILE_MODULE_DIV_ID}>
{segments.map((o, i) => (
{(segments as TransformModuleV2[]).map((o, i) => (
<div class="file-item" data-symbol-item={i} key={o.path}>
<div class="file-info">
<span>{o.segment?.canonicalFilename}</span>
{o.segment!.paramNames && (
<div>
Params: <code>{o.segment!.paramNames.join(', ')}</code>
</div>
)}
{o.segment!.captureNames && (
<div>
Captures: <code>{o.segment!.captureNames.join(', ')}</code>
</div>
)}
</div>
<div class="file-text">
<CodeBlock
Expand Down
7 changes: 6 additions & 1 deletion packages/docs/src/repl/ui/repl-share-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ export const strToFiles = (str: string) => {
// You can add new entries to the beginning though.
export const dictionary = strToU8(
filesToStr([
{
path: '/app.tsx',
code: `import { component$ } from '@qwik.dev/core';\n\nexport default component$(() => {\n return (\n <div>\n <h1>Hello from Qwik!</h1>\n </div>\n );\n`,
},
{
path: '',
// Extra words to help with compression
Expand All @@ -98,7 +102,7 @@ export const dictionary = strToU8(
// You need to add a new section like this before this section instead
code: `<div> </div> </button> props: class return ( story component$( store string state export const span type href={ page strong count useSignal< useStore< qwik import { } from searchInput console.log( searchResults builder useTask$( stories style={ news export default data </article> track onClick$= new nav map link debounced controller user useStyles$( useStylesScoped$( url title timeoutId time_ago second response Date.now() minute main item interface hour disabled aria any State update transform the target suggestion setTimeout selectedValue rotate render people number list label https:// header deg debouncedGetPeople debounce component comments_count comments clock background await new Promise args SuggestionsListComponent IStory IState IComment GrandChild Clock Child AutoComplete 360 yellow with view useVisibleTask$( true tmrId timer then swapi styles signal section search results resolve rel prev points parsedResponse null noreferrer name more length json job items isServer index github getPeople function fetch example domain dev delay css container com click clearTimeout async api _blank Star Wars API This The StoryPreview Stories ReturnType Qwik App Page Nav HackerNewsCSS AbortController server$( routeAction$( routeLoader$( useContent( useDocumentHead( useLocation( useNavigate( validator$( zod$( noSerialize( </Slot> useComputed$( useOnDocument( useOnWindow( useResource$( useContext( useContextProvider( createContextId<`,
},
// The default hello world app + supporting files
// The old default hello world app + supporting files
{
path: '/app.tsx',
code: `import { component$ } from '@builder.io/qwik';\n\nexport default component$(() => {\n return <p>Hello Qwik</p>;\n});\n`,
Expand Down Expand Up @@ -168,6 +172,7 @@ export function parseCompressedFiles(filesBase64: string) {
const filesBuf = inflateSync(compressedUint8Array, { dictionary });
filesStr = strFromU8(filesBuf);
} catch (error) {
console.error('Could not decode URL, falling back to uncompressed');
// Treat string as not compressed
filesStr = decodeURIComponent(encoded);
}
Expand Down
97 changes: 88 additions & 9 deletions packages/docs/src/repl/ui/repl-share-url.unit.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { assert, test } from 'vitest';
import { strFromU8 } from 'fflate';
import { assert, expect, test } from 'vitest';
import {
filesToStr,
strToFiles,
createPlaygroundShareUrl,
compressFiles,
parseCompressedFiles,
createPlaygroundShareUrl,
dictionary,
filesToStr,
parseCompressedFiles,
parsePlaygroundShareUrl,
strToFiles,
} from './repl-share-url';
import { strFromU8 } from 'fflate';

const data = {
version: '1.2.3',
Expand Down Expand Up @@ -64,8 +65,86 @@ test('createPlaygroundShareUrl 2', () => {
});

test('dictionary is unchanged', () => {
assert.equal(
strFromU8(dictionary),
"0||1448|<div> </div> </button> props: class return ( story component$( store string state export const span type href={ page strong count useSignal< useStore< qwik import { } from searchInput console.log( searchResults builder useTask$( stories style={ news export default data </article> track onClick$= new nav map link debounced controller user useStyles$( useStylesScoped$( url title timeoutId time_ago second response Date.now() minute main item interface hour disabled aria any State update transform the target suggestion setTimeout selectedValue rotate render people number list label https:// header deg debouncedGetPeople debounce component comments_count comments clock background await new Promise args SuggestionsListComponent IStory IState IComment GrandChild Clock Child AutoComplete 360 yellow with view useVisibleTask$( true tmrId timer then swapi styles signal section search results resolve rel prev points parsedResponse null noreferrer name more length json job items isServer index github getPeople function fetch example domain dev delay css container com click clearTimeout async api _blank Star Wars API This The StoryPreview Stories ReturnType Qwik App Page Nav HackerNewsCSS AbortController server$( routeAction$( routeLoader$( useContent( useDocumentHead( useLocation( useNavigate( validator$( zod$( noSerialize( </Slot> useComputed$( useOnDocument( useOnWindow( useResource$( useContext( useContextProvider( createContextId<|8|/app.tsx|114|import { component$ } from '@builder.io/qwik';\n\nexport default component$(() => {\n return <p>Hello Qwik</p>;\n});\n|17|/entry.server.tsx|201|import { renderToString, type RenderOptions } from '@builder.io/qwik/server';\nimport { Root } from './root';\n\nexport default function (opts: RenderOptions) {\n return renderToString(<Root />, opts);\n}\n|9|/root.tsx|192|import App from './app';\n\nexport const Root = () => {\n return (\n <>\n <head>\n <title>Hello Qwik</title>\n </head>\n <body>\n <App />\n </body>\n </>\n );\n};\n"
const dictionaryAsString = strFromU8(dictionary);
// !!! THIS DICTIONARY MUST NEVER CHANGE - ONLY ALLOW PREPENDING !!!
expect(dictionaryAsString).toMatchInlineSnapshot(`
"8|/app.tsx|149|import { component$ } from '@qwik.dev/core';

export default component$(() => {
return (
<div>
<h1>Hello from Qwik!</h1>
</div>
);
|0||1448|<div> </div> </button> props: class return ( story component$( store string state export const span type href={ page strong count useSignal< useStore< qwik import { } from searchInput console.log( searchResults builder useTask$( stories style={ news export default data </article> track onClick$= new nav map link debounced controller user useStyles$( useStylesScoped$( url title timeoutId time_ago second response Date.now() minute main item interface hour disabled aria any State update transform the target suggestion setTimeout selectedValue rotate render people number list label https:// header deg debouncedGetPeople debounce component comments_count comments clock background await new Promise args SuggestionsListComponent IStory IState IComment GrandChild Clock Child AutoComplete 360 yellow with view useVisibleTask$( true tmrId timer then swapi styles signal section search results resolve rel prev points parsedResponse null noreferrer name more length json job items isServer index github getPeople function fetch example domain dev delay css container com click clearTimeout async api _blank Star Wars API This The StoryPreview Stories ReturnType Qwik App Page Nav HackerNewsCSS AbortController server$( routeAction$( routeLoader$( useContent( useDocumentHead( useLocation( useNavigate( validator$( zod$( noSerialize( </Slot> useComputed$( useOnDocument( useOnWindow( useResource$( useContext( useContextProvider( createContextId<|8|/app.tsx|114|import { component$ } from '@builder.io/qwik';

export default component$(() => {
return <p>Hello Qwik</p>;
});
|17|/entry.server.tsx|201|import { renderToString, type RenderOptions } from '@builder.io/qwik/server';
import { Root } from './root';

export default function (opts: RenderOptions) {
return renderToString(<Root />, opts);
}
|9|/root.tsx|192|import App from './app';

export const Root = () => {
return (
<>
<head>
<title>Hello Qwik</title>
</head>
<body>
<App />
</body>
</>
);
};
"
`);
});

test('previous URLs still work', () => {
expect(parsePlaygroundShareUrl('f=G000o4mG5EQDAA')).toHaveProperty(
'files',
// DO NOT UPDATE THIS TEST - all these URLs must work forever
expect.arrayContaining([
expect.objectContaining({
path: '/app.tsx',
code: "import { component$ } from '@builder.io/qwik';\n\nexport default component$(() => {\n return <p>Hello Qwik</p>;\n});\n",
}),
])
);
expect(
parsePlaygroundShareUrl(
'f=Q0o0xgaW2BKNDrDkqNCB15QUpyFIgKTl51uBeGA%2BKO%2BBIwaW0W1A6SI%2FDWQzyKm1wKBDVwyU0lAqUNJRqE4GFc3AqLNSCnENDlGq1QTpAGJ43a5RDa6oa0FOgBsDbxkAXQIMCqAWMIktXqqBSvRgNoNMRg7C0XQ%2FJNM9AA'
)
).toHaveProperty(
'files',
// DO NOT UPDATE THIS TEST - all these URLs must work forever
expect.arrayContaining([
expect.objectContaining({
path: '/app.tsx',
code: `import { component$, jsx, useTask$ } from '@builder.io/qwik';

export default component$(() => {
const foo:{
contents: ReturnType<typeof jsx>
} = {
contents: jsx("p", {children:"TEST"})
}
useTask$(({track}) =>{
console.log(foo);
});
return (
<>
{foo.contents}
</>
);
});
`,
}),
])
);
});
Loading
Loading