sveltejs / svelte

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

New component `root` property may throw errors #6584

Open CatchABus opened 3 years ago

CatchABus commented 3 years ago

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

mskocik commented 3 years ago

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 -->
CatchABus commented 3 years ago

It's strange that context property will check for existence of parent_component but the new root property won't. @mskocik I had the exact same error from plugins that had bundles using latest svelte version.

micschwarz commented 3 years ago

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

nullbio commented 3 years ago

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

mskocik commented 3 years ago

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.

nullbio commented 3 years ago

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.

pateketrueke commented 3 years ago

This breaks on Snowpack's streaming imports where you don't have the svelte/compiler available and it just downloads ESM modules... so, for componentes that were already generated this way then there's no solution.

Sticking to v3.39.0 is fine if you're bundling all way long, but CDN-based approaches are failing due this.

Until #6646 gets merged CDN users are just blocked.

Thank you!

robin-shine commented 3 years ago

does this issue solved? the same error occurs to me.

Conduitry commented 3 years ago

Why is this code ever getting called with neither a target passed in to the constructor nor current_component/parent_component being set?

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.

What's the use case here? 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?

micschwarz commented 3 years ago

@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.

dummdidumm commented 3 years ago

A possibly related post on Reddit: https://www.reddit.com/r/sveltejs/comments/pjo902/svelte_microfrontend_module_federation_bug/?utm_medium=android_app&utm_source=share

roblevintennis commented 3 years ago

I'm getting same error and I've tried downgrading to 3.39.0 and using 3.43.0 latest but still get it:

image

My use case is I have a ui component library I'm attempting to prepare for publishing -- I've npm linked to a ui library which I've built out a dist/index.js via rollup -c and when I try to use it with following I get said error:

<script>
    import { Button } from 'agnosticui-svelte';
</script>

<main>
    <Button mode="primary" isBordered>Testing 1234</Button>
</main>
<style></style>

I've used https://github.com/joeattardi/svelte-tabs and built it out a similar way and it doesn't have the issue but it's locked back at 3.7.1:

    "svelte": {
      "version": "3.7.1",
      "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.7.1.tgz",
      "integrity": "sha512-MKjFy3YZ2pNUVPTwLNQ9JAVx6KVwfYnm/vbUF/pKLrfDDZiCBKfWPwnffpwGlnIAn7aS+aJoKV0soj464DOs4Q==",
      "dev": true
    },

I tried rolling all the way back to 3.7.1 in my project, and I still have same exact error. So that tells me perhaps there's something different (and wrong) between what I'm doing and what svelte-tabs.

UPDATE: I just deleted all my components and copied over the very simple svelte-tabs components and updated my example app to look exactly like his. I can reproduce same exact error with svelte v3.43.0.

