svelte: New component `root` property may throw errors

Read this comment first

The reason why this occurs is that you are likely trying to use a pre-compiled (to JS) component that was compiled with a different Svelte version than the one you use, which is not supported. See this comment for more info and solutions: https://github.com/sveltejs/svelte/issues/6584#issuecomment-1019578529

Describe the bug

I recently experienced issues due to new root property in several svelte plugins. It seems that this line causes issues: https://github.com/sveltejs/svelte/commit/5cfefeb6e72f8085e418150b644cdc4b4f6f260d#diff-da9bae4e28c441de5ba3a074e30775fe69109100b3d921ad8f2592d93cd67b7f

It seems that a null check for parent_component variable is missing at that point.

on_mount: [],
on_destroy: [],
on_disconnect: [],
before_update: [],
after_update: [],
context: new Map(parent_component ? parent_component.$$.context : options.context || []), // Here, there is a null check for parent_component variable

// everything else
callbacks: blank_object(),
dirty,
skip_bound: false,
root: options.target || parent_component.$$.root // Here there is no check for parent_component variable

Reproduction

This suddenly occured on certain svelte plugins.

Logs

No response

System Info

System:
    OS: Linux 5.8 Debian GNU/Linux 10 (buster) 10 (buster)
    CPU: (8) x64 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
    Memory: 517.19 MB / 7.47 GB
    Container: Yes
    Shell: 5.0.3 - /bin/bash
  Binaries:
    Node: 16.0.0 - ~/.n/bin/node
    npm: 7.10.0 - ~/.n/bin/npm
  npmPackages:
    svelte: ^3.31.2 => 3.38.3 
    webpack: ^5.16.0 => 5.44.0

Severity

blocking an upgrade

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 22
  • Comments: 53 (7 by maintainers)

Commits related to this issue

Most upvoted comments

The problem here is instantiating a component compiled with Svelte version X inside an app compiled with Svelte version Y in a declarative way.

The following is not guaranteed to work:

<CompiledComponent />
<svelte:element this={CompiledComponent} />

The following will work:

