Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for ES6 import syntax #1186

Open
romeovs opened this issue Apr 1, 2015 · 59 comments
Open

Add support for ES6 import syntax #1186

romeovs opened this issue Apr 1, 2015 · 59 comments

Comments

@romeovs
Copy link

romeovs commented Apr 1, 2015

This might be a long shot but I'd like to see this in browserify.

The ES6 module syntax has a few nice features compared to commonJS require method,
namely bindings and cyclic dependencies.

Since these things are in the ES6 standard it seems reasonable to work them into browserify too!

Note that just using a ES6 to ES5 transpiler wouldn't work in this case because these are cross module concerns.
For instance, babelify is only able to transpile import foo from 'foo' to var foo = require('foo') at file-level, but this does not allow cyclic dependencies.

The Esperanto project has a nice way of handling things but lacks too many features to be a valid replacement for browserify.

Is there any possibility to see support for ES6 import syntax come to browserify?

@ghost
Copy link

ghost commented Apr 1, 2015

I've been waiting for this feature to land in node first so browserify can match the semantics properly. I'm also concerned that if browserify implements a default es6 import mechanism while node doesn't have one, it will needlessly fragment browser and server ecosystems.

@romeovs
Copy link
Author

romeovs commented Apr 1, 2015

Yes, my approach to writing modules is currently to write them in ES6 but only expose ES5-compliant
code (transpiling everything on npm publish for example).

I get your concerns, and they are indeed important ones.

How feasible would you say it is to expose this as a plugin for browserify (I'm currently looking into
writing that).

@romeovs
Copy link
Author

romeovs commented Apr 1, 2015

I've created a small gist with a proof of concept for a plugin. I'll start working on it!

@nmn
Copy link

nmn commented May 20, 2015

@romeovs
You can already use a transform like babelify to get ES6 imports. With both Node and Browserify. You can turn off the other features if you don't want them.

@romeovs
Copy link
Author

romeovs commented May 20, 2015

@nmn but do these imports with babelify have the complete ES6 semantics?
As far as I can see they just transform the import statements into require statements, but require and import have different semantics with respect to bindings and cyclic dependencies.

The problem is really the way browserify concatenates the imported files. esperanto does this correctly, but lacks other features that make browserify a much better choice.

@romeovs
Copy link
Author

romeovs commented May 20, 2015

@nmn after some testing I can see that it does!

@fregante
Copy link

I think @substack is right in waiting for proper node support. browserify brings node to the browser; if node doesn't support import, browserify shouldn't either.

If babelify doesn't follow the right semantics, raise the issue with them or ask for an esperantify plugin.

@flyingrobots
Copy link

How about now?

@yoshuawuyts
Copy link
Member

yoshuawuyts commented Jan 25, 2016

It appears a good deal of fragmentation will start occurring because of d3@4.0.0 relying on es6 imports. It's not seen an official release yet, but given the buzz around its components I think it's already becoming somewhat of an issue.

I know that Node doesn't support ES6 imports quite yet, but arguably browserify is in a somewhat different spot since it doesn't support dynamic imports (or at least I don't think it does out of the box).

The reason why I think we should consider options is because if other loaders become popular because of only a single feature, it might signal there's a need for it.

I'm not quite sure what the correct resolution is; for the package I'm writing that consumes d3 I might include a compiled build (breaking npm reliance) or include babelify (making the package very heavy) - though neither is ideal. Perhaps an importify transform needs to be written, or perhaps this should be resolved at the browserify level - I'm not sure.

For now I'm posting this here to signal that changes are happening, and we'll probably need to think of a solution for this 😕

edit July 2016: nope nope, we shouldn't move ahead on import unless Node does so (which it frankly might not, so hey)

@terinjokes
Copy link
Contributor

@yoshuawuyts Implying that d3@4.0.0 only works with loaders that support ES6 modules is a bit disingenuous. The main field in the root's package.json and the individual modules all point at Node.js compatible files (and I'm successfully using several d3 modules with Browserify just fine)

@dantman
Copy link

dantman commented Jan 25, 2016

Browserify's greatest feature is the transform pipeline (heck the parts that actually break require into a dependency tree and combine that tree into a bundle aren't even in this repo, they're the module-deps and browser-pack packages) and the collection of transforms we have that make . IMHO rollup's greatest feature is not ES6 imports, but the static analysis and tree shaking it does. So even if browserify rushes to hastily implement non-babel ES6 import support before any JS engine natively supports it, I doubt that'll stop may people from using rollup.

