Hosting local web assets

A CefBrowser control can serve HTML, JavaScript, CSS, and any other assets straight from a folder on disk — no embedded HTTP server required. Chromium’s SetVirtualHostNameToFolderMapping routes a virtual https:// hostname to a local folder so that resources behave as if they came from a real origin: same-origin fetch, Content Security Policy, service workers, and so on all work as expected.

This tutorial walks through the pattern used by Sample 1b — Chromium Embedded Framework Examples (forms Example 2, Example 3, Example 4).

The three-step pattern

  1. Choose a folder. It must exist on disk and contain index.html (plus whatever assets the page wants — scripts, styles, images).
  2. Register a virtual host mapping to that folder.
  3. Navigate to a URL under the virtual hostname.

Hook into the Ready event so the control is fully initialised before the mapping is installed:

Private Sub WebView_Ready() Handles WebView.Ready
    Dim folderPath As String = _
        Environ$("USERPROFILE") & "\Documents\MyApp"
    WebView.SetVirtualHostNameToFolderMapping _
        "myapp.example", folderPath & "\"
    WebView.Navigate "https://myapp.example/index.html"
End Sub

Once mapped, every request to https://myapp.example/<path> is served from folderPath\<path>. A <script src="/script.js"> on the page resolves to folderPath\script.js exactly as if a real web server were sitting on myapp.example.

The trailing backslash on the folder path is required — the runtime concatenates the incoming URL path onto the folder string verbatim, so a missing separator turns folderPath + /index.html into a nonsense path.

Picking a hostname

The safe convention is to pick a hostname under a TLD that will never resolve on the public Internet:

Recommended Avoid
myapp.example myapp.com, app.local
editor.invalid editor.dev
assets.test assets.io

The .example, .invalid, and .test TLDs are formally reserved by IANA and will never be allocated to a real domain, so they’re safe to use indefinitely.

Bundling assets in the project’s Resources folder

Most applications want to ship their HTML / JS / CSS inside the executable and drop them onto disk on first run. twinBASIC’s Resources folder is the right place to keep them.

  1. In the IDE’s Project explorer, expand Resources and add a sub-folder (right-click → Add new subfolder). Name it something memorable like WEB_APP.
  2. Drop the assets in — index.html, script.js, styles.css, plus any sub-directories you need.

At runtime, the helper below copies the contents of a Resources sub-folder out to a local path. Drop it into a .twin module in your project:

Module Files

    Private Sub CreateFile(ByVal Path As String, ByRef Data() As Byte)
        On Error Resume Next : Kill Path : On Error GoTo 0
        Dim fileNum As Integer = FreeFile
        Open Path For Binary As fileNum
        Put fileNum, 1, Data
        Close fileNum
    End Sub

    Private Sub CreateLocalFileFromResource( _
            ByVal OutputLocalFolderPath As String, _
            ByVal InputResourceSubFolderName As String, _
            ByVal ResourceName As String)

        Dim splitPath As Variant = Split(ResourceName, "~")
        On Error Resume Next : MkDir OutputLocalFolderPath : On Error GoTo 0

        Dim i As Long
        For i = 0 To UBound(splitPath) - 1
            OutputLocalFolderPath &= "\" & splitPath(i)
            On Error Resume Next : MkDir OutputLocalFolderPath : On Error GoTo 0
        Next

        Dim Data() As Byte
        Data = LoadResData(ResourceName, InputResourceSubFolderName)
        CreateFile(OutputLocalFolderPath & "\" & splitPath(i), Data)
    End Sub

    [Description("Copy every file from a Resources subfolder onto disk. " & _
                 "'~' characters in resource names represent subfolders.")]
    Public Sub CopyResourcesFolderContentsToLocalPath( _
            ByVal InputResourceSubFolderName As String, _
            ByVal OutputLocalFolderPath As String)

        Dim resourceId As Variant
        For Each resourceId In LoadResIdList(InputResourceSubFolderName)
            CreateLocalFileFromResource _
                OutputLocalFolderPath, InputResourceSubFolderName, resourceId
        Next
    End Sub

End Module

LoadResIdList returns every resource ID under the named sub-folder; LoadResData hands back the bytes. The helper splits each resource name on ~ to reconstruct the original sub-directory tree on disk — the twinBASIC IDE flattens nested folders by joining their names with ~ when the resources are compiled in.

Putting it together

The complete deploy-on-Ready pattern looks like this:

Private Sub WebView_Ready() Handles WebView.Ready
    ' Resources/WEB_APP/* is copied here on every launch.
    Dim folderPath As String = _
        Environ$("USERPROFILE") & "\Documents\MyApp"

    CopyResourcesFolderContentsToLocalPath "WEB_APP", folderPath

    WebView.SetVirtualHostNameToFolderMapping _
        "myapp.example", folderPath & "\"
    WebView.Navigate "https://myapp.example/index.html"
End Sub

Once deployed, the application can launch DevTools (OpenDevToolsWindow) to inspect the loaded files, and users can edit index.html directly on disk and hit Refresh — useful for rapid iteration during development.

Removing a mapping

ClearVirtualHostNameToFolderMapping removes a mapping previously installed by SetVirtualHostNameToFolderMapping:

WebView.ClearVirtualHostNameToFolderMapping "myapp.example"

The browser keeps cached assets until a hard reload, so a navigation that hits the just-removed hostname may still succeed for a short while.

Where next