Then, I backed down my package.json for the library and for the example app the was npm linked to `"svelte": "3.39.0", removed both lock files and reinstalled. I was able to run the example. So, there's something specific in my components that still was causing the error even after downgrading; so I'll have to just add my components back one at a time to figure that out. However, it looks like this is definitely a bug on the latest version.

This seems like a blocker for anyone trying to develop a component library.

gcruchon commented 3 years ago

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.

bluwy commented 2 years ago

Re @mskocik I tested your example in a REPL and I don't see any errors.

Re streaming imports @pateketrueke I'm not familiar with it, but usually using CDN or pre-bundled code for Svelte components, they should always be instantiated with a target to mount to. Svelte doesn't guarantee components to work cross version AFAIK, so target would help "isolate" it.

Re jest mocks @gcruchon that seems like a bug of how Svelte components are mocked and interacting with the component initialization phase. I'm not familiar with how it works under-the-hood, but probably somewhere multiple svelte instances are created when being bundled (?).

I agree with Conduitry here. While the PR that fixes this works, I don't see any reason why the error would happen in the first place. Either options.target is defined for the component to mount to, or it has a parent_component. It shouldn't be dangling around without a reference. If anything, versions above 3.39.0 have surfaced many implementation bugs around the ecosystem, and we should fix them instead. And they largely seem to be components not being compiled/bundled correctly.

mskocik commented 2 years ago

@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.

gcruchon commented 2 years ago

Re @mskocik that seems like a bug of how Svelte components are mocked and interacting with the component initialization phase. I'm not familiar with how it works under-the-hood, but probably somewhere multiple svelte instances are created when being bundled (?).

Thanks for acknowledging this bug.

If I’m not mocking svelte component in the right way, how would you recommend to mock a svelte component? Using an import of a much simpler .svelte file seems to me pretty simple and straightforward… how come this works without if statement and suddenly fails with an if?

I’m not a specialist of the svelte compiler, so I need more info to investigate. Can someone help me understand and improve the maturity of Svelte to embrace such software craftsmanship practices?

Do you have all what is necessary to reproduce the bug? (I see a label “need repro”)

bluwy commented 2 years ago

If I’m not mocking svelte component in the right way, how would you recommend to mock a svelte component?

The way you mock it now is correct, but I'm guessing there's a bug when transforming svelte files in jest. Might be an issue in https://github.com/mihar-22/svelte-jester.

Do you have all what is necessary to reproduce the bug? (I see a label “need repro”)

I'd say a no since the repros given so far are Svelte + [third-party integration] repros. Maybe it's fair as it only happens in these very specific scenarios, but it makes it hard to nail down what actually is the bug in Svelte. Or the real bug resides in that third-party integration.

crisward commented 2 years ago

@nullbio I was also getting this error with Esbuild and it seemed to happen when I linked other libraries that also included svelte. Esbuild wasn't de-duping the svelte import, so I was getting multiple copies inside my bundle. I couldn't find a plugin to de-dupe svelte so added this inline one.

// part of esbuild config
plugins: [
    {
      name: 'dedupe-svelte',
      setup({ onResolve }) {
        const svelte = require.resolve('svelte')
        onResolve({ filter: /^svelte/ }, args => {
          let path = svelte.replace(/svelte\/[^\.]+.js$/,args.path+"/index.mjs")// .mjs for browser path
          return { path }
        })
      }
    },
    sveltePlugin({compileOptions: {css: true}})
  ],

Hope that helps.

madeleineostoja commented 2 years ago

Just adding another data point for why this error occurred for me — I was consuming an internal design system where svelte was listed as a dependency rather than peerDependency, resulting in multiple instances existing in my app (verified by running yarn why svelte). Fixing that so only one svelte instance (regardless of version) was installed fixed the issue for me.

I agree with the sentiment that if anything recent versions of Svelte have just surfaced ecosystem issues. Could svelte throw a more useful error? From reading this thread seems it's often due to mismatching/several versions of svelte existing at once.

EDIT: This also happened when I yarn linked said design system for local development, presumably for the same reason, which is frustrating. I got around it by also linking the svelte instance in the design system, which feels hacky.

bluwy commented 2 years ago

Could svelte throw a more useful error?

Yes, we could do that as it's detectable.

This also happened when I yarn linked said design system for local development, presumably for the same reason, which is frustrating. I got around it by also linking the svelte instance in the design system, which feels hacky.

I have a design system component library as well and have not encountered this issue. Are you compiling the library into JS? Make sure to export raw Svelte files instead so that the consumer of that library can share the Svelte instance.

te-online commented 2 years ago

For what it's worth, I'm going to add my boring story of how I came across this error 🤷 In a progressively-enhanced PHP app, I used to create svelte components regardless of whether the target existed or not, because one main.js file creates components on different pages that don't always use all of them. Svelte used to handle this gracefully, skipping those components that didn't have a valid target element. After I updated svelte from 3.38.3 to 3.44.2, I came across this error. Either I missed it while browsing through or this “change” in behavior wasn't mentioned in the changelog. 🤭

I added a conditional checking for the element to exist before creating a component, which seems more reasonable anyways.

Having gone through the debugging process, I can however confirm that catching the error and printing a more specific error message could be nice if both, target and parent are undefined for one or another reason.

FunctionalMetatable commented 2 years ago

I've been having issues with this recently, such as when using svelte-loader + svelte with electron-forge image

micschwarz commented 2 years ago

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

aral commented 2 years ago

So this was breaking components that couldn’t be server-side rendered and so had to be instantiated using <svelte:component> in NodeKit.

In case anyone else comes across this Svelte bug while using their Svelte plugin with esbuild, you can apply the patch in #6646 in your plugin’s build onEnd() handler:

await esbuild.build({
  //…
  plugins: [
    sveltePlugin(route),
    {
      name: 'Apply patches',
      setup(build) {
        build.onEnd(result => {
          // Apply PR: https://github.com/sveltejs/svelte/pull/6646/files
          // by https://github.com/hgiesel
          if (result.outputFiles) {
            result.outputFiles.forEach((outputFile, index) => {
              // Note $$ is escaped as $$$$.
              const patchedResult = outputFile.text.replace(/root: options\.target \|\| parent_component\.\$\$\.root/g, 'root: options.target || (parent_component ? parent_component.$$$$.root : document)')
              result.outputFiles[index].contents = Buffer.from(patchedResult, 'utf-8')
            })
          }
        })
      }
    }
  ]
})

This does, however, slow down the esbuild build so I do hope that #6646 will be accepted.

In case it helps, I was testing with the svelte-boring-avatars and tsParticles components. Both break without the patch.

With the patch, svelte-boring-avatars works but, since the distribution build for tsParticles is minified, the patch doesn’t apply and that’s still failing.

So I guess the only option left is to patch Svelte itself.

aral commented 2 years ago

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).

dummdidumm commented 2 years ago

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.

aral commented 2 years ago

@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.

jindrahm commented 2 years ago

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.

ghost commented 2 years ago

@jindrahm Agreed; @Conduitry this could also be mentioned in the Svelte FAQ, or Svelte Society. I'm trying to downgrade as well; I'm just trying to re-spin up nodemon real quick.

ghost commented 2 years ago

:heavy_check_mark: Got it on my end; make sure to run:

> npm i -s svelte@^3.39.0
# for yarn:
> yarn add svelte@^3.39.0

So npm/yarn knows to downgrade.

However, I can see this tripping other newbies up as there's nothing mentioned in the Svelte docs/FAQ/Society; I think this is very essential for the Svelte ecosystem/community to be aware of as an error like this can hinder the modularity of a Svelte app.

wobedi commented 2 years ago

To add another use case to this discussion: I have encountered this error when dynamically importing a component in the markup like so:

{#await import('./AsyncComponent.svelte')}
    <LoadingIndicatator />
{:then { default: AsyncComponent }}
    <AsyncComponent />
{/await}
richerfu commented 2 years ago

Same error. And it will reproduce in a pre-build component with esm/cjs module ?

// rollup.config.js
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import svelte from 'rollup-plugin-svelte';
import sveltePreprocess from 'svelte-preprocess';
import typescript from '@rollup/plugin-typescript';
import pkg from './package.json';

export default [
  {
    input: 'src/index.ts',
    output: [
      {
        sourcemap: true,
        file: pkg.module,
        format: 'es',
      },
      {
        sourcemap: true,
        file: pkg.main,
        format: 'umd',
        name: 'XXX',
      },
    ],
    plugins: [
      svelte({
        preprocess: sveltePreprocess(),
        compilerOptions: {
          dev: false,
        },
      }),
      nodeResolve(),
      typescript({
        sourceMap: true,
        inlineSourceMap: true,
      }),
      commonjs(),
    ],
  },
];

And other project use this package and then it will reproduce. Add svelte field in package.json will resolve it,but i don't want to do this.

richerfu commented 2 years ago

I think there are two different current_component with pre-build which makes init get wrong parent_component And then get this error

matpen commented 2 years ago

I am experiencing a similar situation when using svelte-forms-lib: the error I get is similar to the one described in https://github.com/sveltejs/svelte/issues/6584#issuecomment-887581518. However, downgrading svelte to 3.39.0 in my package.json, as suggested above, won't fix this case, because the error originates in the third-party package, which has been built with svelte 3.40.

I now reported the problem upstream, but I would like to add the following info to this thread:

pateketrueke commented 2 years ago

Add svelte field in package.json will resolve it,but i don't want to do this.

Actually that's the solution, the svelte compiler should consume all the .svelte sources to produce correct builds.

If you don't want to follow that rule then it is your fault, not svelte's fault.

Bundlers should be aware of that, and MUST load "svelte": "./path/to/sources" to properly work.

If your bundler does not resolve from the svelte field then it'll fail, and that's not your fault... just pick another bundler that will do his job correctly!

matpen commented 2 years ago

I second @pateketrueke's opinion. See https://github.com/tjinauyeung/svelte-forms-lib/issues/169#issuecomment-1145736546 for a discussion.

richerfu commented 2 years ago

Actually that's the solution, the svelte compiler should consume all the .svelte sources to produce correct builds.

It's an amazing rule, and i can't find it in the docs.

So i must provide all of the source files to avoid this problem? If it is, esm/cjs module may be useless?

pateketrueke commented 2 years ago

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

jindrahm commented 2 years ago

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.

atomcat1978 commented 2 years ago

@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.

ivanhofer commented 2 years ago

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.

atomcat1978 commented 2 years ago

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.

Tlahey commented 2 years ago

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

vpalos commented 1 year ago

Having this same issue:

  1. Built a library with Svelte components and packaged it using svelte-package;
  2. Importing the library in a TypeScript project which uses Rollup to compile the end result.

In this setup, I could instruct Rollup (second project) to use it's own version of Svelte (including for the components inside the library) by using the dedupe option of the @rollup/plugin-node-resolve plugin:

// rollup.config.js
import { visualizer } from "rollup-plugin-visualizer";

import commonjs from "@rollup/plugin-commonjs";
import esbuild from "rollup-plugin-esbuild";
import json from "@rollup/plugin-json";
import polyfills from "rollup-plugin-polyfill-node";
import resolve from "@rollup/plugin-node-resolve";
import svelte from "rollup-plugin-svelte";
import sveltePreprocess from "svelte-preprocess";

export default {
    input: "./src/index.ts",
    output: [
        {
            name: "TPScript",
            file: "dist/script.js",
            format: "umd",
            sourcemap: true,
        },
    ],

    plugins: [
        svelte({
            preprocess: sveltePreprocess({
                sourceMap: true,
            }),
            emitCss: false,
            compilerOptions: {
                sourcemap: true,
            },
        }),

        json(),
        commonjs(),
        polyfills(),
        resolve({
            browser: true,
            dedupe: ["svelte"],
        }),

        esbuild({
            minify: process.env.NODE_ENV === "production",
        }),

        visualizer(),
    ],
};

Posted the whole thing in case it helps anyone, but the relevant bit is just the dedupe: ["svelte"] part.

kennethnym commented 1 year ago

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.

jindrahm commented 1 year ago

@kennethnym It's not that much about the svelte version as it is about having the same context of svelte/internal. Make sure you don't have svelte/internal bundled in each module.

I'm not familiar with esbuild (I use rollup) so I cannot give you a concrete tip to set it up. But I have svelte/internal like separate module which I import into the other modules. In rollup I set to threat svelte/internal as extrenal module and override the default path to it.

kennethnym commented 1 year ago

I suspected that was the issue. Thanks for the pointer, I have managed to fix the issue by making sure the plugin and the application is loading the same instance of svelte.

vitmsrk commented 1 year ago

@kennethnym How did you manage to do that?

I'm having the same use case with svelte components as plugins, and trying to make the plugins load svelte/internal stuff from the host application. I'm looking into Import maps option for the browser, but not sure that will work good enough.

I'm wondering what was your solution. Any hint will be much appreciated 🙏🏻

jare25 commented 1 year ago

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 , 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>
kennethnym commented 1 year ago

@kennethnym How did you manage to do that?

I'm having the same use case with svelte components as plugins, and trying to make the plugins load svelte/internal stuff from the host application. I'm looking into Import maps option for the browser, but not sure that will work good enough.

I'm wondering what was your solution. Any hint will be much appreciated 🙏🏻

@vitmsrk In my case I am using Svelte in a Tauri app, so the way I make it work will probably not be applicable to you. Anyways, this is how I made it work. Hopefully it will be helpful to you.

Basically, you need to make sure that both the internal code and the plugin code uses the same import URL to import svelte. I use Vite to bundle the internal code. Since it doesn't support import rewriting OOTB, I wrote this simple Vite plugin to do exactly that:

/**
 * This vite plugin prevents vite from rewriting import paths,
 * because some libraries are imported through the lib:// protocol
 * instead of being bundled directly into the application.
 *
 * https://github.com/vitejs/vite/issues/6393
 */
function libProtocol() {
  return {
    name: "lib-protocol",
    enforce: "pre",
    configResolved(resolvedConfig) {
      const VALID_ID_PREFIX = "/@id/"
      const reg = new RegExp(`${VALID_ID_PREFIX}(lib:\/.+)`, "g")
      resolvedConfig.plugins.push({
        name: "vite-plugin-ignore-static-import-replace-idprefix",
        transform: (code) => reg.test(code)
          ? code.replace(reg, (m, s1) => s1)
          : code
      })
    },
    resolveId(id, importer) {
      if (id === "svelte" || id === "svelte/internal") {
        return {id: `lib://localhost/svelte_internal.js`, external: true}
      }
      if (id === "@twind/core") {
        return {id: "lib://localhost/twind_core.js", external: true}
      }
      if (id.startsWith("svelte/")) {
        // `svelte/store` gets flattened to `svelte_store.js`
        return {id: `lib://localhost/${
            id.replace("/", "_")
          }.js`, external: true}
      }
    }
  }
}

