最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Support for ES6 imports in ES5 module - Stack Overflow

programmeradmin1浏览0评论

For my 1st year students, I have provided a simple ES5-based library written using the Revealing Module Pattern. Here is a snippet of the "main" module/namespace, which will house other extensions:

window.Library = (function ($) {
    if (!$) {
        alert("The Library is dependent on jQuery, which is not loaded!");
    }

    return {};
})(window.jQuery);

This works for pretty much 99.9% of the students who are new to web-development and are not using fancy things like ES6 in bination with Webpack or Babel.

The 0.1% has now requested me to provide an ES6 based version, which can be imported properly. I'd be happy to provide this, but I'm kind of stuck on how to best approach this.

I obviously want to keep the ES5 way, so my students can just include the file using a script-tag and type Library.SomeExtension.aFunction(); where-ever they please. On top of that, some of the extensions are reliant on jQuery, which gets injected in a similar way as the snippet above.

I'm now looking for some maintainable way to get the best of both worlds, with one code-base, with jQuery as a dependency. I want to give 99.9% a window.Library, whereas I want to give the 0.1% a way to use import Library from 'library'.

Can I acplish this with a single JS file that does both? Or would I need a special ES6 version (not a problem to do)? And most of all: How would I reshuffle my code (all similar to above snippet) in such a way I can support both situations?

Any and all pointers will be greatly appreciated!

EDIT: Just as a side note, I already have a gulpfile.js in place which runs this library through Babel, minifiers and other things. So having to extend that to solve the above problem is not a problem!

For my 1st year students, I have provided a simple ES5-based library written using the Revealing Module Pattern. Here is a snippet of the "main" module/namespace, which will house other extensions:

window.Library = (function ($) {
    if (!$) {
        alert("The Library is dependent on jQuery, which is not loaded!");
    }

    return {};
})(window.jQuery);

This works for pretty much 99.9% of the students who are new to web-development and are not using fancy things like ES6 in bination with Webpack or Babel.

The 0.1% has now requested me to provide an ES6 based version, which can be imported properly. I'd be happy to provide this, but I'm kind of stuck on how to best approach this.

I obviously want to keep the ES5 way, so my students can just include the file using a script-tag and type Library.SomeExtension.aFunction(); where-ever they please. On top of that, some of the extensions are reliant on jQuery, which gets injected in a similar way as the snippet above.

I'm now looking for some maintainable way to get the best of both worlds, with one code-base, with jQuery as a dependency. I want to give 99.9% a window.Library, whereas I want to give the 0.1% a way to use import Library from 'library'.

Can I acplish this with a single JS file that does both? Or would I need a special ES6 version (not a problem to do)? And most of all: How would I reshuffle my code (all similar to above snippet) in such a way I can support both situations?

Any and all pointers will be greatly appreciated!

EDIT: Just as a side note, I already have a gulpfile.js in place which runs this library through Babel, minifiers and other things. So having to extend that to solve the above problem is not a problem!

Share Improve this question edited Dec 5, 2019 at 14:54 ghovat 1,0431 gold badge13 silver badges38 bronze badges asked Nov 25, 2019 at 23:48 Lennard FonteijnLennard Fonteijn 2,6512 gold badges26 silver badges39 bronze badges 5
  • Probably best solved by using ES6 yourself and a transpiler/bundler that generates the global revealing module. Then offer your source module to the 0.1%. so yes, a single source file, but two distributed files. – Bergi Commented Nov 26, 2019 at 0:53
  • I'm hoping for an answer to Can I acplish this with a single JS file that does both? - if this is possible, which it might not be, what would the script look like? – Snow Commented Nov 30, 2019 at 7:23
  • @Snow Currently playing around with the resources provided in the answers. Have to spend a bit more time getting something I'm satisfied with, but it seems possible to do if you are a bit creative building your scripts! In my particular case, I really want to keep the Library.SubLibrary.functionName setup and also in separate files, even if someone imports it as ES6 or whatever else. (This is mostly because extensions can use each other as well and using a standard ES6 setup this would cause circular references). – Lennard Fonteijn Commented Nov 30, 2019 at 14:08
  • Yep, it's trivial to acplish this using separate files: have one use export, and have the other assign to window. I was thinking it would be great if both could be done in a single file, since that'd feel so much more elegant, but the export keyword looks to only work inside a type=module. I don't know if there's a workaround, or if it's just impossible. – Snow Commented Dec 1, 2019 at 0:17
  • @LennardFonteijn Circular dependencies work totally fine with ES6 modules, just make sure that you only declare exported functionality and do not run initialisation code (which depends on module evaluation order) on the top level. I would remend against using nested namespace objects, that's pretty unidiomatic in ES6. – Bergi Commented Dec 2, 2019 at 0:35
