Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow rendering static files to disk and dynamic to memory in server mode #9626

Merged
merged 2 commits into from
Mar 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion commands/commandeer.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ type commandeer struct {
languagesConfigured bool
languages langs.Languages
doLiveReload bool
renderStaticToDisk bool
fastRenderMode bool
showErrorInBrowser bool
wasError bool
Expand Down Expand Up @@ -368,8 +369,9 @@ func (c *commandeer) loadConfig() error {
}

createMemFs := config.GetBool("renderToMemory")
c.renderStaticToDisk = config.GetBool("renderStaticToDisk")

if createMemFs {
if createMemFs && !c.renderStaticToDisk {
// Rendering to memoryFS, publish to Root regardless of publishDir.
config.Set("publishDir", "/")
}
Expand All @@ -380,6 +382,14 @@ func (c *commandeer) loadConfig() error {
if c.destinationFs != nil {
// Need to reuse the destination on server rebuilds.
fs.Destination = c.destinationFs
} else if createMemFs && c.renderStaticToDisk {
// Writes the dynamic output on memory,
// while serve others directly from publishDir
publishDir := config.GetString("publishDir")
writableFs := afero.NewBasePathFs(afero.NewMemMapFs(), publishDir)
publicFs := afero.NewOsFs()
fs.Destination = afero.NewCopyOnWriteFs(afero.NewReadOnlyFs(publicFs), writableFs)
fs.DestinationStatic = publicFs
} else if createMemFs {
// Hugo writes the output to memory instead of the disk.
fs.Destination = new(afero.MemMapFs)
Expand All @@ -397,10 +407,13 @@ func (c *commandeer) loadConfig() error {

changeDetector.PrepareNew()
fs.Destination = hugofs.NewHashingFs(fs.Destination, changeDetector)
fs.DestinationStatic = hugofs.NewHashingFs(fs.DestinationStatic, changeDetector)
c.changeDetector = changeDetector
}

if c.Cfg.GetBool("logPathWarnings") {
// Note that we only care about the "dynamic creates" here,
// so skip the static fs.
fs.Destination = hugofs.NewCreateCountingFs(fs.Destination)
}

Expand Down
3 changes: 3 additions & 0 deletions commands/hugo.go
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,9 @@ func (c *commandeer) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint6
syncer.ChmodFilter = chmodFilter
syncer.SrcFs = fs
syncer.DestFs = c.Fs.Destination
if c.renderStaticToDisk {
syncer.DestFs = c.Fs.DestinationStatic
}
// Now that we are using a unionFs for the static directories
// We can effectively clean the publishDir on initial sync
syncer.Delete = c.Cfg.GetBool("cleanDestinationDir")
Expand Down
25 changes: 15 additions & 10 deletions commands/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,16 @@ type serverCmd struct {
// Can be used to stop the server. Useful in tests
stop <-chan bool

disableLiveReload bool
navigateToChanged bool
renderToDisk bool
serverAppend bool
serverInterface string
serverPort int
liveReloadPort int
serverWatch bool
noHTTPCache bool
disableLiveReload bool
navigateToChanged bool
renderToDisk bool
renderStaticToDisk bool
serverAppend bool
serverInterface string
serverPort int
liveReloadPort int
serverWatch bool
noHTTPCache bool

disableFastRender bool
disableBrowserError bool
Expand Down Expand Up @@ -98,7 +99,8 @@ of a second, you will be able to save and see your changes nearly instantly.`,
cc.cmd.Flags().BoolVarP(&cc.serverAppend, "appendPort", "", true, "append port to baseURL")
cc.cmd.Flags().BoolVar(&cc.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild")
cc.cmd.Flags().BoolVar(&cc.navigateToChanged, "navigateToChanged", false, "navigate to changed content file on live browser reload")
cc.cmd.Flags().BoolVar(&cc.renderToDisk, "renderToDisk", false, "render to Destination path (default is render to memory & serve from there)")
cc.cmd.Flags().BoolVar(&cc.renderToDisk, "renderToDisk", false, "serve all files from disk (default is from memory)")
cc.cmd.Flags().BoolVar(&cc.renderStaticToDisk, "renderStaticToDisk", false, "serve static files from disk and dynamic files from memory")
cc.cmd.Flags().BoolVar(&cc.disableFastRender, "disableFastRender", false, "enables full re-renders on changes")
cc.cmd.Flags().BoolVar(&cc.disableBrowserError, "disableBrowserError", false, "do not show build errors in the browser")

Expand Down Expand Up @@ -141,6 +143,7 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {

cfgInit := func(c *commandeer) error {
c.Set("renderToMemory", !sc.renderToDisk)
c.Set("renderStaticToDisk", sc.renderStaticToDisk)
if cmd.Flags().Changed("navigateToChanged") {
c.Set("navigateToChanged", sc.navigateToChanged)
}
Expand Down Expand Up @@ -332,6 +335,8 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
if i == 0 {
if f.s.renderToDisk {
jww.FEEDBACK.Println("Serving pages from " + absPublishDir)
} else if f.s.renderStaticToDisk {
jww.FEEDBACK.Println("Serving pages from memory and static files from " + absPublishDir)
} else {
jww.FEEDBACK.Println("Serving pages from memory")
}
Expand Down
9 changes: 8 additions & 1 deletion commands/static_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
syncer.ChmodFilter = chmodFilter
syncer.SrcFs = sourceFs.Fs
syncer.DestFs = c.Fs.Destination
if c.renderStaticToDisk {
syncer.DestFs = c.Fs.DestinationStatic
}

// prevent spamming the log on changes
logger := helpers.NewDistinctErrorLogger()
Expand Down Expand Up @@ -101,7 +104,11 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
toRemove := filepath.Join(publishDir, relPath)

logger.Println("File no longer exists in static dir, removing", toRemove)
_ = c.Fs.Destination.RemoveAll(toRemove)
if c.renderStaticToDisk {
_ = c.Fs.DestinationStatic.RemoveAll(toRemove)
} else {
_ = c.Fs.Destination.RemoveAll(toRemove)
}
} else if err == nil {
// If file still exists, sync it
logger.Println("Syncing", relPath, "to", publishDir)
Expand Down
12 changes: 8 additions & 4 deletions hugofs/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ type Fs struct {
// Destination is Hugo's destination file system.
Destination afero.Fs

// Destination used for `renderStaticToDisk`
DestinationStatic afero.Fs

// Os is an OS file system.
// NOTE: Field is currently unused.
Os afero.Fs
Expand Down Expand Up @@ -69,10 +72,11 @@ func NewFrom(fs afero.Fs, cfg config.Provider) *Fs {

func newFs(base afero.Fs, cfg config.Provider) *Fs {
return &Fs{
Source: base,
Destination: base,
Os: &afero.OsFs{},
WorkingDir: getWorkingDirFs(base, cfg),
Source: base,
Destination: base,
DestinationStatic: base,
Os: &afero.OsFs{},
WorkingDir: getWorkingDirFs(base, cfg),
}
}

Expand Down
13 changes: 9 additions & 4 deletions hugolib/filesystems/basefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ type BaseFs struct {
// A read-only filesystem starting from the project workDir.
WorkDir afero.Fs

// The filesystem used for renderStaticToDisk.
PublishFsStatic afero.Fs

theBigFs *filesystemsCollector

// Locks.
Expand Down Expand Up @@ -438,15 +441,17 @@ func NewBase(p *paths.Paths, logger loggers.Logger, options ...func(*BaseFs) err

publishFs := hugofs.NewBaseFileDecorator(afero.NewBasePathFs(fs.Destination, p.AbsPublishDir))
sourceFs := hugofs.NewBaseFileDecorator(afero.NewBasePathFs(fs.Source, p.WorkingDir))
publishFsStatic := afero.NewBasePathFs(fs.Source, p.AbsPublishDir)

// Same as sourceFs, but no decoration. This is what's used by os.ReadDir etc.
workDir := afero.NewBasePathFs(afero.NewReadOnlyFs(fs.Source), p.WorkingDir)

b := &BaseFs{
SourceFs: sourceFs,
WorkDir: workDir,
PublishFs: publishFs,
buildMu: lockedfile.MutexAt(filepath.Join(p.WorkingDir, lockFileBuild)),
SourceFs: sourceFs,
WorkDir: workDir,
PublishFs: publishFs,
PublishFsStatic: publishFsStatic,
buildMu: lockedfile.MutexAt(filepath.Join(p.WorkingDir, lockFileBuild)),
}

for _, opt := range options {
Expand Down
16 changes: 12 additions & 4 deletions hugolib/pages_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ func newPagesProcessor(h *HugoSites, sp *source.SourceSpec) *pagesProcessor {
procs := make(map[string]pagesCollectorProcessorProvider)
for _, s := range h.Sites {
procs[s.Lang()] = &sitePagesProcessor{
m: s.pageMap,
errorSender: s.h,
itemChan: make(chan interface{}, config.GetNumWorkerMultiplier()*2),
m: s.pageMap,
errorSender: s.h,
itemChan: make(chan interface{}, config.GetNumWorkerMultiplier()*2),
renderStaticToDisk: h.Cfg.GetBool("renderStaticToDisk"),
}
}
return &pagesProcessor{
Expand Down Expand Up @@ -118,6 +119,8 @@ type sitePagesProcessor struct {
ctx context.Context
itemChan chan interface{}
itemGroup *errgroup.Group

renderStaticToDisk bool
}

func (p *sitePagesProcessor) Process(item interface{}) error {
Expand Down Expand Up @@ -162,7 +165,12 @@ func (p *sitePagesProcessor) copyFile(fim hugofs.FileMetaInfo) error {

defer f.Close()

return s.publish(&s.PathSpec.ProcessingStats.Files, target, f)
fs := s.PublishFs
if p.renderStaticToDisk {
fs = s.PublishFsStatic
}

return s.publish(&s.PathSpec.ProcessingStats.Files, target, f, fs)
}

func (p *sitePagesProcessor) doProcess(item interface{}) error {
Expand Down
4 changes: 2 additions & 2 deletions hugolib/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -1824,10 +1824,10 @@ func (s *Site) lookupTemplate(layouts ...string) (tpl.Template, bool) {
return nil, false
}

func (s *Site) publish(statCounter *uint64, path string, r io.Reader) (err error) {
func (s *Site) publish(statCounter *uint64, path string, r io.Reader, fs afero.Fs) (err error) {
s.PathSpec.ProcessingStats.Incr(statCounter)

return helpers.WriteToDisk(filepath.Clean(path), r, s.BaseFs.PublishFs)
return helpers.WriteToDisk(filepath.Clean(path), r, fs)
}

func (s *Site) kindFromFileInfoOrSections(fi *fileInfo, sections []string) string {
Expand Down