Weak dependencies with `import`

A very handy feature of Meteor’s package system is the ability to have weak dependencies.

If package posts has a regular depency on package users, then loading posts will always imply loading users.

On the other hand a weak dependency on users means that if users was going to be loaded anyway (because, say, comments depends on it) then it’ll be loaded before posts. But if users was not going to be loaded, the weak dependency won’t require it to load.

Here’s an example straight out of Nova: the nova:users package has a weak dependency on nova:email. If nova:email is present, nova:users will use it to send notifications (say, email.send(...)) when new user accounts are created. If nova:email is not present on the other hand (because you’ve run meteor remove nova:email for example), nothing will happen.

Now with the move away from globals and towards using import, this doesn’t work anymore. In the previous scenario, you’d have to do import email from 'meteor/nova:email' in order to use email.send(), and that sets a hard dependency (i.e. it’ll throw an error if it can’t find nova:email).

This seems like a problem at the NPM level, but I’m curious to know if Meteor has a solution in mind? Or should we just give up on weak dependencies altogether?

2 Likes

If you use require instead of import, you can just catch this error with try/catch and handle it like anything else, that way you can decide what to do if the module isn’t present.

I think… I’m not 100% sure that it works this way.

Edit: Now that Meteor supports nested imports, I think you should be able to do it with the import syntax as well.

Huh, I didn’t know about nested imports, pretty cool! But is that something that’s supported by the “standard” import, or is it idiosyncratic to Meteor? Because our whole reason for moving to import in the first place was to be more compatible with the rest of the JS ecosystem and be able to use tools like Storybook and Webpack.

Using require might work though. But in a post-Meteor-packages world, I wonder how you’d specify a weak dependency in your package.json file…

Oh also the 1.3.3 release notes mention that:

this new compilation strategy is enabled by default for application code, but not yet enabled for Meteor package code. Modules in Meteor packages will continue to work exactly as they did in Meteor 1.3.2.4, so that Meteor package authors can easily publish their packages for pre-1.3.3 apps.

Is that changed in 1.3.4? Can we use nested imports in package code as well now?

Found a lot more info about nested imports here: https://github.com/benjamn/reify/blob/master/WHY_NEST_IMPORTS.md

It does sound like a great feature, I hope it gets adopted by the rest of the Node ecosystem soon!

There is such a thing as an optionalDependency in npm…

1 Like

I think that the fact that Ben’s reify package was created on May 3 and has already garnered 316 stars on github says a lot. That is a lot of attention in a short time for a very technical package. That package brings import not just to meteor, but to the node ecosystem.

Supporting nested imports is the only way to eliminate the odd mixture of two module systems, imports and requires, in the same file. This is an important and necessary move for node as es6 eliminates this battle of modules.

Of course, there will be growing pains. One of the problems you’ll encounter with conditional imports is that ESLint will throw a syntax error and ignore the rest of the module. These types of problems are just things that we have to work through when on the bleeding edge.

Luckily, the underlying parsers for ESLint, “acorn” when using espree and “babylon” when using babel-eslint, both support an allowImportExportEverywhere option. The maintainers of babel-eslint already accepted a PR to expose it (released in babel-eslint 6.1.0) so that we can avoid this pain.

To get ESLint to work with nested imports and exports (yes, don’t know why you would need them but conditional exports work too with Ben’s changes), just run

meteor npm install --save-dev babel-eslint

and add the following into your ESLint configuration in package.json or .eslintrc.json:

  "parser": "babel-eslint",
  "parserOptions": {
    "allowImportExportEverywhere": true
  },
3 Likes

Sorry to revive old thread but I landed here from google and ended up finding a better answer. With Meteor 1.5 out and more folks googling for import help (static and dynamic) I thought I’d add something

if you’ve declared a weak dependency you can check for the presence of the package on the Package global using: Package['foo:bar'] and using a nested import statement

if (Package['foo:bar']) {
    import { baz } from 'meteor/foo:bar';
    baz();
}

If eslint complains about a nested import you can swap the parser to babel-eslint and set allowImportExportEverywhere to true in your .eslintrc:

"parser": "babel-eslint",
  "parserOptions": {
    "sourceType": "module",
    "allowImportExportEverywhere": true
  }
4 Likes