Application Structure

I think this is probably a good recommendation to make… perhaps we can just recommend imports/client instead of imports/ui until this is improved? What do you think @tmeasday?

1 Like

Yeah, this is a bummer. See: https://github.com/meteor/meteor/issues/7434

2 Likes

Regarding the client/server code separation, how would you structure your imports ? For instance, I want to create a token login module for my app. There is business logic that happens on the client side only and some on the server.

Client :

  • Read the query url to extract token from parameters (in startup.js ?)
  • Call the login method and handle the callback

Server :

  • Generate the token
  • Login handler

How do I structure my imports, so when I import my module api on the client side I only get the client api and the same for server? Also, I would have codes that would need to be run at startup.

Here is a startup from how it could be done but I need your opinion

/imports
  /token-login
    index.js
    token-login.js      # Defines the api object TokenLogin
    /client
      token-login.client.js    # Defines logic to call the login method 
      startup.js           # Code extracting the token form url
    /server
      token-login.server.js    # Defines the login method

Would it be better to have two different object like LoginTokenClient and LoginTokenServer ?

Thanks !!

That is a pretty reasonable approach. You’re following the packages-only structure, expect within the /imports folder; and using the classic directory naming conventions in your feature.

The only consideration that I can think of is whether the classic directories should go up a level, and whether to put your feature in an /imports/features dir.

1 Like

How do you name additional functions within a ‘component’, ‘module’ etc? Sometimes I may have plenty of them in external files within a folder located in the same place with the component or module, and name the folder ‘helpers’, though they are not typical Meteor template helpers, just additional functions that are extracted into separate files to keep the actual Meteor templates code/logic clear.

For example:

imports/
  ui/
    layouts/
      header/
        helpers/
          some-function-one.js
          some-additional-function-two.js
        header.less
        header.html
        header.js
        

Renaming the folder to ‘functions’ seem to be not correct because sometimes I can store there constants and not functions… :-/ While lib is also reserved keyword for folders… And I don’t want that to load automatically before I specifically import that function to a helper etc… I wonder if there is a better approach…

@arisetobe
Very good question what I do is I have a folder called “lib” :smiley:

But if to name the folder lib all the content within it will be loaded/imported automatically, no? Because after getting used to explicitly control every imported file I don’t want to return to old times of load order mess…

No. You are inside imports folder. There’s no such thing as autoloading. :wink:

ah… then it might actually be an option! :+1:

I got 2 ‘types’ of what would be generally called ‘startup’ code… for example:

  1. Definition of some global ‘classes’, like Schemas or Collections, which should start before api/ and ui/ is loaded
  2. Some code that needs to run after everything in api/ and ui/ is loaded

At the moment the first code is in folder init/ and the second is in startup/… But it seems a bit confusing.
Any ideas on how these folders can be better named?

don’t use global classes! please :smiley:

Well, anyway thanks for feedback… :wink:

As I understand, one should not use global classes because of modularity, right? But at the same time I wanted to have a single object to which I could refer to (and potentially validate existence of a collection or a schema through this object). As now I’ve been using ugly eval('Collection') which I wanted to change… :confused: Will try to find other ways then…

…and just a quick comment to the above. Collections['Collection'] seem to be much reliable than eval('Collection'). And thus at the moment I see no way how to avoid the global class (or object)… unless anyone has a better solution.

What’s wrong with simply importing the collection where you want to use it? I see no reason for global stuff like this.

These thoughts are just an outcome of my current review of app structure while trying to optimize it. The app has about 15 collections (completely different but sometimes heavily interconnected), so I don’t know if we can call it big according to Meteor standards.

Currently the structure is like this:

lib/
  some-function-1.js
  ...
init/
  both/
  client/
  server/
api/
  modules-group-1/
    module-1/
      both/
      client/
      server/
  modules-group-2/
    module-.../ 
  modules-group-.../

Initial thoughts about the globals:


