Meteor File Structure


#1

If you ask me, right now the Meteor file structure for an actual app is a little complex:

In my honest opinion, things should work and look different.

project/
|- both/
|   |- routes/
|   |- methods/
|   |- tests/
|- client/
|   |- index.js
|   |- assets/
|   |- components/
|   |- stylesheets/
|- server/
|   |- index.js
|   |- emails/
|   |- fixtures/
|   |- publications/

That’s the basic idea, but edit/add to it if you’d like.

By definition, any assets in the client folder would be made public. After all - it’s the client and it’s being sent to the user. If the client does not require/import a specific asset, it can still be requested later with the use of a URL. It just won’t be sent to the browser over the network by default.

Both the client and server could require/import files from both for testing, and all of that confusing imports folder crap that currently renders our client and server folders useless would be done away with.

File structure and importing, beyond this basic folder structure, would be left up to the user like in any Node.js project.


#2

@sashko @helfer @rohit2b what are your thoughts on this? Easy to understand, hard to implement? Too ambitious, too simple?

For me personally, coming back to examples that look like this (todos-react) are a bit unsettling and confusing. If the directory structure and load system were just a little simpler, it’d make things a whole lot easier.

To be honest, right now I’m almost wondering what the use of top-level client and server folders are, if all they do is have a few files that import other files from the imports directory. There are literally just stagnant files in a directory.

I think this kind of structure would clear those grey areas up.


#3

I am migrating a few projects to the new structures according to Meteor Guide and taking reference from Mantra, other suggestions form the forum, etc…

In fact, the existence of /imports is for the sake of backward compatibility while moving forward. If we are all going the imports way, then inside /imports, they are almost the duplication of the old ways outside /imports.

Perhaps Meteor (MDG) could offer a project wide option, so that the use of /imports is not mandatory and stuff are required to import by default from the first folder level ?


#4

You’re right. The new folder structure is basically a discombobulated recreation of the previous file structure. Now everything is just in one directory and it makes little/no sense.


#5

The directory structure shown in the guide is a guide. There are some rules on directory structure, but a tremendous amount of flexibility within them.

I’ve seen indications that they will likely get rid of the legacy burden in 1.4 and just require imports everywhere.

But, even with the current system’s legacy burden, you can do anything you want under that one directory. So, it should make as much or little sense as you choose. There is little difference between everything being under one project directory and everything being under one imports directory. It’s what you do under that level that matters.

Also, though I haven’t tried it myself, I’ve seen others that are apparently taking a totally different approach. Apparently, any file under any “imports” directory is handled the same way. So, whether you put “imports” at the trunk or at the leaves is a personal choice. Maybe someone here can confirm that that approach is available and explain why you might do it.


#6

I did not have time yet to complete this working paper, but maybe you find it helpful: 🗳 [Feedback] 1.3 App Structure with Screens & Components: Get ready for A/B/Multivariate testing and continuous improvement


#7

We have just refactored our app to Meteor 1.3 and imports. We basically followed the Meteor Guide with a few small tweaks. We are still shipping with Blaze, but are writing all new UI components in React. We also have both web and mobile apps as well as some microservices built using Meteor.

Clozer Meteor App high level directory layout
app/			# a Meteor app directory, multiple app directories are supported
builds/		        # app client and server builds
config/			# settings.json and env variables for dev, staging, production
scripts/		# misc. scripts for build or testing 

A Meteor app directory layout
client/
  main.js                      # client entry point, imports all client code
  stylesheets/              # global style code, imports other styles from imports/components
  main.html		# tags in <head> or use package to inject like react-helmet

server/
  main.js                      # server entry point, imports all server code
 
imports/
  startup/		# All app-wide, common, and startup configuration
    client/                      # configuration for routes, subscriptions, or any other client side services
      routes.js                # set up all routes in the app
      subscriptions.js	# global subscriptions, if any
    server/		# configuration for users, email, oauth or any other server side services
      index.js		# imports all server bootstrap, security, config and api code
      bootstrap.js           # general configuration startup code
      security.js              # set browser and security policies
      register-api.js        # imports server only code from api/*/server and all other api code
      user-config.js	 # configure user account profile and email templates

  api/			# business logic
    contacts/		# a unit of business logic
      index.js		# imports all contacts apis and re-export public apis for outside use
      Contacts.js            # contacts collections
      methods.js            # contacts methods
      schema.js             # schema definitions for contacts
      Contacts.tests.js   # tests for the contacts collection
      server/                  # contacts code to run only on the server
        index.js               # imports all server code for contacts
        methods.js          # server only methods for contacts
        publications.js	# publications for contacts
    lib/			# api utility functions and shared libraries

  components/	            # reusable ui components
    contacts/		# contains template or react component code
      contacts.scss        # component ui styles
      Contacts.js            # stateless React ui component
      index.js                 # imports all contacts JS code for router
      contacts.tests.js    # tests for the contacts component
    layouts/                   # wrapper components for behaviour and visuals
    pages/                    # entry points for rendering used by the router
    lib/			# component utility functions and shared libraries


  containers/		# react code to pass state to component composed using react-komposer
    contacts/                 # contains reusable stateful react container
      contacts.js            # stateful container to compose component
      contacts.tests.js    # tests for the contacts container
    lib/			# container utility functions and shared libraries

  templates/                 # legacy Blaze UI template and JS helper code

node_modules/	        # local app NPM packages
packages/		# app specific Meteor packages, modified forks of public packages
public/			# fonts, images, icons, favicon
resources/		# mobile icons and splash screens
tests/			# integration tests not run by Meteor built-in test tools


#8

That looks really cool! What would you say the benefits of this structure have been?

Do you think the imports director adds to your workflow and makes things easier?


#9

Much easier to reason about which specific functionality is located where. If we need to add a feature to contacts, all the biz logic is in once place. No more hunting down and editing files used for multiple features.

Two key things we are doing. First, we use relative imports within a module/functional area and absolute imports across modules. This is helping us future proof code so we can eventually get rid of /imports if lazy loading becomes the default in the future.

Secondly, we are using index.js files to import all public apis and re-export them, which makes it really easy to use the functionality somewhere else. For instance, /imports/api/contacts/index.js contains this:

// Initialize all key files and re-export contacts public facing api
export {default as Contacts} from './Contacts.js';
export {
  contactsAddMethod,
  contactsEditMethod,
  contactsDeleteMethod,
  contactsUndeleteMethod
} from './methods.js';
export {contactFormSchema} from './schema.js';

If I need to add the ability to add a new contact in my foo module I know exactly where to look for what APIs I can use and how to import them like: import {Contacts, contactsAddMethod, contactFormSchema} from '/imports/api/contacts';

We are really liking the additional productive we are getting with ES6 and this new app structure.

We are also just about to have all our code pass AirBnB ESLint rules with the following rule variations we added:

'rules': {

    // The total number of characters allowed on each line of code including indentation
    // http://eslint.org/docs/rules/max-len
    'max-len': [2, 120, 2, {'ignoreUrls': true, 'ignoreComments': false}],

    // Require constructors to use initial caps
    // http://eslint.org/docs/rules/new-cap
    'new-cap': [2, {'capIsNewExceptions': ['Match', 'Match.ObjectIncluding']}],

    // Require a space after unary operator ! and not before new
    // http://eslint.org/docs/rules/space-unary-ops
    'space-unary-ops': [2, {
      'words': true,
      'nonwords': false,
      'overrides': {
        'new': false,
        '!': true
      }
    }],

    // Disallow reassignment of function parameters, but allow modifying the properties of parameters
    // http://eslint.org/docs/rules/no-param-reassign
    'no-param-reassign': [2, {'props': false}],

    // Dangling commas on multiple lines are not required
    // http://eslint.org/docs/rules/comma-dangle
    'comma-dangle': [2, 'only-multiline'],

    // Allow quoting properties as needed
    // http://eslint.org/docs/rules/quote-props
    'quote-props': [2, 'as-needed', {'keywords': true, 'unnecessary': false, 'numbers': false}],

    // Wrap the function expression for immediately-invoked function expressions (IIFE)
    // http://eslint.org/docs/rules/wrap-iife
    'wrap-iife': [2, 'inside'],

    // No space after or before a curly brace wrapping an object or import
    // http://eslint.org/docs/rules/object-curly-spacing.html
    'object-curly-spacing': [2, 'never'],

    // Put a blank space before line comments except at beginning of an object or block
    // http://eslint.org/docs/rules/lines-around-comment
    'lines-around-comment': [2, { 'beforeLineComment': true, 'allowObjectStart': true, 'allowBlockStart': true }],
  }