Created
May 7, 2020 14:14
-
-
Save deviousasti/b63f98b66401676511f00148ae38ce8f to your computer and use it in GitHub Desktop.
Html Imports Bundler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Project Sdk="Microsoft.NET.Sdk"> | |
<PropertyGroup> | |
<OutputType>Exe</OutputType> | |
<TargetFramework>netcoreapp3.1</TargetFramework> | |
<RootNamespace>html_imports</RootNamespace> | |
</PropertyGroup> | |
<ItemGroup> | |
<Compile Include="Html.fs" /> | |
<Compile Include="Program.fs" /> | |
</ItemGroup> | |
<ItemGroup> | |
<PackageReference Include="HtmlAgilityPack" Version="1.11.23" /> | |
</ItemGroup> | |
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module HtmlAgilityPack.FSharp | |
open HtmlAgilityPack | |
let parent (node : HtmlNode) = | |
node.ParentNode | |
let element name (node : HtmlNode) = | |
node.Element name | |
let elements name (node : HtmlNode) = | |
node.Elements name | |
let children (node : HtmlNode) = | |
node.ChildNodes |> Seq.cast<HtmlNode> | |
let descendants name (node : HtmlNode) = | |
node.Descendants (name = name) | |
let descendantsAndSelf name (node : HtmlNode) = | |
node.DescendantsAndSelf name | |
let ancestors name (node : HtmlNode) = | |
node.Ancestors name | |
let ancestorsAndSelf name (node : HtmlNode) = | |
node.AncestorsAndSelf name | |
let inline attr name (node : HtmlNode) = | |
node.GetAttributeValue(name, "") | |
let inline attrMap name map (node : HtmlNode) = | |
let clone = node.Clone() | |
clone.SetAttributeValue(name, map (node.GetAttributeValue(name, ""))) |> ignore | |
clone | |
let inline name (node : HtmlNode) = | |
node.Name | |
let inline hasAttr name value node = | |
attr name node = value | |
let inline hasText value (node : HtmlNode) = | |
node.InnerText = value | |
let createDoc html = | |
let doc = new HtmlDocument() | |
doc.LoadHtml html | |
doc | |
let rootNode (doc:HtmlDocument) = | |
doc.DocumentNode | |
let isValid (doc:HtmlDocument) = | |
doc.ParseErrors |> Seq.isEmpty |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open System | |
open System.IO | |
open HtmlAgilityPack.FSharp | |
open HtmlAgilityPack | |
open System.Collections.Generic | |
let log value = printfn "%A" value | |
let warnWith text = | |
Console.ForegroundColor <- ConsoleColor.Yellow | |
Console.WriteLine(string text) | |
Console.ResetColor() | |
type ImportDocument = { document: HtmlDocument; file: string } | |
let fullPath = Path.GetFullPath | |
let parentDir file = | |
Path.GetDirectoryName(fullPath file) | |
let relativeTo file other = | |
Path.Combine((parentDir file), other) |> fullPath | |
let partialRelativeTo root file = | |
Path.GetRelativePath((parentDir root), file).Replace("\\", "/") | |
let someIf condition value = if condition then Some value else None | |
let (|Import|_|) node = someIf ((node |> name = "link") && (node |> attr "rel" = "import")) node | |
let (|Script|_|) node = someIf ((node |> name = "script") && (node |> attr "src" <> "")) node | |
let (|Style|_|) node = someIf ((node |> name = "link") && (node |> attr "rel" = "stylesheet")) node | |
let scanFile file = | |
try | |
let doc = (createDoc (File.ReadAllText file)) | |
Some { document = doc; file = file; } | |
with ex -> | |
warnWith (sprintf "Could not parse: %s\n%A" file ex) | |
None | |
let unfoldImports source root hasResource = | |
let rec unfold source rel = | |
let importfile = source |> attr "href" |> relativeTo rel | |
let relativeToImport rel = rel |> relativeTo importfile |> partialRelativeTo root.file | |
let imported = if hasResource importfile then None else scanFile importfile | |
match imported with | |
| Some(imported) -> | |
seq { | |
for elem in imported.document |> rootNode |> children do | |
match elem with | |
| Import(elem) -> yield! unfold elem importfile | |
| Script(elem) -> yield elem |> attrMap "src" relativeToImport | |
| Style(elem) -> yield elem |> attrMap "href" relativeToImport | |
| _ -> yield elem | |
} | |
| None -> Seq.empty | |
unfold source root.file | |
let replaceImports doc = | |
let set = new HashSet<_>() | |
let add = not << set.Add | |
let replaceImport source = | |
unfoldImports source doc add | |
|> Seq.fold (fun (cur: HtmlNode) elem -> cur.ParentNode.InsertAfter(elem, cur)) source | |
|> ignore | |
source.Remove() | |
doc.document | |
|> rootNode | |
|> descendants "link" | |
|> Seq.choose (|Import|_|) | |
|> Seq.toArray | |
|> Seq.iter replaceImport | |
[<EntryPoint>] | |
let main argv = | |
let input, output = | |
match argv with | |
| [||] -> failwith "Input file not specified" | |
| [| input |] -> input, Console.Out | |
| [| input; output |] -> input, new StreamWriter(File.OpenWrite(output)) :> TextWriter | |
| other -> failwith (sprintf "Unknown arguments: %A" other) | |
match scanFile input with | |
| None -> printfn "Input file was invalid" | |
| Some (root) -> | |
replaceImports root | |
root.document.Save(output) | |
0 // return an integer exit code |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment