Shared methods with module import

which is in a file shared between client and server

It looks like GameManager isn’t importable within the client because you’ve got it living under /server/. Is that true?

No need to ping me! Just read the Methods article of the meteor guide, and the security article about hiding secret code and you’ll know where I stand.

Basically the method should be defined in common code, and then you import the secret logic from a server only file. So you still get to ability to do a client simulation, but the backend logic isn’t shipped to the client.

2 Likes

Has anyone been able to ES6-import server only code without sending it to the client? From what I see in the guide it seems you have to use a eagerly-loaded global (eg. put the secret code in /server/) to be able to use the secret code on the server only, in a shared method. Is that so or am I missing something?

I’ve found two ways;

  1. Create two methods with the same name, import one from the server and the other from the client. Then only import server modules in the server method.

  2. Create a shared method, import it from both server and client. Then import server modules in a server only file and make them global, so the objects can be reached from an isSimulation conditional without importing them to the shared method-module (which would otherwise send the server code to the client).

The ideal case would be to be able to have a shared method without using globals for the server-only code. Not only for concealing proprietary code, but sending server-only code to the client just makes the client app bigger without any benefits.

You can do conditional imports:

if (Meteor.isServer) {
  import { secretCode } from './server/secretCode';
}

Or something like that.

1 Like

If you do conditional imports, the code is not executed but it is still shipped to the client.

Didn’t think of it before but if you put the secret code in a “server” folder it won’t be shipped to the client. I didn’t realize “server” folders are honored as server-only in the new “/imports” folder, but they are.

In all the confusion, the correct way is:

  1. Put all secret code in “server” folders. The folders can be located anywhere in the dir tree.
  2. Use if (Meteor.isServer) {import...} to import the modules (the app will anyways not compile if you don’t, since the client would try to import a module that does not exist.
2 Likes

After recent update to Meteor@1.4.2.7 that had stopped working. I’m getting the “ReferenceError: XXX is not defined” error on the server. And if I replace import with require() syntax it works.
I’ve been also forced to install babel-runtime@6.23.0 npm package after Meteor update. Can you clarify what’s the reason of such behavior. Should I never use nested imports in my Meteor projects from now on?

I think that would qualify as a bug in that version of Meteor - can you please file an issue?

Just to fulfill this thread, I’m posting here a link to the issue I’ve submitted. Thanks for your answers.

1 Like

The guy on GitHub pointed out that this is expected behavior now. See his comment for details. So good to know that for the future.

You only get a ReferenceError if you try to access the variable outside the conditional block where you import the module. Which in my view is the proper behaviour. You can probably work around by defining a variable scoped from outside the conditional block where you do the import and reassign what you need to it after the import. Although, as a programming practice…

2 Likes

Well I thought import statements should be stacked in the beginning of the file, so just by looking at first lines of file the viewer would know which components are used here. If you’re suggesting moving import to the block where I actually use the variable, that will spread the references over the file. Which is not much better than using require() on demand.
Other thing is that I may use the component multiple times in the same file. Using import in every function I plan to use component in seems kind of code duplication problem to me. I’d rather stick to var { … } = require() solution as it helps to overcome both of described problems even if it looks ugly. (2)

I wasn’t suggesting that the imports should not be done at the top whenever possible. And I can only see the need to do so when the imported code shouldn’t be available (maybe because it contains sensitive info and we only want the server to have it) or somehow is dependent on server side only technology (e.g., node FS). And this scenarios should arise mainly in shared (client and server) methods.

1 Like

Same issue here. Any news on a patch?

Patch for what? I don’t think there is anything to patch…

@hluz My common code imports some server-only functions.

if ( Meteor.isServer  ) {
  import { someFunction } from './someFile.js';
}

Meteor.methods({
   coolMethod( arg ) {
        //do some stuff in common code

        if ( Meteor.isServer ) {
           //execute private logic on the server
            someFunction(arg);
        }
   }
})

This used to work fine and all of sudden, it doesn’t. Hence patch. I assume it has to do with recent meteor upgrades but of course I’m not sure.

I think the correct way to do that is to put the import right next to where you call the function. With ES2015 scoping, you won’t be able to access that import outside and that’s correct.

You could also do:

let someFunction;
if (Meteor.isServer) {
  someFunction = require('./someFunction');
}

But doing the import right next to the code is probably better:

coolMethod(arg) {
  if (Meteor.isServer) {
    import someFunction from './someFunction';
    someFunction();
  }
}
2 Likes

Thanks for your quick and knowledgable reply, @sashko. I’ll make the change as you suggest.

Ran into this situation today. This works, but Is there a side-effect of having the import called many times over inline in the code like this?

ESM imports are only executed once, even when they appear many times. The JS engine (or in Meteor’s case the bundler) will just return an immutable reference to the already executed/executing module

One issue having it in an isServer block is that it will ship the imported code to the client, even if it’s not executed, as import statements are analysed statically by the bundler

2 Likes