Is it possible to remove function declaration for one-function-export-per-file?

Consider this file which is called "camelize.js":

import * as changeCase from "change-case"

export const camelize = object => {
    if (Array.isArray(object)) {
        return object.map(camelize)
    }
    else if (object !== null && object && typeof object === "object") {
        const newObject = {}
        for (const key in object) {
            if (object.hasOwnProperty(key)) {
                const value = object[key]
                if (typeof value === "object") {
                    newObject[camelize(key)] = camelize(value)
                }
                else {
                    newObject[camelize(key)] = value
                }
            }
        }
        return newObject
    }
    if (!object) {
        return ""
    }
    if (typeof object !== "string") {
        return object
    }
    const camelCased = changeCase.camelCase(object)
    return camelCased
}

This file contains only one named export; the function name is just the file name.

We should indent all lines, just because they are part of that function. While that indentation works very well for files with multiple functions or classes, it's boilerplate when the file only contains one function or class.

Is it possible to somehow write that file as:

import * as changeCase from "change-case"

if (Array.isArray(object)) {
    return object.map(camelize)
}
else if (object !== null && object && typeof object === "object") {
    const newObject = {}
    for (const key in object) {
        if (object.hasOwnProperty(key)) {
            const value = object[key]
            if (typeof value === "object") {
                newObject[camelize(key)] = camelize(value)
            }
            else {
                newObject[camelize(key)] = value
            }
        }
    }
    return newObject
}
if (!object) {
    return ""
}
if (typeof object !== "string") {
    return object
}
const camelCased = changeCase.camelCase(object)
return camelCased

In other words, is it possible to assume that every code line in a file (other than imports at the top) are part of a function that is called the same as the file name and it's a named export?

In a node CJS module, yes; otherwise, no.

In Node CJS, no as well... you could write code kind-of like that, but it wouldn't cause a function to be exported, it would just cause the code to be ran when you import the moduel.

In general, no, there isn't a way to write a bunch of code and have it automatically be wrapped in a function that gets exported.

Also, how would you name the parameters being passed in if such a syntax short-cut were to exist? In your second code example, you're refering to a variable object, but we have no way of knowing if that was supposed to be a parameter (and if so, which), or a global.

We could do

const [object] = arguments;

At the top of the file

The next issue - how do you distinguish one of these "the module is the function" modules from a regular old module? For example, if this code is saved in a file named "doThing.js", should this be treated as a module that calls console.log() when it gets imported, or should it be treated as a module that exports a function named doThing() that takes no parameters, returns nothing, and calls console.log() when ran? How do you disambiguate the two?

console.log('hi');

Indenting the lines is a choice. If an application desires to have modules that only export one function they can choose a formatting style that allows such modules to be authored as:

export default function(x, y) {

return x + y;

}

@aclaymore, that's not possible when we work inside a project. Because we have configured the VS Code to auto-format on save. We can't exclude some files.

@ljharb could you please expand? How? Can you give me an example or maybe a link to a doc somewhere?

The distinguishing could be configurable or by convention. For example, all files inside this directory should be treated as function-per-file. Or, we can write a simple #!/function-per-file shebang-like at the top. To be honest, there are tons of ways for that.

The parameters could all be accessible in a params object. Just like props that's a convention in React. Makes sense. Anybody can destructure it to get the props.

You could write a prettier plugin that formats in a way that does not indent files when the module has a single export.

Next, you said it would be special syntax that causes a function to be exported with the same name as the module. But I feel like people's expectations would be to have the function be default exported. I personally don't like to use default exports, but others do, and if such a feature were released, I'm sure this would be one of the first complaints.

How would this situation be handled, or would it not be handled?

As far as I can tell, this entire topic is just "I'd prefer not to indent my function if it's the only thing in the file". You can just.... do that, you know. Nobody's stopping you. We don't need to add new syntax to support it.

If your formatter doesn't support it, raise a bug on the formatter. (Or just learn to live with the single extra indent.) This is not something the language itself needs to worry about.

4 Likes

I think it's entirely possible to implement in user land—Node exposes some module import hooks, and you can do something like this:

  • Only handle files with extension .fjs (note: I think it would be tricky to implement with an in-band hint like a magic comment, though not undoable)
  • Write a Babel plugin that wraps all statements, except imports, in a default-exported function declaration export default function ${filename}() {...}

It would be very similar to how ts-node supports importing .ts files.

1 Like

@aclaymore we don't use prettier. To be honest, the default VS Code formatting is way better than prettiers's formatting. But regardless of the formatting tool, it's not a good idea to bind JS fundamental features to some IDE extensions.

I think this can be a configuration matter. Something like:

{
    functionPerFile: {
        exportType: "default"
    }
}

The point is, many things have defaults (conventions) and when developers want to, they can override those defaults via configuration.

@tabatkins that's not true. It's CI. It's continuously and constantly improving a thing even if it's a small step. It's not about just the indentation. Look at it this way: I have a file in which a simple JS code is written. If I import 'path-to-that-file' it would be executed. If I import someName from 'path-to-that-file' then the content of that file is available to me to run multiple times.

@Josh-Cena that's very interesting. Can it be done without Bable? I use Vite for my React projects and its plugin system enables file transformation before execution. Do we have something similar in Node.js?

I'm all for incremental improvements (I'm a TC39 member and one of the main CSS editors), but you have to balance benefits vs costs. The benefit here is what I said: "I can remove one level of indentation without my code auto-formatter complaining". The cost is:

  • it's unclear whether a module contains code that'll execute on import, or a function body that'll only be run if you call the default export.
  • arguments to the default export are much less clear, since you have to instead unpack them from arguments rather than listing them the normal way.
  • whether an import statement is inside or outside the function is completely implicit.
  • if you ever add a second export, you have to add the function wrapper back to your default export anyway.

These are definitely non-trivial costs in comprehension complexity! They do not, imo, remotely balance with the "one level less indentation" benefit, especially since there's no evidence that this is a widespread issue that many people want solved.

The correct solution to this is to just have your auto-formatter allow one less indent on the function body if your file is solely imports and a default export. No engines need to be updated, no new concepts need to be learned, all the (quite minor) cost is borne by you, the person who wants it.

3 Likes

If you want in Vite it should be even easier. Just write a Rollup plugin that handles .fjs imports. And yes it should be similar in Node.js; just look for module loader hooks.