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!
- 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 towindow
. 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 theexport
keyword looks to only work inside atype=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
3 Answers
Reset to default 5 +50After 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.
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 aLibrary.es6.js
with only aexport default Library
in it.In both files, I placed a multiline-ment
/*[Library.js]*/
where I wanted to inject the concatenated un-minified Library.js.I modified my existing Gulp task to just build the
Library.js
withconcat
andBabel
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.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 finalconcat
(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 {};
}));