Repository: sveltejs/svelte (JavaScript)
Date: 2020-02-03 14:50:33+00:00
Task type: Bug Fix

Issue:
Module-level binding in svelte 3.16
Hi, after I updated svelte to the version `3.16.0`, the build command doesn't work anymore.

Modules versions:
* `"svelte": "3.16.0"`
* `"rollup-plugin-svelte": "5.1.1",`

Output:

```bash
⟩ yarn run build
yarn run v1.19.1
$ rollup -c

src/main.js → public/bundle.js...
[!] (plugin svelte) TypeError: Cannot read property 'index' of undefined
src/components/Header.svelte
TypeError: Cannot read property 'index' of undefined
    at Renderer.invalidate (/Users/jeremiecorpinot/GitHub/memorare/front-app/node_modules/svelte/src/compiler/compile/render_dom/Renderer.ts:169:35)
    at bind_this (/Users/jeremiecorpinot/GitHub/memorare/front-app/node_modules/svelte/src/compiler/compile/render_dom/wrappers/shared/bind_this.ts:32:22)
    at ElementWrapper.add_bindings (/Users/jeremiecorpinot/GitHub/memorare/front-app/node_modules/svelte/src/compiler/compile/render_dom/wrappers/Element/index.ts:592:29)
    at ElementWrapper.render (/Users/jeremiecorpinot/GitHub/memorare/front-app/node_modules/svelte/src/compiler/compile/render_dom/wrappers/Element/index.ts:380:8)
    at FragmentWrapper.render (/Users/jeremiecorpinot/GitHub/memorare/front-app/node_modules/svelte/src/compiler/compile/render_dom/wrappers/Fragment.ts:151:18)
    at new Renderer (/Users/jeremiecorpinot/GitHub/memorare/front-app/node_modules/svelte/src/compiler/compile/render_dom/Renderer.ts:95:17)
    at dom (/Users/jeremiecorpinot/GitHub/memorare/front-app/node_modules/svelte/src/compiler/compile/render_dom/index.ts:17:19)
    at render_dom (/Users/jeremiecorpinot/GitHub/memorare/front-app/node_modules/svelte/src/compiler/compile/index.ts:97:6)
    at /Users/jeremiecorpinot/GitHub/memorare/front-app/node_modules/rollup-plugin-svelte/index.js:252:22

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

What's in the file:
`Header.svelte`

```JavaScript
<script context="module">
  let domHeader
  // ...other variables

  export function hideHeader() {
   // ...function's body
  }
</script>

<script>
  // ...some JavaScript code
</script>

<style>
  /*...some styles */
</style>

<header>
  <!-- some dom -->
</header>
```

To see the full code you can go to my repo [Header.svelte](https://github.com/memorare/memorare.app/blob/master/src/components/Header.svelte)

The same code works with the svelte version `3.15.0`
The bug appears when I require the exported functions in the `script module` with svelte `3.16.0`.

Example:

```JavaScript
// Some other module

import { hideHeader } from 'Header.svelte';
```


Issue discussion:
@rootasjey What did you upgrade *from*? What's the latest version that works?

Is it possible to create a REPL to demonstrate the issue?
The root of the matter seems to be:

```svelte
<script context='module'>
	let foo;
</script>

<div bind:this={foo}/>
```

I haven't checked whether this did the right thing in 3.15.0. It's somewhat weird having a binding to a module-level variable (I guess all of the instances of the component would just overwrite one another?)
@Conduitry, 3.15.0 compiles OK, at least according to REPL.
https://svelte.dev/repl/77e6667473c34065a02e2ab675343107?version=3.15.0
I see that it compiles okay - what I haven't checked is whether the code does the right thing.
> I haven't checked whether this did the right thing in 3.15.0. It's somewhat weird having a binding to a module-level variable (I guess all of the instances of the component would just overwrite one another?)

Yeah I thought I was doing something not-classy when writing this (the module-level binding).
I can find a workaround now that you pointed the issue :)

Thanks!

svelte component script natively is module, why use syntax: "<script context="module">" ? I am a fresh man, Am I misunderstood svelte official tutorial?
@anlexN https://svelte.dev/docs#script_context_module
@Conduitry,

In 3.15, it has the last in behavior you'd expect (though probably not a good thing)... every button in the repl below prints "Third".

https://svelte.dev/repl/623d6db3ad1242208a191c5ac1a411fc?version=3.15.0

The invalidate routine is much simpler in 3.15 than 3.16.5... for all the tests in the routine, we still miss `member` being undefined.

Here's some devtools info:
![image](https://user-images.githubusercontent.com/5039735/71103593-6317e280-2188-11ea-9798-929588362eb8.png)
```
variable = {
hoistable: true
module: true
name: "foo"
reassigned: true
referenced: true
writable: true
}
```
Maybe I don't understand the use case here, but shouldn't this just be a [store](https://svelte.dev/tutorial/store-bindings) instead of a module level variable?
I think the broader problem here is that we shouldn't try to generate invalidation code for any module-level variables (unless we're updating it as a store). This:

```diff
diff --git a/src/compiler/compile/render_dom/Renderer.ts b/src/compiler/compile/render_dom/Renderer.ts
index 046a90b68..be9e700a0 100644
--- a/src/compiler/compile/render_dom/Renderer.ts
+++ b/src/compiler/compile/render_dom/Renderer.ts
@@ -160,6 +160,10 @@ export default class Renderer {
 			return x`${name.slice(1)}.set(${value || name})`;
 		}
 
+		if (variable && variable.module) {
+			return value;
+		}
+
 		if (
 			variable &&
 			!variable.referenced &&
```

seems to generate sensible code from my example component above, and doesn't break any existing tests.
Additionally, we probably shouldn't be checking the dirty bitmask for changes to module-level variables, nor should these variables even be given a bit in the bitmask, but I don't have quick fixes for those yet.

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
 
 ## Unreleased
 
+* Fix binding to module-level variables ([#4086](https://github.com/sveltejs/svelte/issues/4086))
 * Disallow attribute/prop names from matching two-way-bound names or `{shorthand}` attribute/prop names ([#4325](https://github.com/sveltejs/svelte/issues/4325))
 
 ## 3.18.1
diff --git a/src/compiler/compile/render_dom/Renderer.ts b/src/compiler/compile/render_dom/Renderer.ts
--- a/src/compiler/compile/render_dom/Renderer.ts
+++ b/src/compiler/compile/render_dom/Renderer.ts
@@ -161,11 +161,14 @@ export default class Renderer {
 		}
 
 		if (
-			variable &&
-			!variable.referenced &&
-			!variable.is_reactive_dependency &&
-			!variable.export_name &&
-			!name.startsWith('$$')
+			variable && (
+				variable.module || (
+					!variable.referenced &&
+					!variable.is_reactive_dependency &&
+					!variable.export_name &&
+					!name.startsWith('$$')
+				)
+			)
 		) {
 			return value || name;
 		}


Test patch:
diff --git a/test/runtime/samples/module-context-bind/_config.js b/test/runtime/samples/module-context-bind/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/module-context-bind/_config.js
@@ -0,0 +1,4 @@
+export default {
+	skip_if_ssr: true,
+	html: '<div>object</div>'
+};
diff --git a/test/runtime/samples/module-context-bind/main.svelte b/test/runtime/samples/module-context-bind/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/module-context-bind/main.svelte
@@ -0,0 +1,11 @@
+<script context='module'>
+	let foo;
+</script>
+
+<script>
+	import { onMount } from 'svelte';
+	let bar;
+	onMount(() => bar = foo);
+</script>
+
+<div bind:this={foo}>{typeof bar}</div>