[Compiler] Enhancement - per file control over compiled file extension
Introduction
The capacity to control the file extension of a file would allow us to have files compile to JavaScript configuration files in a build process. The use case in other target languages is unknown to me.
Essentially, there are many frameworks, libraries, and others which depend on a specific file name and extension schema for their configuration files.
This might not coincide with your target (such as compiling to JSX,), and so this would normally require you to configure these files in JavaScript.
My intention is to remove this obstruction by allowing explicit control of a files compiled extension.
Issues
Safety issues with regards to the capacity for a malicious library to inject shell scripts or other by compiling to a non-intended target extension when a consumer compiles it.
This might need to be an unsafe switch on the fable compiler to permit.
Solutions
Plugin Only
We could expose a method or mutable member of the PluginHelper which allows a plugin to set the intended file extension.
We could perhaps expose this as a method which accepts a union of predefined extensions (such as .config.js, .config.jsx, .jsx, .js etc). This would remove any issues regarding safety.
Top File Directives
We can modify the file read method in the compilation process to first peek the stream for #. With the presence of #, it would consume the first line (and if future directives were added, could continue until no more directives are available).
The line would be parsed for a valid key,value pair such as FABLE_FILE_EXT ".js" and provide that, wrapped in an Option, in a tuple with the complete file text. In the case where it is not a valid key,value pair, we consume the rest of the file, append the consumed line, and return the tuple with a None value .
This optional value would be utilised when writing the file to disk.
The advantage is that we could also perform the peek/read separate to the entire file consumption. In that case, we would be able to force the stream to position 0 without being concerned about dissecting characters.
The issue with this would be that we won't have access to typed unions as parameters. We could still enforce restriction on the file extensions.
Absurd Gimmicks
As a gimmick, we would be able to explicitly compile a .json file extension, therefore having capacity to create package.json through fable, whether with a key,value list or just embedded JS 😂.
Hello,
What is the use case for this feature ?
Asking because to me it seems like this is trying to fix a problem that don't exist or where the solution is too complex compared to the initial issue.
Are you saying you want to generate files like vite.config.js ?
root
|- app.config.js // strict name
|- app.jsx // strict name
|- entry-client.jsx // strict name
|- entry-server.jsx // strict name
|- global.d.js // not important
|- Program.fs
|- Program.fs.jsx // compiled output
This is in the case of a framework such as SolidStart.
SolidStart is permissive enough to allow app.config.jsx, but they don't usually allow something like .config.fs.jsx.
I naturally want to avoid compiling to .jsx directly, for the same reason that we, by default, avoid compiling to .js directly.
I don't think this is such a complex issue, or solution. I would be happy to get permission to just make a method/member available in the PluginHelper so this option is at least available to me to explore usage in plugins.
There are multiple areas that I see this as being a boon, and it would open possibilities that may have fruitful rewards (such as enabling a plugin that compiles mdx).
Edit: Further to above, I feel that adding this type of preprocessing should seem like a natural direction that fable would take to achieve things like compiling to multiple languages.
.config.fs.jsx you can use dotnet fable -e jsx in order to not have the .fs.jsx prefix and if you don't want to mix us the files, you can use --outDir in order to place the file in another location.
Further to above, I feel that adding this type of preprocessing should seem like a natural direction that fable would take to achieve things like compiling to multiple languages.
In theory, you don't want in a single compilation to generate Python and JavaScript files.
Also, even if we were to add this feature we would probably not allow compiling to multiple target at the same time because it would probably need big rewrite of how Fable works.
Up to now, the solution for such scenario is to create the config files manually and/or use dummy files with the correct names to import the generated Fable files.
Which often means having 1-3 static files which makes the glue before delegating all the work to Fable generated code.
On another note, I don't think we can add custom directives for #.
Right now, it doesn't seems to make F# compiler complains but this would need to be checked in F# lang spec.
And perhaps, we should look if we can add an attribute to a file similarly to how its done for:
[<assembly: JavaScript>]
[<assembly: AssemblyTitle("Scatter")>]
[<assembly: AssemblyDescription("")>]
[<assembly: AssemblyConfiguration("")>]
[<assembly: AssemblyCompany("")>]
[<assembly: AssemblyProduct("Scatter")>]
[<assembly: AssemblyCopyright("Copyright © 2016")>]
[<assembly: AssemblyTrademark("")>]
[<assembly: AssemblyCulture("")>]
Another solution could be attributes on a module but it means that the file needs to have a top level module and not namespace (I am not sure if we can have attributes on namespace).