Shared methods with module import

I’m attempting to restructure my app into a modular structure with Meteor 1.3 (beta 11) and ES6 imports/exports. Pretty new to this new style of structuring a Meteor app so would appreciate some help with this!

I’ve hit a snag with my shared methods, which is in a file shared between client and server. This is to leverage Meteor’s method simulation on the client.

I have conditional statements in my shared methods that run only on the server, and that requires a function in a server-only import. Here’s an example:

//shared.js

import GameManager from './server/imports/GameManager';

Meteor.methods({
	advancePhase() {
		// do stuff

		//server only code
		if (!this.isSimulation) {
			GameManager.advancePhase();
		}
	}
});

Obviously this errors immediately on the client as it can’t import that server-only module. What’s the best way to structure in this case?

1 Like

Hi!

Try placing this method in a new file called gameManagerMethods.js inside the Server folder of your project.

You’ll have to adjust the markup as follows:

import GameManager from './server/imports/GameManager';

Meteor.methods({    
        advancePhase: function() {
            //server only code
            if (!this.isSimulation) {
        	GameManager.advancePhase();
            }
        }
    });

Then you’ll call this Method from the** client side** as follows and execute the client modifications as a callback on success:

Meteor.call('advancePhase', optionalParams , function(err,res){
   if(err){
       console.log(err);
   } else {
       // do client side stuff like jQuery or mo.js
   }
});

Hope this helps.

Thanks for the response! I know that this will be resolved by placing this in server-only code, however I wanted to place it in shared code because Meteor will simulate its effects on the client while waiting for the server to come back with a result. If the method is only defined on the server, that doesn’t happen.

If “hiding” that code is not a concern, you could place gamemanager.js in a “common” folder so that it can be imported from both the client and the server.

Let’s say that hiding the code is a concern. I’m just trying to see if someone has a pattern for shared methods that use server-side imports. For the time being I’ve just used the old style ‘require’ (which works), but it feels messy.

Meteor.methods({
	advancePhase() {
		// do stuff

		//server only code
		if (!this.isSimulation) {
			const GameManager = require('./server/imports/GameManager');
			GameManager.default.advancePhase();
		}
	}
});

well it is unfortunate though for a highly dynamic language like javascript implementing modules im such a static amd strict way.

but I think we may have better options in the future since there is an api proposal to programmatically manipulate imports that just did not make it into es2015

in the meantime, I am interested in this as well. please ping me if you find a more elegant solution.

also, did you post this on github?

Cheers for the response anyway :slightly_smiling:

This seems to me like an issue that needs to be resolved by Meteor, as it requires you to implement methods in code shared between client and server to leverage simulation / latency compensation. Perhaps they’ll get to this in a future release as they encourage people to use ES6.

Using ‘require’ works fine for now but I would love to go full-on ES6 style in future! Will surely update this if I figure out a better way.

Hmm perhaps if we pinged @sashko

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)