Coder Social home page Coder Social logo

Comments (19)

eventualbuddha avatar eventualbuddha commented on June 8, 2024

Interesting. So it looks like a module is "… a prototype-less object that provides read-only access to the exports of the module. All of the exports are provided as getters without setters". What is supposed to happen when there is a default export? When would I use module foo from "foo"; vs import foo from "foo";?

from es6-module-transpiler.

eventualbuddha avatar eventualbuddha commented on June 8, 2024

To clarify, I think it would be unexpected for import fs from "fs"; to transpile to var fs = require("fs").__default__; since you'd end up with undefined. My understanding of why export default was added was to address exactly this use case where someone might reasonably export a single object, like a function. I could easily be misinterpreting the spec, though.

from es6-module-transpiler.

domenic avatar domenic commented on June 8, 2024

You should be using module fs from "fs" for the fs case. import foo from "foo" should only be used for modules that have default exports defined via export default [AssignmentExpression].

In other words:

  • export default [AssignmentExpression] and import [Identifier] from work together;
  • all other export forms and all other import forms work together;
  • but you cannot mix these two sets and expect it to work, e.g. export default [AssignmentExpression] and import [ImportSpecifierSet] from/module [Identifier] from do not work together.

from es6-module-transpiler.

domenic avatar domenic commented on June 8, 2024

Concrete example: in ES6 code you will have the following.

// foo.js
export var foo = 5;
export default 10;

// bar.js
import { foo } from "foo"; // foo == 5
import bar from "foo"; // bar == 10
module baz from "foo"; // baz deepEquals { foo: 5 }
// x.js
export var a = 10;
export var b = 20;

// y.js
import x from "x"; // compilation error: x.js did not have an `export default` statement
// a.js
export default 10;

// b.js
module a from "a"; // a deepEquals {}

from es6-module-transpiler.

thomasboyt avatar thomasboyt commented on June 8, 2024

@domenic in your first example, you have

export qux;

this should be

export { qux };

right? the syntax doesn't seem to support exporting identifiers without the curly braces.

(not trying to be pedantic about your example, mind you, just making sure I understand the syntax correctly)

from es6-module-transpiler.

domenic avatar domenic commented on June 8, 2024

@thomasboyt good catch, thanks; will edit OP to fix.

from es6-module-transpiler.

kpdecker avatar kpdecker commented on June 8, 2024

This __default__ bit just threw me for a loop, just as @domenic suggested. I can't say that I quite agree with the spec on this one as I think quite a few people are going to be confused by this.

Regardless of my opinions of the spec on this one, this implementation has a very real impact on code that is trying to interop with non-transpiled code. Since there is no way to use arbitrary objects as modules, i.e. node's module.exports = and AMD's function return value, a fair amount of code is going to have a difficult time being migrated.

In particular this is having an impact here: https://github.com/wycats/handlebars.js/blob/es6-modules/lib/handlebars.js#L35-38

For this pattern non-transpiled code is going to have to do something like:

var Handlebars = require('handlebars').__default__;

or