There are already a pile of other packages using ES6 import; but they all do it alongside the use of other ES2015 features. So I expect packages like d3 that only use ES6 import is going to be a rarity. And the moment you use any other feature, you need Babel, whether you're using rollup or browserify. So I doubt "rollup lets me use packages that only use ES6 import without needing Babel" will be the reason people use rollup.

I think the problem browserify is going to have is rollup's plugins. That functionality is what turns rollup from just a tool to pack up ES6 exports/imports into something that can pass as a standalone loader.

Rather than trying to hack ES6 import in with require schematics, just as a way to bypass babel in a few cases (because if you use Babel this is already entirely possible). I think it would be better to accept the area that rollup is better at (tree shaking ES6 imports) and find a way to break up rollup and create a rollupify plugin that will integrate rollup's tree-shaking and also act as an optional ES6 import handler for those that don't want to use babelify.


It's a different topic. But I think browserify's other undoing may be how easy it I've seen it is to make a simple transform but hard it is to make one that correctly preserves source maps. I don't see any easy tools for source-map aware string manipulation. And while I'd love to transform using an AST, that's not viable when each transform has to source -> parse to AST -> transform -> AST to source -> source instead of sharing an AST. At this point, in my own code, I can't tell if my source maps work or not anymore and I have no clue which piece might be at fault for messing with them.

@jokeyrhyme
Copy link

FYI: there's an enhancement proposal for Node.js being discussed here regarding ES2015 modules:

@gregtatum
Copy link

Coming in to say I'm actively getting issues because d3's latest version is only using ES6 syntax. I can't use my browserify tooling now. The time seems ripe for this change as the ecosystem has already begun the move, otherwise I think browserify will have lost its relevance in this increasingly competitive bundling space.

@jmm
Copy link
Collaborator

jmm commented Jul 27, 2016

Hello @TatumCreative. D3's README says their releases support CommonJS. Is that not the case? Even so, there are multiple tools for compiling ES6 module syntax to CJS -- can you not use one of those?

@gregtatum
Copy link

Hmm... Yeah my bad. It looks like user error on my part. Sorry about that. My opinion still stands though. Three.js just landed a big patch to go the ES6 route as well. Personally I'm in a weird position as I tend to primarily target one browser and I want what Browserify does with bundling without the side-effect of mangling my ES6 code and turning it into ES5 code.

I can probably come up with a solution if I do more research, but regardless I still feel it is important to support ES6 imports sooner rather than later, as the community is already adopting it in projects. I feel like fragmentation is happening as people have moved on to other bundling solutions, which I would really not have to do, as I really love the browserify tooling ecosystem. All that said, I probably don't have the time to contribute to this project to make it happen, and I appreciate the work everyone does here!

@terinjokes
Copy link
Contributor

terinjokes commented Jul 28, 2016

@TatumCreative I'm glad that you were able to correct this issue. The contributors to Browserify, including myself, understand your frustration.

Browserify has always been a way to use Node.js-compatible modules in the browser. As a result, Browserify has always looked to Node.js for how to implement the module resolution and loader. Right now Node.js doesn't support ES6 imports; there are blockers in other parts of the stack. We don't yet know the final algorithms or behaviors.

While slower than we would all like, these efforts are moving ahead. I'm excited for the day! Once Node.js has implemented ES6 imports and exports, we can add support to Browserify.

You may wish to opt-in to a possible future, and Browserify's plugin system allows you to do so. But it's not the place for us to make that decision for everyone.


As an aside, there's probably still a few improvements to Browserify to make using standardized ES6 a little more comfortable, even without imports and exports. I'm happy to take a look at those issues. 💖

@jmm
Copy link
Collaborator

jmm commented Jul 28, 2016

@TatumCreative Ok, no problem, thanks for the feedback.

Personally I'm in a weird position as I tend to primarily target one browser and I want what Browserify does with bundling without the side-effect of mangling my ES6 code and turning it into ES5 code.

I understand. That's not that weird of a position, as people increasingly want to selectively transpile parts of ES6. For example, only transpile the parts not natively supported by recent versions of Node. ES6 has no native module bundling story anyway, so even if a browser supported ES6 100% you might still want to bundle them via transpiling just module syntax to ES5.

I think there's probably a solution for you. If I'm not mistaken some people run Rollup to convert module syntax before carrying on with bundling with Browserify, or you could run Babel (Babelify) with just the module transform enabled. With Rollup you could get the benefit of tree shaking as well.

