56

EDIT: This is not about fat arrows. It's also not about passing this to an IIFE. It's a transpiler-related question.

So I've created a simple pub-sub for a little app I'm working on. I wrote it in ES6 to use spread/rest and save some headaches. I set it up with npm and gulp to transpile it but it's driving me crazy.

I made it a browser library but realized it could be used anywhere so I decided to make it Commonjs and AMD compatible.

Here's a trimmed down version of my code:

(function(root, factory) {
 if(typeof define === 'function' && define.amd) {
    define([], function() {
        return (root.simplePubSub = factory())
    });
  } else if(typeof module === 'object' && module.exports) {
    module.exports = (root.simplePubSub = factory())
  } else {
    root.simplePubSub = root.SPS = factory()
  }
}(this, function() {
 // return SimplePubSub
});

But no matter what I try (such as making this a variable and passing it) it sets it to undefined.

}(undefined, function() {

It probably has something to do with Babel not knowing what this will be and transpiling it away but is there any other approach I can take?

UPDATE: Passing }((window || module || {}), function() { instead of this seems to work. I'm not sure this is the best approach though.

loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
JR Halchak
  • 697
  • 1
  • 5
  • 14
  • You could just use Browserify and let it handle all of this for you. – elclanrs Jan 24 '16 at 08:01
  • 2
    Well, in that case `this` equals to `undefined`, which means both are identical. "not knowing what this will be" --- it and everyone knows it will be `undefined`, as per the standard. – zerkms Jan 24 '16 at 08:02
  • 2
    @zerkms In a browser, `this === window` and in Node `this === module` when used in the global scope (well, as global as a Node module can be). – Alexander O'Mara Jan 24 '16 at 08:06
  • 1
    @AlexanderO'Mara well, not in every case. Hint: how about strict mode? – zerkms Jan 24 '16 at 08:06
  • @zerkms I'm pretty sure even then, but only when used in the global scope. – Alexander O'Mara Jan 24 '16 at 08:07
  • I understand the reasoning, that's fine. The point is I'm intentionally leaving _this_ open to whatever the global context is and Babel is deciding to change it for me. Any ideas? – JR Halchak Jan 24 '16 at 08:08
  • @zerkms That's what I get in my browsers console. In any case, there's not indication the `this` is in strict mode. – Alexander O'Mara Jan 24 '16 at 08:08
  • @JRHalchak your code is broken already: `this` equals to `undefined` when it's a strict mode that is set by babel. – zerkms Jan 24 '16 at 08:08
  • Possible duplicate of [Passing THIS into an Immediately-Invoked Function Expression](http://stackoverflow.com/questions/12456830/passing-this-into-an-immediately-invoked-function-expression) – JJJ Jan 24 '16 at 08:09
  • Maybe I just pass (window || module || {})? Not sure what negative ramifications that may have. – JR Halchak Jan 24 '16 at 08:09
  • @JRHalchak well, at the moment it's a XY-problem - instead of solving it you're trying to solve the consequences. If you explain the original task in details we may help you doing it properly I believe. – zerkms Jan 24 '16 at 08:10
  • As per the duplicate, call the function with `.call(this, function() {...` and `this` inside it will refer to the global object. – JJJ Jan 24 '16 at 08:11
  • It's pretty straight forward. I'm creating a small library that can be used in browser or in node. It's meant to be browser, amd, and commonjs compatible. – JR Halchak Jan 24 '16 at 08:11
  • @JRHalchak it's not pretty straight forward what toolchain you're using: it's babel, anything else? How do you modularize it? – zerkms Jan 24 '16 at 08:12
  • 1
    @zerkms Said console does run in global scope, but see for yourself: https://rawgit.com/AlexanderOMara/b8a118613be73545c1ea/raw/b0a227bbfeeaf9ae97a378d1ebd5966e4fc82247/index.html – Alexander O'Mara Jan 24 '16 at 08:12
  • @AlexanderO'Mara oh gosh, feeling stupid now. My apologies. – zerkms Jan 24 '16 at 08:15
  • @Juhana I don't think this is a duplicate of that question. This is a question about a transpiler issue, not how to do it (this code is otherwise fine, and known as UMD). – Alexander O'Mara Jan 24 '16 at 08:20
  • Babel, gulp, uglify... that's it. The whole thing is 1 file. The code I posted wraps the actual library (the factory returns the object). – JR Halchak Jan 24 '16 at 08:20
  • @AlexanderO'Mara btw, is that as per the standard? The script you provided does not return `true` in all browsers (not in IE11). Now I'm curious. – zerkms Jan 24 '16 at 08:21
  • @zerkms Now I'm not so sure. I always understood this rule to only apply to functions. However, looking through the spec I'm not finding explicitly how it should behave in global strict code. I know historically IE has issues where the window object would not equal itself when referenced by different variables (weird I know), but I believe this was fixed. – Alexander O'Mara Jan 24 '16 at 08:28
  • @AlexanderO'Mara yep, the only relevant place I could find http://www.ecma-international.org/ecma-262/6.0/#sec-strict-mode-of-ecmascript is extremely vague on that. – zerkms Jan 24 '16 at 08:29
  • @zerkms The mystery deepens, IE11 in my Win10 VM returns true for the comparison. What do you get with: https://rawgit.com/AlexanderOMara/b8a118613be73545c1ea/raw/b78f5b2067de947bac23bf326452faac3124ca3c/thisvalue.html – Alexander O'Mara Jan 24 '16 at 08:34
  • @AlexanderO'Mara it's `[object Window]`. It's IE 11.0.9600.18163 under windows 7 x64. Anyway, let's ask a proper question here on SO, wait a second... http://stackoverflow.com/q/34973752/251311 – zerkms Jan 24 '16 at 08:38

2 Answers2

75

For Babel >= 7.x

ES6 code has two processing modes:

  • "script" - When you load a file via a <script>, or any other standard ES5 way of loading a file
  • "module" - When a file is processed as an ES6 module

In Babel 7.x, files are parsed as "module" by default. The thing that is causing you trouble is that in an ES6 module, this is undefined, whereas in the "script" case, this varies depending on the environment, like window in a browser script or exports in CommonJS code. Similarly, "module" files are automatically strict, so Babel will insert "use strict";.

In Babel 7, you'll need to tell Babel what type your file is if you want to avoid this behavior. The simplest option would be to use the "sourceType" option to set sourceType: "unambiguous" in your Babel options, which essentially tells Babel to guess the type (scripts vs module), based on the presence of import and export statements. The primary downside there being that it's technically fine to have an ES6 module that doesn't use import or export, and those would be incorrectly treated as scripts. On the other hand, that's really not that common.

Alternatively, you can use Babel 7's "overrides" option to set specific files as scripts, e.g.

overrides: [{
  test: "./vendor/something.umd.js",
  sourceType: "script",
}],

Either approach allows Babel to know that some files are script types, and thus shouldn't have this converted to undefined.

For Babel < 7.x

ES6 code has two processing modes:

  • "script" - When you load a file via a <script>, or any other standard ES5 way of loading a file
  • "module" - When a file is processed as an ES6 module

When using Babel 6 and babel-preset-es2015 (or Babel 5), Babel by default assumes that files it processes are ES6 modules. The thing that is causing you trouble is that in an ES6 module, this is undefined and files are all strict, whereas in the "script" case, this varies depending on the environment, like window in a browser script or exports in CommonJS code.

If you are using Babel, the easiest option is to write your code without the UMD wrapper, and then bundle your file using something like Browserify to automatically add the UMD wrapper for you. Babel also provides a babel-plugin-transform-es2015-modules-umd. Both are geared toward simplicity, so if you want a customized UMD approach, they may not be for you.

Alternatively, you would need to explicitly list all of the Babel plugins in babel-preset-es2015, making sure to exclude the module-processing babel-plugin-transform-es2015-modules-commonjs plugin. Note, this will also stop the automatic addition of use strict since that is part of the ES6 spec too, you may want to add back babel-plugin-transform-strict-mode to keep your code strict automatically.

As of babel-core@6.13 presets are able to take options, so you can also do

{
  "presets": [
    [ "es2015", { "modules": false } ]
  ]
}

in your Babel config (.babelrc) to use babel-preset-es2015 with module processing disabled.

loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
  • Thanks! This makes sense of it. I've opted for passing in all of the possible _this_'s but I'll probably revisit based on your recommendation. – JR Halchak Jan 25 '16 at 01:44
  • 2
    Then use [`babel-preset-es2015-script`](https://github.com/Collaborne/babel-preset-es2015-script) instead of `babel-preset-es2015`. `babel-preset-es2015-script` has excluded `babel-plugin-transform-es2015-modules-commonjs`. – Illuminator May 06 '16 at 08:57
  • I ran into this issue trying to import a module that used the UMD syntax to wrap it. The wierd thing I ran into is that when I installed my dependencies using npm3 everything worked fine. but when I had to drop back to npm2 all the sudden I was getting this being undefined when my module was required by a part of my code – ThrowsException Jul 07 '16 at 21:36
  • 5
    According to the GitHub page for `babel-preset-es2015-script`, that module is deprecated and you should use this instead: `{ "presets": [ [ "es2015", { modules: false } ] ] }` – Stewart Jan 19 '17 at 19:25
  • Just randomly came back to this question and saw the updates. Thanks! That's very helpful. – JR Halchak Feb 22 '17 at 01:39
  • @JRHalchak -> Take a look [here](http://exploringjs.com/es6/ch_modules.html#_browsers-scripts-versus-modules) ... By default, modules do not have access to the global space. And by default, `this` is returned as `undefined` – mutantkeyboard Oct 18 '17 at 13:08
  • Since packages like `es2015` are deprecated: How can this be done in `@babel/preset-env`? `"modules": false` and `"esmodules": false` doesn't seem to work. – Daniel Oct 09 '18 at 16:40
  • 1
    @Daniel Give me a minute to write an update here and then we can close your other question as a duplicate. – loganfsmyth Oct 09 '18 at 16:42
  • @loganfsmyth As I said my question was about Babel 7 where those package is deprecated. So it's not a duplicate cause it works different there. – Daniel Oct 10 '18 at 07:10
  • @Daniel I'm not sure I understand. What packages are deprecated? My answer for Babel 7 doesn't mention any packages. – loganfsmyth Oct 10 '18 at 16:21
  • @loganfsmyth "As of Babel v6, all the yearly presets have been deprecated. We recommend using @babel/preset-env instead." So they're deprecated in 7 and even 6, see https://babeljs.io/docs/en/babel-preset-es2015 The same happened to the `babel-preset-es2015-script` preset. – Daniel Oct 11 '18 at 06:43
  • @Daniel Yes, but in your question you were using `@babel/preset-env`, and here I never mention `es2015` in the section of my answer that applies to Babel 7. Just set `sourceType` and use `preset-env` and you'll be all set. – loganfsmyth Oct 11 '18 at 08:00
  • @loganfsmyth From the perspective of a newbie, that's incomplete and readers are directed to use the deprecated packages above. That's the reason why I made my own question and I don't think it should be closed, so that I can post my full solution there. – Daniel Oct 11 '18 at 11:20
3

The "es2015" preset wraps the Babel output in a commonJs wrapper by default. Use the "babel-preset-es2015-script" (you must npm install --save babel-preset-es2015-script first) to output for "script" (no modules). This was wreaking havoc on other libraries I was wrapping up using Babel.

The preset: https://www.npmjs.com/package/babel-preset-es2015-script

user2503764
  • 121
  • 1
  • 3