Collect desired exports in one file?
Description
We are developing a large library in F# that will also be used in JavaScript. However, our classes are now spread over several dozen files and we would like to give JS developers the ability to import all necessary classes from one file.
Is this possible? Is there already best practice for this?
Repro code
The library in question is this
Expected and actual results
Best case: Being able to import everything from the given npm package name, instead of single files:
Hello @Freymaurer,
I believe what you are looking for is exportDefault:
Makes an expression the default export for the JS module. Used to interact with JS tools that require a default export. ATTENTION: This statement must appear on the root level of the file module.
If you have the following 3 files:
|- FileA.fs
|- FileB.fs
|- Index.fs
FileA.fs
module FileA
let add x y = x + y
FileA.fs
module FileB
let sub x y = x + y
Index.fs
module Index
open Fable.Core
open Fable.Core.JsInterop
exportDefault {|
add = FileA.add
sub = FileB.sub
|}
This will generates:
import { add } from "./FileA.fs.js";
import { sub } from "./FileB.fs.js";
export default {
add: add,
sub: sub,
};
Meaning that you are still able to import function individually from each files (I don't think this is possible to prevent that even in JavaScript). But more importantly, you can now import everything available from the index.js file via a single import statement.
Yes something like this is what i am looking for. But sadly this does not work for types?
In the following case "ArcAssay", "ArcStudy" and "ArcInvestigation" are classes with constructor for which i can reference the main constructor (optional parameters do not work out of the box though).
But for a true and simple record type this is not usable.
I would require something like this:
export { OntologyAnnotation } from "./ARCtrl/ISA/ISA/JsonTypes/OntologyAnnotation.js"
export { ArcAssay, ArcStudy, ArcInvestigation } from "./ARCtrl/ISA/ISA/ArcTypes/ArcTypes.js";
Hum indeed, the problem is I don't think F# as a mechanism for re-exporting types from modules.
So we will need a custom Fable syntax / code to cover this kind of re-export.
I think right now, you are best to manually edit an index.js file for exposing the types your want or have a post-build script which walk through the generated code to generate it.
I think right now, you are best to manually edit an index.js file for exposing the types your want or have a post-build script which walk through the generated code to generate it.
Yeah that is what i thought 😄
So if anybody finds this, here is how i solved it:
module GenerateIndexJs =
open System
open System.IO
open System.Text.RegularExpressions
let private getAllJsFiles(path: string) =
let options = EnumerationOptions()
options.RecurseSubdirectories <- true
IO.Directory.EnumerateFiles(path,"*.js",options)
let private pattern (className: string) = sprintf @"^export class (?<ClassName>%s)+[\s{].*({)?" className
type private FileInformation = {
FilePath : string
Lines : string []
} with
static member create(filePath: string, lines: string []) = {
FilePath = filePath
Lines = lines
}
let private findClasses (rootPath: string) (cois: string []) (filePaths: seq<string> ) =
let files = [|
for fp in filePaths do
yield FileInformation.create(fp, System.IO.File.ReadAllLines (fp))
|]
let importStatements = ResizeArray()
let findClass (className: string) =
/// maybe set this as default if you do not want any whitelist
let classNameDefault = @"[a-zA-Z_0-9]"
let regex = Regex(Regex.Escape(className) |> pattern)
let mutable found = false
let mutable result = None
let enum = files.GetEnumerator()
while not found && enum.MoveNext() do
let fileInfo = enum.Current :?> FileInformation
for line in fileInfo.Lines do
let m = regex.Match(line)
match m.Success with
| true ->
found <- true
result <- Some <| (className, IO.Path.GetRelativePath(rootPath,fileInfo.FilePath))
| false ->
()
match result with
| None ->
failwithf "Unable to find %s" className
| Some r ->
importStatements.Add r
for coi in cois do findClass coi
importStatements
|> Array.ofSeq
open System.Text
let private createImportStatements (info: (string*string) []) =
let sb = StringBuilder()
let importCollection = info |> Array.groupBy snd |> Array.map (fun (p,a) -> p, a |> Array.map fst )
for filePath, imports in importCollection do
let p = filePath.Replace("\\","/")
sb.Append "export { " |> ignore
sb.AppendJoin(", ", imports) |> ignore
sb.Append " } from " |> ignore
sb.Append (sprintf "\"./%s\"" p) |> ignore
sb.Append ";" |> ignore
sb.AppendLine() |> ignore
sb.ToString()
let private writeJsIndexfile (path: string) (fileName: string) (content: string) =
let filePath = Path.Combine(path, fileName)
File.WriteAllText(filePath, content)
let generateIndexfile (rootPath: string, fileName: string, whiteList: string []) =
getAllJsFiles(rootPath)
|> findClasses rootPath whiteList
|> createImportStatements
|> writeJsIndexfile rootPath fileName
Creates something like this:
export { Comment$ } from "./ISA/ISA/JsonTypes/Comment.js";
export { Person } from "./ISA/ISA/JsonTypes/Person.js";
export { OntologyAnnotation } from "./ISA/ISA/JsonTypes/OntologyAnnotation.js";
export { IOType, CompositeHeader } from "./ISA/ISA/ArcTypes/CompositeHeader.js";
export { CompositeCell } from "./ISA/ISA/ArcTypes/CompositeCell.js";
export { CompositeColumn } from "./ISA/ISA/ArcTypes/CompositeColumn.js";
export { ArcTable } from "./ISA/ISA/ArcTypes/ArcTable.js";
export { ArcAssay, ArcStudy, ArcInvestigation } from "./ISA/ISA/ArcTypes/ArcTypes.js";
export { Template, Organisation } from "./Templates/Template.js";
export { JsWeb } from "./Templates/Template.Web.js";
export { ARC } from "./ARCtrl.js";
@Freymaurer Thank you for sharing the script