Skip to content

Commit 2c0f4af

Browse files
authored
Merge pull request #8610 from QwikDev/fix-leaking-import.meta.env-in-non-vite-environments
fix: import.meta.env can be undefined in built core.mjs
2 parents a207ea7 + b2d3c0a commit 2c0f4af

32 files changed

Lines changed: 142 additions & 71 deletions

.changeset/thin-brooms-stay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/core': patch
3+
---
4+
5+
fix: non-Vite consumers (webpack, etc.) don't blow up at runtime anymore when `import.meta.env` is undefined.

eslint.config.mjs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,64 @@ export default tseslint.config(
133133
'qwik-local/loop-style': 'error',
134134
},
135135
},
136+
{
137+
// Webpack-safety: every `import.meta.env.X` access must use optional chaining.
138+
// Applies to both core and qwik-router source — non-Vite consumers ship both.
139+
files: ['packages/qwik/src/**/*.{ts,tsx}', 'packages/qwik-router/src/**/*.{ts,tsx}'],
140+
ignores: ['**/*.unit.*', '**/*.spec.*', '**/*.d.ts'],
141+
rules: {
142+
'no-restricted-syntax': [
143+
'error',
144+
{
145+
selector:
146+
"MemberExpression[object.type='MemberExpression']" +
147+
"[object.object.type='MetaProperty']" +
148+
"[object.property.name='env']" +
149+
':not([optional=true])',
150+
message:
151+
'Use `import.meta.env?.X` (optional chaining). Non-Vite consumers (webpack, ' +
152+
'plain Node, etc.) ship our published bundles where `import.meta.env` is ' +
153+
'undefined and `.X` access throws a TypeError.',
154+
},
155+
],
156+
},
157+
},
158+
{
159+
// Test-mode gating: prefer the `qTest` const over `import.meta.env?.TEST`.
160+
// Scoped to qwik core only. qwik-router source can't reach `qTest` (it's in core's
161+
// internal `shared/utils/qdev`, not part of the public API), so this rule doesn't
162+
// apply there — its `import.meta.env?.TEST` usage is intentional.
163+
files: ['packages/qwik/src/**/*.{ts,tsx}'],
164+
ignores: ['**/*.unit.*', '**/*.spec.*', '**/*.d.ts'],
165+
rules: {
166+
'no-restricted-syntax': [
167+
'error',
168+
{
169+
selector:
170+
"MemberExpression[object.type='MemberExpression']" +
171+
"[object.object.type='MetaProperty']" +
172+
"[object.property.name='env']" +
173+
':not([optional=true])',
174+
message:
175+
'Use `import.meta.env?.X` (optional chaining). Non-Vite consumers (webpack, ' +
176+
'plain Node, etc.) ship our published bundles where `import.meta.env` is ' +
177+
'undefined and `.X` access throws a TypeError.',
178+
},
179+
{
180+
selector:
181+
'MemberExpression[optional=true]' +
182+
"[object.type='MemberExpression']" +
183+
"[object.object.type='MetaProperty']" +
184+
"[object.property.name='env']" +
185+
"[property.name='TEST']",
186+
message:
187+
'Use the `qTest` const from `<...>/shared/utils/qdev` instead of ' +
188+
'`import.meta.env?.TEST`. `qTest` reads `globalThis.qTest` (webpack-safe) AND ' +
189+
'lets Terser fold it via `global_defs` for prod tree-shaking.',
190+
},
191+
],
192+
},
193+
},
136194
{
137195
files: ['packages/qwik/src/server/**/*.ts'],
138196
ignores: ['packages/qwik/src/server/qwik-copy.ts'],

packages/qwik-router/src/runtime/src/link-component.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export const Link = component$<LinkProps>((props) => {
132132
}
133133
}
134134

135-
const isProdOrTest = !isDev || import.meta.env.TEST;
135+
const isProdOrTest = !isDev || import.meta.env?.TEST;
136136

137137
if (isProdOrTest && anchorRef.value) {
138138
if (

packages/qwik-router/src/runtime/src/typed-routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const untypedAppUrl = function appUrl(
2121
}
2222
}
2323
let url = path.join('/');
24-
let baseURL = import.meta.env.BASE_URL;
24+
let baseURL = import.meta.env?.BASE_URL;
2525
if (baseURL) {
2626
if (!baseURL.endsWith('/')) {
2727
baseURL += '/';

packages/qwik/src/core/client/vnode-diff.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isDev } from '@qwik.dev/core/build';
2+
import { qTest } from '../shared/utils/qdev';
23
import { _EFFECT_BACK_REF } from '../reactive-primitives/backref';
34
import { clearAllEffects, clearEffectSubscription } from '../reactive-primitives/cleanup';
45
import { AsyncSignalImpl } from '../reactive-primitives/impl/async-signal-impl';
@@ -180,7 +181,7 @@ function setAttribute(
180181
scopedStyleIdPrefix: string | null,
181182
originalValue: any
182183
) {
183-
import.meta.env.TEST &&
184+
qTest &&
184185
scopedStyleIdPrefix &&
185186
vnode_setProp(vnode, debugStyleScopeIdPrefixAttr, scopedStyleIdPrefix);
186187
vnode_setProp(vnode, key, originalValue);
@@ -1129,7 +1130,7 @@ function createElementWithNamespace(diffContext: DiffContext, elementName: strin
11291130
const domParentVNode = vnode_getDomParentVNode(diffContext.$vParent$, true);
11301131
const namespaceData = getNewElementNamespaceData(domParentVNode, elementName);
11311132

1132-
const currentDocument = import.meta.env.TEST ? diffContext.$container$.document : document;
1133+
const currentDocument = qTest ? diffContext.$container$.document : document;
11331134

11341135
const element =
11351136
namespaceData.elementNamespaceFlag === VNodeFlags.NS_html
@@ -1312,7 +1313,7 @@ const patchProperty = (
13121313
};
13131314

13141315
function registerQwikLoaderEvent(diffContext: DiffContext, eventName: string) {
1315-
const qWindow = import.meta.env.TEST
1316+
const qWindow = qTest
13161317
? (diffContext.$container$.document.defaultView as qWindow | null)
13171318
: (window as unknown as qWindow);
13181319
if (qWindow) {
@@ -1751,7 +1752,7 @@ function expectText(diffContext: DiffContext, text: string) {
17511752
diffContext.$journal$,
17521753
diffContext.$vParent$,
17531754
(diffContext.$vNewNode$ = vnode_newText(
1754-
(import.meta.env.TEST ? diffContext.$container$.document : document).createTextNode(text),
1755+
(qTest ? diffContext.$container$.document : document).createTextNode(text),
17551756
text
17561757
)),
17571758
getCurrentInsertBefore(diffContext)

packages/qwik/src/core/client/vnode-utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
*/
119119

120120
import { isDev } from '@qwik.dev/core/build';
121+
import { qTest } from '../shared/utils/qdev';
121122
import { qwikDebugToString } from '../debug';
122123
import { assertDefined, assertEqual, assertFalse, assertTrue } from '../shared/error/assert';
123124
import { QError, qError } from '../shared/error/error';
@@ -437,7 +438,7 @@ export const vnode_setAttr = (
437438
scopedStyleIdPrefix: string | null = null
438439
) => {
439440
if (vnode_isElementVNode(vNode)) {
440-
import.meta.env.TEST &&
441+
qTest &&
441442
scopedStyleIdPrefix &&
442443
vnode_setProp(vNode, debugStyleScopeIdPrefixAttr, scopedStyleIdPrefix);
443444
vnode_setProp(vNode, key, value);

packages/qwik/src/core/control-flow/each.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { PublicProps } from '../shared/component.public';
2+
import { qTest } from '../shared/utils/qdev';
23
import type { DevJSX, JSXOutput } from '../shared/jsx/types/jsx-node';
34
import type { QRL } from '../shared/qrl/qrl.public';
45
import { inlinedQrl } from '../shared/qrl/qrl';
@@ -33,7 +34,7 @@ export const eachCmpTask = async ({ track }: TaskCtx) => {
3334
const host = context.$hostElement$!;
3435
const container = context.$container$!;
3536
markVNodeDirty(container, host, ChoreBits.RECONCILE);
36-
const isSsr = import.meta.env.TEST ? isServerPlatform() : isServer;
37+
const isSsr = qTest ? isServerPlatform() : isServer;
3738
if (isSsr) {
3839
await container.$renderPromise$;
3940
}

packages/qwik/src/core/control-flow/reveal.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isBrowser } from '@qwik.dev/core/build';
2+
import { qTest } from '../shared/utils/qdev';
23
import type { Signal } from '../reactive-primitives/signal.public';
34
import { createSignal } from '../reactive-primitives/signal.public';
45
import { componentQrl } from '../shared/component.public';
@@ -103,7 +104,7 @@ export const revealCleanupTask = ({ cleanup }: TaskCtx) => {
103104
const registration = _captures![0] as RevealRegistration;
104105
cleanup(() => {
105106
// Keep the SSR registry intact so `reveal.items` serializes for resume.
106-
if (import.meta.env.TEST ? isServerPlatform() : !isBrowser) {
107+
if (qTest ? isServerPlatform() : !isBrowser) {
107108
return;
108109
}
109110
const items = registration.reveal.items;

packages/qwik/src/core/control-flow/suspense.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isBrowser } from '@qwik.dev/core/build';
2+
import { qTest } from '../shared/utils/qdev';
23
import { _wrapProp } from '../reactive-primitives/internal-api';
34
import type { Signal } from '../reactive-primitives/signal.public';
45
import { componentQrl } from '../shared/component.public';
@@ -56,7 +57,7 @@ export const suspenseTask = ({ track, cleanup }: TaskCtx) => {
5657
state = _captures![2] as Signal<SuspenseState>,
5758
revealRegistration = _captures![3] as RevealRegistration | null;
5859
const pendingCount = track(cursorBoundary.pending);
59-
const isBrowserEnv = import.meta.env.TEST ? !isServerPlatform() : isBrowser;
60+
const isBrowserEnv = qTest ? !isServerPlatform() : isBrowser;
6061
if (revealRegistration !== null && isBrowserEnv) {
6162
revealRegistration.reveal.version.value++;
6263
}

packages/qwik/src/core/preloader/bundle-graph.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
import { isServer } from '@qwik.dev/core/build';
2-
import { isServerPlatform } from '../shared/platform/platform';
31
import { createMacroTask } from '../shared/platform/next-tick';
4-
import { config, isJSRegex, yieldInterval } from './constants';
2+
import { config, isBrowser, isJSRegex, yieldInterval } from './constants';
53
import { adjustProbabilities, bundles, shouldResetFactor, nextTriggerMacroTask } from './queue';
64
import type { BundleGraph, BundleImport, ImportProbability } from './types';
75
import { BundleImportState_None, BundleImportState_Alias } from './types';
86

97
export let base: string | undefined;
108
export let graph: BundleGraph;
11-
const isBrowser = import.meta.env.TEST ? !isServerPlatform() : !isServer;
129

1310
const makeBundle = (name: string, deps?: ImportProbability[]) => {
1411
return {

0 commit comments

Comments
 (0)