RequireJS: bundling module with plugins - javascript

I'm using an external library that consists of a "core" plus multiple "extensions". Every extension depends on the core. Think jQuery or Rx.
What I need to do is to bundle the core together with some of the extensions and provide that as a single module. On the surface, it seems that something like this should work:
// lib.js
define(
"lib",
["./Lib/lib", "./Lib/ext1", "./Lib/ext2"],
function(lib) { return lib; }
);
The problem, however, is that extensions expect the "core" to be available by the module ID of "lib". In other words, "ext1" is defined like this:
// Lib/ext1.js
define( ["lib"], function(lib) { lib.ext.someFunc = ... } );
One can spot the problem here: because the name "lib" refers to my "bundled" module instead of just the "core", it is not yet available at the time ext1 loads, so the whole chain becomes circular and falls apart.
Of course, I could map the core to "lib" and then give my bundled module a different name:
// main.js
require.config( { paths: { lib: "Lib/lib" } } );
// lib.js
define(
"bundled-lib",
["./Lib/lib", "./Lib/ext1", "./Lib/ext2"],
function(lib) { return lib; }
);
But that approach is highly undesirable for a few reasons:
It's just plain inconvenient to use a different name. There is no good common sense name that I could use instead, "lib" is pretty much the only option, and anything else will look ugly.
But more importantly, this may lead to hard-to-catch bugs. Down the road, when I have forgotten all about this little hack, I may just follow my common sense and import "lib" instead of "bundled-lib", and then my extensions will not get loaded. Or sometimes they will. If some other module which correctly imports "bundled-lib" just happens to load before the new "lib"-importing module, then it will work. Otherwise, it won't. Meaning that my application will either crash or not depending on whether certain features have or have not been used.
So the bottom line is, I would like to bundle the core with extensions, call the bundle "lib", but somehow have the extensions to only import the core, while not modifying the extensions themselves.
Any ideas anyone?

This sounds very similar to jQuery and jQuery plugins. Seems like you have an understanding of the implications, so you just need to make a decision on which method is preferred.
I would not go with a module that returns 'lib', which already has those extensions. If you feel feel that you only need single dependency where you reference extended lib, just go with your 'bundled-lib' approach.
To stick with best practices and not confuse yourself in a future I believe it would be best not to bundle, but for those modules where you rely on extensions, include dependency to your core lib and those extensions:
define(['lib', 'ext1'], function(lib){
// module definition...
});
This way it is VERY clear what are dependencies for this module. I'm sure you thought about it and I just hope it help you with making a decision.

So just a few hours after asking the question, I have found the answer on my own.
The answer is - the map config of RequireJS.
Basically, as it turns out, I can define a mapping of module names as seen by each module individually.
In particular, my problem as it is described in the question, would be solved by the following configuration:
require.config( {
map: {
'ext1': { 'lib': 'Lib/lib' }
'ext2': { 'lib': 'Lib/lib' }
}
} );
This will make both ext1 and ext2 see module 'Lib/lib' as 'lib', while not affecting all other modules.

Related

Exporting outside webpack

