In our last post discussing the benefits of moving your methods, publications, and templates into modules, we mentioned that all of this Meteor-specific functionality relied on modifying global state.
This means that our modules didn’t need to export anything. However, they do need to be imported at least once by your main Meteor application.
Importing these modules executes your calls to
Meteor.publish(...), etc… and makes them accessible to the rest of your application.
Depending on how you structure your imports, this kind of upfront importing can quickly get out of hand.
The Problem With Direct Imports
Imagine we have an
/imports/lib folder in our project. Within that folder we break up our application’s functionality into distinct components, like
bar. Each component has it’s own set of Meteor methods defined in a
. └── imports └── lib ├── bar │ └── methods.js └── foo └── methods.js
To make sure these methods are registered when our application starts, we’ll need to update both our
/client/mains.js and our
/server/main.js to import these method files:
import "/imports/lib/foo/methods"; import "/imports/lib/bar/methods";
This import structure seems to make sense so far.
It might get more difficult to deal with if we start to aggressively break up our methods, but we’ll put that out of our minds for now.
When we begin adding template modules, our
/imports folder structure will quickly begin to balloon in size:
. └── imports ├── client │ ├── bar │ │ ├── template-1 │ │ │ ├── template-1.js │ │ │ └── template-2.html │ │ └── template-2 │ │ └── ... │ └── foo │ ├── template-3 │ │ ├── template-3.js │ │ └── template-3.html │ └── template-4 │ └── ... └── lib ├── bar │ └── methods.js └── foo └── methods.js
Now we’ll have to update our
/client/main.js to pull in each of these templates:
import "/imports/lib/foo/methods"; import "/imports/lib/bar/methods"; import "/imports/client/bar/template-1/template-1"; import "/imports/client/bar/template-2/template-2"; import "/imports/client/foo/template-3/template-3"; import "/imports/client/foo/template-4/template-4";
/client/main.js file has to keep up with every defined method and template in the system. Similarly, our
/server/main.js will have to keep up with ever method definition and publication definition (and potentially every template definition, if we’re using SSR).
This breaks the clean modularity of our system. Our
main.js files need to be intimately aware of the structure and implementation of all of our component pieces.
Index Files to the Rescue
Thankfully, index files can lead us out of this increasingly hairy situation.
index.js file is present in a directory, attempting to import that directory will cause the
index.js file to be imported on its behalf. For example, consider this folder structure:
. └── baz └── index.js
If we import
index.js will be imported instead.
We can leverage this to organize our
/imports structure and clean up our
main.js files. Let’s start by adding an
index.js file to each method and template “component”:
. └── imports ├── client │ ├── bar │ │ ├── template-1 │ │ │ ├── index.js │ │ │ ├── template-1.js │ │ │ └── template-2.html │ │ └── template-2 │ │ └── ... │ └── foo │ └── ... └── lib ├── bar │ │── index.js │ └── methods.js └── foo └── ...
/imports/client/bar/template-1/index.js file only needs to be concerned about importing the files related to the
/imports/lib/bar/index.js file only needs to be concerned about importing the method and other server-side functionality related to the
Fantastic. Now, let’s move up in our folder tree, adding
index.js files at each step along the way until we hit our
. └── imports ├── client │ ├── index.js │ ├── bar │ │ ├── index.js │ │ ├── template-1 │ │ │ ├── index.js │ │ │ ├── template-1.js │ │ │ └── template-2.html │ │ └── template-2 │ │ └── ... │ └── foo │ └── ... └── lib ├── index.js ├── bar │ │── index.js │ └── methods.js └── foo └── ...
Our newly created
/imports/client/bar/index.js file is concerned about importing all of the templates and functionality related to the
import "./template-1"; import "./template-2";
We can finish up our import chain on the client by updating our new
/imports/client/index.js file to import the
bar client-side components:
import "./bar"; import "./foo";
We can do the same thing in our
/imports/lib folder by updating our new
import "./bar"; import "./foo";
Finally, we can drastically simplify our
/server/main.js files to only pull in what we need at a very high level.
On the client (
/client/main.js), we’ll just want to import client-only and shared components:
import "/imports/lib"; import "/imports/client";
And on the server (
/server/main.js), we (currently) only want to import the shared components:
If we had a set of server-only components we could easily include it there as well.
Reaping the Benefits
I’m a big fan of this structure.
Each level of our dependency tree only has to concern itself with the next level. Our
client folder only has to know that it wants to pull in the
bar components. It doesn’t need to know which templates those components use. The
bar components manage the importing of their templates themselves!
If you wanted to add a new template to the
bar component, you’d simply add the template folder into
/imports/client/bar/, with an index file that pulls in the required files. Lastly, you’d update
/imports/client/bar/index.js to import that new template.
Removing a template is as simple as deleting its folder and removing the
import reference from its parent’s