Will Meteor 1.3 output CommonJS modules?

@benjamn Will Meteor 1.3 compile Meteor packages into code that follows the CommonJS module semantics? So they just use require to get their dependencies. And they export via module.exports. This is would be very useful to use standard testing tools, like Jest with Wallaby for unit testing. Otherwise I’m forced to load all Meteor packages before I can run the tests and that’s slow.

I understand that some Meteor package code is coupled to Meteor. But that wouldn’t be a big problem, because I can mock packages.

Well, I think BABEL will do this, just try it: https://babeljs.io/repl/. Meteor’s module system from 1.3 will then use the output, which ist based on require syntax. Please correct me, if I’m wrong.

I’m no expert, but how is anything else (than CommonJS) possible, if we’re transpiling with Babel, and our browsers don’t support true ES6 modules? :smile:

I’m positive that we’re building to CommonJS.

I think right now in the beta, packages still get their dependencies via the Package global. You can see that when you look at the built files in .meteor/local/build. So the Meteor build tool does some transformation and concatenation after Babel compiling.

my web-browser build’s app.js goes a little something like this at the end:

...
exports.default = Rooms;                  //
////////////////////////////////////////////

}}}}});

require("./client/view/layouts/navigation/authButton.html");
require("./client/view/components/authModal.html");
require("./client/view/layouts/navigation.html");
require("./client/view/pages/home.html");
require("./client/view/pages/notFound.html");
require("./client/view/pages/room.html");
require("./client/view/pages/rooms.html");
require("./client/view/document.html");
require("./client/semanticUI/definitions/behaviors/api.js"); <------------
require("./client/semanticUI/definitions/behaviors/colorize.js");
require("./client/semanticUI/definitions/behaviors/form.js");
require("./client/semanticUI/definitions/behaviors/state.js");
require("./client/semanticUI/definitions/behaviors/visibility.js");
require("./client/semanticUI/definitions/behaviors/visit.js");
require("./client/semanticUI/definitions/globals/site.js");
require("./client/semanticUI/definitions/modules/accordion.js");
require("./client/semanticUI/definitions/modules/checkbox.js");
require("./client/semanticUI/definitions/modules/dimmer.js");
require("./client/semanticUI/definitions/modules/dropdown.js");
require("./client/semanticUI/definitions/modules/embed.js");
require("./client/semanticUI/definitions/modules/modal.js");
require("./client/semanticUI/definitions/modules/nag.js");
require("./client/semanticUI/definitions/modules/popup.js");
require("./client/semanticUI/definitions/modules/progress.js");
require("./client/semanticUI/definitions/modules/rating.js");
require("./client/semanticUI/definitions/modules/search.js");
require("./client/semanticUI/definitions/modules/shape.js");
require("./client/semanticUI/definitions/modules/sidebar.js");
require("./client/semanticUI/definitions/modules/sticky.js");
require("./client/semanticUI/definitions/modules/tab.js");
require("./client/semanticUI/definitions/modules/transition.js"); <------------
require("./client/view/layouts/navigation/authButton.js");
require("./client/view/components/authModal.js");
require("./client/view/layouts/navigation.js");
require("./client/view/pages/room.js");
require("./client/view/pages/rooms.js");
require("./client/view/helpers.js");
require("./client/helpers.js");
require("./client/router.js");

Semantic UI is a Meteor package, and is clearly imported as a numer of modules… But that might be a special case, as SemanticUI generates client side files…?

Partially.

Packages still need to be backwards compatible, so the final linked version (try view-source on a package’s JS in 1.3) is still an IIFE with regular Meteor imports (Package global, local vars for file-level “globals”), etc.

However, if your package doesn’t use the old API - api.use()/export() - and instead relies solely on import and export, I think you’ll be ok. I never tried though :smile: It will still be inside an IIFE and you might need to stub a Package global to avoid a ReferenceError, but it uses the new meteorInstall for deps, which is based on CommonJS - each file is wrapped in a function(require,exports,module) and receives deps via this require.

Check out e.g.

However, see also:

i.e. MDG doesn’t want to be tied to CommonJS in the long term.

1 Like

This is an excellent & accurate summary, @gadicc! Thanks for making my job easier :smiley:

It is definitely true that Meteor package code, though implemented using a CommonJS module system, is eagerly required before your application code has a chance to import it.

While that behavior is important for backwards compatibility, Meteor 1.3 is introducing a new package.js API, namely api.mainModule, so we might have an opportunity to allow lazy Meteor packages in a backwards compatible way. Specifically, api.mainModule could take an options object, like so:

api.mainModule("client.js", "client", {
  lazy: true
});

If the main module is lazy, we would avoid emitting an eager require("/node_modules/meteor/<package name>/client.js") call at the end of the package bundle, and your application code would become responsible for importing meteor/<package name> when needed.

Of course this would mean the package might never be loaded at all, but that sounds more like a feature than a pitfall. Also, you could still define other eager entry point files using api.addFiles.

How does that sound?

4 Likes

I think based on that information and what I have seen in the code, the Jest auto-mocking feature will not work with Meteor packages, because it only works with CommonJS modules.

It would be nice if the build tool could output Meteor packages as real CommonJS modules at some point, just for the use case of using those packages outside of a Meteor app.

For now I will just load all packages like the Meteor start script does and run the tests after the packages have been loaded. This is a bit slower, but works ok.

Can’t you also use require instead of import? So in your code you could do { Tracker } = require('meteor/tracker'); and then it would work with Jest?