module.exports = libProtocol

The lib:// is a special protocol that triggers a custom protocol handler that I registered with Tauri. The handler then reads the compiled Svelte code and responds with the content of the compiled source code.

For the plugins, I use esbuild to compile the plugins. When compiling the plugins, I used esbuild-plugin-import-map] to re-write all svelte imports to the same URL that the internal code of the app uses to import svelte:

function rewriteSvelteImport() {
  importMap.load({
    imports: {
      "@powermacro/api": "lib:/localhost/powermacro-api.js",
      "@powermacro/block-api": "lib:/localhost/powermacro-block-api.js",
      "@twind/core": "lib:/localhost/twind_core.js",
      svelte: "lib:/localhost/svelte_internal.js",
      "svelte/internal": "lib:/localhost/svelte_internal.js",
      "svelte/action": "lib:/localhost/svelte_action.js",
      "svelte/animate": "lib:/localhost/svelte_animate.js",
      "svelte/easing": "lib:/localhost/svelte_easing.js",
      "svelte/motion": "lib:/localhost/svelte_motion.js",
      "svelte/store": "lib:/localhost/svelte_store.js",
      "svelte/transition": "lib:/localhost/svelte_transition.js"
    }
  }, ["lib:/"])
  return importMap.plugin()
}

You can safely ignore the single slash lib:/ vs lib:// - it is a weird workaround for a strange quirk of Tauri that I couldn't quite figure out.

Since both the internal code and the plugin code use the same URL to import svelte libraries, the browser will use the same instance of svelte for the import.

I am assuming that your app is run in a browser instead of Tauri. I can see a potential solution here, which is to import svelte through a CDN, then use the same technique that I used to rewrite imports for internal and plugin code.

Edit: note that the esbuild plugin I linked only supports https protocol and nothing else. I forked the plugin to allow for lib:// protocol.