@tabatkins, thank you for spending time on this. We should consider that this is a very specific use case. It's only valid for files that:
Do not have an export in them (default or named)
Are configured (or are following a convention) to be a function per file
Accept that all imports are only module-level imports and there would be no function-level imports
Accept that their parameters are passed to them as (...params)
As you can see from my example above, which is a real file among many many files that follow the same pattern in our codebase, the code becomes way cleaner. And this boilerplate line is also removed:
export const camelize = object => {
That's also important. The export can become conventional. The const is really unnecessary in this very specific constant. The camelize is the name of the file: camelize.js, or even if the file is called Camelize.js it can be derived from the file's name. The parameter in this case is only one object and no object-destructuring is needed.
All of these files are like that. When multiplied by thousands of files and tens of thousands of lines of code, a very small improvement becomes extremely beneficial.
I can send you many images like the above. Our database files are like that. Our read/write utility directories in our business directory are full of files like that.
The point is, we emphasize clean code so much. We rarely have files with two or more exports in them. To us, each file represents a small block of code that should do one semantically unit of operation. We could create a validators.js file with hundreds of lines of code, but we split it into 50 files, each with 10 lines of code for example. That reduces maintenance costs drastically.
If we get this feature too, then the code becomes even easier and simpler.
That export line isn't really removed, it's just reshaped to something like this, which is about equally verbose, maybe more so:
use function-per-file;
const [object] = arguments;
You mentioned configuration as another option to the directive, but that's actually a really tricky subject - there's a reason why JavaScript doesn't currently have config files. 1. How would browsers know where to look to find this config file, without always performing an extra unnecessary request to look for it, 2. Config files should only effect your project, not dependent projects - how do you tell it how to distinguish your project from another one you just copy-pasted inside, etc - there are ways to overcome these issues, but they add even more complexity to the language. And there's the fact that config files just aren't fun. We already have too many in the JavaScript ecosystem. I would prefer not adding more if we can help it. They also tend to promote code that's not easily shareabe - it's nice being able to copy code examples to and from the internet without having to first understand how their JavaScript was configured.
Shebangs don't really work either, as "function-per-file" isn't the name of a program on your machine that is capable of running this file (that's what shebangs are for).
But in the spirit of your shebang idea, I made up that "use function-per-file" directive.
There already is a "config file"βthe import map, so the premise is wrong: you already have to worry about how the environment is configured before you could use any code. As for this reason: all JavaScript programs in a browser have an entrypoint, which is either an HTML file or a new Worker() constructor, or something similar, so the config is also specified there.
@theScottyJam as I mentioned this small improvement shows itself in large codebases. And for large codebases dealing with some configuration items to support thousands of files is definitely worth it.
For example, right now we have more than 270 thousand lines of code. More than 70 thousand JS files in Node. More than 70 thousand files in React and JSX.
So, for me, it's not a big deal to have this config:
{
functionPerFile: {
exportType: "default",
paths: [
"./src/Core/**/*.js",
"./src/Taxonomy/**/.js",
// and even 50 more items here
]
}
}
This means that I would benefit from not having to indent at least 30 thousand lines, each with 4 tabs. That's definitely valuable.
And yes, as you mentioned in your example when it comes to making the configuration per file and restructuring the parameters, then the export line is less verbose. But I mention it again. It's for a very specific use case. My example above (which we have tons of it in our codebase) does not include your mentioned two lines.
And the opt-out would be very easy. As soon as a file contained an export statement, then it's not part of this configuration.
It's weird somebody manually indenting those lines. The default code formatting of VS Code is very good for JavaScript. We don't manually indent the lines. We just find those indentations to reduce code cleanness and clarity.
The point is we try to take code cleanness to extremes. Again, I'm pointing to the example in my original post. To us, there is no reason to write code like that. Cleaner code is the second file with no indentations in this very specific case.
I have a file called camelize.js and from the name of the file, I understand what it does. If it contains a simple script that does the job, then that code would be cleaner than seeing an isolated exported function with those indentations in it.
As a sample please let me show you a link to Microsoft's motivation on creating File Scoped Namespaces.
I agree with tabatkins. The only benefit I see is having the name of the function come from the filename, without having to duplicate it in code. Indentation is purely cosmetic. If you don't like how your tool of choice formats your code, change the tool.
I understand you don't want to maintain plugins / patches for your tools, which would only increase your dependence on those tools; you're free to not make that trade-off. But what you're proposing seems unnecessarily complicated to me. It would require adding a new goal symbol to the grammar, a parameter to allow imports in function body, and all tools, not just yours, would need to learn the new grammar and configuration driving it. Next someone asks for classes to receive similar treatment.
There would also likely be some inconsistencies with how regular functions work. What will be the function's length, toString()? How do you define argument defaults? How can such function call itself?
Once we're in "defining new grammar" territory, I suggest an alternative function declaration syntax, that doesn't require a new goal, nor configuration:
export default function #FILENAME(x, y)
// gets its name from the filename
// and its body is the rest of the file
return x ?? y
Same for class declaration:
export default class #FILENAME : extends Whatever
// gets its name from the filename
// rest is class body
#FILENAME is currently not a valid token at these locations, that's how it branches off the current grammar, and then doesn't require braces around function / class body. I'm sure someone will object it looks like private identifier yet is not one. It's a placeholder, I see no harm in having it serve this singular purpose.
JavaScript is a general-purpose language, and it will not change just for that one use case. It may be a big deal for you, but it is not for anyone else - and yet you are asking them to change the whole language standard just so you don't have to configure your tooling. You want to permit opt-out, but really this is a rarely needed feature that requires opt-in.
I would disagree with that. It becomes more unreadable if you leave out that very important part you call "boilerplate", and no longer being able to distinguish plain module code from code that is automatically going to be wrapped in a function is a dealbreaker.
What you call "extreme code cleanness" is just extreme minimalism. Very few people prefer thousands of files with just a couple lines over some hundred files with a more reasonable size. At some point, all your business logic is represented by the file paths and your files can stay empty :-)