mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-07 20:30:36 -05:00
Allow rendering static files to disk and dynamic to memory in server mode
Updates #9625
This commit is contained in:
parent
b9a1be2f99
commit
7d8011ed63
8 changed files with 69 additions and 25 deletions
|
@ -92,6 +92,7 @@ type commandeer struct {
|
||||||
languagesConfigured bool
|
languagesConfigured bool
|
||||||
languages langs.Languages
|
languages langs.Languages
|
||||||
doLiveReload bool
|
doLiveReload bool
|
||||||
|
renderStaticToDisk bool
|
||||||
fastRenderMode bool
|
fastRenderMode bool
|
||||||
showErrorInBrowser bool
|
showErrorInBrowser bool
|
||||||
wasError bool
|
wasError bool
|
||||||
|
@ -368,8 +369,9 @@ func (c *commandeer) loadConfig() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
createMemFs := config.GetBool("renderToMemory")
|
createMemFs := config.GetBool("renderToMemory")
|
||||||
|
c.renderStaticToDisk = config.GetBool("renderStaticToDisk")
|
||||||
|
|
||||||
if createMemFs {
|
if createMemFs && !c.renderStaticToDisk {
|
||||||
// Rendering to memoryFS, publish to Root regardless of publishDir.
|
// Rendering to memoryFS, publish to Root regardless of publishDir.
|
||||||
config.Set("publishDir", "/")
|
config.Set("publishDir", "/")
|
||||||
}
|
}
|
||||||
|
@ -380,6 +382,14 @@ func (c *commandeer) loadConfig() error {
|
||||||
if c.destinationFs != nil {
|
if c.destinationFs != nil {
|
||||||
// Need to reuse the destination on server rebuilds.
|
// Need to reuse the destination on server rebuilds.
|
||||||
fs.Destination = c.destinationFs
|
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 {
|
} else if createMemFs {
|
||||||
// Hugo writes the output to memory instead of the disk.
|
// Hugo writes the output to memory instead of the disk.
|
||||||
fs.Destination = new(afero.MemMapFs)
|
fs.Destination = new(afero.MemMapFs)
|
||||||
|
@ -397,11 +407,13 @@ func (c *commandeer) loadConfig() error {
|
||||||
|
|
||||||
changeDetector.PrepareNew()
|
changeDetector.PrepareNew()
|
||||||
fs.Destination = hugofs.NewHashingFs(fs.Destination, changeDetector)
|
fs.Destination = hugofs.NewHashingFs(fs.Destination, changeDetector)
|
||||||
|
fs.DestinationStatic = hugofs.NewHashingFs(fs.DestinationStatic, changeDetector)
|
||||||
c.changeDetector = changeDetector
|
c.changeDetector = changeDetector
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Cfg.GetBool("logPathWarnings") {
|
if c.Cfg.GetBool("logPathWarnings") {
|
||||||
fs.Destination = hugofs.NewCreateCountingFs(fs.Destination)
|
fs.Destination = hugofs.NewCreateCountingFs(fs.Destination)
|
||||||
|
fs.DestinationStatic = hugofs.NewCreateCountingFs(fs.DestinationStatic)
|
||||||
}
|
}
|
||||||
|
|
||||||
// To debug hard-to-find path issues.
|
// To debug hard-to-find path issues.
|
||||||
|
|
|
@ -652,6 +652,9 @@ func (c *commandeer) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint6
|
||||||
syncer.ChmodFilter = chmodFilter
|
syncer.ChmodFilter = chmodFilter
|
||||||
syncer.SrcFs = fs
|
syncer.SrcFs = fs
|
||||||
syncer.DestFs = c.Fs.Destination
|
syncer.DestFs = c.Fs.Destination
|
||||||
|
if c.renderStaticToDisk {
|
||||||
|
syncer.DestFs = c.Fs.DestinationStatic
|
||||||
|
}
|
||||||
// Now that we are using a unionFs for the static directories
|
// Now that we are using a unionFs for the static directories
|
||||||
// We can effectively clean the publishDir on initial sync
|
// We can effectively clean the publishDir on initial sync
|
||||||
syncer.Delete = c.Cfg.GetBool("cleanDestinationDir")
|
syncer.Delete = c.Cfg.GetBool("cleanDestinationDir")
|
||||||
|
|
|
@ -48,15 +48,16 @@ type serverCmd struct {
|
||||||
// Can be used to stop the server. Useful in tests
|
// Can be used to stop the server. Useful in tests
|
||||||
stop <-chan bool
|
stop <-chan bool
|
||||||
|
|
||||||
disableLiveReload bool
|
disableLiveReload bool
|
||||||
navigateToChanged bool
|
navigateToChanged bool
|
||||||
renderToDisk bool
|
renderToDisk bool
|
||||||
serverAppend bool
|
renderStaticToDisk bool
|
||||||
serverInterface string
|
serverAppend bool
|
||||||
serverPort int
|
serverInterface string
|
||||||
liveReloadPort int
|
serverPort int
|
||||||
serverWatch bool
|
liveReloadPort int
|
||||||
noHTTPCache bool
|
serverWatch bool
|
||||||
|
noHTTPCache bool
|
||||||
|
|
||||||
disableFastRender bool
|
disableFastRender bool
|
||||||
disableBrowserError bool
|
disableBrowserError bool
|
||||||
|
@ -101,6 +102,7 @@ of a second, you will be able to save and see your changes nearly instantly.`,
|
||||||
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, "render to Destination path (default is render to memory & serve from there)")
|
||||||
cc.cmd.Flags().BoolVar(&cc.disableFastRender, "disableFastRender", false, "enables full re-renders on changes")
|
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")
|
cc.cmd.Flags().BoolVar(&cc.disableBrowserError, "disableBrowserError", false, "do not show build errors in the browser")
|
||||||
|
cc.cmd.Flags().BoolVar(&cc.renderStaticToDisk, "renderStaticToDisk", false, "render static files to disk but dynamic files render to memory.")
|
||||||
|
|
||||||
cc.cmd.Flags().String("memstats", "", "log memory usage to this file")
|
cc.cmd.Flags().String("memstats", "", "log memory usage to this file")
|
||||||
cc.cmd.Flags().String("meminterval", "100ms", "interval to poll memory usage (requires --memstats), valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\".")
|
cc.cmd.Flags().String("meminterval", "100ms", "interval to poll memory usage (requires --memstats), valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\".")
|
||||||
|
@ -141,6 +143,7 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
cfgInit := func(c *commandeer) error {
|
cfgInit := func(c *commandeer) error {
|
||||||
c.Set("renderToMemory", !sc.renderToDisk)
|
c.Set("renderToMemory", !sc.renderToDisk)
|
||||||
|
c.Set("renderStaticToDisk", sc.renderStaticToDisk)
|
||||||
if cmd.Flags().Changed("navigateToChanged") {
|
if cmd.Flags().Changed("navigateToChanged") {
|
||||||
c.Set("navigateToChanged", sc.navigateToChanged)
|
c.Set("navigateToChanged", sc.navigateToChanged)
|
||||||
}
|
}
|
||||||
|
@ -332,6 +335,8 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
if f.s.renderToDisk {
|
if f.s.renderToDisk {
|
||||||
jww.FEEDBACK.Println("Serving pages from " + absPublishDir)
|
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 {
|
} else {
|
||||||
jww.FEEDBACK.Println("Serving pages from memory")
|
jww.FEEDBACK.Println("Serving pages from memory")
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,9 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
|
||||||
syncer.ChmodFilter = chmodFilter
|
syncer.ChmodFilter = chmodFilter
|
||||||
syncer.SrcFs = sourceFs.Fs
|
syncer.SrcFs = sourceFs.Fs
|
||||||
syncer.DestFs = c.Fs.Destination
|
syncer.DestFs = c.Fs.Destination
|
||||||
|
if c.renderStaticToDisk {
|
||||||
|
syncer.DestFs = c.Fs.DestinationStatic
|
||||||
|
}
|
||||||
|
|
||||||
// prevent spamming the log on changes
|
// prevent spamming the log on changes
|
||||||
logger := helpers.NewDistinctErrorLogger()
|
logger := helpers.NewDistinctErrorLogger()
|
||||||
|
@ -101,7 +104,11 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
|
||||||
toRemove := filepath.Join(publishDir, relPath)
|
toRemove := filepath.Join(publishDir, relPath)
|
||||||
|
|
||||||
logger.Println("File no longer exists in static dir, removing", toRemove)
|
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 {
|
} else if err == nil {
|
||||||
// If file still exists, sync it
|
// If file still exists, sync it
|
||||||
logger.Println("Syncing", relPath, "to", publishDir)
|
logger.Println("Syncing", relPath, "to", publishDir)
|
||||||
|
|
12
hugofs/fs.go
12
hugofs/fs.go
|
@ -35,6 +35,9 @@ type Fs struct {
|
||||||
// Destination is Hugo's destination file system.
|
// Destination is Hugo's destination file system.
|
||||||
Destination afero.Fs
|
Destination afero.Fs
|
||||||
|
|
||||||
|
// Destination used for `renderStaticToDisk`
|
||||||
|
DestinationStatic afero.Fs
|
||||||
|
|
||||||
// Os is an OS file system.
|
// Os is an OS file system.
|
||||||
// NOTE: Field is currently unused.
|
// NOTE: Field is currently unused.
|
||||||
Os afero.Fs
|
Os afero.Fs
|
||||||
|
@ -69,10 +72,11 @@ func NewFrom(fs afero.Fs, cfg config.Provider) *Fs {
|
||||||
|
|
||||||
func newFs(base afero.Fs, cfg config.Provider) *Fs {
|
func newFs(base afero.Fs, cfg config.Provider) *Fs {
|
||||||
return &Fs{
|
return &Fs{
|
||||||
Source: base,
|
Source: base,
|
||||||
Destination: base,
|
Destination: base,
|
||||||
Os: &afero.OsFs{},
|
DestinationStatic: base,
|
||||||
WorkingDir: getWorkingDirFs(base, cfg),
|
Os: &afero.OsFs{},
|
||||||
|
WorkingDir: getWorkingDirFs(base, cfg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,9 @@ type BaseFs struct {
|
||||||
// A read-only filesystem starting from the project workDir.
|
// A read-only filesystem starting from the project workDir.
|
||||||
WorkDir afero.Fs
|
WorkDir afero.Fs
|
||||||
|
|
||||||
|
// The filesystem used for renderStaticToDisk.
|
||||||
|
PublishFsStatic afero.Fs
|
||||||
|
|
||||||
theBigFs *filesystemsCollector
|
theBigFs *filesystemsCollector
|
||||||
|
|
||||||
// Locks.
|
// Locks.
|
||||||
|
@ -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))
|
publishFs := hugofs.NewBaseFileDecorator(afero.NewBasePathFs(fs.Destination, p.AbsPublishDir))
|
||||||
sourceFs := hugofs.NewBaseFileDecorator(afero.NewBasePathFs(fs.Source, p.WorkingDir))
|
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.
|
// Same as sourceFs, but no decoration. This is what's used by os.ReadDir etc.
|
||||||
workDir := afero.NewBasePathFs(afero.NewReadOnlyFs(fs.Source), p.WorkingDir)
|
workDir := afero.NewBasePathFs(afero.NewReadOnlyFs(fs.Source), p.WorkingDir)
|
||||||
|
|
||||||
b := &BaseFs{
|
b := &BaseFs{
|
||||||
SourceFs: sourceFs,
|
SourceFs: sourceFs,
|
||||||
WorkDir: workDir,
|
WorkDir: workDir,
|
||||||
PublishFs: publishFs,
|
PublishFs: publishFs,
|
||||||
buildMu: lockedfile.MutexAt(filepath.Join(p.WorkingDir, lockFileBuild)),
|
PublishFsStatic: publishFsStatic,
|
||||||
|
buildMu: lockedfile.MutexAt(filepath.Join(p.WorkingDir, lockFileBuild)),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range options {
|
for _, opt := range options {
|
||||||
|
|
|
@ -33,9 +33,10 @@ func newPagesProcessor(h *HugoSites, sp *source.SourceSpec) *pagesProcessor {
|
||||||
procs := make(map[string]pagesCollectorProcessorProvider)
|
procs := make(map[string]pagesCollectorProcessorProvider)
|
||||||
for _, s := range h.Sites {
|
for _, s := range h.Sites {
|
||||||
procs[s.Lang()] = &sitePagesProcessor{
|
procs[s.Lang()] = &sitePagesProcessor{
|
||||||
m: s.pageMap,
|
m: s.pageMap,
|
||||||
errorSender: s.h,
|
errorSender: s.h,
|
||||||
itemChan: make(chan interface{}, config.GetNumWorkerMultiplier()*2),
|
itemChan: make(chan interface{}, config.GetNumWorkerMultiplier()*2),
|
||||||
|
renderStaticToDisk: h.Cfg.GetBool("renderStaticToDisk"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &pagesProcessor{
|
return &pagesProcessor{
|
||||||
|
@ -118,6 +119,8 @@ type sitePagesProcessor struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
itemChan chan interface{}
|
itemChan chan interface{}
|
||||||
itemGroup *errgroup.Group
|
itemGroup *errgroup.Group
|
||||||
|
|
||||||
|
renderStaticToDisk bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *sitePagesProcessor) Process(item interface{}) error {
|
func (p *sitePagesProcessor) Process(item interface{}) error {
|
||||||
|
@ -162,7 +165,12 @@ func (p *sitePagesProcessor) copyFile(fim hugofs.FileMetaInfo) error {
|
||||||
|
|
||||||
defer f.Close()
|
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 {
|
func (p *sitePagesProcessor) doProcess(item interface{}) error {
|
||||||
|
|
|
@ -1824,10 +1824,10 @@ func (s *Site) lookupTemplate(layouts ...string) (tpl.Template, bool) {
|
||||||
return nil, false
|
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)
|
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 {
|
func (s *Site) kindFromFileInfoOrSections(fi *fileInfo, sections []string) string {
|
||||||
|
|
Loading…
Reference in a new issue