<script>
   //..import
   let el;
   onMount(() => new CompiledComponent({target: el, props: {..}));
</script>
<div bind:this={el} />

If using a component from a library, ensure that an uncompiled version exists and is used by your build tool. SvelteKit’s package command ensures this for example.

Try downgrading svelte version until it works again. Version 3.39.0 worked fine for me.

On 30. 7. 2021 12:01, nullbio wrote:

I’ve been racking my brain for the last 3 hours trying to figure out why my app suddenly started exploding… Is there a simple fix to reverting this bug so I can continue development until it’s patched?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sveltejs/svelte/issues/6584#issuecomment-889783745, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGCU5FABFDMDYYYU3OTM6PTT2JZ7BANCNFSM5BCL6OBQ.

https://github.com/sveltejs/svelte/pull/6646 What about this pr? Its open for month now

Hello,

I do have the same issue in my app while trying to test a component which contains an “if” statement and a mock module. I did not have the issue back when I was using Svelte 3.35.0. I tried to upgrade yesterday to 3.43.0, but did not succeed. I had to rollback to 3.39.0.

To ease a bit the debug, here’s a simplified repo to reproduce the issue: https://github.com/gcruchon/tests/tree/main/svelte-null-parent-component

I’m using last version of all libraries (svelte, jest, svelte-jester, babel, testing-library, …). I used the svelte template and just added the bug (i.e. testing a mocked “Link” within an “if” statement)

Feel free to comment if I missed something.

Try downgrading svelte version until it works again. Version 3.39.0 worked fine for me. On 30. 7. 2021 12:01, nullbio wrote: I’ve been racking my brain for the last 3 hours trying to figure out why my app suddenly started exploding… Is there a simple fix to reverting this bug so I can continue development until it’s patched? — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#6584 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGCU5FABFDMDYYYU3OTM6PTT2JZ7BANCNFSM5BCL6OBQ.

Thanks. Clearing my package-lock.json, node_modules, and adding "svelte": "3.39.0", to my package.json seems to have worked. Didn’t think it would because in my build svelte is a dependency of esbuild-svelte, but I suppose in the land of node it enforces a version for sub-dependencies if you declare one explicitly. News to me. Just leaving this here for anyone else who needs it and is new to all of this like I am.

Actually, I could resolve this issue with a correct rollup config in my library project. I defined all svelte related stuff as external and excluded from resolve, so it is fetched from within the project that is using my components fro the library. My rollup config looks as follows:

import svelte from 'rollup-plugin-svelte'
import autoPreprocess from 'svelte-preprocess'
import pkg from './package.json'
import typescript from '@rollup/plugin-typescript';
import {terser} from 'rollup-plugin-terser'
import resolve from '@rollup/plugin-node-resolve';

const globals = {
  'svelte/internal': 'svelte/internal',
  'svelte': 'svelte',
};

export default
  {
    external: /^svelte.*$/,
    input: 'src/index.ts',
    output: [
      {
        file: pkg.module,
        format: 'es',
        sourcemap: true,
        globals
      },
      {
        file: pkg.main,
        format: 'umd',
        name: 'Autocomplete',
        sourcemap: true,
        globals
      },
    ],
    plugins: [
      svelte({
          preprocess: [
            autoPreprocess()
          ],
          emitCss: false,
        },
      ),
      resolve({
        // Exclude all svelte related stuff
        resolveOnly: [/^(?!svelte.*$)/]
      }),
      typescript({sourceMap: true}),
      terser(),
    ],
  };

Apparently works fine, since all svelte stuff gets provided by the build system of the application using the component.

I’ve been racking my brain for the last 3 hours trying to figure out why my app suddenly started exploding… Eventually managed to find this issue. Is there a simple fix to reverting this bug so I can continue development until it’s patched? I’m using esbuild-svelte which is pulling in a broken version I guess, so I can’t just define an older version of svelte in my package.json as far as I’m aware?

For others trying to Google this issue, the console output for Chrome is:

Uncaught (in promise) TypeError: Cannot read property '$$' of null

And for Firefox:

Uncaught (in promise) TypeError: parent_component is null

What if someone uses a svelte component written in typescript, but the project is using pure JS

That’s why there exists the sveltekit package feature.

Library authors should ideally use this feature to create the package. Authors can use SCSS, TypeScript and other language variants and in the end normal JavaScript and CSS components will be generated that can be consumed by any other Svelte project.

Here is an example of a TypesScript demo package: https://github.com/ivanhofer/svienna-meetup-package-demo. You can run npm run package and see the exported files in the package folder. All files are beeing converted to normal JavaScript versions.

If you are using a library that doesn’t get published in the right way, you could open an issue and let the authors know that this feature exists.

@Conduitry

What’s the use case here?

Microfrontends are the use case 😃

Would the change in #6646 prevent this immediate crash, but still leave the other less-visible issues with apps being bundled with multiple copies of Svelte’s internals?

The change in #6646 would prevent this, yes. image Of course the other issues would still exist, but if you use svelte this way, you might be aware of them.

Experiencing the same issue. I had svelte app, which includes npm svelte component and it throws this error: image

This is my simplified code snippet:

/** app.js */
<script>
import Example from './example.svelte';

new Example({ target: document.body });
</script>

/** example.svelte **/
<script>
import Svelecte from 'svelecte';
</script>

<Svelecte></Svelecte> <!-- when I add component, it breaks -->

The problem here is instantiating a component compiled with Svelte version X inside an app compiled with Svelte version Y in a declarative way.

The following is not guaranteed to work:

<CompiledComponent />
<svelte:element this={CompiledComponent} />

The following will work:

<script>
   //..import
   let el;
   onMount(() => new CompiledComponent({target: el, props: {..}));
</script>
<div bind:this={el} />

If using a component from a library, ensure that an uncompiled version exists and is used by your build tool. SvelteKit’s package command ensures this for example.

Thanks for this! I’m trying to use shared components, with webpack module federation. Tried with <svelte:component />, but I also get error like others (parent_component).

This is my working RemoteButton component (should add some error handling):

<script>
    import {onMount} from "svelte";

    let el;
    let Component
    let Module

    onMount(async () => {
        Module = (await import('components/Button')).default;
        Component = new Module({target: el, props: $$props});
    });

    $: {
        if (Component) {
            Component.$set($$props)
        }
    }

</script>

<div bind:this={el}></div>

I am still getting this error, even though I made sure the components were precompiled with the same version of svelte as the one the app uses (3.53.1). The components were compiled using esbuild via the esbuild-svelte & svelte-preprocess. AFAIK, esbuild-svelte declares svelte as peerDependency since 0.7.0, and I am using 0.7.3, so it is definitely using the correct svelte version to compile the components.

For context, I am trying to develop a desktop application that has a plugin system that can extend the application’s functionality, and one part of it is that plugins can export svelte components that can then be used in the application itself. The application imports the compiled svelte files via the import() syntax. The imported component is then consumed via <svelte:component>. Each <svelte:component> is rendered inside of a div, so I would expect svelte to be able to pick up the parent_component correctly.

So i must provide all of the source files to avoid this problem?

yes, definitely

If it is, esm/cjs module may be useless?

not really, because you can still consume compiled components manually, using the new Component() syntax

it is very useful to have, i.e. a well-crafted component that can operate standalone

This also occurs when sharing components with webpacks module federation. (In the current version of svelte, older versions work). An example is here: https://github.com/micschwarz/svelte-module-federation

Thanks @atomcat1978, set svelte (and svelte/internal) as a external librairie fix the build issue on client side 😃 Potatoes for everyone ! 🥔

@pateketrueke What if someone uses a svelte component written in typescript, but the project is using pure JS? Then one would have to add typescript just because of the component used? Feels like an overhead definetely.

I finally have a solution… building svelte/internal as a separate module and treating svelte and svelte/internal dependencies as external and pointing them to the module. That makes the internal svelte logic to be shared for all the components. I updated the repo I created for reproduction of one of the issues I had with the pre-bundled components …so you can have a look.

If it’s one Svelte component trying to use another pre-compiled pre-bundled Svelte component, there are already other known issues with that (problems with the transitions scheduler, context not inheriting, probably others), and it’s not something that’s supported.

@Conduitry There should be a big warning in the Svelte doc that using pre-bundled Svelte components is not supported. It’s a blocker for our project and probably for some others too.

@dummdidumm Hey Simon, thank you, that does indeed work. I’m going to see how I can incorporate checking if the source exists for a component and using that in my loader’s resolution. I’m assuming that’s what the svelte entries in the package.json files are for. Appreciate your help.

Esbuild wasn’t de-duping the svelte import, so I was getting multiple copies inside my bundle.

It’s odd because I’m using a custom resolver in my esbuild to ensure that all Svelte references get mapped to the single instance of Svelte that’s in my app. And I’m still seeing this error (see above).

@bluwy Cannot duplicate in the REPL neither. But it was happening. It was quite hard to replicate back then. But since when I tried 3.43.1 it worked for me since then.