Add a ment  | 

3 Answers 3

Reset to default 5 +50

After a few nights of moving code around and installing so many gulp packages I don't even remember all the ones I've tried, I eventually settled on something I'm semi-happy with.

First, to answer my own question, which was also backed up by @Bergi in the ments:

Short answer: No, you cannot, in any way (right now), mix ES6 syntax with ES5 (modules) in a single file because the browser will error out on the use of export.

Long answer: Technically you can set your script-element type to "module", which will make browsers accept the export keyword. However, your code will simply run twice (once on load, and once after importing). If you can live with this, then the no bees a yes, kinda.

So what did I end up doing? Let me put up front I really wanted to keep the IIFE Module setup I had (for now) in the output ES5 file. I updated my code-base to pure ES6 and tried all kinds of binations of plugins (including Rollup.js mentioned by @David Bradshaw). But I simply couldn't get them working, or they would pletely mangle the output in such a way the browsers couldn't load the module anymore, or drop sourcemap support. With the project the students are doing right now drawing to a close, I already wasted enough of my spare time for the 0.1% of my students. So better luck next year for a "nicer" solution.

  1. I based my UMD pattern (as mentioned by @Sly_cardinal) on jQuery and threw that code into a Library.umd.js. For the ES6 part, I made a Library.es6.js with only a export default Library in it.

  2. In both files, I placed a multiline-ment /*[Library.js]*/ where I wanted to inject the concatenated un-minified Library.js.

  3. I modified my existing Gulp task to just build the Library.js with concat and Babel and stored it on a temporary location. I chose to sacrifice source-map support in this process, as the concatenated code was perfectly readable in the output. Technically source-map support "worked", but it would still throw off the debugger too much.

  4. I added an additional Gulp task to read the contents of the temp Library.js, then for each of the files from Step 1, I would find and replace the multi-line ment with the contents. Save the resulting files, and then throw them through the final concat (eg. bundle with jQuery), terser and sourcemap generation.

The end result is two files:

  • One with UMD/ES5-browser support
  • One with ES6 support

While I'm happy with the result, I don't like the Gulp task chain and the loss of sourcemap support in the first half of the process. As said earlier, I've tried numerous gulp plugins to achieve the same effect (eg. gulp-inject), but they either didn't work properly or did weird things to the sourcemap (even if they claimed to support it).

For a next endeavour I might look into something different than Gulp, or whip up my own plugin which does the above, but properly :P

You can use Rollup.js to bundle your code as an ES5 library as well as an acpanying ES6 module.

Rollup has built-in support for Gulp so something like this would be a reasonable starting point (based on Rollup's Gulp example):

    const gulp = require('gulp');
    const rollup = require('rollup');
    const rollupTypescript = require('rollup-plugin-typescript');

    gulp.task('bundle', () => {
      return rollup.rollup({
        input: './src/main.ts',
        plugins: [
          rollupTypescript()
        ]
      }).then(bundle => {
          return Promise.all([
              // Build for ES5
              bundle.write({
                file: './dist/library.js',
                format: 'umd',
                name: 'Library',
                sourcemap: true
            }),

            // Build for ES6 modules
            bundle.write({
                file: './dist/library.esm.js',
                format: 'esm',
                sourcemap: true
            })
          ]);
      });
    });

You can see more information on the different output formats that are available: https://rollupjs/guide/en/#outputformat

It's generally easiest if your library source is written using ES modules and then bundled/transpiled down to the ES5 bundle.

Their is the Universal JS Loader Patten that allows you to export your code in multiple different ways.

;(function (root, factory) {

  if (typeof define === 'function' && define.amd) {
    define(factory);
  } else if (typeof exports === 'object') {
    module.exports = factory();
  } else {
    root.YourModule = factory();
  }

}(this, function () {
  return {};
}));
发布评论

评论列表(0)

  1. 暂无评论