sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
77.94k stars 4.07k forks source link

[Error] undefined is not an object (evaluating 'if_block.p') #11063

Open qupig opened 4 months ago

qupig commented 4 months ago

Describe the bug

The last Svelte version that worked properly was v4.2.0, and after upgrading to later versions including the latest v4.2.12, I encountered the following error:

[Error] Unhandled Promise Rejection: TypeError: undefined is not an object (evaluating 'if_block.p')    flush (chunk-663KAPRR.js:1322)

I tried tracing and debugging the error and didn't get any valuable information, I'm not familiar with the internal workings of svelte.

Any help or what I can try to do?

Reproduction

This is a complex project that I can't currently separate and make a minimal reproducible demo, because I can't pinpoint the part where the problem occurs.

If there's some help or guidance, maybe I can isolate or locate that wrong part.

Logs

No response

System Info

macOS latest
Safari latest
Node latest
Vite latest
Svelte v4.2.1-4.2.12

Severity

blocking an upgrade

dummdidumm commented 4 months ago

Try finding out what exact version broke, that might help understand what changes were introduced in that version that could've broken you.

qupig commented 4 months ago

Try finding out what exact version broke, that might help understand what changes were introduced in that version that could've broken you.

Did I not write that clearly? The last version that worked was Svelte v4.2.0, I have tried lot of subsequent versions and the latest version, but they are all broken.

qupig commented 4 months ago

I spent an extra 2 hours checking and testing and finally locked the issue at #9122.

I tested two local builds https://github.com/sveltejs/svelte/compare/6aca6127ca51ec95ea95b420c759f1ea27f4e180...115ea1ff4afc57766f06bb8d9444ec59d00bc0e1 is before and after this PR and confirmed that this is where the problem started.

It looks like this PR fixed a bug but didn't take into account that blocks using module variables are still reactive.

I can fix the error in my project now, but I think this should be handled accordingly in Svelte as well.

qupig commented 4 months ago

If it helps, this is where I found the error being thrown when debugging in the browser: (Please note the two // ==========> comments I added)

/node_modules/svelte/src/runtime/internal/scheduler.js

/** @returns {void} */
export function flush() {
    // Do not reenter flush while dirty components are updated, as this can
    // result in an infinite loop. Instead, let the inner flush handle it.
    // Reentrancy is ok afterwards for bindings etc.
    if (flushidx !== 0) {
        return;
    }
    const saved_component = current_component;
    do {
        // first, call beforeUpdate functions
        // and update components
        try {
            while (flushidx < dirty_components.length) {
                const component = dirty_components[flushidx];
                flushidx++;
                set_current_component(component);
                update(component.$$);
            }
        } catch (e) {
            // reset dirty state to not end up in a deadlocked state and then rethrow
            dirty_components.length = 0;
            flushidx = 0;
            throw e; // ==========> throw the issue error!!!
        }
        set_current_component(null);
        dirty_components.length = 0;
        flushidx = 0;
        while (binding_callbacks.length) binding_callbacks.pop()();
        // then, once components are updated, call
        // afterUpdate functions. This may cause
        // subsequent updates...
        for (let i = 0; i < render_callbacks.length; i += 1) {
            const callback = render_callbacks[i];
            if (!seen_callbacks.has(callback)) {
                // ...so guard against infinite loops
                seen_callbacks.add(callback);
                callback();
            }
        }
        render_callbacks.length = 0;
    } while (dirty_components.length);
    while (flush_callbacks.length) {
        flush_callbacks.pop()();
    }
    update_scheduled = false;
    seen_callbacks.clear();
    set_current_component(saved_component);
}

/** @returns {void} */
function update($$) {
    if ($$.fragment !== null) {
        $$.update();
        run_all($$.before_update);
        const dirty = $$.dirty;
        $$.dirty = [-1];
        $$.fragment && $$.fragment.p($$.ctx, dirty); // ==========> The actual line of code where the error occurred
        $$.after_update.forEach(add_render_callback);
    }
}

And I can see that just before the error is thrown, $$.fragment.p is:

function update(ctx, [dirty]) {
    if (moduleVariable) if_block.p(ctx, dirty);
}

And then there is the error message in the console:

Unhandled Promise Rejection: TypeError: undefined is not an object (evaluating 'if_block.p')

Ok, now I know where the problem is, the moduleVariable is a module variable and since it loses reactivity in PR #9122, the if_block is not instantiated so it is undefined.

So the question becomes, if moduleVariable is not reactive, why is if_block being updated? You should also eliminate the reactivity and proxies for {#if moduleVariable} block that use module variables.

qupig commented 4 months ago

Finally able to make a minimal reproduction: StackBlitz-Svelte-4.2.15-Reproduction

It can also be reproduced in Svelte4-REPL, but since it requires a login to share... Please copy the following code yourself:

App.svelte

<script>
    import Comp1 from "./Comp1.svelte";
    import { writable } from "svelte/store";

    const store = writable();
    let comp;

    function buttonClick() {
        comp.test();
        $store = 1;
    }
</script>

<button on:click={buttonClick}>open console first, then click me</button>

<Comp1 bind:this={comp} {store}/>

Comp1.svelte

<script context="module">
    let moduleVariable = false;
</script>

<script>
    import Comp2 from "./Comp2.svelte";

    export function test() {
        moduleVariable = true;
    }

    export let store;
    console.log($store);
</script>

<h1>Hello!</h1>

{#if moduleVariable}
<Comp2 {moduleVariable} />
{/if}

Comp2.svelte (just a empty file)