Skip to content

feat: added support for registering embedded filesystems as view sources - #1546

Open
protibimbok wants to merge 3 commits into
goravel:masterfrom
protibimbok:feat/embeded-view-templates
Open

feat: added support for registering embedded filesystems as view sources#1546
protibimbok wants to merge 3 commits into
goravel:masterfrom
protibimbok:feat/embeded-view-templates

Conversation

@protibimbok

Copy link
Copy Markdown

📑 Description

Closes goravel/goravel#990

Packages can now ship their views inside the binary with go:embed instead of
requiring template files on disk at runtime.

Added

  • View.LoadViewsFromFS(fsys fs.FS, root string) — registers an fs.FS
    (e.g. an embed.FS) as a package view source. root selects the directory
    inside the FS; pass "." to use the whole FS.
  • View.RegisteredViewFS() []fs.FS — returns the registered filesystems in
    registration order, for HTTP drivers to load templates from.
  • Errors ViewFSRequired and ViewInvalidFSRoot (panics on a nil FS or an
    invalid root, matching how a bad path fails today).
//go:embed views/*
var views embed.FS

facades.View().LoadViewsFromFS(views, "views")

Behaviour

  • Each FS is rooted with fs.Sub at registration time, so every source looks
    the same to consumers: template paths are relative to "." (e.g.
    layouts/app.tmpl), including nested directories.
  • root accepts loose forms ("", ./views, /views/, views\admin) and is
    normalised to a valid io/fs path.
  • Exists() also checks registered filesystems.
  • Precedence is unchanged: app views > LoadViewsFrom dirs >
    LoadViewsFromFS
    , each in registration order. Existing LoadViewsFrom
    users are unaffected.

Notes

  • The mocks/view mock is regenerated for the new interface methods.
  • Actual template loading from these filesystems lands in the HTTP driver;
    a companion goravel/gin PR follows once this is tagged.

✅ Checks

  • Added test cases for my code

Added View.LoadViewsFromFS(fsys, root) and View.RegisteredViewFS() so
packages
can ship templates in an embed.FS instead of an on-disk directory. The
fs is rooted with fs.Sub at registration time, so consumers get uniform
sources with template paths relative to \".\". Exists() now also checks
registered filesystems.
Precedence is unchanged: app views > LoadViewsFrom dirs >
LoadViewsFromFS, each in registration order.
embedded views

Added an embed.FS fixture under view/testdata and tests for: registering
directories and filesystems side by side, root normalisation ("views/",
"/views", "./views", OS separators, ".", "" and "/"), nil/escaping
roots, Exists() across app, directory and embedded sources, and
end-to-end template resolution with app > directory > embedded
precedence, including a nested layout/partial/block chain and missing
templates.
@protibimbok
protibimbok requested a review from a team as a code owner September 1, 2026 11:58
@protibimbok protibimbok changed the title Feat/embeded view templates feat: added support for registering embedded filesystems as view sources Sep 1, 2026
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.96970% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 72.69%. Comparing base (cfde3ce) to head (f7d4fc1).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
view/view.go 96.96% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1546      +/-   ##
==========================================
+ Coverage   72.41%   72.69%   +0.27%     
==========================================
  Files         409      412       +3     
  Lines       26475    26772     +297     
==========================================
+ Hits        19172    19462     +290     
- Misses       7301     7308       +7     
  Partials        2        2              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@hwbrzzl

hwbrzzl commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Thanks, checking

@protibimbok

Copy link
Copy Markdown
Author

Not sure if you have noticed but please check goravel/gin#242 too
Both are completed and goes together

@goravel-coder

Copy link
Copy Markdown
Contributor

Review

Thanks for the PR — clean, well-tested change overall. A few findings below.

Please double-confirm each finding against your local environment before resolving. Several of these (especially #1 and #2) depend on Go stdlib and OS behavior (fs.Sub validation, filepath.ToSlash) that can vary by Go version / platform, so please verify the exact behavior on your setup rather than taking them at face value.

Should Fix

1. LoadViewsFromFS never verifies the root actually existsview/view.go:63-66

fs.Sub only enforces fs.ValidPath (rejects .././empty elements); it does not stat the directory. For embed.FS (which doesn't implement SubFS), LoadViewsFromFS(fsys, "typo") registers a silently-empty source with no error — every later fs.Stat/fs.ReadFile returns fs.ErrNotExist. This contradicts the contract doc ("panics if … root is not a valid fs path") and the ViewInvalidFSRoot name. Either fs.Stat(sub, ".") at registration and panic on ErrNotExist, or narrow the docs to "rejects ../absolute roots; nonexistent roots are accepted lazily."

2. Backslash normalization is a no-op on non-Windowsview/view.go:119

filepath.ToSlash is an identity on Linux/macOS, so the documented views\admin form is not converted there; the backslash survives as a literal path element and fs.Sub accepts it, silently registering a source rooted at a nonexistent name. The "os separator" test (view/view_test.go:92,157) is a no-op on non-Windows CI, so this is invisible to the current test matrix. Either add explicit strings.ReplaceAll(p, "\\", "/"), or drop the backslash claim from the doc comments.

3. Panic vs silent divergence between sibling methodsview/view.go:50-54 vs :56-71

LoadViewsFrom silently appends any path; LoadViewsFromFS panics on nil/invalid root. Two methods doing the same conceptual thing now fail differently. Defensible, but worth reconciling or documenting the rationale.

Nits

  • view/view.go:40normalizeFSPath runs unconditionally on every Exists miss, even when no FS sources are registered. Guard with if len(r.filesystems) > 0.
  • view/view.go:40-45Exists("")/Exists(".") return true once any FS is registered (fs.Stat(sub, ".") stats the root dir). Edge case; a cheap empty/dot guard closes it.
  • Rendering depends on the companion driverExists is only half the story; actual View().Make(...) requires the gin/fiber driver to parse RegisteredViewFS(). That's deferred to a companion PR.
  • contracts/view/view.go:17 — godoc example //go:embed views/* embeds only direct children; nested dirs (layouts/, partials/) need //go:embed views or views/**.
  • contracts/view/view.go:23RegisteredViewFS reads singular next to plural RegisteredViews; consider RegisteredViewFSs.
  • view/view_test.go:245-253setupAppViews mutates process-global support.RelativePath/support.Config.Paths.Resources; race-free today (no t.Parallel(), verified with -race) but fragile.

Verified correct

  • Root-escape (../views) is correctly rejected via fs.ValidPathfs.ErrInvalidViewInvalidFSRoot.
  • RegisteredViews/RegisteredViewFS both make+copy; no mutation leaks.
  • Mutex discipline is race-free; errors use the New(...) constructor; gofmt/go vet clean; mock regenerated correctly.

- LoadViewsFromFS now stats the root after fs.Sub: a missing root panics
  with ViewInvalidFSRoot and a file root panics with the new
  ViewFSRootNotDirectory, instead of registering a source that never
  matches anything.
- Exists no longer reports directories, "" or "." as views for app
  paths, LoadViewsFrom paths or embedded filesystems.
- normalizeFSPath replaces backslashes on every platform rather than
  only on Windows via filepath.ToSlash.
- Document accepted root spellings and the eager-panic rationale on the
  View contract; add tests for the new cases.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: Support Embedded Package Views with io/fs

3 participants