1 - Since modules are interconnected, there is tendency of having same code (even dealing with collections inserts or updates). Before I made it universal by having some external functions that deal with inserts to where a string with collection name is passed and then eval('CollectionName') was used. But since eval is evil, there should be more reliable solution. At the moment my best option is having global Collection object to which all other collections are attached in each module and thus I can validate it easily with something like this:

  // /init/both/collection.js
  Collection = {};
  // /module-1/both/collection.js
  Collection.TestCollection1 = new Mongo.Collection('testCollection1');
  // /module-2/both/collection.js
  Collection.TestCollection2 = new Mongo.Collection('testCollection2');

  // in /lib/query-collection.js
  function queryCollection(collectionString) {
    const collection = Collection[collectionString];
    if (collection) return collection.find().fetch();
  }

  // some other file
  const data = queryCollection('TestCollection1');

While writing this I have found this: if (something instanceof Meteor.Collection) …, will try to check it, maybe it will work better.


2 - There are ideas in future to distribute the app as separate Docker (or anything else) instance, so the clients could install it on their premises. Thus I want to be able to easily detach certain modules (like website and admin) without headaches of interconnections of elements. Having such globals allows me to populate them by just adding or removing specific files and commenting the module import string in a loading order file, so these modifications of the structure will not affect the universal functions. But again, if the instanceof Meteor.Collection works, then I will use that of course.


3 - With Schemas my initial thought was to follow the guide and attach them to the collection object. But then I thought that maybe I would need a way to extract, modify and track changes of all the schemas available… I still need to check the percolate:migrations to see if it might work in my case…


From the other hand, can please someone explain what is so bad in having the globals then?

So after double checking this thread on StackOverflow: Meteor: how can I drop all Mongo collections and clear all data on startup?, I am still confused.

Because if one uses this:

    var globalObject=Meteor.isClient?window:global;
    for(var property in globalObject){
        var object=globalObject[property];
        if(object instanceof Meteor.Collection){
            object.remove({});
        }
    }

…reiterating window or global each time to validate if the object is a collection? ( Really? :slight_smile: ) this seems like a huge overkill…

While having the collections in the Collection global object seem to be very clean solution (except Meteor.users is special, but that is not a big deal to deal with it separately). Here is what I use at the moment:

  Meteor.users.remove({});
  _.forEach(Collection, (collection) => {
    collection.remove({});
  });

Alright… in my current situation there is indeed no need to have separated global object Schema as while using collection2 all the schemas are anyways attached to collections (this actually means that there is no need to specifically attach them to the collection as it is stated in the guide). But still without Collection global there is no way to get a list of collections in one place.

The only concern that I have now is that the global Collection object can somehow affect Minimongo, or not?

I assume not, because:

  1. Even though it is declared, the actual population of data happens during subscriptions which is rather dynamic since it depends on templates…
  2. Since we do not have incremental loading and everything ever imported is available across all the clients at any time, so why to bother? Actually, so far it seems that it might be even better to have one global, called Collection instead of 15 different globals each representing its separate collection…

Comments are welcome…

Hi I have only recently been updated to the latest version. The structure is a lot more complex the previously when it was just client, lib and server. Pardon my lack of knowledge, but hope to learn more about the benefits of using the new structure. For example with the imports/exports and the many cross referencing of files internally?

I had the same reaction when I started doing the migration to the new file structure. However I can assure you from experience that the change is worth it for the more complex apps in the long run.

Some benefits on the top of my head:

  1. Precise control over the files load order
  2. Transparent documentation about which files, functions and components are being used and where. You just need look at the head of the file to figure out what is being used within your code, good for maintainability
  3. Ability to use NPM packages and full alignment with the rest of the JS ecosystem
  4. Ability to do exact code splitting with dynamic imports in Meteor 1.5 which speeds up the initial load time
  5. Better IDE support and tooling

And I’m sure others can add to this list.

3 Likes

You can just use the old structure, which still works. If you don’t feel the urge to use the new structure, then probably your app / the team doesn’t need that.