but regardless I still feel it is important to support ES6 imports sooner rather than later, as the community is already adopting it in projects.

Like other people have mentioned, the community is adopting the syntax and speculating about what behavior will be standardized. So it's not necessarily all smooth sailing to implement it now. For example, from version 5 to 6 the core Babel CommonJS module transform made a big change in its output. If you look at the issue linked in the comment before yours up above you'll see that the discussion about how to implement ES modules in Node is epic and very complex.

I feel like fragmentation is happening as people have moved on to other bundling solutions

I think that has more to do with other factors than this.

I appreciate the work everyone does here!

Thanks!

@gregtatum
Copy link

Thanks @jmm and @terinjokes! I think that is very well reasoned, and I agree with where you all are coming from. I'll probably end up adding another tool to help me deal with this until all of this gets sorted out on the node end. I can deal with a little bit more tooling complexity on my end :) I'm almost afraid to read that node module discussion, lol.

Keep on rocking it! 😀

@gregtatum
Copy link

For anyone coming into this thread, the browserify transform rollupify will take all of your ES6 import statements and hoist them all into a single module scope.

@gkatsanos
Copy link

I am assuming I can't use ES6 style imports with browserify and babelify still?

@yoshuawuyts
Copy link
Member

yoshuawuyts commented Jan 4, 2017 via email

@gkatsanos
Copy link

I use browserify in Gulp.. does this change things?:)

@yoshuawuyts
Copy link
Member

yoshuawuyts commented Jan 4, 2017 via email

@gkatsanos
Copy link

What does "not recommended" mean? The end result seems to work?
Should I just drop gulp/browserify and use webpack instead?

@yoshuawuyts
Copy link
Member

yoshuawuyts commented Jan 4, 2017 via email

@gkatsanos
Copy link

Alright. From reading around it seems to me that's the basic syntax is pretty finalized and is the future but I could be wrong of course.

@terinjokes
Copy link
Contributor

@gkatsanos the syntax is stable, but the mechanics of how to do imports (and specifically, how Node.js will do imports) are still being figured out.

Browserify is a set of tools that together bring Node.js's module resolution and loading to the browser. Since Node.js doesn't support importing ES6 modules, we have nothing to implement.

@gkatsanos
Copy link

@terinjokes alright, it's clear. How does browserify compare to webpack in that respect?

@terinjokes
Copy link
Contributor

terinjokes commented Jan 5, 2017

@gkatsanos Being module bundlers, Webpack and Browserify are similar. They take input, resolve a tree of dependencies, and generate a bundle (or bundles) as output.

As described above, Browserify supports only require at the moment. Webpack supports the illusion of other module types, including AMD and ES6, but the resolution algorithm is the same. If you used AMD modules expecting dependencies resolution according to AMD's rules, you'd be surprised to learn that it instead uses the Node.js require rules!

Neither project knows how Node.js will handle ES6 resolution and loading. There have been discussions of using separate file extensions or looking up a key in the module's package.json. We'll have to wait and see gets implemented.

Webpack's plugin system would likely allow them to pivot and allow developers to configure which behavior they want. Browserify prefers waiting for Node.js to decide and not have the need for configuration or the fears of breaking user applications.

@mattdesl
Copy link
Contributor

mattdesl commented Jul 9, 2018

I'm curious what the current feel is on this, now that ES6 modules are in browsers (at least in some form). I've also noticed more modules on npm being published with code that works fine in Webpack/Rollup/Parcel (and other ES6-able bundlers) but does not work in Browserify.

I personally don't like import/export but if the rest of the community is using it, and browsers are adopting it, I feel Browserify should definitely follow suit, even if Node.js hasn't fully adopted it yet.

@ljharb
Copy link
Member

ljharb commented Jul 9, 2018

I don't agree; until node supports it unflagged, browserify shouldn't support it.

Browsers only currently support URLs with import, and node doesn't support URLs with import.

@gkatsanos

This comment has been minimized.

@mattdesl
Copy link
Contributor

mattdesl commented Jul 9, 2018

@ljharb Makes sense.

Another option: what about allowing import syntax and just ignoring it? Or perhaps providing a warning that it was skipped? I'm enjoying the new dynamic import() support in browsers (which isn't possible to fully re-create with a bundler), but I can't use that syntax currently in browserify.

In the mean time I am considering writing a small transform to handle this transparently so users don't need to deal with babel/config/etc.