This is just something I thought today and I didn't see a lot of information so I'm going to share this weird cases and how I personally solved them (if there's a better way please comment, but meanwhile this might help others ^^)
In a regular module, you would do something like this to export your function/library/object/data:
// regular NodeJS way:
module.exports = data;
// ES6 way
// (will get transpiled to the regular way using the module variable by webpack)
export data;
default export data;
When compiling the library usually babel or tsc are used, but if for any reason you want not only to compile (transpile) your library but also pack it using webpack, you will encounter this case.
As you know, in a webpack bundle the module variable is local to the bundle (every module/file gets wrapped with a function where module is a parameter = local variable), so nothing really gets exported outside the bundle, is just nicely managed by webpack.
That means that you can't also access the contents using the regular require/import methods.
In some case you might find necessary to export outside webpack. (i.e. you are trying to build a library using webpack and you want it to be accessible by other people). This basically means you need to access the original module variable, but webpack doesn't expose it like it happened with __non_webpack_require__.
See also: Importing runtime modules from outside webpack bundle
The solution is to create our own __non_webpack_module__ (as webpack does with __non_webpack_require__.
How I did it is using webpack.BannerPlugin to inject some code outside the bundle. This code is prepended to the build after the minification is done, so it's preserved safely.
In your webpack.config.js:
plugins: [
new BannerPlugin({
raw: true,
banner: `const __non_webpack_module__ = module;`,
}),
]
And again, if you are using TypeScript, in global.d.ts:
declare const __non_webpack_module__: NodeModule;
And now, you can do something like this in your code:
__non_webpack_module__.exports = /* your class/function/data/whatever */
This will allow to import it as usual from other files
Tip: You might want to look at BannerPlugin to check other options, like include or exclude so this variable is only generated on the desired files, etc.

How do I properly include JS libraries using Webpack?

I am quite new to Webpack and I am using it to bundle the assets used for a static website I am creating. This website uses multiple JS files from npm packages like
fullpage.js and parallax.js. I need to know how I can include these JS files using Webpack.
What I have tried
The first option I tried was import JS files via app.js
app.js
import './style.css';
import '../node_modules/fullpage.js/dist/jquery.fullpage.css';
window.$ = require('jquery');
window.$ = require('fullpage.js');
window.$ = require('parallax-js');
require('./index.js');
index.js
var scene = document.getElementById('scene');
var parallaxInstance = new Parallax(scene,{
frictionX: 0.1,
frictionY: 0.1
});
$(document).ready(function() {
$('#fullpage').fullpage({
recordHistory: false,
scrollBar: true,
});
});
However, since parallax-js is not imported in index.js, this doesn't work. Chrome debugger says that Parallax is not defined -- index.js
I can overcome this by moving the require section to the index.js but that doesn't seem clean.
My second option was to import these via webpack.config.js which seems to me is the correct approach.
webpack.config.js
module.exports = {
entry: {
main: './src/app.js',
libs: [
'./node_modules/parallax-js/dist/parallax.js',
'./node_modules/fullpage.js/dist/jquery.fullpage.js'
]
}
}
I included both the above created JS files (libs.js and main.js).
However I am still getting Parallax is not defined -- index.js
Which of the above two methods are correct? Is there a better way to achieve this?
I've read through a lot of documentation and articles but I could not find a proper answer for this very basic question which is why I am posting this here, wondering if I am missing something big.
Thank You.
Yep, you're missing something conceptually. You're still trying to use the global context (& window object). Webpack doesn't want you to work that way. Webpack wraps everything up and puts everything in modules.
You need to define your dependencies in every file you use them in. Then, you let webpack figure out how to bundle everything together. It's actually not messy to define dependencies per file; it's the right way of doing things, and the way every other programming language does it.
I would also recommend switching from require syntax to import syntax, as it helps you to think of it as a declaration, not a function call.
It might be helpful to poke around an ES6 tutorial/project to learn the new syntax and approach. It's a fairly major shift, and particularly difficult for existing JS developers who aren't drawing from experiences in other languages.
You may find this tutorial (or others) helpful: https://www.youtube.com/watch?v=lziuNMk_8eQ

How to use ember.js without module support

The guides for ember.js are assuming one has the full ES6 support e.g. http://guides.emberjs.com/v2.2.0/routing/specifying-a-routes-model/ shows using the export default construct and doesn't specify any alternative way to achieve the goals. However the module feature is not implemented in all browsers that ember is supporting.
How can I use these features with a browser that doesn't support modules? How would the code in these examples translate to ES5?
Documentation assumes you are using a transpiling tool, because the recommended tool, ember-cli does. Unless you have good reasons not to use it, you definitely should look into it.
It is, however, perfectly fine to work without it. For instance, without a module system, Ember will map controller:posts.index to App.PostsIndexController. So this should work for the example you linked:
App.Router.map(function() {
this.route('favorite-posts');
});
App.FavoritePostsRoute = Ember.Route.extend({
model() {
return this.store.query('post', { favorite: true });
}
});
You may also use Ember with your own module support. I successfully have an Ember project based on rollup. It does require a bit more work though, to have the resolver find your classes (that resolver link also documents how ember relates does the name mapping). Nothing hard, but you must build a short script to generate registrations.
Edit for blessenm: Ember with rollup
Unfortunately I cannot share this code, but it works like this:
A script scans the project directory and compiles templates by invoking ember-template-compiler.js on every .hbs file it encounters.
A script (the same one, actually) scans the project directory and generates the main entry point. It's pretty simple, if it sees, say gallery/models/collection.js and `gallery/routes/picture.js', it will generate a main file that looks like this:
import r1 from 'gallery/models/collection.js';
import r2 from 'gallery/routes/picture/index.js';
// ...
Ember.Application.initializer({
name: 'registrations',
initialize: function (app) {
app.register("model:collection", r1);
app.register("route:picture.index", r2);
// ...
}
});
It should just map your filenames to resolver names. As a bonus, you get to control how your directories are organized.
Invoke rollup on the generated file. It will pull everything together. I use IIFE export format, skipping all the run-time resolution mess. I suggest you setup rollup to work with babel so you can use ES6 syntax.
I don't use any ember-specific module, but it should not be too hard to add. My guess is it's mostly a matter of setting up rollup import resolution properly. For all I know, it may work out of the box.
You should look into using Ember CLI http://ember-cli.com/
You write your code in ES6 and it transpiles down to ES5.

If I optimize my RequireJS project using r.js, do I have to change the path and dependency configuration?

I'm new to RequireJS. I understand it for the most part. However, the r.js optimization process confuses me. Two questions:
Doesn't concatenating all source into a single file defeat the purpose of RequireJS's lazy-loading abilities?
If I do optimize using r.js and have everything in a single file, do I then have to manually update the path info in the config to point to that single file? And do the dependencies I've defined as individual modules have to now be changed throughout the entire application to point to this single file? Here's just a pretend source to illustrate how I'm currently setup:
requirejs.config({
paths : {
mod1 : 'app/common/module1',
mod2 : 'app/common/module2',
mod3 : 'app/common/module3',
},
});
-- MOD 1
define(["mod2", "mod3"], function(mod2, mod3) {
// do something
}
Does that now have to be manually updated after optimization to look like this?
requirejs.config({
paths : {
optimizedMod : 'build-dir/optimizedModule',
},
});
-- MOD 1
define(["optimizedMod"], function(optimizedMod) {
// do something
}
Re. 1. No, it doesn't. r.js analyzes your dependency tree and (by default) only includes modules you'd need to load on application startup anyway. The dependencies that are required dynamically won't be included, they'll be lazy-loaded at runtime (unless you set findNestedDependencies to true).
However, lazy-loading is arguably not the main benefit of using RequireJS, a bigger thing is modularisation itself. Being forced to manage dependencies makes it harder to write code that's not testable or refactorable - bad architecture can be immediatelly spotted (lengthy dependencies lists, "god" modules, etc.)
Re. 2. This is precisely the reason why you shouldn't be naming your own modules or mapping them in the paths configuration element. paths should be used for third party libraries and not your own code, centralising name->path mappings reduces flexibility. Once you refer to dependencies via their paths (relative to baseUrl) r.js can rewrite them at build time:
define(["app/common/module2", "app/common/module3"], function(mod2, mod3) {
// do something
}

Use r.js to package a library written using RequireJS for reuse in other RequireJS applications

So lets say I have some small bit of library code that I develop and test in isolation. I use RequireJS during development and have a root level file that depends on 1 other file. So it's define looks something like...
// lib/main.js
define(['lib/dep1'] function(dep1) {
...
})
I run r.js on the code which results in dist/myLibrary.js, which looks something like this:
define('lib/dep1',[], function(){...})
define('lib/main',["lib/dep1"], function(dep1){...})
If I pull myLibrary.js straight into another project it won't work. Nothing is defining itself as the module for that file. But if I append an actual module definition, it works.
define('lib/dep1',[], function(){...})
define('lib/main',["lib/dep1"], function(dep1){...})
define(['lib/main'], function(lib) {
return lib;
})
And the ['lib/main'] seems to be scoped to the module, because if I have an actual lib/main in my app, it doesn't get used.
Questions:
Regarding the scoping, is that the normal behavior? The fact that lib/main is recognized as a module id from the same file rather than going to look for it someplace else. If I import 10 such libraries that all have a lib/main, they won't collide?
Is there a better way? I am at least initially unconcerned about supporting the non-AMD use case as this is all internal lib development and we all use RequireJS. So within a fully AMDed environment, is there another, better way to do this? Assuming there's no pitfall to this approach, it seems fairly simple and boilerplate to support.

Categories