define(['handlebars'], function(Handlebars) {
  Handlebars = Handlebars.default;

Both of which feel a bit painful, not to mention will break existing code. Right now the only solution that comes to mind is a non-transpiled shim that will effectively do the above redirects but this feels like it's a hack as well.

/cc @wycats

from es6-module-transpiler.

thomasboyt avatar thomasboyt commented on June 8, 2024

easy solution: we could add a flag to the transpiler (and grunt task), i.e. exportDefaultBare: true (tho something more descriptive would be better), that exports the default as the object for those modules. for example, if you were using grunt-es6-module-transpiler for handlebars, your config would become:

"transpile": {
  "handlebars": {
    type: 'amd',
    files: [{
      expand: true,
      cwd: 'tmp/transpiled/handlebars/',
      src: '**/*.js',
      dest: 'tmp/transpiled/handlebars/'
    }
  },
  "handlebars_root": {
    type: 'amd',
    src: 'tmp/transpiled/handlebars.js'  // root module
    dest: 'tmp/transpiled/handlebars.js',
    exportDefaultBare: true
  }
}

sound good?

from es6-module-transpiler.

kpdecker avatar kpdecker commented on June 8, 2024

This seems like a nice stop gap for sure. I think the module vs. default confusion will still be there but that would allow the "legacy" interop without yet another buld step.

I already have a fork so I can look into making a PR here while I'm working out the config in handlebars land.

from es6-module-transpiler.

domenic avatar domenic commented on June 8, 2024

Just be aware you are not writing ES6 if you do this, and your modules will start failing to work together if you start using native module support.

from es6-module-transpiler.

wycats avatar wycats commented on June 8, 2024

My preferred strategy is to write ES6 modules as ES6 modules and translate "single imports" from AMD into default on the way in by wrapping.

This should work reliably because we're only talking about having code written as ES6 importing code written as AMD, and not the other way around.

from es6-module-transpiler.

kpdecker avatar kpdecker commented on June 8, 2024

@domenic the original source would still work properly under a compliant engine would it not? If you have module support you would load those files, if you do not you would load the output of the transpiler. This really is an issue of trying to compile to the existing module definitions that are not quite a perfect match with how es6 handles modules.

I totally agree that things would likely blow up if a legacy loaded attempted to load the native es6 module but that is a concern for the require.js and browserify, etc projects to figure out and any solution there would need to be aware of the formal definition of default exports in es6.

@wycats the exportDefaultBare would be on the way out. I.e. so vanilla AMD implementations that are using handlebars don't have to know to look at default rather than the root object. It's a relatively minor thing given there are presumably few people using the amd module as it was never formally released but still an annoyance none the less.

from es6-module-transpiler.

domenic avatar domenic commented on June 8, 2024

@kpdecker

the original source would still work properly under a compliant engine would it not?

Yes, but it would not work in the way you'd expect, causing compat problems as your entire module graph starts breaking upon upgrade. It would be syntactically valid, but semantically mean completely different things, causing errors in the places you forced incorrect semantics via this exportDefaultBare option.

from es6-module-transpiler.

thomasboyt avatar thomasboyt commented on June 8, 2024

@domenic the only place that exportDefaultBare is used is to create an AMD/CJS import-friendly version of the "index" (public-facing export) module. everything else has the exact same semantics as expected. nothing will break when

var Handlebars = require("handlebars");

becomes

import Handlebars from "handlebars";

assuming that the handlebars module exports a single default.

from es6-module-transpiler.

domenic avatar domenic commented on June 8, 2024

@thomasboyt that's just not true. A lot of code will break; in fact, any code that uses a property of Handlebars in your example will break, because Handlebars does not have properties in ES6 semantics. Instead the "handlebars" module has non-default exports.

More generally, adding exportDefaultBare creates a body of code that does not distinguish correctly between import x from "y" and module x from "y". With exportDefaultBare's wrong semantics, you can use them interchangeably; as you showed above, people will do exactly that. Whereas with real ES6, you must use the correct one, or in some cases both. Code written with ES6 syntax (but wrong semantics), then transpiled, will break when upgraded to ES6 semantics and used without transpilation.

from es6-module-transpiler.

kpdecker avatar kpdecker commented on June 8, 2024

@domenic as @thomasboyt mentioned the whole tenant here is that you are explicitly creating "non-es6" code with this system.

I hear you on the confusion possible for users who are attempting to load the transpiled es5 module using the es6 loader. The same concern occurs with a hand-written es5 module and the es6 loader. @wycats solution seems like a reasonable one to this problem. Ideally they would switch to using the es6 source files directly but should they not do this for whatever reason then the legacy failover logic will prevent them from blowing up.

What the flag allows for is for libraries to start moving forward without fear of leaving their users behind or adding overhead to wrap the transpiled code, which puts you in the same situation as above.

from es6-module-transpiler.

domenic avatar domenic commented on June 8, 2024

@kpdecker

as @thomasboyt mentioned the whole tenant here is that you are explicitly creating "non-es6" code with this system.

In that case I would suggest using a different syntax than the ES6 module syntax. To be perfectly clear, what will happen is:

  • People will wrote code in ES6 syntax
  • People will use this transpiler
  • Browsers will get native support, so they will stop using this transpiler and try loading their original code directly
  • Everything will break

If you are not creating ES6 code, you should not be using ES6 syntax. You are just creating a third module syntax, after AMD and CommonJS. Worse, it's one that looks confusingly like ES6 syntax, and gives users a false sense of future proofing, but is not ES6.

I hear you on the confusion possible for users who are attempting to load the transpiled es5 module using the es6 loader.

Sentences like this make me not clear you are getting my point. It's not the transpiled code that will break. It's the original source that will break. Here is an example.

// fs.js
export function readFile() { };

// myCode.js
import fs from "./fs";
fs.readFile();

This code will work when transpiled with exportDefaultBare, becoming

// fs.js
exports.readFile = function () { };

// myCode.js
var fs = require("./fs");
fs.readFile();

but will break in ES6. In fact it will break on the first line, import fs from "./fs", because no default export is declared for "./fs".

What the flag allows for is for libraries to start moving forward without fear of leaving their users behind or adding overhead to wrap the transpiled code

If ES5 consumers want to consume ES6 modules, they should do one of two things:

  • Fix their ES5 module loader to recognize exports named "default" and treat them the same way they currently treat default exports, just like ES6 does.
  • Just write var glob = require("glob").default when they want to get the default export.

from es6-module-transpiler.

domenic avatar domenic commented on June 8, 2024

A second example of breakage:

// glob.js
export function default() { };
export function sync() { };

// myCode.js
module glob from "./glob";

glob(); // invalid in ES6; glob is not a function, it's a module instance object

Transpiles (with exportDefaultBare) to:

// glob.js
module.exports = function default() { };
module.exports.sync = function () { };

// myCode.js
var glob = require("./glob");

glob(); // this works with exportDefaultBare, even though it doesn't in ES6

from es6-module-transpiler.

kpdecker avatar kpdecker commented on June 8, 2024

In that case I would suggest using a different syntax than the ES6 module syntax.

So you'd rather have people not use ES6 than to transition to it? That combined with libraries wanting to avoid breaking changes is a wonderful way to kill off ES6 module adaptation.

The intended users of exportDefaultBare output would NOT be anyone using any form ES6 already. It's a migration path that maintains the API compatibility with our existing users, 100% of which are running ES5 right now. Ideally libraries would create a build for these users and then another build for users of the transpiler that sticks to the ES6 implementation. (Or the transpiler users would build in the original sources directly, I'm not clear on the packaging best practices under the transpiler world). In the case of handlebars this might look something like handlebars-amd.js, handlebars-es6.js, etc (This splats out to a lot of different files once you add minimization, etc but such is life)

While not ideal as there is an admitted footgun for users attempting to link to the ES5 module from code that is loading using native module support, I think that the maintainer of libraries should be given the option to create a build that continues to support their existing clients with an eye on the future.

from es6-module-transpiler.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.