EDIT: I just realized browserify actually supports dynamic import() already, it only fails on static import/export. So maybe no need to change browserify yet.

@ljharb
Copy link
Member

ljharb commented Jul 9, 2018

(dynamic import() is also available in Scripts, import is only available in Modules)

@ljharb
Copy link
Member

ljharb commented Jul 9, 2018

It wouldn't be safe to treat a Module as if it was a Script - modules are in auto-strict mode, for examples, and there's a number of things that could behave differently.

@mattdesl
Copy link
Contributor

For the mean time, I've created a browserify plugin that:

  • Adds .mjs extension to browserify so that it takes precedence over .js
  • Use "module" field in package.json (when "browser" is not specified)
  • Transform ES Module import/export syntax into CommonJS so that it can be consumed & used by browserify

See here:
https://github.com/mattdesl/esmify

@mukururo

This comment has been minimized.

@mukururo

This comment has been minimized.

@Swoorup
Copy link

Swoorup commented Nov 14, 2018

Since now its in experimental state in node? What is the state of this?

@ljharb
Copy link
Member

ljharb commented Nov 14, 2018

Experimental means it’s too soon for it to be used. Browserify should not support ESM until it’s supported unflagged in node; and once it is, imo browserify must support it.

@fregante
Copy link

ES Modules are no longer experimental in Node 13.2 and are supported natively.

nodejs/node@796f3d0
https://github.com/nodejs/modules/blob/master/doc/unflagging-announcement.md

@ljharb
Copy link
Member

ljharb commented Dec 14, 2019

They remain experimental, they’re just unflagged.

However, it means the clock has started, so it would be good to begin working on the necessary changes - although best to leave them unmerged/unreleased for now.

@katiejanekilian
Copy link

It looks to me like the discussion here is directly related to the problem I'm experiencing as outlined on my question here at StackOverflow: Browserify --standalone with ES6 modules and multiple source files and exports

In short, Browserify does not support ES6-style modules using the import syntax with the --standalone option.

Is that correct? If so, it's going to require some big-ish changes to either my code or my build pipeline, and I would like to make sure I'm understanding this properly before I do.

@ljharb
Copy link
Member

ljharb commented Jan 8, 2020

I would expect that currently browserify, with every option, requires a code transform first to convert from ESM to CJS before being able to continue. In other words, use Babel.

@katiejanekilian
Copy link

I am already using babel via the babelify transformation. Should that work?

I have a minimal example to reproduce my problem on my StackOverflow question.

What I'm seeing is, browserify creates the expected API module in its output, but it includes exports from only one of the input source files. See pastebin here (Chrome pretty-printed version of the same thing here); source files are a.js and b.js, each of which export a single function. When running in the browser, the TestModule created by Browserify includes exports from all the source files in its definitions. But for some reason, the TestModule only gets the very last module that was included in those definitions. So in my example, TestModule only includes the fromB() function exported in b.js. TestModule does not end up with the fromA() function exported in a.js.

I thought the issue discussed here might be related to my problem, since I'm defining the exports using the ES6 syntax. But maybe since I am already using babelify, this is different? From the gulpfile:

function minimalExample(done) {
    return browserify({
            entries: [
                './src/a.js',
                './src/b.js'
            ],
            standalone: 'TestModule'
        })
        .transform('babelify')
        .bundle()
            .on('error', log.error)
        .pipe(source('minimalExample.js'))
        .pipe(plumber())
        .pipe(gulp.dest('./dist'));
}

@katiejanekilian
Copy link

BrowserifyModuleTest.zip

Here is a complete example of the problem I am having. Unzip the file into its own folder. I use yarn, so 'yarn install' followed by 'yarn build' creates output files in the dist/ folder. Open test.html in a browser, then go to the console to see the output.

Let me know if this helps, or if there is a better way of sending this.

@goto-bus-stop
Copy link
Member

If you have multiple entry points, all of them are executed, but browserify doesn't merge modules, which could cause bad unexpected behaviour. Usually multiple entry points are used primarily for things like polyfills, where you have one or more modules that do not export anything, and then one module that does:

entries: [
  './polyfills.js', // no exports
  './app.js', // yes exports
]

or with browserify plugins that then split each entry point's dependency tree into separate bundles at a later stage, so each bundle only ends up with a single entry point in the end.

If you want to expose multiple modules as one object, you need to combine them in a separate module:

// main.js
export * from './a.js'
export * from './b.js'

and then use that as the entry point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests