alextes / javascript-best-practice

Repository for common JavaScript related questions with helpful answers.
3 stars 0 forks source link

Can you require relative to the project root to shorten the path? #1

Open alextes opened 7 years ago

alextes commented 7 years ago

Writing relative paths gets tiresome for big project trees. For example:

const mod = require('../../../server/routes/util');

Is it okay to use globals, a module, or any other method to shorten the required path?

ljharb commented 7 years ago

There are a number of approaches people tend to use:

  1. Webpack aliases: this tends to make configuring other tools harder, since they have to know Webpack exists to be able to resolve paths - plus, it tightly couples you to Webpack.
  2. a custom "require" function: this approach means all sorts of tools stop being able to statically detect your requires, including linters - also, this is extra complexity that your devs have to learn.
  3. a symlink inside node_modules: this approach means you use the default node require algorithm, which is a good thing! However, managing symlinks across platforms and via git is very tricky to get right.
  4. Nested node_modules directories: this is just gross; there should only be one package.json and node_modules in your entire project, at the root.
  5. a Babel transform: as long as you select something that can't possibly be a valid requireable path, this is probably the least bad option. One major bonus is that you can easily eject out of this by just running babel in-place on all your files with only the one transformation, and easily go back to the dots.

However

Deviating from node's require algorithm in any way makes your code harder to reason about, understand, and maintain. Additionally, if you have a ton of ../ in your project - that's a symptom of your project being too large - the solution is to make it smaller (by extracting things into separate npm-installed packages), not to sweep the problem under the rug by masking the dots.

mockdeep commented 7 years ago

Separating things out into different npm packages sounds like a huge pain. Even managing a couple of libraries is awkward, let alone having to manage your entire project that way. It also conflates external vs internal dependencies, which makes the project more confusing to maintain. (Is this something I can manage directly? Or is it something I have to go open an issue on somewhere?)

Saying a project is "too large" sounds like a bit of a subjective assessment. It's possible to have extremely large projects that are also well structured and maintainable, and there's nothing right or wrong about that. It depends on the needs of your team and the problems you're trying to solve. In our case, for the moment, it makes a lot more sense to make it easy to reason about where something is coming from and quickly make changes rather than adding the complexity of managing internal dependencies as npm packages.

For our part, we set a path to our project files using NODE_PATH or within Webpack configurations using resolve: { modules: [...] }. To make it clear what comes from our project vs an external dependency, we put all of project files in an src/ directory and prefix our imports with that:

import React from 'react';

import Blah from 'src/blah';

I've seen others use their project name or something as the prefix path, but we wanted something that's short.

ljharb commented 7 years ago

External dependencies make projects easier to maintain - that's the point. You shouldn't be managing much directly - needing to do so is an indicator that things aren't properly encapsulated.

In my experience, no project that gets large enough ends up being well structured or maintainable - just "consistently structured" and "maintainable enough", which isn't the same thing.

The solution you're describing would be better achieved without NODE_PATH or webpack aliases, but would be achievable with most of the above solutions.

mockdeep commented 7 years ago

You shouldn't be managing much directly - needing to do so is an indicator that things aren't properly encapsulated.

This doesn't make any sense. We have to manage the code whether we're putting it in external npm packages or inside our codebase directly. It's just a question of what the barrier is to actually managing it. Already when we're dealing with 3rd party dependencies it can be a huge pain to get bugs fixed and add new updates, so adding this additional overhead to managing our own code as well seems kind of wasteful.

In my experience, no project that gets large enough ends up being well structured or maintainable - just "consistently structured" and "maintainable enough", which isn't the same thing.

Your experience differs from mine, in that case. I've worked on a handful of large codebases where solid coding practices keep them maintainable and enjoyable to work in. And I've also worked in a number of codebases broken down into smaller packages that are a huge pain to work with as digging through layers of separation becomes a tedious and confusing process. Not to mention when you realize code belongs somewhere else it becomes that much more difficult to refactor.

wswoodruff commented 6 years ago

Using babel-plugin-module-resolver works great for me. This is my .babelrc on a react-native project:

{
    "presets": [
        "react-native"
    ],
    "sourceMaps": true,
    "plugins": [
        [
            "module-resolver",
            {
                "cwd": "babelrc",
                "root": ["./app"],
                "extensions": [".js", ".ios.js", ".android.js"]
            }
        ],
        "transform-class-properties"
    ]
}

Being able to get rid of the dots for me is great because my front-end project structure is very flat, with folders for action-types, actions, selectors, routes, components (reusable across the app), utils, reducers, styles, anims, normals, and a couple more

But I know where every last dang thing is at any time, I know where to find it 👍

wswoodruff commented 6 years ago

Of course another big benefit of ridding yourself of the ../ dots is you can copy and paste imports without hassle of redoing dots wherever it's pasted. To me that's a huge time-saver

ljharb commented 6 years ago

Use import-js to automate that refactoring for you.

The dots belong there, and just because you might not need them, doesn’t mean other devs don’t.