Repository: sveltejs/svelte (JavaScript)
Date: 2017-04-03 22:43:46+00:00
Task type: Bug Fix

Issue:
todomvc is not working on old Android device
http://svelte-todomvc.surge.sh/#/ is not working on my Android 4.0.3 and is shown as follows: 

![image](https://cloud.githubusercontent.com/assets/6639874/24564709/83ea686a-165b-11e7-8e17-180280dd9bd9.png)

I don't know which information should I deliver to help debugging. 



Issue discussion:
Thanks. My *hunch* is that it's because Svelte generates code that uses `Object.assign`, maybe that's not supported on the version of Chrome running on that device (is it Chrome? Or the Android browser that preceded it?)

If there's any way to get an error log by [remotely inspecting the browser](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/) over USB, that would be amazing, but if not then I'll try deploying a version with a polyfill later to see if that works.
According to Google, `Object.assign` is [supported as of v45](https://www.chromestatus.com/feature/5742083411279872).

@ceremcem Could you check out [ES Checker](https://ruanyf.github.io/es-checker/) with that device + browser?

[Support tables](http://webbrowsercompatibility.com/es6/tablet-phone/) indicate a good support
It's Chrome, yes. I followed that instructions (thank you, I learned something else :+1: ), and your guess is correct. Here is the console output: 

```
Uncaught TypeError: Object.assign is not a function
```
MDN has [a polyfill](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill).
So, there are two possible routes here:

* Ask developers to polyfill their apps with `Object.assign` support
* Add an `assign` helper

Since we don't need to cover all the edge cases that a true polyfill would, ours could be as simple as

```js
export function assign ( target ) {
  for ( var i = 1; i < arguments.length; i += 1 ) {
    var source = arguments[i];
    for ( var k in source ) target[k] = source[k];
  }

  return target;
}
```
So I understand that the issue is not related directly with Svelte itself? If so, this issue can be closed. 
Oh, btw. I haven't heard of that ES Checker before, too.
But I know of similar tools for support checking.

Action derived from this issue is offering an `assign` helper or ask developers include a polyfill.

@Rich-Harris I wonder whether it is worth the effort to implement a Babel-like API to polyfill ESnext features according to presets/target browser versions...
It's related to Svelte insofar as it affects the environments we can plausibly claim to support. Some people still have to get their apps working on IE9, legacy Android etc... if it's no bother for us to bake in support (or offer it via e.g. a `legacy: true` option) then we should at least discuss it. Off the top of my head, the only vaguely exotic things we need (unless you want to talk about polyfilling `addEventListener` etc, which I don't!) are `Object.assign` and `Object.create(null)`. We could use `hasOwnProperty` instead of `Object.create(null)`.

> @Rich-Harris I wonder whether it is worth the effort to implement a Babel-like API to polyfill ESnext features according to presets/target browser versions...

Nah, not at this stage. Maybe if we decided to e.g. implement something using `WeakMap` (such as associating data and event handlers with nodes, rather than adding private `_svelte` properties) that would make sense, but that's in the distant future if at all.
Thanks. Writing parsers sounds like too much what Computer Scientists would do, so I haven't tried to understand how it works, yet (knowledge gap is too large).

Good to know, that most is ES5 code!

Solution patch:
diff --git a/src/generators/dom/index.js b/src/generators/dom/index.js
--- a/src/generators/dom/index.js
+++ b/src/generators/dom/index.js
@@ -241,7 +241,7 @@ export default function dom ( parsed, source, options ) {
 	}
 
 	builders._set.addLine( 'var oldState = this._state;' );
-	builders._set.addLine( 'this._state = Object.assign( {}, oldState, newState );' );
+	builders._set.addLine( `this._state = ${generator.helper( 'assign' )}( {}, oldState, newState );` );
 
 	if ( computations.length ) {
 		const builder = new CodeBuilder();
@@ -340,7 +340,7 @@ export default function dom ( parsed, source, options ) {
 	if ( generator.usesRefs ) constructorBlock.addLine( `this.refs = {};` );
 
 	constructorBlock.addLine(
-		`this._state = ${templateProperties.data ? `Object.assign( ${generator.alias( 'template' )}.data(), options.data )` : `options.data || {}`};`
+		`this._state = ${templateProperties.data ? `${generator.helper( 'assign' )}( ${generator.alias( 'template' )}.data(), options.data )` : `options.data || {}`};`
 	);
 
 	if ( !generator.builders.metaBindings.isEmpty() ) {
@@ -393,7 +393,7 @@ export default function dom ( parsed, source, options ) {
 
 	if ( sharedPath ) {
 		const base = templateProperties.methods ? `{}, ${generator.alias( 'template' )}.methods` : `{}`;
-		builders.main.addBlock( `${name}.prototype = Object.assign( ${base}, ${generator.helper( 'proto' )} );` );
+		builders.main.addBlock( `${name}.prototype = ${generator.helper( 'assign' )}( ${base}, ${generator.helper( 'proto' )} );` );
 	} else {
 		if ( templateProperties.methods ) {
 			builders.main.addBlock( `${name}.prototype = ${generator.alias( 'template' )}.methods;` );
diff --git a/src/shared/index.js b/src/shared/index.js
--- a/src/shared/index.js
+++ b/src/shared/index.js
@@ -3,6 +3,15 @@ export * from './methods.js';
 
 export function noop () {}
 
+export function assign ( target ) {
+	for ( var i = 1; i < arguments.length; i += 1 ) {
+		var source = arguments[i];
+		for ( var k in source ) target[k] = source[k];
+	}
+
+	return target;
+}
+
 export function differs ( a, b ) {
 	return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) );
 }


Test patch:
diff --git a/test/generator/index.js b/test/generator/index.js
--- a/test/generator/index.js
+++ b/test/generator/index.js
@@ -30,6 +30,8 @@ require.extensions[ '.html' ] = function ( module, filename ) {
 	return module._compile( code, filename );
 };
 
+const Object_assign = Object.assign;
+
 describe( 'generate', () => {
 	before( setupHtmlEqual );
 
@@ -93,6 +95,10 @@ describe( 'generate', () => {
 
 			return env()
 				.then( window => {
+					Object.assign = () => {
+						throw new Error( 'cannot use Object.assign in generated code, as it is not supported everywhere' );
+					};
+
 					global.window = window;
 
 					// Put the constructor on window for testing
@@ -145,6 +151,9 @@ describe( 'generate', () => {
 						if ( !config.show ) console.log( addLineNumbers( code ) ); // eslint-disable-line no-console
 						throw err;
 					}
+				})
+				.then( () => {
+					Object.assign = Object_assign;
 				});
 		});
 	}