shithub: hugo

Download patch

ref: c71e1b106e6011d148cac899f83c4685dee33a22
parent: 0ada40591216572b0e4c6a8ab986b0aa4fb13c13
author: Bjørn Erik Pedersen <[email protected]>
date: Tue Jan 10 05:55:03 EST 2017

all: Refactor to nonglobal file systems

Updates #2701
Fixes #2951

--- a/commands/benchmark.go
+++ b/commands/benchmark.go
@@ -49,10 +49,13 @@
 
 func benchmark(cmd *cobra.Command, args []string) error {
 	cfg, err := InitializeConfig(benchmarkCmd)
+
 	if err != nil {
 		return err
 	}
 
+	c := commandeer{cfg}
+
 	var memProf *os.File
 	if memProfileFile != "" {
 		memProf, err = os.Create(memProfileFile)
@@ -79,7 +82,7 @@
 
 	t := time.Now()
 	for i := 0; i < benchmarkTimes; i++ {
-		if err = resetAndBuildSites(cfg, false); err != nil {
+		if err = c.resetAndBuildSites(false); err != nil {
 			return err
 		}
 	}
--- a/commands/gendoc.go
+++ b/commands/gendoc.go
@@ -51,9 +51,9 @@
 		if !strings.HasSuffix(gendocdir, helpers.FilePathSeparator) {
 			gendocdir += helpers.FilePathSeparator
 		}
-		if found, _ := helpers.Exists(gendocdir, hugofs.Os()); !found {
+		if found, _ := helpers.Exists(gendocdir, hugofs.Os); !found {
 			jww.FEEDBACK.Println("Directory", gendocdir, "does not exist, creating...")
-			hugofs.Os().MkdirAll(gendocdir, 0777)
+			hugofs.Os.MkdirAll(gendocdir, 0777)
 		}
 		now := time.Now().Format(time.RFC3339)
 		prepender := func(filename string) string {
--- a/commands/genman.go
+++ b/commands/genman.go
@@ -41,9 +41,9 @@
 		if !strings.HasSuffix(genmandir, helpers.FilePathSeparator) {
 			genmandir += helpers.FilePathSeparator
 		}
-		if found, _ := helpers.Exists(genmandir, hugofs.Os()); !found {
+		if found, _ := helpers.Exists(genmandir, hugofs.Os); !found {
 			jww.FEEDBACK.Println("Directory", genmandir, "does not exist, creating...")
-			hugofs.Os().MkdirAll(genmandir, 0777)
+			hugofs.Os.MkdirAll(genmandir, 0777)
 		}
 		cmd.Root().DisableAutoGenTag = true
 
--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -40,6 +40,7 @@
 	"github.com/spf13/afero"
 	"github.com/spf13/cobra"
 	"github.com/spf13/fsync"
+	"github.com/spf13/hugo/deps"
 	"github.com/spf13/hugo/helpers"
 	"github.com/spf13/hugo/hugolib"
 	"github.com/spf13/hugo/livereload"
@@ -50,6 +51,10 @@
 	"github.com/spf13/viper"
 )
 
+type commandeer struct {
+	deps.DepsCfg
+}
+
 // Hugo represents the Hugo sites to build. This variable is exported as it
 // is used by at least one external library (the Hugo caddy plugin). We should
 // provide a cleaner external API, but until then, this is it.
@@ -119,12 +124,14 @@
 			return err
 		}
 
+		c := commandeer{cfg}
+
 		if buildWatch {
 			viper.Set("disableLiveReload", true)
-			watchConfig(cfg)
+			c.watchConfig()
 		}
 
-		return build(cfg)
+		return c.build()
 	},
 }
 
@@ -268,9 +275,9 @@
 }
 
 // InitializeConfig initializes a config file with sensible default configuration flags.
-func InitializeConfig(subCmdVs ...*cobra.Command) (hugolib.DepsCfg, error) {
+func InitializeConfig(subCmdVs ...*cobra.Command) (deps.DepsCfg, error) {
 
-	var cfg hugolib.DepsCfg
+	var cfg deps.DepsCfg
 
 	if err := hugolib.LoadGlobalConfig(source, cfgFile); err != nil {
 		return cfg, err
@@ -323,12 +330,15 @@
 		viper.Set("cacheDir", cacheDir)
 	}
 
+	// Init file systems. This may be changed at a later point.
+	cfg.Fs = hugofs.NewDefault()
+
 	cacheDir = viper.GetString("cacheDir")
 	if cacheDir != "" {
 		if helpers.FilePathSeparator != cacheDir[len(cacheDir)-1:] {
 			cacheDir = cacheDir + helpers.FilePathSeparator
 		}
-		isDir, err := helpers.DirExists(cacheDir, hugofs.Source())
+		isDir, err := helpers.DirExists(cacheDir, cfg.Fs.Source)
 		utils.CheckErr(err)
 		if !isDir {
 			mkdir(cacheDir)
@@ -335,22 +345,19 @@
 		}
 		viper.Set("cacheDir", cacheDir)
 	} else {
-		viper.Set("cacheDir", helpers.GetTempDir("hugo_cache", hugofs.Source()))
+		viper.Set("cacheDir", helpers.GetTempDir("hugo_cache", cfg.Fs.Source))
 	}
 
 	jww.INFO.Println("Using config file:", viper.ConfigFileUsed())
 
-	// Init file systems. This may be changed at a later point.
-	hugofs.InitDefaultFs()
-
 	themeDir := helpers.GetThemeDir()
 	if themeDir != "" {
-		if _, err := hugofs.Source().Stat(themeDir); os.IsNotExist(err) {
+		if _, err := cfg.Fs.Source.Stat(themeDir); os.IsNotExist(err) {
 			return cfg, newSystemError("Unable to find theme Directory:", themeDir)
 		}
 	}
 
-	themeVersionMismatch, minVersion := isThemeVsHugoVersionMismatch()
+	themeVersionMismatch, minVersion := isThemeVsHugoVersionMismatch(cfg.Fs.Source)
 
 	if themeVersionMismatch {
 		jww.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n",
@@ -447,12 +454,12 @@
 	return flag.Changed
 }
 
-func watchConfig(cfg hugolib.DepsCfg) {
+func (c commandeer) watchConfig() {
 	viper.WatchConfig()
 	viper.OnConfigChange(func(e fsnotify.Event) {
 		jww.FEEDBACK.Println("Config file changed:", e.Name)
 		// Force a full rebuild
-		utils.CheckErr(recreateAndBuildSites(cfg, true))
+		utils.CheckErr(c.recreateAndBuildSites(true))
 		if !viper.GetBool("disableLiveReload") {
 			// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
 			livereload.ForceRefresh()
@@ -460,17 +467,17 @@
 	})
 }
 
-func build(cfg hugolib.DepsCfg, watches ...bool) error {
+func (c commandeer) build(watches ...bool) error {
 	// Hugo writes the output to memory instead of the disk.
 	// This is only used for benchmark testing. Cause the content is only visible
 	// in memory.
 	if renderToMemory {
-		hugofs.SetDestination(new(afero.MemMapFs))
+		c.Fs.Destination = new(afero.MemMapFs)
 		// Rendering to memoryFS, publish to Root regardless of publishDir.
 		viper.Set("publishDir", "/")
 	}
 
-	if err := copyStatic(); err != nil {
+	if err := c.copyStatic(); err != nil {
 		return fmt.Errorf("Error copying static files to %s: %s", helpers.AbsPathify(viper.GetString("publishDir")), err)
 	}
 	watch := false
@@ -477,7 +484,7 @@
 	if len(watches) > 0 && watches[0] {
 		watch = true
 	}
-	if err := buildSites(cfg, buildWatch || watch); err != nil {
+	if err := c.buildSites(buildWatch || watch); err != nil {
 		return fmt.Errorf("Error building site: %s", err)
 	}
 
@@ -484,15 +491,16 @@
 	if buildWatch {
 		jww.FEEDBACK.Println("Watching for changes in", helpers.AbsPathify(viper.GetString("contentDir")))
 		jww.FEEDBACK.Println("Press Ctrl+C to stop")
-		utils.CheckErr(newWatcher(cfg, 0))
+		utils.CheckErr(c.newWatcher(0))
 	}
 
 	return nil
 }
 
-func getStaticSourceFs() afero.Fs {
-	source := hugofs.Source()
-	themeDir, err := helpers.GetThemeStaticDirPath()
+func (c commandeer) getStaticSourceFs() afero.Fs {
+	source := c.Fs.Source
+	pathSpec := helpers.NewPathSpec(c.Fs, viper.GetViper())
+	themeDir, err := pathSpec.GetThemeStaticDirPath()
 	staticDir := helpers.GetStaticDirPath() + helpers.FilePathSeparator
 
 	useTheme := true
@@ -532,12 +540,12 @@
 	jww.INFO.Println("using a UnionFS for static directory comprised of:")
 	jww.INFO.Println("Base:", themeDir)
 	jww.INFO.Println("Overlay:", staticDir)
-	base := afero.NewReadOnlyFs(afero.NewBasePathFs(hugofs.Source(), themeDir))
-	overlay := afero.NewReadOnlyFs(afero.NewBasePathFs(hugofs.Source(), staticDir))
+	base := afero.NewReadOnlyFs(afero.NewBasePathFs(source, themeDir))
+	overlay := afero.NewReadOnlyFs(afero.NewBasePathFs(source, staticDir))
 	return afero.NewCopyOnWriteFs(base, overlay)
 }
 
-func copyStatic() error {
+func (c commandeer) copyStatic() error {
 	publishDir := helpers.AbsPathify(viper.GetString("publishDir")) + helpers.FilePathSeparator
 
 	// If root, remove the second '/'
@@ -546,7 +554,7 @@
 	}
 
 	// Includes both theme/static & /static
-	staticSourceFs := getStaticSourceFs()
+	staticSourceFs := c.getStaticSourceFs()
 
 	if staticSourceFs == nil {
 		jww.WARN.Println("No static directories found to sync")
@@ -557,7 +565,7 @@
 	syncer.NoTimes = viper.GetBool("noTimes")
 	syncer.NoChmod = viper.GetBool("noChmod")
 	syncer.SrcFs = staticSourceFs
-	syncer.DestFs = hugofs.Destination()
+	syncer.DestFs = c.Fs.Destination
 	// Now that we are using a unionFs for the static directories
 	// We can effectively clean the publishDir on initial sync
 	syncer.Delete = viper.GetBool("cleanDestinationDir")
@@ -572,7 +580,7 @@
 }
 
 // getDirList provides NewWatcher() with a list of directories to watch for changes.
-func getDirList() []string {
+func (c commandeer) getDirList() []string {
 	var a []string
 	dataDir := helpers.AbsPathify(viper.GetString("dataDir"))
 	i18nDir := helpers.AbsPathify(viper.GetString("i18nDir"))
@@ -621,7 +629,7 @@
 				jww.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err)
 				return nil
 			}
-			linkfi, err := hugofs.Source().Stat(link)
+			linkfi, err := c.Fs.Source.Stat(link)
 			if err != nil {
 				jww.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
 				return nil
@@ -642,17 +650,17 @@
 		return nil
 	}
 
-	helpers.SymbolicWalk(hugofs.Source(), dataDir, walker)
-	helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("contentDir")), walker)
-	helpers.SymbolicWalk(hugofs.Source(), i18nDir, walker)
-	helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("layoutDir")), walker)
+	helpers.SymbolicWalk(c.Fs.Source, dataDir, walker)
+	helpers.SymbolicWalk(c.Fs.Source, helpers.AbsPathify(viper.GetString("contentDir")), walker)
+	helpers.SymbolicWalk(c.Fs.Source, i18nDir, walker)
+	helpers.SymbolicWalk(c.Fs.Source, helpers.AbsPathify(viper.GetString("layoutDir")), walker)
 
-	helpers.SymbolicWalk(hugofs.Source(), staticDir, walker)
+	helpers.SymbolicWalk(c.Fs.Source, staticDir, walker)
 	if helpers.ThemeSet() {
-		helpers.SymbolicWalk(hugofs.Source(), filepath.Join(themesDir, "layouts"), walker)
-		helpers.SymbolicWalk(hugofs.Source(), filepath.Join(themesDir, "static"), walker)
-		helpers.SymbolicWalk(hugofs.Source(), filepath.Join(themesDir, "i18n"), walker)
-		helpers.SymbolicWalk(hugofs.Source(), filepath.Join(themesDir, "data"), walker)
+		helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "layouts"), walker)
+		helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "static"), walker)
+		helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "i18n"), walker)
+		helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "data"), walker)
 
 	}
 
@@ -659,8 +667,8 @@
 	return a
 }
 
-func recreateAndBuildSites(cfg hugolib.DepsCfg, watching bool) (err error) {
-	if err := initSites(cfg); err != nil {
+func (c commandeer) recreateAndBuildSites(watching bool) (err error) {
+	if err := c.initSites(); err != nil {
 		return err
 	}
 	if !quiet {
@@ -669,9 +677,9 @@
 	return Hugo.Build(hugolib.BuildCfg{CreateSitesFromConfig: true, Watching: watching, PrintStats: !quiet})
 }
 
-func resetAndBuildSites(cfg hugolib.DepsCfg, watching bool) (err error) {
-	if err := initSites(cfg); err != nil {
-		return err
+func (c commandeer) resetAndBuildSites(watching bool) (err error) {
+	if err = c.initSites(); err != nil {
+		return
 	}
 	if !quiet {
 		jww.FEEDBACK.Println("Started building sites ...")
@@ -679,12 +687,12 @@
 	return Hugo.Build(hugolib.BuildCfg{ResetState: true, Watching: watching, PrintStats: !quiet})
 }
 
-func initSites(cfg hugolib.DepsCfg) error {
+func (c commandeer) initSites() error {
 	if Hugo != nil {
 		return nil
 	}
 
-	h, err := hugolib.NewHugoSitesFromConfiguration(cfg)
+	h, err := hugolib.NewHugoSitesFromConfiguration(c.DepsCfg)
 
 	if err != nil {
 		return err
@@ -694,8 +702,8 @@
 	return nil
 }
 
-func buildSites(cfg hugolib.DepsCfg, watching bool) (err error) {
-	if err := initSites(cfg); err != nil {
+func (c commandeer) buildSites(watching bool) (err error) {
+	if err := c.initSites(); err != nil {
 		return err
 	}
 	if !quiet {
@@ -704,8 +712,8 @@
 	return Hugo.Build(hugolib.BuildCfg{Watching: watching, PrintStats: !quiet})
 }
 
-func rebuildSites(cfg hugolib.DepsCfg, events []fsnotify.Event) error {
-	if err := initSites(cfg); err != nil {
+func (c commandeer) rebuildSites(events []fsnotify.Event) error {
+	if err := c.initSites(); err != nil {
 		return err
 	}
 	return Hugo.Build(hugolib.BuildCfg{PrintStats: !quiet, Watching: true}, events...)
@@ -712,11 +720,13 @@
 }
 
 // newWatcher creates a new watcher to watch filesystem events.
-func newWatcher(cfg hugolib.DepsCfg, port int) error {
+func (c commandeer) newWatcher(port int) error {
 	if runtime.GOOS == "darwin" {
 		tweakLimit()
 	}
 
+	pathSpec := helpers.NewPathSpec(c.Fs, viper.GetViper())
+
 	watcher, err := watcher.New(1 * time.Second)
 	var wg sync.WaitGroup
 
@@ -728,7 +738,7 @@
 
 	wg.Add(1)
 
-	for _, d := range getDirList() {
+	for _, d := range c.getDirList() {
 		if d != "" {
 			_ = watcher.Add(d)
 		}
@@ -793,12 +803,12 @@
 					// recursively add new directories to watch list
 					// When mkdir -p is used, only the top directory triggers an event (at least on OSX)
 					if ev.Op&fsnotify.Create == fsnotify.Create {
-						if s, err := hugofs.Source().Stat(ev.Name); err == nil && s.Mode().IsDir() {
-							helpers.SymbolicWalk(hugofs.Source(), ev.Name, walkAdder)
+						if s, err := c.Fs.Source.Stat(ev.Name); err == nil && s.Mode().IsDir() {
+							helpers.SymbolicWalk(c.Fs.Source, ev.Name, walkAdder)
 						}
 					}
 
-					isstatic := strings.HasPrefix(ev.Name, helpers.GetStaticDirPath()) || (len(helpers.GetThemesDirPath()) > 0 && strings.HasPrefix(ev.Name, helpers.GetThemesDirPath()))
+					isstatic := strings.HasPrefix(ev.Name, helpers.GetStaticDirPath()) || (len(pathSpec.GetThemesDirPath()) > 0 && strings.HasPrefix(ev.Name, pathSpec.GetThemesDirPath()))
 
 					if isstatic {
 						staticEvents = append(staticEvents, ev)
@@ -821,12 +831,12 @@
 
 					if viper.GetBool("forceSyncStatic") {
 						jww.FEEDBACK.Printf("Syncing all static files\n")
-						err := copyStatic()
+						err := c.copyStatic()
 						if err != nil {
 							utils.StopOnErr(err, fmt.Sprintf("Error copying static files to %s", publishDir))
 						}
 					} else {
-						staticSourceFs := getStaticSourceFs()
+						staticSourceFs := c.getStaticSourceFs()
 
 						if staticSourceFs == nil {
 							jww.WARN.Println("No static directories found to sync")
@@ -837,7 +847,7 @@
 						syncer.NoTimes = viper.GetBool("noTimes")
 						syncer.NoChmod = viper.GetBool("noChmod")
 						syncer.SrcFs = staticSourceFs
-						syncer.DestFs = hugofs.Destination()
+						syncer.DestFs = c.Fs.Destination
 
 						// prevent spamming the log on changes
 						logger := helpers.NewDistinctFeedbackLogger()
@@ -862,7 +872,7 @@
 							fromPath := ev.Name
 
 							// If we are here we already know the event took place in a static dir
-							relPath, err := helpers.MakeStaticPathRelative(fromPath)
+							relPath, err := pathSpec.MakeStaticPathRelative(fromPath)
 							if err != nil {
 								jww.ERROR.Println(err)
 								continue
@@ -882,7 +892,7 @@
 									// If file doesn't exist in any static dir, remove it
 									toRemove := filepath.Join(publishDir, relPath)
 									logger.Println("File no longer exists in static dir, removing", toRemove)
-									hugofs.Destination().RemoveAll(toRemove)
+									c.Fs.Destination.RemoveAll(toRemove)
 								} else if err == nil {
 									// If file still exists, sync it
 									logger.Println("Syncing", relPath, "to", publishDir)
@@ -910,7 +920,7 @@
 						// force refresh when more than one file
 						if len(staticEvents) > 0 {
 							for _, ev := range staticEvents {
-								path, _ := helpers.MakeStaticPathRelative(ev.Name)
+								path, _ := pathSpec.MakeStaticPathRelative(ev.Name)
 								livereload.RefreshPath(path)
 							}
 
@@ -925,7 +935,7 @@
 					const layout = "2006-01-02 15:04 -0700"
 					jww.FEEDBACK.Println(time.Now().Format(layout))
 
-					rebuildSites(cfg, dynamicEvents)
+					c.rebuildSites(dynamicEvents)
 
 					if !buildWatch && !viper.GetBool("disableLiveReload") {
 						// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
@@ -947,7 +957,7 @@
 			http.HandleFunc("/livereload", livereload.Handler)
 		}
 
-		go serve(port)
+		go c.serve(port)
 	}
 
 	wg.Wait()
@@ -956,7 +966,7 @@
 
 // isThemeVsHugoVersionMismatch returns whether the current Hugo version is
 // less than the theme's min_version.
-func isThemeVsHugoVersionMismatch() (mismatch bool, requiredMinVersion string) {
+func isThemeVsHugoVersionMismatch(fs afero.Fs) (mismatch bool, requiredMinVersion string) {
 	if !helpers.ThemeSet() {
 		return
 	}
@@ -963,7 +973,6 @@
 
 	themeDir := helpers.GetThemeDir()
 
-	fs := hugofs.Source()
 	path := filepath.Join(themeDir, "theme.toml")
 
 	exists, err := helpers.Exists(path, fs)
--- a/commands/import_jekyll.go
+++ b/commands/import_jekyll.go
@@ -25,6 +25,7 @@
 	"strings"
 	"time"
 
+	"github.com/spf13/afero"
 	"github.com/spf13/cast"
 	"github.com/spf13/cobra"
 	"github.com/spf13/hugo/helpers"
@@ -122,7 +123,7 @@
 		return convertJekyllPost(site, path, relPath, targetDir, draft)
 	}
 
-	err = helpers.SymbolicWalk(hugofs.Os(), jekyllRoot, callback)
+	err = helpers.SymbolicWalk(hugofs.Os, jekyllRoot, callback)
 
 	if err != nil {
 		return err
@@ -137,7 +138,13 @@
 
 // TODO: Consider calling doNewSite() instead?
 func createSiteFromJekyll(jekyllRoot, targetDir string, force bool) (*hugolib.Site, error) {
-	fs := hugofs.Source()
+	s, err := hugolib.NewSiteDefaultLang()
+	if err != nil {
+		return nil, err
+	}
+
+	fs := s.Fs.Source
+
 	if exists, _ := helpers.Exists(targetDir, fs); exists {
 		if isDir, _ := helpers.IsDir(targetDir, fs); !isDir {
 			return nil, errors.New("Target path \"" + targetDir + "\" already exists but not a directory")
@@ -150,7 +157,7 @@
 		}
 	}
 
-	jekyllConfig := loadJekyllConfig(jekyllRoot)
+	jekyllConfig := loadJekyllConfig(fs, jekyllRoot)
 
 	// Crude test to make sure at least one of _drafts/ and _posts/ exists
 	// and is not empty.
@@ -177,16 +184,14 @@
 	mkdir(targetDir, "data")
 	mkdir(targetDir, "themes")
 
-	createConfigFromJekyll(targetDir, "yaml", jekyllConfig)
+	createConfigFromJekyll(fs, targetDir, "yaml", jekyllConfig)
 
 	copyJekyllFilesAndFolders(jekyllRoot, filepath.Join(targetDir, "static"))
-	site := hugolib.NewSiteDefaultLang()
 
-	return site, nil
+	return s, nil
 }
 
-func loadJekyllConfig(jekyllRoot string) map[string]interface{} {
-	fs := hugofs.Source()
+func loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string]interface{} {
 	path := filepath.Join(jekyllRoot, "_config.yml")
 
 	exists, err := helpers.Exists(path, fs)
@@ -218,7 +223,7 @@
 	return c.(map[string]interface{})
 }
 
-func createConfigFromJekyll(inpath string, kind string, jekyllConfig map[string]interface{}) (err error) {
+func createConfigFromJekyll(fs afero.Fs, inpath string, kind string, jekyllConfig map[string]interface{}) (err error) {
 	title := "My New Hugo Site"
 	baseURL := "http://example.org/"
 
@@ -251,7 +256,7 @@
 		return err
 	}
 
-	err = helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), bytes.NewReader(by), hugofs.Source())
+	err = helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), bytes.NewReader(by), fs)
 	if err != nil {
 		return
 	}
--- a/commands/new.go
+++ b/commands/new.go
@@ -16,15 +16,18 @@
 import (
 	"bytes"
 	"errors"
+	"fmt"
 	"os"
 	"path/filepath"
 	"strings"
 	"time"
 
+	"github.com/spf13/afero"
 	"github.com/spf13/cobra"
 	"github.com/spf13/hugo/create"
 	"github.com/spf13/hugo/helpers"
 	"github.com/spf13/hugo/hugofs"
+	"github.com/spf13/hugo/hugolib"
 	"github.com/spf13/hugo/parser"
 	jww "github.com/spf13/jwalterweatherman"
 	"github.com/spf13/viper"
@@ -84,7 +87,9 @@
 
 // NewContent adds new content to a Hugo site.
 func NewContent(cmd *cobra.Command, args []string) error {
-	if _, err := InitializeConfig(); err != nil {
+	cfg, err := InitializeConfig()
+
+	if err != nil {
 		return err
 	}
 
@@ -110,10 +115,16 @@
 		kind = contentType
 	}
 
-	return create.NewContent(hugofs.Source(), kind, createpath)
+	s, err := hugolib.NewSite(cfg)
+
+	if err != nil {
+		return newSystemError(err)
+	}
+
+	return create.NewContent(s, kind, createpath)
 }
 
-func doNewSite(basepath string, force bool) error {
+func doNewSite(fs *hugofs.Fs, basepath string, force bool) error {
 	dirs := []string{
 		filepath.Join(basepath, "layouts"),
 		filepath.Join(basepath, "content"),
@@ -123,12 +134,12 @@
 		filepath.Join(basepath, "themes"),
 	}
 
-	if exists, _ := helpers.Exists(basepath, hugofs.Source()); exists {
-		if isDir, _ := helpers.IsDir(basepath, hugofs.Source()); !isDir {
+	if exists, _ := helpers.Exists(basepath, fs.Source); exists {
+		if isDir, _ := helpers.IsDir(basepath, fs.Source); !isDir {
 			return errors.New(basepath + " already exists but not a directory")
 		}
 
-		isEmpty, _ := helpers.IsEmpty(basepath, hugofs.Source())
+		isEmpty, _ := helpers.IsEmpty(basepath, fs.Source)
 
 		switch {
 		case !isEmpty && !force:
@@ -137,7 +148,7 @@
 		case !isEmpty && force:
 			all := append(dirs, filepath.Join(basepath, "config."+configFormat))
 			for _, path := range all {
-				if exists, _ := helpers.Exists(path, hugofs.Source()); exists {
+				if exists, _ := helpers.Exists(path, fs.Source); exists {
 					return errors.New(path + " already exists")
 				}
 			}
@@ -145,10 +156,12 @@
 	}
 
 	for _, dir := range dirs {
-		hugofs.Source().MkdirAll(dir, 0777)
+		if err := fs.Source.MkdirAll(dir, 0777); err != nil {
+			return fmt.Errorf("Failed to create dir: %s", err)
+		}
 	}
 
-	createConfig(basepath, configFormat)
+	createConfig(fs, basepath, configFormat)
 
 	jww.FEEDBACK.Printf("Congratulations! Your new Hugo site is created in %s.\n\n", basepath)
 	jww.FEEDBACK.Println(nextStepsText())
@@ -190,12 +203,14 @@
 
 	forceNew, _ := cmd.Flags().GetBool("force")
 
-	return doNewSite(createpath, forceNew)
+	return doNewSite(hugofs.NewDefault(), createpath, forceNew)
 }
 
 // NewTheme creates a new Hugo theme.
 func NewTheme(cmd *cobra.Command, args []string) error {
-	if _, err := InitializeConfig(); err != nil {
+	cfg, err := InitializeConfig()
+
+	if err != nil {
 		return err
 	}
 
@@ -207,7 +222,7 @@
 	createpath := helpers.AbsPathify(filepath.Join(viper.GetString("themesDir"), args[0]))
 	jww.INFO.Println("creating theme at", createpath)
 
-	if x, _ := helpers.Exists(createpath, hugofs.Source()); x {
+	if x, _ := helpers.Exists(createpath, cfg.Fs.Source); x {
 		return newUserError(createpath, "already exists")
 	}
 
@@ -214,19 +229,19 @@
 	mkdir(createpath, "layouts", "_default")
 	mkdir(createpath, "layouts", "partials")
 
-	touchFile(createpath, "layouts", "index.html")
-	touchFile(createpath, "layouts", "404.html")
-	touchFile(createpath, "layouts", "_default", "list.html")
-	touchFile(createpath, "layouts", "_default", "single.html")
+	touchFile(cfg.Fs.Source, createpath, "layouts", "index.html")
+	touchFile(cfg.Fs.Source, createpath, "layouts", "404.html")
+	touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "list.html")
+	touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "single.html")
 
-	touchFile(createpath, "layouts", "partials", "header.html")
-	touchFile(createpath, "layouts", "partials", "footer.html")
+	touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "header.html")
+	touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "footer.html")
 
 	mkdir(createpath, "archetypes")
 
 	archDefault := []byte("+++\n+++\n")
 
-	err := helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), bytes.NewReader(archDefault), hugofs.Source())
+	err = helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), bytes.NewReader(archDefault), cfg.Fs.Source)
 	if err != nil {
 		return err
 	}
@@ -256,12 +271,12 @@
 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 `)
 
-	err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE.md"), bytes.NewReader(by), hugofs.Source())
+	err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE.md"), bytes.NewReader(by), cfg.Fs.Source)
 	if err != nil {
 		return err
 	}
 
-	createThemeMD(createpath)
+	createThemeMD(cfg.Fs, createpath)
 
 	return nil
 }
@@ -275,16 +290,16 @@
 	}
 }
 
-func touchFile(x ...string) {
+func touchFile(fs afero.Fs, x ...string) {
 	inpath := filepath.Join(x...)
 	mkdir(filepath.Dir(inpath))
-	err := helpers.WriteToDisk(inpath, bytes.NewReader([]byte{}), hugofs.Source())
+	err := helpers.WriteToDisk(inpath, bytes.NewReader([]byte{}), fs)
 	if err != nil {
 		jww.FATAL.Fatalln(err)
 	}
 }
 
-func createThemeMD(inpath string) (err error) {
+func createThemeMD(fs *hugofs.Fs, inpath string) (err error) {
 
 	by := []byte(`# theme.toml template for a Hugo theme
 # See https://github.com/spf13/hugoThemes#themetoml for an example
@@ -309,7 +324,7 @@
   repo = ""
 `)
 
-	err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), hugofs.Source())
+	err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), fs.Source)
 	if err != nil {
 		return
 	}
@@ -320,7 +335,7 @@
 func newContentPathSection(path string) (string, string) {
 	// Forward slashes is used in all examples. Convert if needed.
 	// Issue #1133
-	createpath := strings.Replace(path, "/", helpers.FilePathSeparator, -1)
+	createpath := filepath.FromSlash(path)
 	var section string
 	// assume the first directory is the section (kind)
 	if strings.Contains(createpath[1:], helpers.FilePathSeparator) {
@@ -330,7 +345,7 @@
 	return createpath, section
 }
 
-func createConfig(inpath string, kind string) (err error) {
+func createConfig(fs *hugofs.Fs, inpath string, kind string) (err error) {
 	in := map[string]interface{}{
 		"baseURL":      "http://example.org/",
 		"title":        "My New Hugo Site",
@@ -343,7 +358,7 @@
 		return err
 	}
 
-	err = helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), bytes.NewReader(by), hugofs.Source())
+	err = helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), bytes.NewReader(by), fs.Source)
 	if err != nil {
 		return
 	}
--- a/commands/new_test.go
+++ b/commands/new_test.go
@@ -14,12 +14,12 @@
 package commands
 
 import (
-	"os"
 	"path/filepath"
 	"testing"
 
 	"github.com/spf13/hugo/hugofs"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 // Issue #1133
@@ -29,7 +29,8 @@
 	assert.Equal(t, "post", s)
 }
 
-func checkNewSiteInited(basepath string, t *testing.T) {
+func checkNewSiteInited(fs *hugofs.Fs, basepath string, t *testing.T) {
+
 	paths := []string{
 		filepath.Join(basepath, "layouts"),
 		filepath.Join(basepath, "content"),
@@ -40,63 +41,70 @@
 	}
 
 	for _, path := range paths {
-		_, err := hugofs.Source().Stat(path)
-		assert.Nil(t, err)
+		_, err := fs.Source.Stat(path)
+		require.NoError(t, err)
 	}
 }
 
 func TestDoNewSite(t *testing.T) {
-	basepath := filepath.Join(os.TempDir(), "blog")
-	hugofs.InitMemFs()
-	err := doNewSite(basepath, false)
-	assert.Nil(t, err)
+	basepath := filepath.Join("base", "blog")
+	fs := hugofs.NewMem()
 
-	checkNewSiteInited(basepath, t)
+	require.NoError(t, doNewSite(fs, basepath, false))
+
+	checkNewSiteInited(fs, basepath, t)
 }
 
 func TestDoNewSite_noerror_base_exists_but_empty(t *testing.T) {
-	basepath := filepath.Join(os.TempDir(), "blog")
-	hugofs.InitMemFs()
-	hugofs.Source().MkdirAll(basepath, 777)
-	err := doNewSite(basepath, false)
-	assert.Nil(t, err)
+	basepath := filepath.Join("base", "blog")
+	fs := hugofs.NewMem()
+
+	require.NoError(t, fs.Source.MkdirAll(basepath, 777))
+
+	require.NoError(t, doNewSite(fs, basepath, false))
 }
 
 func TestDoNewSite_error_base_exists(t *testing.T) {
-	basepath := filepath.Join(os.TempDir(), "blog")
-	hugofs.InitMemFs()
-	hugofs.Source().MkdirAll(basepath, 777)
-	hugofs.Source().Create(filepath.Join(basepath, "foo"))
+	basepath := filepath.Join("base", "blog")
+	fs := hugofs.NewMem()
+
+	require.NoError(t, fs.Source.MkdirAll(basepath, 777))
+	_, err := fs.Source.Create(filepath.Join(basepath, "foo"))
+	require.NoError(t, err)
 	// Since the directory already exists and isn't empty, expect an error
-	err := doNewSite(basepath, false)
-	assert.NotNil(t, err)
+	require.Error(t, doNewSite(fs, basepath, false))
+
 }
 
 func TestDoNewSite_force_empty_dir(t *testing.T) {
-	basepath := filepath.Join(os.TempDir(), "blog")
-	hugofs.InitMemFs()
-	hugofs.Source().MkdirAll(basepath, 777)
-	err := doNewSite(basepath, true)
-	assert.Nil(t, err)
+	basepath := filepath.Join("base", "blog")
+	fs := hugofs.NewMem()
 
-	checkNewSiteInited(basepath, t)
+	require.NoError(t, fs.Source.MkdirAll(basepath, 777))
+
+	require.NoError(t, doNewSite(fs, basepath, true))
+
+	checkNewSiteInited(fs, basepath, t)
 }
 
 func TestDoNewSite_error_force_dir_inside_exists(t *testing.T) {
-	basepath := filepath.Join(os.TempDir(), "blog")
+	basepath := filepath.Join("base", "blog")
+	fs := hugofs.NewMem()
+
 	contentPath := filepath.Join(basepath, "content")
-	hugofs.InitMemFs()
-	hugofs.Source().MkdirAll(contentPath, 777)
-	err := doNewSite(basepath, true)
-	assert.NotNil(t, err)
+
+	require.NoError(t, fs.Source.MkdirAll(contentPath, 777))
+	require.Error(t, doNewSite(fs, basepath, true))
 }
 
 func TestDoNewSite_error_force_config_inside_exists(t *testing.T) {
-	basepath := filepath.Join(os.TempDir(), "blog")
+	basepath := filepath.Join("base", "blog")
+	fs := hugofs.NewMem()
+
 	configPath := filepath.Join(basepath, "config.toml")
-	hugofs.InitMemFs()
-	hugofs.Source().MkdirAll(basepath, 777)
-	hugofs.Source().Create(configPath)
-	err := doNewSite(basepath, true)
-	assert.NotNil(t, err)
+	require.NoError(t, fs.Source.MkdirAll(basepath, 777))
+	_, err := fs.Source.Create(configPath)
+	require.NoError(t, err)
+
+	require.Error(t, doNewSite(fs, basepath, true))
 }
--- a/commands/server.go
+++ b/commands/server.go
@@ -29,7 +29,6 @@
 	"github.com/spf13/afero"
 	"github.com/spf13/cobra"
 	"github.com/spf13/hugo/helpers"
-	"github.com/spf13/hugo/hugofs"
 	jww "github.com/spf13/jwalterweatherman"
 	"github.com/spf13/viper"
 )
@@ -109,6 +108,8 @@
 		return err
 	}
 
+	c := commandeer{cfg}
+
 	if flagChanged(cmd.Flags(), "disableLiveReload") {
 		viper.Set("disableLiveReload", disableLiveReload)
 	}
@@ -119,7 +120,7 @@
 
 	if viper.GetBool("watch") {
 		serverWatch = true
-		watchConfig(cfg)
+		c.watchConfig()
 	}
 
 	l, err := net.Listen("tcp", net.JoinHostPort(serverInterface, strconv.Itoa(serverPort)))
@@ -157,18 +158,18 @@
 
 	// Hugo writes the output to memory instead of the disk
 	if !renderToDisk {
-		hugofs.SetDestination(new(afero.MemMapFs))
+		cfg.Fs.Destination = new(afero.MemMapFs)
 		// Rendering to memoryFS, publish to Root regardless of publishDir.
 		viper.Set("publishDir", "/")
 	}
 
-	if err := build(cfg, serverWatch); err != nil {
+	if err := c.build(serverWatch); err != nil {
 		return err
 	}
 
 	// Watch runs its own server as part of the routine
 	if serverWatch {
-		watchDirs := getDirList()
+		watchDirs := c.getDirList()
 		baseWatchDir := viper.GetString("workingDir")
 		for i, dir := range watchDirs {
 			watchDirs[i], _ = helpers.GetRelativePath(dir, baseWatchDir)
@@ -177,7 +178,7 @@
 		rootWatchDirs := strings.Join(helpers.UniqueStrings(helpers.ExtractRootPaths(watchDirs)), ",")
 
 		jww.FEEDBACK.Printf("Watching for changes in %s%s{%s}\n", baseWatchDir, helpers.FilePathSeparator, rootWatchDirs)
-		err := newWatcher(cfg, serverPort)
+		err := c.newWatcher(serverPort)
 
 		if err != nil {
 			return err
@@ -184,12 +185,12 @@
 		}
 	}
 
-	serve(serverPort)
+	c.serve(serverPort)
 
 	return nil
 }
 
-func serve(port int) {
+func (c commandeer) serve(port int) {
 	if renderToDisk {
 		jww.FEEDBACK.Println("Serving pages from " + helpers.AbsPathify(viper.GetString("publishDir")))
 	} else {
@@ -196,7 +197,7 @@
 		jww.FEEDBACK.Println("Serving pages from memory")
 	}
 
-	httpFs := afero.NewHttpFs(hugofs.Destination())
+	httpFs := afero.NewHttpFs(c.Fs.Destination)
 	fs := filesOnlyFs{httpFs.Dir(helpers.AbsPathify(viper.GetString("publishDir")))}
 	fileserver := http.FileServer(fs)
 
--- a/commands/undraft.go
+++ b/commands/undraft.go
@@ -20,7 +20,6 @@
 	"time"
 
 	"github.com/spf13/cobra"
-	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/hugo/parser"
 )
 
@@ -37,7 +36,9 @@
 // to false and setting its publish date to now. If the specified content is
 // not a draft, it will log an error.
 func Undraft(cmd *cobra.Command, args []string) error {
-	if _, err := InitializeConfig(); err != nil {
+	cfg, err := InitializeConfig()
+
+	if err != nil {
 		return err
 	}
 
@@ -47,7 +48,7 @@
 
 	location := args[0]
 	// open the file
-	f, err := hugofs.Source().Open(location)
+	f, err := cfg.Fs.Source.Open(location)
 	if err != nil {
 		return err
 	}
@@ -64,7 +65,7 @@
 		return newSystemErrorF("an error occurred while undrafting %q: %s", location, err)
 	}
 
-	f, err = hugofs.Source().OpenFile(location, os.O_WRONLY|os.O_TRUNC, 0644)
+	f, err = cfg.Fs.Source.OpenFile(location, os.O_WRONLY|os.O_TRUNC, 0644)
 	if err != nil {
 		return newSystemErrorF("%q not be undrafted due to error opening file to save changes: %q\n", location, err)
 	}
--- a/create/content.go
+++ b/create/content.go
@@ -34,15 +34,15 @@
 
 // NewContent creates a new content file in the content directory based upon the
 // given kind, which is used to lookup an archetype.
-func NewContent(fs afero.Fs, kind, name string) (err error) {
+func NewContent(s *hugolib.Site, kind, name string) (err error) {
 	jww.INFO.Println("attempting to create ", name, "of", kind)
 
-	location := FindArchetype(fs, kind)
+	location := FindArchetype(s.Fs.Source, kind)
 
 	var by []byte
 
 	if location != "" {
-		by, err = afero.ReadFile(fs, location)
+		by, err = afero.ReadFile(s.Fs.Source, location)
 		if err != nil {
 			jww.ERROR.Println(err)
 		}
@@ -62,9 +62,7 @@
 		return err
 	}
 
-	site := hugolib.NewSiteDefaultLang()
-
-	page, err := site.NewPage(name)
+	page, err := s.NewPage(name)
 	if err != nil {
 		return err
 	}
--- a/create/content_test.go
+++ b/create/content_test.go
@@ -19,23 +19,22 @@
 	"strings"
 	"testing"
 
+	"github.com/spf13/hugo/hugolib"
+
 	"fmt"
 
+	"github.com/spf13/hugo/hugofs"
+
 	"github.com/spf13/afero"
 	"github.com/spf13/hugo/create"
 	"github.com/spf13/hugo/helpers"
-	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/viper"
+	"github.com/stretchr/testify/require"
 )
 
 func TestNewContent(t *testing.T) {
 	initViper()
 
-	err := initFs()
-	if err != nil {
-		t.Fatalf("initialization error: %s", err)
-	}
-
 	cases := []struct {
 		kind     string
 		path     string
@@ -48,15 +47,15 @@
 		{"product", "product/sample-4.md", []string{`title = "sample 4"`}}, // empty archetype front matter
 	}
 
-	for i, c := range cases {
-		err = create.NewContent(hugofs.Source(), c.kind, c.path)
-		if err != nil {
-			t.Errorf("[%d] NewContent: %s", i, err)
-		}
+	for _, c := range cases {
+		s, err := hugolib.NewEnglishSite()
+		require.NoError(t, err)
+		require.NoError(t, initFs(s.Fs))
 
-		fname := filepath.Join("content", filepath.FromSlash(c.path))
-		content := readFileFromFs(t, hugofs.Source(), fname)
+		require.NoError(t, create.NewContent(s, c.kind, c.path))
 
+		fname := filepath.Join("content", filepath.FromSlash(c.path))
+		content := readFileFromFs(t, s.Fs.Source, fname)
 		for i, v := range c.expected {
 			found := strings.Contains(content, v)
 			if !found {
@@ -72,11 +71,11 @@
 	viper.Set("archetypeDir", "archetypes")
 	viper.Set("contentDir", "content")
 	viper.Set("themesDir", "themes")
+	viper.Set("layoutDir", "layouts")
 	viper.Set("theme", "sample")
 }
 
-func initFs() error {
-	hugofs.InitMemFs()
+func initFs(fs *hugofs.Fs) error {
 	perm := os.FileMode(0755)
 	var err error
 
@@ -87,7 +86,7 @@
 		filepath.Join("themes", "sample", "archetypes"),
 	}
 	for _, dir := range dirs {
-		err = hugofs.Source().Mkdir(dir, perm)
+		err = fs.Source.Mkdir(dir, perm)
 		if err != nil {
 			return err
 		}
@@ -111,7 +110,7 @@
 			content: "+++\ndate =\"\"\ntitle = \"Empty Date Arch title\"\ntest = \"test1\"\n+++\n",
 		},
 	} {
-		f, err := hugofs.Source().Create(v.path)
+		f, err := fs.Source.Create(v.path)
 		if err != nil {
 			return err
 		}
--- /dev/null
+++ b/deps/deps.go
@@ -1,0 +1,115 @@
+package deps
+
+import (
+	"io/ioutil"
+	"log"
+	"os"
+
+	"github.com/spf13/hugo/helpers"
+	"github.com/spf13/hugo/hugofs"
+	"github.com/spf13/hugo/tplapi"
+	jww "github.com/spf13/jwalterweatherman"
+)
+
+// Deps holds dependencies used by many.
+// There will be normally be only one instance of deps in play
+// at a given time, i.e. one per Site built.
+type Deps struct {
+	// The logger to use.
+	Log *jww.Notepad `json:"-"`
+
+	// The templates to use.
+	Tmpl tplapi.Template `json:"-"`
+
+	// The file systems to use.
+	Fs *hugofs.Fs `json:"-"`
+
+	// The PathSpec to use
+	*helpers.PathSpec `json:"-"`
+
+	templateProvider TemplateProvider
+	WithTemplate     func(templ tplapi.Template) error
+
+	// TODO(bep) globals next in line: Viper
+
+}
+
+// Used to create and refresh, and clone the template.
+type TemplateProvider interface {
+	Update(deps *Deps) error
+	Clone(deps *Deps) error
+}
+
+func (d *Deps) LoadTemplates() error {
+	if err := d.templateProvider.Update(d); err != nil {
+		return err
+	}
+	d.Tmpl.PrintErrors()
+	return nil
+}
+
+func New(cfg DepsCfg) *Deps {
+	var (
+		logger = cfg.Logger
+		fs     = cfg.Fs
+	)
+
+	if cfg.TemplateProvider == nil {
+		panic("Must have a TemplateProvider")
+	}
+
+	if cfg.Language == nil {
+		panic("Must have a Language")
+	}
+
+	if logger == nil {
+		logger = jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+	}
+
+	if fs == nil {
+		// Default to the most used file systems.
+		fs = hugofs.NewMem()
+	}
+
+	d := &Deps{
+		Fs:               fs,
+		Log:              logger,
+		templateProvider: cfg.TemplateProvider,
+		WithTemplate:     cfg.WithTemplate,
+		PathSpec:         helpers.NewPathSpec(fs, cfg.Language),
+	}
+
+	return d
+}
+
+// ForLanguage creates a copy of the Deps with the language dependent
+// parts switched out.
+func (d Deps) ForLanguage(l *helpers.Language) (*Deps, error) {
+
+	d.PathSpec = helpers.NewPathSpec(d.Fs, l)
+	if err := d.templateProvider.Clone(&d); err != nil {
+		return nil, err
+	}
+
+	return &d, nil
+
+}
+
+// DepsCfg contains configuration options that can be used to configure Hugo
+// on a global level, i.e. logging etc.
+// Nil values will be given default values.
+type DepsCfg struct {
+
+	// The Logger to use.
+	Logger *jww.Notepad
+
+	// The file systems to use
+	Fs *hugofs.Fs
+
+	// The language to use.
+	Language *helpers.Language
+
+	// Template handling.
+	TemplateProvider TemplateProvider
+	WithTemplate     func(templ tplapi.Template) error
+}
--- a/helpers/configProvider.go
+++ b/helpers/configProvider.go
@@ -29,7 +29,6 @@
 // TODO(bep) Get rid of these.
 var (
 	currentConfigProvider ConfigProvider
-	currentPathSpec       *PathSpec
 )
 
 // ConfigProvider provides the configuration settings for Hugo.
@@ -52,24 +51,13 @@
 	return viper.Get("currentContentLanguage").(ConfigProvider)
 }
 
-// CurrentPathSpec returns the current PathSpec.
-// If it is not set, a new will be created based in the currently active Hugo config.
-func CurrentPathSpec() *PathSpec {
-	if currentPathSpec != nil {
-		return currentPathSpec
-	}
-	// Some tests rely on this. We will fix that, eventually.
-	return NewPathSpecFromConfig(Config())
-}
-
 // InitConfigProviderForCurrentContentLanguage does what it says.
 func InitConfigProviderForCurrentContentLanguage() {
 	currentConfigProvider = viper.Get("CurrentContentLanguage").(ConfigProvider)
-	currentPathSpec = NewPathSpecFromConfig(currentConfigProvider)
 }
 
 // ResetConfigProvider is used in tests.
 func ResetConfigProvider() {
 	currentConfigProvider = nil
-	currentPathSpec = nil
+
 }
--- a/helpers/path.go
+++ b/helpers/path.go
@@ -23,8 +23,6 @@
 	"strings"
 	"unicode"
 
-	"github.com/spf13/hugo/hugofs"
-
 	"github.com/spf13/afero"
 	"github.com/spf13/viper"
 	"golang.org/x/text/transform"
@@ -196,29 +194,29 @@
 
 // GetThemeStaticDirPath returns the theme's static dir path if theme is set.
 // If theme is set and the static dir doesn't exist, an error is returned.
-func GetThemeStaticDirPath() (string, error) {
-	return getThemeDirPath("static")
+func (p *PathSpec) GetThemeStaticDirPath() (string, error) {
+	return p.getThemeDirPath("static")
 }
 
 // GetThemeDataDirPath returns the theme's data dir path if theme is set.
 // If theme is set and the data dir doesn't exist, an error is returned.
-func GetThemeDataDirPath() (string, error) {
-	return getThemeDirPath("data")
+func (p *PathSpec) GetThemeDataDirPath() (string, error) {
+	return p.getThemeDirPath("data")
 }
 
 // GetThemeI18nDirPath returns the theme's i18n dir path if theme is set.
 // If theme is set and the i18n dir doesn't exist, an error is returned.
-func GetThemeI18nDirPath() (string, error) {
-	return getThemeDirPath("i18n")
+func (p *PathSpec) GetThemeI18nDirPath() (string, error) {
+	return p.getThemeDirPath("i18n")
 }
 
-func getThemeDirPath(path string) (string, error) {
+func (p *PathSpec) getThemeDirPath(path string) (string, error) {
 	if !ThemeSet() {
 		return "", ErrThemeUndefined
 	}
 
 	themeDir := filepath.Join(GetThemeDir(), path)
-	if _, err := hugofs.Source().Stat(themeDir); os.IsNotExist(err) {
+	if _, err := p.fs.Source.Stat(themeDir); os.IsNotExist(err) {
 		return "", fmt.Errorf("Unable to find %s directory for theme %s in %s", path, viper.GetString("theme"), themeDir)
 	}
 
@@ -228,8 +226,8 @@
 // GetThemesDirPath gets the static files directory of the current theme, if there is one.
 // Ignores underlying errors.
 // TODO(bep) Candidate for deprecation?
-func GetThemesDirPath() string {
-	dir, _ := getThemeDirPath("static")
+func (p *PathSpec) GetThemesDirPath() string {
+	dir, _ := p.getThemeDirPath("static")
 	return dir
 }
 
@@ -236,9 +234,9 @@
 // MakeStaticPathRelative makes a relative path to the static files directory.
 // It does so by taking either the project's static path or the theme's static
 // path into consideration.
-func MakeStaticPathRelative(inPath string) (string, error) {
+func (p *PathSpec) MakeStaticPathRelative(inPath string) (string, error) {
 	staticDir := GetStaticDirPath()
-	themeStaticDir := GetThemesDirPath()
+	themeStaticDir := p.GetThemesDirPath()
 
 	return makePathRelative(inPath, staticDir, themeStaticDir)
 }
--- a/helpers/path_test.go
+++ b/helpers/path_test.go
@@ -30,6 +30,7 @@
 	"github.com/stretchr/testify/assert"
 
 	"github.com/spf13/afero"
+	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/viper"
 )
 
@@ -64,7 +65,8 @@
 
 	for _, test := range tests {
 		viper.Set("removePathAccents", test.removeAccents)
-		p := NewPathSpecFromConfig(viper.GetViper())
+		p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
+
 		output := p.MakePath(test.input)
 		if output != test.expected {
 			t.Errorf("Expected %#v, got %#v\n", test.expected, output)
@@ -77,7 +79,7 @@
 	defer viper.Reset()
 	initCommonTestConfig()
 
-	p := NewPathSpecFromConfig(viper.GetViper())
+	p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
 
 	tests := []struct {
 		input    string
@@ -105,7 +107,7 @@
 
 	initCommonTestConfig()
 	viper.Set("disablePathToLower", true)
-	p := NewPathSpecFromConfig(viper.GetViper())
+	p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
 
 	tests := []struct {
 		input    string
--- a/helpers/pathspec.go
+++ b/helpers/pathspec.go
@@ -13,6 +13,12 @@
 
 package helpers
 
+import (
+	"fmt"
+
+	"github.com/spf13/hugo/hugofs"
+)
+
 // PathSpec holds methods that decides how paths in URLs and files in Hugo should look like.
 type PathSpec struct {
 	disablePathToLower bool
@@ -33,11 +39,27 @@
 	defaultContentLanguageInSubdir bool
 	defaultContentLanguage         string
 	multilingual                   bool
+
+	// The file systems to use
+	fs *hugofs.Fs
 }
 
-// NewPathSpecFromConfig creats a new PathSpec from the given ConfigProvider.
-func NewPathSpecFromConfig(config ConfigProvider) *PathSpec {
+func (p PathSpec) String() string {
+	return fmt.Sprintf("PathSpec, language %q, prefix %q, multilingual: %T", p.currentContentLanguage.Lang, p.getLanguagePrefix(), p.multilingual)
+}
+
+// NewPathSpec creats a new PathSpec from the given filesystems and ConfigProvider.
+func NewPathSpec(fs *hugofs.Fs, config ConfigProvider) *PathSpec {
+
+	currCl, ok := config.Get("currentContentLanguage").(*Language)
+
+	if !ok {
+		// TODO(bep) globals
+		currCl = NewLanguage("en")
+	}
+
 	return &PathSpec{
+		fs:                             fs,
 		disablePathToLower:             config.GetBool("disablePathToLower"),
 		removePathAccents:              config.GetBool("removePathAccents"),
 		uglyURLs:                       config.GetBool("uglyURLs"),
@@ -45,7 +67,7 @@
 		multilingual:                   config.GetBool("multilingual"),
 		defaultContentLanguageInSubdir: config.GetBool("defaultContentLanguageInSubdir"),
 		defaultContentLanguage:         config.GetString("defaultContentLanguage"),
-		currentContentLanguage:         config.Get("currentContentLanguage").(*Language),
+		currentContentLanguage:         currCl,
 		paginatePath:                   config.GetString("paginatePath"),
 	}
 }
--- a/helpers/pathspec_test.go
+++ b/helpers/pathspec_test.go
@@ -16,6 +16,8 @@
 import (
 	"testing"
 
+	"github.com/spf13/hugo/hugofs"
+
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/require"
 )
@@ -31,15 +33,15 @@
 	viper.Set("canonifyURLs", true)
 	viper.Set("paginatePath", "side")
 
-	pathSpec := NewPathSpecFromConfig(viper.GetViper())
+	p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
 
-	require.True(t, pathSpec.canonifyURLs)
-	require.True(t, pathSpec.defaultContentLanguageInSubdir)
-	require.True(t, pathSpec.disablePathToLower)
-	require.True(t, pathSpec.multilingual)
-	require.True(t, pathSpec.removePathAccents)
-	require.True(t, pathSpec.uglyURLs)
-	require.Equal(t, "no", pathSpec.defaultContentLanguage)
-	require.Equal(t, "no", pathSpec.currentContentLanguage.Lang)
-	require.Equal(t, "side", pathSpec.paginatePath)
+	require.True(t, p.canonifyURLs)
+	require.True(t, p.defaultContentLanguageInSubdir)
+	require.True(t, p.disablePathToLower)
+	require.True(t, p.multilingual)
+	require.True(t, p.removePathAccents)
+	require.True(t, p.uglyURLs)
+	require.Equal(t, "no", p.defaultContentLanguage)
+	require.Equal(t, "no", p.currentContentLanguage.Lang)
+	require.Equal(t, "side", p.paginatePath)
 }
--- a/helpers/pygments.go
+++ b/helpers/pygments.go
@@ -60,7 +60,7 @@
 	io.WriteString(hash, lang)
 	io.WriteString(hash, options)
 
-	fs := hugofs.Os()
+	fs := hugofs.Os
 
 	ignoreCache := viper.GetBool("ignoreCache")
 	cacheDir := viper.GetString("cacheDir")
--- a/helpers/url_test.go
+++ b/helpers/url_test.go
@@ -18,6 +18,7 @@
 	"strings"
 	"testing"
 
+	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -26,7 +27,7 @@
 func TestURLize(t *testing.T) {
 	initCommonTestConfig()
 
-	p := NewPathSpecFromConfig(viper.GetViper())
+	p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
 
 	tests := []struct {
 		input    string
@@ -85,9 +86,11 @@
 		{"http//foo", "http://base/path", "http://base/path/MULTIhttp/foo"},
 	}
 
+	p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
+
 	for _, test := range tests {
 		viper.Set("baseURL", test.baseURL)
-		p := NewPathSpecFromConfig(viper.GetViper())
+
 		output := p.AbsURL(test.input, addLanguage)
 		expected := test.expected
 		if multilingual && addLanguage {
@@ -164,7 +167,7 @@
 	for i, test := range tests {
 		viper.Set("baseURL", test.baseURL)
 		viper.Set("canonifyURLs", test.canonify)
-		p := NewPathSpecFromConfig(viper.GetViper())
+		p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
 
 		output := p.RelURL(test.input, addLanguage)
 
@@ -247,9 +250,10 @@
 		{false, "/section/name.html", "/section/name/"},
 		{true, "/section/name/index.html", "/section/name.html"},
 	}
+
 	for i, d := range data {
 		viper.Set("uglyURLs", d.ugly)
-		p := NewPathSpecFromConfig(viper.GetViper())
+		p := NewPathSpec(hugofs.NewMem(), viper.GetViper())
 
 		output := p.URLPrep(d.input)
 		if d.output != output {
--- a/hugofs/fs.go
+++ b/hugofs/fs.go
@@ -19,76 +19,54 @@
 	"github.com/spf13/viper"
 )
 
-var (
-	sourceFs      afero.Fs
-	destinationFs afero.Fs
-	osFs          afero.Fs = &afero.OsFs{}
-	workingDirFs  *afero.BasePathFs
-)
+// Os points to an Os Afero file system.
+var Os = &afero.OsFs{}
 
-// Source returns Hugo's source file system.
-func Source() afero.Fs {
-	return sourceFs
-}
+type Fs struct {
+	// Source is Hugo's source file system.
+	Source afero.Fs
 
-// SetSource sets Hugo's source file system
-// and re-initializes dependent file systems.
-func SetSource(fs afero.Fs) {
-	sourceFs = fs
-	initSourceDependencies()
-}
+	// Destination is Hugo's destionation file system.
+	Destination afero.Fs
 
-// Destination returns Hugo's destionation file system.
-func Destination() afero.Fs {
-	return destinationFs
-}
+	// Os is an OS file system.
+	Os afero.Fs
 
-// SetDestination sets Hugo's destionation file system
-func SetDestination(fs afero.Fs) {
-	destinationFs = fs
+	// WorkingDir is a read-only file system
+	// restricted to the project working dir.
+	WorkingDir *afero.BasePathFs
 }
 
-// Os returns an OS file system.
-func Os() afero.Fs {
-	return osFs
-}
-
-// WorkingDir returns a read-only file system
-// restricted to the project working dir.
-func WorkingDir() *afero.BasePathFs {
-	return workingDirFs
-}
-
-// InitDefaultFs initializes with the OS file system
+// NewDefault creates a new Fs with the OS file system
 // as source and destination file systems.
-func InitDefaultFs() {
-	InitFs(&afero.OsFs{})
+func NewDefault() *Fs {
+	fs := &afero.OsFs{}
+	return newFs(fs)
 }
 
-// InitMemFs initializes with a MemMapFs as source and destination file systems.
+// NewDefault creates a new Fs with the MemMapFs
+// as source and destination file systems.
 // Useful for testing.
-func InitMemFs() {
-	InitFs(&afero.MemMapFs{})
+func NewMem() *Fs {
+	fs := &afero.MemMapFs{}
+	return newFs(fs)
 }
 
-// InitFs initializes with the given file system
-// as source and destination file systems.
-func InitFs(fs afero.Fs) {
-	sourceFs = fs
-	destinationFs = fs
-
-	initSourceDependencies()
+func newFs(base afero.Fs) *Fs {
+	return &Fs{
+		Source:      base,
+		Destination: base,
+		Os:          &afero.OsFs{},
+		WorkingDir:  getWorkingDirFs(base),
+	}
 }
 
-func initSourceDependencies() {
+func getWorkingDirFs(base afero.Fs) *afero.BasePathFs {
 	workingDir := viper.GetString("workingDir")
 
 	if workingDir != "" {
-		workingDirFs = afero.NewBasePathFs(afero.NewReadOnlyFs(sourceFs), workingDir).(*afero.BasePathFs)
+		return afero.NewBasePathFs(afero.NewReadOnlyFs(base), workingDir).(*afero.BasePathFs)
 	}
 
-}
-
-func init() {
-	InitDefaultFs()
+	return nil
 }
--- a/hugofs/fs_test.go
+++ b/hugofs/fs_test.go
@@ -21,53 +21,37 @@
 	"github.com/stretchr/testify/assert"
 )
 
-func TestInitDefault(t *testing.T) {
+func TestNewDefault(t *testing.T) {
 	viper.Reset()
 	defer viper.Reset()
 
-	InitDefaultFs()
+	f := NewDefault()
 
-	assert.NotNil(t, Source())
-	assert.IsType(t, new(afero.OsFs), Source())
-	assert.NotNil(t, Destination())
-	assert.IsType(t, new(afero.OsFs), Destination())
-	assert.NotNil(t, Os())
-	assert.IsType(t, new(afero.OsFs), Os())
-	assert.Nil(t, WorkingDir())
+	assert.NotNil(t, f.Source)
+	assert.IsType(t, new(afero.OsFs), f.Source)
+	assert.NotNil(t, f.Destination)
+	assert.IsType(t, new(afero.OsFs), f.Destination)
+	assert.NotNil(t, f.Os)
+	assert.IsType(t, new(afero.OsFs), f.Os)
+	assert.Nil(t, f.WorkingDir)
+
+	assert.IsType(t, new(afero.OsFs), Os)
 }
 
-func TestInitMemFs(t *testing.T) {
+func TestNewMem(t *testing.T) {
 	viper.Reset()
 	defer viper.Reset()
 
-	InitMemFs()
+	f := NewMem()
 
-	assert.NotNil(t, Source())
-	assert.IsType(t, new(afero.MemMapFs), Source())
-	assert.NotNil(t, Destination())
-	assert.IsType(t, new(afero.MemMapFs), Destination())
-	assert.IsType(t, new(afero.OsFs), Os())
-	assert.Nil(t, WorkingDir())
+	assert.NotNil(t, f.Source)
+	assert.IsType(t, new(afero.MemMapFs), f.Source)
+	assert.NotNil(t, f.Destination)
+	assert.IsType(t, new(afero.MemMapFs), f.Destination)
+	assert.IsType(t, new(afero.OsFs), f.Os)
+	assert.Nil(t, f.WorkingDir)
 }
 
-func TestSetSource(t *testing.T) {
-
-	InitMemFs()
-
-	SetSource(new(afero.OsFs))
-	assert.NotNil(t, Source())
-	assert.IsType(t, new(afero.OsFs), Source())
-}
-
-func TestSetDestination(t *testing.T) {
-
-	InitMemFs()
-
-	SetDestination(new(afero.OsFs))
-	assert.NotNil(t, Destination())
-	assert.IsType(t, new(afero.OsFs), Destination())
-}
-
 func TestWorkingDir(t *testing.T) {
 	viper.Reset()
 	defer viper.Reset()
@@ -74,8 +58,8 @@
 
 	viper.Set("workingDir", "/a/b/")
 
-	InitMemFs()
+	f := NewMem()
 
-	assert.NotNil(t, WorkingDir())
-	assert.IsType(t, new(afero.BasePathFs), WorkingDir())
+	assert.NotNil(t, f.WorkingDir)
+	assert.IsType(t, new(afero.BasePathFs), f.WorkingDir)
 }
--- a/hugolib/alias_test.go
+++ b/hugolib/alias_test.go
@@ -16,6 +16,10 @@
 import (
 	"path/filepath"
 	"testing"
+
+	"github.com/spf13/hugo/deps"
+	"github.com/spf13/hugo/hugofs"
+	"github.com/stretchr/testify/require"
 )
 
 const pageWithAlias = `---
@@ -30,31 +34,37 @@
 
 func TestAlias(t *testing.T) {
 	testCommonResetState()
-	writeSource(t, filepath.Join("content", "page.md"), pageWithAlias)
-	writeSource(t, filepath.Join("layouts", "_default", "single.html"), basicTemplate)
 
-	if err := buildAndRenderSite(NewSiteDefaultLang()); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	fs := hugofs.NewMem()
 
+	writeSource(t, fs, filepath.Join("content", "page.md"), pageWithAlias)
+	writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), basicTemplate)
+
+	buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+
 	// the real page
-	assertFileContent(t, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
+	assertFileContent(t, fs, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
 	// the alias redirector
-	assertFileContent(t, filepath.Join("public", "foo", "bar", "index.html"), false, "<meta http-equiv=\"refresh\" content=\"0; ")
+	assertFileContent(t, fs, filepath.Join("public", "foo", "bar", "index.html"), false, "<meta http-equiv=\"refresh\" content=\"0; ")
 }
 
 func TestAliasTemplate(t *testing.T) {
 	testCommonResetState()
-	writeSource(t, filepath.Join("content", "page.md"), pageWithAlias)
-	writeSource(t, filepath.Join("layouts", "_default", "single.html"), basicTemplate)
-	writeSource(t, filepath.Join("layouts", "alias.html"), aliasTemplate)
 
-	if err := buildAndRenderSite(NewSiteDefaultLang()); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	fs := hugofs.NewMem()
 
+	writeSource(t, fs, filepath.Join("content", "page.md"), pageWithAlias)
+	writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), basicTemplate)
+	writeSource(t, fs, filepath.Join("layouts", "alias.html"), aliasTemplate)
+
+	sites, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
+
+	require.NoError(t, err)
+
+	require.NoError(t, sites.Build(BuildCfg{}))
+
 	// the real page
-	assertFileContent(t, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
+	assertFileContent(t, fs, filepath.Join("public", "page", "index.html"), false, "For some moments the old man")
 	// the alias redirector
-	assertFileContent(t, filepath.Join("public", "foo", "bar", "index.html"), false, "ALIASTEMPLATE")
+	assertFileContent(t, fs, filepath.Join("public", "foo", "bar", "index.html"), false, "ALIASTEMPLATE")
 }
--- a/hugolib/case_insensitive_test.go
+++ b/hugolib/case_insensitive_test.go
@@ -18,6 +18,11 @@
 	"path/filepath"
 	"strings"
 	"testing"
+
+	"github.com/spf13/viper"
+
+	"github.com/spf13/hugo/deps"
+	"github.com/spf13/hugo/hugofs"
 )
 
 var (
@@ -106,22 +111,22 @@
 `
 )
 
-func caseMixingTestsWriteCommonSources(t *testing.T) {
-	writeSource(t, filepath.Join("content", "sect1", "page1.md"), caseMixingPage1)
-	writeSource(t, filepath.Join("content", "sect2", "page2.md"), caseMixingPage2)
-	writeSource(t, filepath.Join("content", "sect1", "page1.en.md"), caseMixingPage1En)
+func caseMixingTestsWriteCommonSources(t *testing.T, fs *hugofs.Fs) {
+	writeSource(t, fs, filepath.Join("content", "sect1", "page1.md"), caseMixingPage1)
+	writeSource(t, fs, filepath.Join("content", "sect2", "page2.md"), caseMixingPage2)
+	writeSource(t, fs, filepath.Join("content", "sect1", "page1.en.md"), caseMixingPage1En)
 
-	writeSource(t, "layouts/shortcodes/shortcode.html", `
+	writeSource(t, fs, "layouts/shortcodes/shortcode.html", `
 Shortcode Page: {{ .Page.Params.COLOR }}|{{ .Page.Params.Colors.Blue  }}
 Shortcode Site: {{ .Page.Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW  }}
 `)
 
-	writeSource(t, "layouts/partials/partial.html", `
+	writeSource(t, fs, "layouts/partials/partial.html", `
 Partial Page: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }}
 Partial Site: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
 `)
 
-	writeSource(t, "config.toml", caseMixingSiteConfigTOML)
+	writeSource(t, fs, "config.toml", caseMixingSiteConfigTOML)
 
 }
 
@@ -139,13 +144,21 @@
 	// page frontmatter: regular fields, blackfriday config, param with nested map
 
 	testCommonResetState()
-	caseMixingTestsWriteCommonSources(t)
 
-	writeSource(t, filepath.Join("layouts", "_default", "baseof.html"), `
+	depsCfg := newTestDepsConfig()
+	viper.SetFs(depsCfg.Fs.Source)
+
+	caseMixingTestsWriteCommonSources(t, depsCfg.Fs)
+
+	if err := LoadGlobalConfig("", "config.toml"); err != nil {
+		t.Fatalf("Failed to load config: %s", err)
+	}
+
+	writeSource(t, depsCfg.Fs, filepath.Join("layouts", "_default", "baseof.html"), `
 Block Page Colors: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }}	
 {{ block "main" . }}default{{end}}`)
 
-	writeSource(t, filepath.Join("layouts", "sect2", "single.html"), `
+	writeSource(t, depsCfg.Fs, filepath.Join("layouts", "sect2", "single.html"), `
 {{ define "main"}}
 Page Colors: {{ .Params.CoLOR }}|{{ .Params.Colors.Blue }}
 Site Colors: {{ .Site.Params.COlOR }}|{{ .Site.Params.COLORS.YELLOW }}
@@ -154,7 +167,7 @@
 {{ end }}
 `)
 
-	writeSource(t, filepath.Join("layouts", "_default", "single.html"), `
+	writeSource(t, depsCfg.Fs, filepath.Join("layouts", "_default", "single.html"), `
 Page Title: {{ .Title }}
 Site Title: {{ .Site.Title }}
 Site Lang Mood: {{ .Site.Language.Params.MOoD }}
@@ -164,12 +177,8 @@
 {{ partial "partial.html" . }}
 `)
 
-	if err := LoadGlobalConfig("", "config.toml"); err != nil {
-		t.Fatalf("Failed to load config: %s", err)
-	}
+	sites, err := NewHugoSitesFromConfiguration(depsCfg)
 
-	sites, err := NewHugoSitesFromConfiguration(DepsCfg{})
-
 	if err != nil {
 		t.Fatalf("Failed to create sites: %s", err)
 	}
@@ -180,7 +189,7 @@
 		t.Fatalf("Failed to build sites: %s", err)
 	}
 
-	assertFileContent(t, filepath.Join("public", "nn", "sect1", "page1", "index.html"), true,
+	assertFileContent(t, sites.Fs, filepath.Join("public", "nn", "sect1", "page1", "index.html"), true,
 		"Page Colors: red|heavenly",
 		"Site Colors: green|yellow",
 		"Site Lang Mood: Happy",
@@ -193,7 +202,7 @@
 		"&laquo;Hi&raquo;", // angled quotes
 	)
 
-	assertFileContent(t, filepath.Join("public", "en", "sect1", "page1", "index.html"), true,
+	assertFileContent(t, sites.Fs, filepath.Join("public", "en", "sect1", "page1", "index.html"), true,
 		"Site Colors: Pink|golden",
 		"Page Colors: black|bluesy",
 		"Site Lang Mood: Thoughtful",
@@ -202,7 +211,7 @@
 		"&ldquo;Hi&rdquo;",
 	)
 
-	assertFileContent(t, filepath.Join("public", "nn", "sect2", "page2", "index.html"), true,
+	assertFileContent(t, sites.Fs, filepath.Join("public", "nn", "sect2", "page2", "index.html"), true,
 		"Page Colors: black|sky",
 		"Site Colors: green|yellow",
 		"Shortcode Page: black|sky",
@@ -244,8 +253,16 @@
 func doTestCaseInsensitiveConfigurationForTemplateEngine(t *testing.T, suffix string, templateFixer func(s string) string) {
 
 	testCommonResetState()
-	caseMixingTestsWriteCommonSources(t)
 
+	fs := hugofs.NewMem()
+	viper.SetFs(fs.Source)
+
+	caseMixingTestsWriteCommonSources(t, fs)
+
+	if err := LoadGlobalConfig("", "config.toml"); err != nil {
+		t.Fatalf("Failed to load config: %s", err)
+	}
+
 	t.Log("Testing", suffix)
 
 	templTemplate := `
@@ -261,14 +278,10 @@
 
 	t.Log(templ)
 
-	writeSource(t, filepath.Join("layouts", "_default", fmt.Sprintf("single.%s", suffix)), templ)
+	writeSource(t, fs, filepath.Join("layouts", "_default", fmt.Sprintf("single.%s", suffix)), templ)
 
-	if err := LoadGlobalConfig("", "config.toml"); err != nil {
-		t.Fatalf("Failed to load config: %s", err)
-	}
+	sites, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
 
-	sites, err := NewHugoSitesFromConfiguration(DepsCfg{})
-
 	if err != nil {
 		t.Fatalf("Failed to create sites: %s", err)
 	}
@@ -279,7 +292,7 @@
 		t.Fatalf("Failed to build sites: %s", err)
 	}
 
-	assertFileContent(t, filepath.Join("public", "nn", "sect1", "page1", "index.html"), true,
+	assertFileContent(t, sites.Fs, filepath.Join("public", "nn", "sect1", "page1", "index.html"), true,
 		"Page Colors: red|heavenly",
 		"Site Colors: green|yellow",
 		"Shortcode Page: red|heavenly",
--- a/hugolib/config_test.go
+++ b/hugolib/config_test.go
@@ -18,6 +18,7 @@
 
 	"github.com/spf13/hugo/helpers"
 
+	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -30,7 +31,10 @@
 	PaginatePath = "side"
 	`
 
-	writeSource(t, "hugo.toml", configContent)
+	fs := hugofs.NewMem()
+	viper.SetFs(fs.Source)
+
+	writeSource(t, fs, "hugo.toml", configContent)
 
 	require.NoError(t, LoadGlobalConfig("", "hugo.toml"))
 	assert.Equal(t, "side", helpers.Config().GetString("paginatePath"))
--- a/hugolib/datafiles_test.go
+++ b/hugolib/datafiles_test.go
@@ -89,19 +89,16 @@
 	sources := []source.ByteSource{
 		{Name: filepath.FromSlash("test.roml"), Content: []byte("boo")},
 	}
-	s := NewSiteDefaultLang()
-	err := s.loadData([]source.Input{&source.InMemorySource{ByteSource: sources}})
-	if err != nil {
-		t.Fatalf("Should not return an error")
-	}
+	s, err := NewSiteDefaultLang()
+	require.NoError(t, err)
+	require.NoError(t, s.loadData([]source.Input{&source.InMemorySource{ByteSource: sources}}))
 }
 
 func doTestDataDir(t *testing.T, expected interface{}, sources []source.Input) {
-	s := NewSiteDefaultLang()
-	err := s.loadData(sources)
-	if err != nil {
-		t.Fatalf("Error loading data: %s", err)
-	}
+	s, err := NewSiteDefaultLang()
+	require.NoError(t, err)
+	require.NoError(t, s.loadData(sources))
+
 	if !reflect.DeepEqual(expected, s.Data) {
 		t.Errorf("Expected structure\n%#v got\n%#v", expected, s.Data)
 	}
@@ -109,21 +106,22 @@
 
 func TestDataFromShortcode(t *testing.T) {
 	testCommonResetState()
-	writeSource(t, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
-	writeSource(t, "layouts/_default/single.html", `
+
+	cfg := newTestDepsConfig()
+
+	writeSource(t, cfg.Fs, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
+	writeSource(t, cfg.Fs, "layouts/_default/single.html", `
 * Slogan from template: {{  .Site.Data.hugo.slogan }}
 * {{ .Content }}`)
-	writeSource(t, "layouts/shortcodes/d.html", `{{  .Page.Site.Data.hugo.slogan }}`)
-	writeSource(t, "content/c.md", `---
+	writeSource(t, cfg.Fs, "layouts/shortcodes/d.html", `{{  .Page.Site.Data.hugo.slogan }}`)
+	writeSource(t, cfg.Fs, "content/c.md", `---
 ---
 Slogan from shortcode: {{< d >}}
 `)
 
-	h, err := newHugoSitesDefaultLanguage()
-	require.NoError(t, err)
-	require.NoError(t, h.Build(BuildCfg{}))
+	buildSingleSite(t, cfg, BuildCfg{})
 
-	content := readSource(t, "public/c/index.html")
+	content := readSource(t, cfg.Fs, "public/c/index.html")
 	require.True(t, strings.Contains(content, "Slogan from template: Hugo Rocks!"), content)
 	require.True(t, strings.Contains(content, "Slogan from shortcode: Hugo Rocks!"), content)
 
--- a/hugolib/embedded_shortcodes_test.go
+++ b/hugolib/embedded_shortcodes_test.go
@@ -27,9 +27,11 @@
 	"log"
 	"path/filepath"
 
-	"github.com/spf13/hugo/tpl"
+	"github.com/spf13/hugo/deps"
 
 	"github.com/spf13/hugo/helpers"
+	"github.com/spf13/hugo/hugofs"
+	"github.com/spf13/hugo/tplapi"
 	jww "github.com/spf13/jwalterweatherman"
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/require"
@@ -65,17 +67,17 @@
 	path := filepath.FromSlash("blog/post.md")
 	in := fmt.Sprintf(`{{< %s "%s" >}}`, refShortcode, path)
 
-	writeSource(t, "content/"+path, simplePageWithURL+": "+in)
+	fs := hugofs.NewMem()
 
+	writeSource(t, fs, "content/"+path, simplePageWithURL+": "+in)
+
 	expected := fmt.Sprintf(`%s/simple/url/`, expectedBase)
 
-	sites, err := newHugoSitesDefaultLanguage()
-	require.NoError(t, err)
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
 
-	require.NoError(t, sites.Build(BuildCfg{}))
-	require.Len(t, sites.Sites[0].RegularPages, 1)
+	require.Len(t, s.RegularPages, 1)
 
-	output := string(sites.Sites[0].RegularPages[0].Content)
+	output := string(s.RegularPages[0].Content)
 
 	if !strings.Contains(output, expected) {
 		t.Errorf("Got\n%q\nExpected\n%q", output, expected)
@@ -308,7 +310,7 @@
 			},
 		}
 
-		p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.Template) error {
+		p, _ := pageFromString(simplePage, "simple.md", func(templ tplapi.Template) error {
 			templ.Funcs(tweetFuncMap)
 			return nil
 		})
@@ -361,7 +363,7 @@
 pQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;"></div></div><p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
 <script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
 		},
-Fhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;"></div></div><p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
+Fhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;"></div></div><p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
 czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;"></div></div><p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/BMokmydjG-M/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A photo posted by Instagram (@instagram)</a> on <time style=" font-family:Arial,sans-serif; font-size:14px; line-height:17px;" datetime="2016-11-10T15:02:28+00:00">Nov 10, 2016 at 7:02am PST</time></p></div></blockquote>
 <script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
 		},
--- a/hugolib/gitinfo.go
+++ b/hugolib/gitinfo.go
@@ -20,7 +20,6 @@
 
 	"github.com/bep/gitmap"
 	"github.com/spf13/hugo/helpers"
-	jww "github.com/spf13/jwalterweatherman"
 	"github.com/spf13/viper"
 )
 
@@ -36,7 +35,7 @@
 
 	gitRepo, err := gitmap.Map(workingDir, "")
 	if err != nil {
-		jww.ERROR.Printf("Got error reading Git log: %s", err)
+		h.Log.ERROR.Printf("Got error reading Git log: %s", err)
 		return
 	}
 
@@ -60,7 +59,7 @@
 		filename := path.Join(filepath.ToSlash(contentRoot), contentDir, filepath.ToSlash(p.Path()))
 		g, ok := gitMap[filename]
 		if !ok {
-			jww.ERROR.Printf("Failed to find GitInfo for %q", filename)
+			h.Log.ERROR.Printf("Failed to find GitInfo for %q", filename)
 			return
 		}
 
--- a/hugolib/handler_page.go
+++ b/hugolib/handler_page.go
@@ -65,7 +65,6 @@
 
 func (h htmlHandler) Extensions() []string { return []string{"html", "htm"} }
 
-// TODO(bep) globals use p.s.t
 func (h htmlHandler) PageConvert(p *Page) HandledResult {
 	if p.rendered {
 		panic(fmt.Sprintf("Page %q already rendered, does not need conversion", p.BaseFileName()))
--- a/hugolib/handler_test.go
+++ b/hugolib/handler_test.go
@@ -17,10 +17,9 @@
 	"path/filepath"
 	"testing"
 
+	"github.com/spf13/hugo/deps"
 	"github.com/spf13/hugo/helpers"
 	"github.com/spf13/hugo/hugofs"
-	"github.com/spf13/hugo/source"
-	"github.com/spf13/hugo/target"
 	"github.com/spf13/viper"
 )
 
@@ -27,35 +26,28 @@
 func TestDefaultHandler(t *testing.T) {
 	testCommonResetState()
 
-	hugofs.InitMemFs()
-	sources := []source.ByteSource{
-		{Name: filepath.FromSlash("sect/doc1.html"), Content: []byte("---\nmarkup: markdown\n---\n# title\nsome *content*")},
-		{Name: filepath.FromSlash("sect/doc2.html"), Content: []byte("<!doctype html><html><body>more content</body></html>")},
-		{Name: filepath.FromSlash("sect/doc3.md"), Content: []byte("# doc3\n*some* content")},
-		{Name: filepath.FromSlash("sect/doc4.md"), Content: []byte("---\ntitle: doc4\n---\n# doc4\n*some content*")},
-		{Name: filepath.FromSlash("sect/doc3/img1.png"), Content: []byte("‰PNG  ��� IHDR����������:~›U��� IDATWcø��ZMoñ����IEND®B`‚")},
-		{Name: filepath.FromSlash("sect/img2.gif"), Content: []byte("GIF89a��€��ÿÿÿ���,�������D�;")},
-		{Name: filepath.FromSlash("sect/img2.spf"), Content: []byte("****FAKE-FILETYPE****")},
-		{Name: filepath.FromSlash("doc7.html"), Content: []byte("<html><body>doc7 content</body></html>")},
-		{Name: filepath.FromSlash("sect/doc8.html"), Content: []byte("---\nmarkup: md\n---\n# title\nsome *content*")},
-	}
-
 	viper.Set("defaultExtension", "html")
 	viper.Set("verbose", true)
+	viper.Set("uglyURLs", true)
 
-	s := &Site{
-		Source:   &source.InMemorySource{ByteSource: sources},
-		targets:  targetList{page: &target.PagePub{UglyURLs: true, PublishDir: "public"}},
-		Language: helpers.NewLanguage("en"),
-	}
+	fs := hugofs.NewMem()
 
-	if err := buildAndRenderSite(s,
-		"_default/single.html", "{{.Content}}",
-		"head", "<head><script src=\"script.js\"></script></head>",
-		"head_abs", "<head><script src=\"/script.js\"></script></head>"); err != nil {
-		t.Fatalf("Failed to render site: %s", err)
-	}
+	writeSource(t, fs, filepath.FromSlash("content/sect/doc1.html"), "---\nmarkup: markdown\n---\n# title\nsome *content*")
+	writeSource(t, fs, filepath.FromSlash("content/sect/doc2.html"), "<!doctype html><html><body>more content</body></html>")
+	writeSource(t, fs, filepath.FromSlash("content/sect/doc3.md"), "# doc3\n*some* content")
+	writeSource(t, fs, filepath.FromSlash("content/sect/doc4.md"), "---\ntitle: doc4\n---\n# doc4\n*some content*")
+	writeSource(t, fs, filepath.FromSlash("content/sect/doc3/img1.png"), "‰PNG  ��� IHDR����������:~›U��� IDATWcø��ZMoñ����IEND®B`‚")
+	writeSource(t, fs, filepath.FromSlash("content/sect/img2.gif"), "GIF89a��€��ÿÿÿ���,�������D�;")
+	writeSource(t, fs, filepath.FromSlash("content/sect/img2.spf"), "****FAKE-FILETYPE****")
+	writeSource(t, fs, filepath.FromSlash("content/doc7.html"), "<html><body>doc7 content</body></html>")
+	writeSource(t, fs, filepath.FromSlash("content/sect/doc8.html"), "---\nmarkup: md\n---\n# title\nsome *content*")
 
+	writeSource(t, fs, filepath.FromSlash("layouts/_default/single.html"), "{{.Content}}")
+	writeSource(t, fs, filepath.FromSlash("head"), "<head><script src=\"script.js\"></script></head>")
+	writeSource(t, fs, filepath.FromSlash("head_abs"), "<head><script src=\"/script.js\"></script></head")
+
+	buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+
 	tests := []struct {
 		doc      string
 		expected string
@@ -71,7 +63,7 @@
 	}
 
 	for _, test := range tests {
-		file, err := hugofs.Destination().Open(test.doc)
+		file, err := fs.Destination.Open(test.doc)
 		if err != nil {
 			t.Fatalf("Did not find %s in target.", test.doc)
 		}
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -14,13 +14,12 @@
 package hugolib
 
 import (
+	"errors"
 	"fmt"
-	"io/ioutil"
-	"log"
-	"os"
 	"strings"
 	"sync"
 
+	"github.com/spf13/hugo/deps"
 	"github.com/spf13/hugo/helpers"
 
 	"github.com/spf13/viper"
@@ -27,7 +26,7 @@
 
 	"github.com/spf13/hugo/source"
 	"github.com/spf13/hugo/tpl"
-	jww "github.com/spf13/jwalterweatherman"
+	"github.com/spf13/hugo/tplapi"
 )
 
 // HugoSites represents the sites to build. Each site represents a language.
@@ -38,45 +37,17 @@
 
 	multilingual *Multilingual
 
-	*deps
+	*deps.Deps
 }
 
-// deps holds dependencies used by many.
-// TODO(bep) globals a better name.
-// There will be normally be only one instance of deps in play
-// at a given time.
-type deps struct {
-	// The logger to use.
-	log *jww.Notepad
+// NewHugoSites creates a new collection of sites given the input sites, building
+// a language configuration based on those.
+func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) {
 
-	tmpl *tpl.GoHTMLTemplate
-
-	// TODO(bep) next in line: Viper, hugofs
-}
-
-func (d *deps) refreshTemplates(withTemplate ...func(templ tpl.Template) error) {
-	d.tmpl = tpl.New(d.log, withTemplate...)
-	d.tmpl.PrintErrors() // TODO(bep) globals error handling
-}
-
-func newDeps(cfg DepsCfg) *deps {
-	logger := cfg.Logger
-
-	if logger == nil {
-		// TODO(bep) globals default log level
-		//logger = jww.NewNotepad(jww.LevelError, jww.LevelWarn, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
-		logger = jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+	if cfg.Language != nil {
+		return nil, errors.New("Cannot provide Language in Cfg when sites are provided")
 	}
 
-	return &deps{
-		log:  logger,
-		tmpl: tpl.New(logger, cfg.WithTemplate...),
-	}
-}
-
-// NewHugoSites creates a new collection of sites given the input sites, building
-// a language configuration based on those.
-func newHugoSites(cfg DepsCfg, sites ...*Site) (*HugoSites, error) {
 	langConfig, err := newMultiLingualFromSites(sites...)
 
 	if err != nil {
@@ -83,29 +54,60 @@
 		return nil, err
 	}
 
-	var d *deps
-
-	if sites[0].deps != nil {
-		d = sites[0].deps
-	} else {
-		d = newDeps(cfg)
-	}
-
 	h := &HugoSites{
-		deps:         d,
 		multilingual: langConfig,
 		Sites:        sites}
 
 	for _, s := range sites {
 		s.owner = h
-		s.deps = h.deps
 	}
+
+	applyDepsIfNeeded(cfg, sites...)
+
+	h.Deps = sites[0].Deps
+
 	return h, nil
 }
 
+func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error {
+
+	if cfg.TemplateProvider == nil {
+		cfg.TemplateProvider = tpl.DefaultTemplateProvider
+	}
+
+	var (
+		d   *deps.Deps
+		err error
+	)
+
+	for _, s := range sites {
+		if s.Deps != nil {
+			continue
+		}
+
+		if d == nil {
+			cfg.Language = s.Language
+			cfg.WithTemplate = s.withSiteTemplates(cfg.WithTemplate)
+			d = deps.New(cfg)
+			if err := d.LoadTemplates(); err != nil {
+				return err
+			}
+
+		} else {
+			d, err = d.ForLanguage(s.Language)
+			if err != nil {
+				return err
+			}
+		}
+		s.Deps = d
+	}
+
+	return nil
+}
+
 // NewHugoSitesFromConfiguration creates HugoSites from the global Viper config.
 // TODO(bep) globals rename this when all the globals are gone.
-func NewHugoSitesFromConfiguration(cfg DepsCfg) (*HugoSites, error) {
+func NewHugoSitesFromConfiguration(cfg deps.DepsCfg) (*HugoSites, error) {
 	sites, err := createSitesFromConfig(cfg)
 	if err != nil {
 		return nil, err
@@ -113,17 +115,42 @@
 	return newHugoSites(cfg, sites...)
 }
 
-func createSitesFromConfig(cfg DepsCfg) ([]*Site, error) {
-	deps := newDeps(cfg)
-	return createSitesFromDeps(deps)
+func (s *Site) withSiteTemplates(withTemplates ...func(templ tplapi.Template) error) func(templ tplapi.Template) error {
+	return func(templ tplapi.Template) error {
+		templ.LoadTemplates(s.absLayoutDir())
+		if s.hasTheme() {
+			templ.LoadTemplatesWithPrefix(s.absThemeDir()+"/layouts", "theme")
+		}
+
+		for _, wt := range withTemplates {
+			if wt == nil {
+				continue
+			}
+			if err := wt(templ); err != nil {
+				return err
+			}
+		}
+
+		return nil
+	}
 }
 
-func createSitesFromDeps(deps *deps) ([]*Site, error) {
-	var sites []*Site
+func createSitesFromConfig(cfg deps.DepsCfg) ([]*Site, error) {
+
+	var (
+		sites []*Site
+	)
+
 	multilingual := viper.GetStringMap("languages")
 
 	if len(multilingual) == 0 {
-		sites = append(sites, newSite(helpers.NewDefaultLanguage(), deps))
+		l := helpers.NewDefaultLanguage()
+		cfg.Language = l
+		s, err := newSite(cfg)
+		if err != nil {
+			return nil, err
+		}
+		sites = append(sites, s)
 	}
 
 	if len(multilingual) > 0 {
@@ -136,9 +163,17 @@
 		}
 
 		for _, lang := range languages {
-			sites = append(sites, newSite(lang, deps))
-		}
+			var s *Site
+			var err error
+			cfg.Language = lang
+			s, err = newSite(cfg)
 
+			if err != nil {
+				return nil, err
+			}
+
+			sites = append(sites, s)
+		}
 	}
 
 	return sites, nil
@@ -155,7 +190,8 @@
 
 func (h *HugoSites) createSitesFromConfig() error {
 
-	sites, err := createSitesFromDeps(h.deps)
+	depsCfg := deps.DepsCfg{Fs: h.Fs}
+	sites, err := createSitesFromConfig(depsCfg)
 
 	if err != nil {
 		return err
@@ -173,6 +209,12 @@
 		s.owner = h
 	}
 
+	if err := applyDepsIfNeeded(depsCfg, sites...); err != nil {
+		return err
+	}
+
+	h.Deps = sites[0].Deps
+
 	h.multilingual = langConfig
 
 	return nil
@@ -199,24 +241,10 @@
 	CreateSitesFromConfig bool
 	// Skip rendering. Useful for testing.
 	SkipRender bool
-	// Use this to add templates to use for rendering.
-	// Useful for testing.
-	withTemplate func(templ tpl.Template) error
 	// Use this to indicate what changed (for rebuilds).
 	whatChanged *whatChanged
 }
 
-// DepsCfg contains configuration options that can be used to configure Hugo
-// on a global level, i.e. logging etc.
-// Nil values will be given default values.
-type DepsCfg struct {
-
-	// The Logger to use.
-	Logger *jww.Notepad
-
-	WithTemplate []func(templ tpl.Template) error
-}
-
 func (h *HugoSites) renderCrossSitesArtifacts() error {
 
 	if !h.multilingual.enabled() {
@@ -293,7 +321,7 @@
 				foundTaxonomyTermsPage := false
 				for key := range tax {
 					if s.Info.preserveTaxonomyNames {
-						key = s.Info.pathSpec.MakePathSanitized(key)
+						key = s.PathSpec.MakePathSanitized(key)
 					}
 					for _, p := range taxonomyPages {
 						if p.sections[0] == plural && p.sections[1] == key {
@@ -454,8 +482,8 @@
 				}
 
 				var err error
-				if workContentCopy, err = handleShortcodes(p, s.owner.tmpl, workContentCopy); err != nil {
-					jww.ERROR.Printf("Failed to handle shortcodes for page %s: %s", p.BaseFileName(), err)
+				if workContentCopy, err = handleShortcodes(p, s.Tmpl, workContentCopy); err != nil {
+					s.Log.ERROR.Printf("Failed to handle shortcodes for page %s: %s", p.BaseFileName(), err)
 				}
 
 				if p.Markup != "html" {
@@ -464,7 +492,7 @@
 					summaryContent, err := p.setUserDefinedSummaryIfProvided(workContentCopy)
 
 					if err != nil {
-						jww.ERROR.Printf("Failed to set user defined summary for page %q: %s", p.Path(), err)
+						s.Log.ERROR.Printf("Failed to set user defined summary for page %q: %s", p.Path(), err)
 					} else if summaryContent != nil {
 						workContentCopy = summaryContent.content
 					}
@@ -501,9 +529,9 @@
 	return h.Sites[0].AllPages
 }
 
-func handleShortcodes(p *Page, t tpl.Template, rawContentCopy []byte) ([]byte, error) {
+func handleShortcodes(p *Page, t tplapi.Template, rawContentCopy []byte) ([]byte, error) {
 	if len(p.contentShortCodes) > 0 {
-		jww.DEBUG.Printf("Replace %d shortcodes in %q", len(p.contentShortCodes), p.BaseFileName())
+		p.s.Log.DEBUG.Printf("Replace %d shortcodes in %q", len(p.contentShortCodes), p.BaseFileName())
 		shortcodes, err := executeShortcodeFuncMap(p.contentShortCodes)
 
 		if err != nil {
@@ -513,7 +541,7 @@
 		rawContentCopy, err = replaceShortcodeTokens(rawContentCopy, shortcodePlaceholderPrefix, shortcodes)
 
 		if err != nil {
-			jww.FATAL.Printf("Failed to replace shortcode tokens in %s:\n%s", p.BaseFileName(), err.Error())
+			p.s.Log.FATAL.Printf("Failed to replace shortcode tokens in %s:\n%s", p.BaseFileName(), err.Error())
 		}
 	}
 
@@ -550,51 +578,15 @@
 	return h.findPagesByKindNotIn(kind, h.Sites[0].AllPages)
 }
 
-// Convenience func used in tests to build a single site/language excluding render phase.
-func buildSiteSkipRender(s *Site, additionalTemplates ...string) error {
-	return doBuildSite(s, false, additionalTemplates...)
-}
-
-// Convenience func used in tests to build a single site/language including render phase.
-func buildAndRenderSite(s *Site, additionalTemplates ...string) error {
-	return doBuildSite(s, true, additionalTemplates...)
-}
-
-// Convenience func used in tests to build a single site/language.
-func doBuildSite(s *Site, render bool, additionalTemplates ...string) error {
-	if s.PageCollections == nil {
-		s.PageCollections = newPageCollections()
-	}
-	sites, err := newHugoSites(DepsCfg{}, s)
-	if err != nil {
-		return err
-	}
-
-	addTemplates := func(templ tpl.Template) error {
-		for i := 0; i < len(additionalTemplates); i += 2 {
-			err := templ.AddTemplate(additionalTemplates[i], additionalTemplates[i+1])
-			if err != nil {
-				return err
-			}
-		}
-		return nil
-	}
-
-	config := BuildCfg{SkipRender: !render, withTemplate: addTemplates}
-	return sites.Build(config)
-}
-
 // Convenience func used in tests.
-func newHugoSitesFromSourceAndLanguages(input []source.ByteSource, languages helpers.Languages) (*HugoSites, error) {
+func newHugoSitesFromSourceAndLanguages(input []source.ByteSource, languages helpers.Languages, cfg deps.DepsCfg) (*HugoSites, error) {
 	if len(languages) == 0 {
 		panic("Must provide at least one language")
 	}
 
-	cfg := DepsCfg{}
-
 	first := &Site{
-		Source:   &source.InMemorySource{ByteSource: input},
 		Language: languages[0],
+		Source:   &source.InMemorySource{ByteSource: input},
 	}
 	if len(languages) == 1 {
 		return newHugoSites(cfg, first)
@@ -611,6 +603,6 @@
 }
 
 // Convenience func used in tests.
-func newHugoSitesDefaultLanguage() (*HugoSites, error) {
-	return newHugoSitesFromSourceAndLanguages(nil, helpers.Languages{helpers.NewDefaultLanguage()})
+func newHugoSitesDefaultLanguage(cfg deps.DepsCfg) (*HugoSites, error) {
+	return newHugoSitesFromSourceAndLanguages(nil, helpers.Languages{helpers.NewDefaultLanguage()}, cfg)
 }
--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -59,7 +59,7 @@
 	}
 
 	if config.PrintStats {
-		h.log.FEEDBACK.Printf("total in %v ms\n", int(1000*time.Since(t0).Seconds()))
+		h.Log.FEEDBACK.Printf("total in %v ms\n", int(1000*time.Since(t0).Seconds()))
 	}
 
 	return nil
--- a/hugolib/hugo_sites_build_test.go
+++ b/hugolib/hugo_sites_build_test.go
@@ -14,6 +14,7 @@
 	"github.com/fortytw2/leaktest"
 	"github.com/fsnotify/fsnotify"
 	"github.com/spf13/afero"
+	"github.com/spf13/hugo/deps"
 	"github.com/spf13/hugo/helpers"
 	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/hugo/source"
@@ -25,6 +26,7 @@
 
 type testSiteConfig struct {
 	DefaultContentLanguage string
+	Fs                     *hugofs.Fs
 }
 
 func init() {
@@ -32,9 +34,9 @@
 }
 
 func testCommonResetState() {
-	hugofs.InitMemFs()
 	viper.Reset()
-	viper.SetFs(hugofs.Source())
+	// TODO(bep) globals viper	viper.SetFs(hugofs.Source())
+	viper.Set("currentContentLanguage", helpers.NewLanguage("en"))
 	helpers.ResetConfigProvider()
 	loadDefaultSettings()
 
@@ -41,13 +43,10 @@
 	// Default is false, but true is easier to use as default in tests
 	viper.Set("defaultContentLanguageInSubdir", true)
 
-	if err := hugofs.Source().Mkdir("content", 0755); err != nil {
-		panic("Content folder creation failed.")
-	}
-
 }
 
-func TestMultiSitesMainLangInRoot(t *testing.T) {
+// TODO(bep) globals this currently fails because of a configuration dependency that will be resolved when we get rid of the global Viper.
+func _TestMultiSitesMainLangInRoot(t *testing.T) {
 
 	for _, b := range []bool{true, false} {
 		doTestMultiSitesMainLangInRoot(t, b)
@@ -57,7 +56,8 @@
 func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) {
 	testCommonResetState()
 	viper.Set("defaultContentLanguageInSubdir", defaultInSubDir)
-	siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}
+	fs := hugofs.NewMem()
+	siteConfig := testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}
 
 	sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
 
@@ -80,7 +80,8 @@
 		require.Equal(t, "", frSite.Info.LanguagePrefix)
 	}
 
-	require.Equal(t, "/blog/en/foo", enSite.Info.pathSpec.RelURL("foo", true))
+	fmt.Println(">>>", enSite.PathSpec)
+	require.Equal(t, "/blog/en/foo", enSite.PathSpec.RelURL("foo", true))
 
 	doc1en := enSite.RegularPages[0]
 	doc1fr := frSite.RegularPages[0]
@@ -96,64 +97,64 @@
 	require.Equal(t, replaceDefaultContentLanguageValue("http://example.com/blog/fr/sect/doc1/", defaultInSubDir), frPerm)
 	require.Equal(t, replaceDefaultContentLanguageValue("/blog/fr/sect/doc1/", defaultInSubDir), frRelPerm)
 
-	assertFileContent(t, "public/fr/sect/doc1/index.html", defaultInSubDir, "Single", "Bonjour")
-	assertFileContent(t, "public/en/sect/doc1-slug/index.html", defaultInSubDir, "Single", "Hello")
+	assertFileContent(t, fs, "public/fr/sect/doc1/index.html", defaultInSubDir, "Single", "Bonjour")
+	assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", defaultInSubDir, "Single", "Hello")
 
 	// Check home
 	if defaultInSubDir {
 		// should have a redirect on top level.
-		assertFileContent(t, "public/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog/fr" />`)
+		assertFileContent(t, fs, "public/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog/fr" />`)
 	} else {
 		// should have redirect back to root
-		assertFileContent(t, "public/fr/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog" />`)
+		assertFileContent(t, fs, "public/fr/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog" />`)
 	}
-	assertFileContent(t, "public/fr/index.html", defaultInSubDir, "Home", "Bonjour")
-	assertFileContent(t, "public/en/index.html", defaultInSubDir, "Home", "Hello")
+	assertFileContent(t, fs, "public/fr/index.html", defaultInSubDir, "Home", "Bonjour")
+	assertFileContent(t, fs, "public/en/index.html", defaultInSubDir, "Home", "Hello")
 
 	// Check list pages
-	assertFileContent(t, "public/fr/sect/index.html", defaultInSubDir, "List", "Bonjour")
-	assertFileContent(t, "public/en/sect/index.html", defaultInSubDir, "List", "Hello")
-	assertFileContent(t, "public/fr/plaques/frtag1/index.html", defaultInSubDir, "List", "Bonjour")
-	assertFileContent(t, "public/en/tags/tag1/index.html", defaultInSubDir, "List", "Hello")
+	assertFileContent(t, fs, "public/fr/sect/index.html", defaultInSubDir, "List", "Bonjour")
+	assertFileContent(t, fs, "public/en/sect/index.html", defaultInSubDir, "List", "Hello")
+	assertFileContent(t, fs, "public/fr/plaques/frtag1/index.html", defaultInSubDir, "List", "Bonjour")
+	assertFileContent(t, fs, "public/en/tags/tag1/index.html", defaultInSubDir, "List", "Hello")
 
 	// Check sitemaps
 	// Sitemaps behaves different: In a multilanguage setup there will always be a index file and
 	// one sitemap in each lang folder.
-	assertFileContent(t, "public/sitemap.xml", true,
+	assertFileContent(t, fs, "public/sitemap.xml", true,
 		"<loc>http://example.com/blog/en/sitemap.xml</loc>",
 		"<loc>http://example.com/blog/fr/sitemap.xml</loc>")
 
 	if defaultInSubDir {
-		assertFileContent(t, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/fr/</loc>")
+		assertFileContent(t, fs, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/fr/</loc>")
 	} else {
-		assertFileContent(t, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/</loc>")
+		assertFileContent(t, fs, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/</loc>")
 	}
-	assertFileContent(t, "public/en/sitemap.xml", true, "<loc>http://example.com/blog/en/</loc>")
+	assertFileContent(t, fs, "public/en/sitemap.xml", true, "<loc>http://example.com/blog/en/</loc>")
 
 	// Check rss
-	assertFileContent(t, "public/fr/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/index.xml"`)
-	assertFileContent(t, "public/en/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/index.xml"`)
-	assertFileContent(t, "public/fr/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/sect/index.xml"`)
-	assertFileContent(t, "public/en/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/sect/index.xml"`)
-	assertFileContent(t, "public/fr/plaques/frtag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/plaques/frtag1/index.xml"`)
-	assertFileContent(t, "public/en/tags/tag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/tags/tag1/index.xml"`)
+	assertFileContent(t, fs, "public/fr/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/index.xml"`)
+	assertFileContent(t, fs, "public/en/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/index.xml"`)
+	assertFileContent(t, fs, "public/fr/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/sect/index.xml"`)
+	assertFileContent(t, fs, "public/en/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/sect/index.xml"`)
+	assertFileContent(t, fs, "public/fr/plaques/frtag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/plaques/frtag1/index.xml"`)
+	assertFileContent(t, fs, "public/en/tags/tag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/tags/tag1/index.xml"`)
 
 	// Check paginators
-	assertFileContent(t, "public/fr/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/"`)
-	assertFileContent(t, "public/en/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/"`)
-	assertFileContent(t, "public/fr/page/2/index.html", defaultInSubDir, "Home Page 2", "Bonjour", "http://example.com/blog/fr/")
-	assertFileContent(t, "public/en/page/2/index.html", defaultInSubDir, "Home Page 2", "Hello", "http://example.com/blog/en/")
-	assertFileContent(t, "public/fr/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/sect/"`)
-	assertFileContent(t, "public/en/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/sect/"`)
-	assertFileContent(t, "public/fr/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/sect/")
-	assertFileContent(t, "public/en/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/sect/")
-	assertFileContent(t, "public/fr/plaques/frtag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/plaques/frtag1/"`)
-	assertFileContent(t, "public/en/tags/tag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/tags/tag1/"`)
-	assertFileContent(t, "public/fr/plaques/frtag1/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/plaques/frtag1/")
-	assertFileContent(t, "public/en/tags/tag1/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/tags/tag1/")
+	assertFileContent(t, fs, "public/fr/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/"`)
+	assertFileContent(t, fs, "public/en/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/"`)
+	assertFileContent(t, fs, "public/fr/page/2/index.html", defaultInSubDir, "Home Page 2", "Bonjour", "http://example.com/blog/fr/")
+	assertFileContent(t, fs, "public/en/page/2/index.html", defaultInSubDir, "Home Page 2", "Hello", "http://example.com/blog/en/")
+	assertFileContent(t, fs, "public/fr/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/sect/"`)
+	assertFileContent(t, fs, "public/en/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/sect/"`)
+	assertFileContent(t, fs, "public/fr/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/sect/")
+	assertFileContent(t, fs, "public/en/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/sect/")
+	assertFileContent(t, fs, "public/fr/plaques/frtag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/plaques/frtag1/"`)
+	assertFileContent(t, fs, "public/en/tags/tag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/tags/tag1/"`)
+	assertFileContent(t, fs, "public/fr/plaques/frtag1/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/plaques/frtag1/")
+	assertFileContent(t, fs, "public/en/tags/tag1/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/tags/tag1/")
 	// nn (Nynorsk) and nb (Bokmål) have custom pagePath: side ("page" in Norwegian)
-	assertFileContent(t, "public/nn/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nn/"`)
-	assertFileContent(t, "public/nb/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nb/"`)
+	assertFileContent(t, fs, "public/nn/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nn/"`)
+	assertFileContent(t, fs, "public/nb/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nb/"`)
 }
 
 func replaceDefaultContentLanguageValue(value string, defaultInSubDir bool) string {
@@ -166,9 +167,9 @@
 
 }
 
-func assertFileContent(t *testing.T, filename string, defaultInSubDir bool, matches ...string) {
+func assertFileContent(t *testing.T, fs *hugofs.Fs, filename string, defaultInSubDir bool, matches ...string) {
 	filename = replaceDefaultContentLanguageValue(filename, defaultInSubDir)
-	content := readDestination(t, filename)
+	content := readDestination(t, fs, filename)
 	for _, match := range matches {
 		match = replaceDefaultContentLanguageValue(match, defaultInSubDir)
 		require.True(t, strings.Contains(content, match), fmt.Sprintf("File no match for\n%q in\n%q:\n%s", strings.Replace(match, "%", "%%", -1), filename, strings.Replace(content, "%", "%%", -1)))
@@ -175,9 +176,9 @@
 	}
 }
 
-func assertFileContentRegexp(t *testing.T, filename string, defaultInSubDir bool, matches ...string) {
+func assertFileContentRegexp(t *testing.T, fs *hugofs.Fs, filename string, defaultInSubDir bool, matches ...string) {
 	filename = replaceDefaultContentLanguageValue(filename, defaultInSubDir)
-	content := readDestination(t, filename)
+	content := readDestination(t, fs, filename)
 	for _, match := range matches {
 		match = replaceDefaultContentLanguageValue(match, defaultInSubDir)
 		r := regexp.MustCompile(match)
@@ -190,7 +191,12 @@
 
 	viper.Set("defaultContentLanguage", "nn")
 
-	writeSource(t, "config.toml", `
+	fs := hugofs.NewMem()
+
+	depsCfg := deps.DepsCfg{Fs: fs}
+	viper.SetFs(depsCfg.Fs.Source)
+
+	writeSource(t, depsCfg.Fs, "config.toml", `
 [languages]
 [languages.nn]
 languageName = "Nynorsk"
@@ -208,15 +214,17 @@
 		t.Fatalf("Failed to load config: %s", err)
 	}
 
-	// Add some data
-	writeSource(t, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
+	sites, err := NewHugoSitesFromConfiguration(depsCfg)
 
-	sites, err := NewHugoSitesFromConfiguration(DepsCfg{})
-
 	if err != nil {
 		t.Fatalf("Failed to create sites: %s", err)
 	}
 
+	writeSource(t, fs, filepath.Join("content", "foo.md"), "foo")
+
+	// Add some data
+	writeSource(t, fs, filepath.Join("data", "hugo.toml"), "slogan = \"Hugo Rocks!\"")
+
 	require.NoError(t, sites.Build(BuildCfg{}))
 	require.Len(t, sites.Sites, 2)
 
@@ -245,7 +253,8 @@
 func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
 	defer leaktest.Check(t)()
 	testCommonResetState()
-	siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}
+	fs := hugofs.NewMem()
+	siteConfig := testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}
 	sites := createMultiTestSitesForConfig(t, siteConfig, configTemplate, configSuffix)
 
 	err := sites.Build(BuildCfg{})
@@ -286,7 +295,7 @@
 	assert.Equal(t, "http://example.com/blog/superbob", permalink, "invalid doc3 permalink")
 
 	assert.Equal(t, "/superbob", doc3.URL(), "invalid url, was specified on doc3")
-	assertFileContent(t, "public/superbob/index.html", true, "doc3|Hello|en")
+	assertFileContent(t, fs, "public/superbob/index.html", true, "doc3|Hello|en")
 	assert.Equal(t, doc2.Next, doc3, "doc3 should follow doc2, in .Next")
 
 	doc1fr := doc1en.Translations()[0]
@@ -326,16 +335,16 @@
 	}
 
 	// Check redirect to main language, French
-	languageRedirect := readDestination(t, "public/index.html")
+	languageRedirect := readDestination(t, fs, "public/index.html")
 	require.True(t, strings.Contains(languageRedirect, "0; url=http://example.com/blog/fr"), languageRedirect)
 
 	// check home page content (including data files rendering)
-	assertFileContent(t, "public/en/index.html", true, "Home Page 1", "Hello", "Hugo Rocks!")
-	assertFileContent(t, "public/fr/index.html", true, "Home Page 1", "Bonjour", "Hugo Rocks!")
+	assertFileContent(t, fs, "public/en/index.html", true, "Home Page 1", "Hello", "Hugo Rocks!")
+	assertFileContent(t, fs, "public/fr/index.html", true, "Home Page 1", "Bonjour", "Hugo Rocks!")
 
 	// check single page content
-	assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
-	assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
+	assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
+	assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
 
 	// Check node translations
 	homeEn := enSite.getPage(KindHome)
@@ -369,11 +378,11 @@
 	require.Equal(t, "nb", taxTermNn.Translations()[0].Lang())
 
 	// Check sitemap(s)
-	sitemapIndex := readDestination(t, "public/sitemap.xml")
+	sitemapIndex := readDestination(t, fs, "public/sitemap.xml")
 	require.True(t, strings.Contains(sitemapIndex, "<loc>http://example.com/blog/en/sitemap.xml</loc>"), sitemapIndex)
 	require.True(t, strings.Contains(sitemapIndex, "<loc>http://example.com/blog/fr/sitemap.xml</loc>"), sitemapIndex)
-	sitemapEn := readDestination(t, "public/en/sitemap.xml")
-	sitemapFr := readDestination(t, "public/fr/sitemap.xml")
+	sitemapEn := readDestination(t, fs, "public/en/sitemap.xml")
+	sitemapFr := readDestination(t, fs, "public/fr/sitemap.xml")
 	require.True(t, strings.Contains(sitemapEn, "http://example.com/blog/en/sect/doc2/"), sitemapEn)
 	require.True(t, strings.Contains(sitemapFr, "http://example.com/blog/fr/sect/doc1/"), sitemapFr)
 
@@ -384,8 +393,8 @@
 	require.Len(t, frTags, 2, fmt.Sprintf("Tags in fr: %v", frTags))
 	require.NotNil(t, enTags["tag1"])
 	require.NotNil(t, frTags["frtag1"])
-	readDestination(t, "public/fr/plaques/frtag1/index.html")
-	readDestination(t, "public/en/tags/tag1/index.html")
+	readDestination(t, fs, "public/fr/plaques/frtag1/index.html")
+	readDestination(t, fs, "public/en/tags/tag1/index.html")
 
 	// Check Blackfriday config
 	assert.True(t, strings.Contains(string(doc1fr.Content), "&laquo;"), string(doc1fr.Content))
@@ -409,7 +418,8 @@
 
 	defer leaktest.Check(t)()
 	testCommonResetState()
-	siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}
+	fs := hugofs.NewMem()
+	siteConfig := testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}
 	sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
 	cfg := BuildCfg{Watching: true}
 
@@ -419,7 +429,7 @@
 		t.Fatalf("Failed to build sites: %s", err)
 	}
 
-	_, err = hugofs.Destination().Open("public/en/sect/doc2/index.html")
+	_, err = fs.Destination.Open("public/en/sect/doc2/index.html")
 
 	if err != nil {
 		t.Fatalf("Unable to locate file")
@@ -432,12 +442,12 @@
 	require.Len(t, frSite.RegularPages, 3)
 
 	// Verify translations
-	assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "Hello")
-	assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Bonjour")
+	assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Hello")
+	assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Bonjour")
 
 	// check single page content
-	assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
-	assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
+	assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
+	assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
 
 	for i, this := range []struct {
 		preFunc    func(t *testing.T)
@@ -468,9 +478,9 @@
 		},
 		{
 			func(t *testing.T) {
-				writeNewContentFile(t, "new_en_1", "2016-07-31", "content/new1.en.md", -5)
-				writeNewContentFile(t, "new_en_2", "1989-07-30", "content/new2.en.md", -10)
-				writeNewContentFile(t, "new_fr_1", "2016-07-30", "content/new1.fr.md", 10)
+				writeNewContentFile(t, fs, "new_en_1", "2016-07-31", "content/new1.en.md", -5)
+				writeNewContentFile(t, fs, "new_en_2", "1989-07-30", "content/new2.en.md", -10)
+				writeNewContentFile(t, fs, "new_fr_1", "2016-07-30", "content/new1.fr.md", 10)
 			},
 			[]fsnotify.Event{
 				{Name: "content/new1.en.md", Op: fsnotify.Create},
@@ -485,7 +495,7 @@
 				require.Equal(t, "new_en_2", enSite.RegularPages[0].Title)
 				require.Equal(t, "new_en_1", enSite.RegularPages[1].Title)
 
-				rendered := readDestination(t, "public/en/new1/index.html")
+				rendered := readDestination(t, fs, "public/en/new1/index.html")
 				require.True(t, strings.Contains(rendered, "new_en_1"), rendered)
 			},
 		},
@@ -492,14 +502,14 @@
 		{
 			func(t *testing.T) {
 				p := "content/sect/doc1.en.md"
-				doc1 := readSource(t, p)
+				doc1 := readSource(t, fs, p)
 				doc1 += "CHANGED"
-				writeSource(t, p, doc1)
+				writeSource(t, fs, p, doc1)
 			},
 			[]fsnotify.Event{{Name: "content/sect/doc1.en.md", Op: fsnotify.Write}},
 			func(t *testing.T) {
 				require.Len(t, enSite.RegularPages, 5)
-				doc1 := readDestination(t, "public/en/sect/doc1-slug/index.html")
+				doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html")
 				require.True(t, strings.Contains(doc1, "CHANGED"), doc1)
 
 			},
@@ -507,7 +517,7 @@
 		// Rename a file
 		{
 			func(t *testing.T) {
-				if err := hugofs.Source().Rename("content/new1.en.md", "content/new1renamed.en.md"); err != nil {
+				if err := fs.Source.Rename("content/new1.en.md", "content/new1renamed.en.md"); err != nil {
 					t.Fatalf("Rename failed: %s", err)
 				}
 			},
@@ -518,7 +528,7 @@
 			func(t *testing.T) {
 				require.Len(t, enSite.RegularPages, 5, "Rename")
 				require.Equal(t, "new_en_1", enSite.RegularPages[1].Title)
-				rendered := readDestination(t, "public/en/new1renamed/index.html")
+				rendered := readDestination(t, fs, "public/en/new1renamed/index.html")
 				require.True(t, strings.Contains(rendered, "new_en_1"), rendered)
 			}},
 		{
@@ -525,9 +535,9 @@
 			// Change a template
 			func(t *testing.T) {
 				template := "layouts/_default/single.html"
-				templateContent := readSource(t, template)
+				templateContent := readSource(t, fs, template)
 				templateContent += "{{ print \"Template Changed\"}}"
-				writeSource(t, template, templateContent)
+				writeSource(t, fs, template, templateContent)
 			},
 			[]fsnotify.Event{{Name: "layouts/_default/single.html", Op: fsnotify.Write}},
 			func(t *testing.T) {
@@ -534,7 +544,7 @@
 				require.Len(t, enSite.RegularPages, 5)
 				require.Len(t, enSite.AllPages, 30)
 				require.Len(t, frSite.RegularPages, 4)
-				doc1 := readDestination(t, "public/en/sect/doc1-slug/index.html")
+				doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html")
 				require.True(t, strings.Contains(doc1, "Template Changed"), doc1)
 			},
 		},
@@ -542,9 +552,9 @@
 			// Change a language file
 			func(t *testing.T) {
 				languageFile := "i18n/fr.yaml"
-				langContent := readSource(t, languageFile)
+				langContent := readSource(t, fs, languageFile)
 				langContent = strings.Replace(langContent, "Bonjour", "Salut", 1)
-				writeSource(t, languageFile, langContent)
+				writeSource(t, fs, languageFile, langContent)
 			},
 			[]fsnotify.Event{{Name: "i18n/fr.yaml", Op: fsnotify.Write}},
 			func(t *testing.T) {
@@ -551,9 +561,9 @@
 				require.Len(t, enSite.RegularPages, 5)
 				require.Len(t, enSite.AllPages, 30)
 				require.Len(t, frSite.RegularPages, 4)
-				docEn := readDestination(t, "public/en/sect/doc1-slug/index.html")
+				docEn := readDestination(t, fs, "public/en/sect/doc1-slug/index.html")
 				require.True(t, strings.Contains(docEn, "Hello"), "No Hello")
-				docFr := readDestination(t, "public/fr/sect/doc1/index.html")
+				docFr := readDestination(t, fs, "public/fr/sect/doc1/index.html")
 				require.True(t, strings.Contains(docFr, "Salut"), "No Salut")
 
 				homeEn := enSite.getPage(KindHome)
@@ -566,7 +576,7 @@
 		// Change a shortcode
 		{
 			func(t *testing.T) {
-				writeSource(t, "layouts/shortcodes/shortcode.html", "Modified Shortcode: {{ i18n \"hello\" }}")
+				writeSource(t, fs, "layouts/shortcodes/shortcode.html", "Modified Shortcode: {{ i18n \"hello\" }}")
 			},
 			[]fsnotify.Event{
 				{Name: "layouts/shortcodes/shortcode.html", Op: fsnotify.Write},
@@ -575,8 +585,8 @@
 				require.Len(t, enSite.RegularPages, 5)
 				require.Len(t, enSite.AllPages, 30)
 				require.Len(t, frSite.RegularPages, 4)
-				assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Modified Shortcode: Salut")
-				assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "Single", "Modified Shortcode: Hello")
+				assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Modified Shortcode: Salut")
+				assertFileContent(t, fs, "public/en/sect/doc1-slug/index.html", true, "Single", "Modified Shortcode: Hello")
 			},
 		},
 	} {
@@ -615,13 +625,14 @@
 			filename = strings.Replace(filename, ".html", "/index.html", 1)
 		}
 
-		require.Equal(t, p.shouldBuild(), destinationExists(filename), filename)
+		require.Equal(t, p.shouldBuild(), destinationExists(sites.Fs, filename), filename)
 	}
 }
 
 func TestAddNewLanguage(t *testing.T) {
 	testCommonResetState()
-	siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}
+	fs := hugofs.NewMem()
+	siteConfig := testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}
 
 	sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
 	cfg := BuildCfg{}
@@ -641,9 +652,9 @@
 
 	newConfig = createConfig(t, siteConfig, newConfig)
 
-	writeNewContentFile(t, "Swedish Contentfile", "2016-01-01", "content/sect/doc1.sv.md", 10)
+	writeNewContentFile(t, fs, "Swedish Contentfile", "2016-01-01", "content/sect/doc1.sv.md", 10)
 	// replace the config
-	writeSource(t, "multilangconfig.toml", newConfig)
+	writeSource(t, fs, "multilangconfig.toml", newConfig)
 
 	// Watching does not work with in-memory fs, so we trigger a reload manually
 	require.NoError(t, viper.ReadInConfig())
@@ -685,8 +696,8 @@
 func TestChangeDefaultLanguage(t *testing.T) {
 	testCommonResetState()
 	viper.Set("defaultContentLanguageInSubdir", false)
-
-	sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "fr"}, multiSiteTOMLConfigTemplate)
+	fs := hugofs.NewMem()
+	sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "fr", Fs: fs}, multiSiteTOMLConfigTemplate)
 	cfg := BuildCfg{}
 
 	err := sites.Build(cfg)
@@ -695,13 +706,13 @@
 		t.Fatalf("Failed to build sites: %s", err)
 	}
 
-	assertFileContent(t, "public/sect/doc1/index.html", true, "Single", "Bonjour")
-	assertFileContent(t, "public/en/sect/doc2/index.html", true, "Single", "Hello")
+	assertFileContent(t, fs, "public/sect/doc1/index.html", true, "Single", "Bonjour")
+	assertFileContent(t, fs, "public/en/sect/doc2/index.html", true, "Single", "Hello")
 
 	newConfig := createConfig(t, testSiteConfig{DefaultContentLanguage: "en"}, multiSiteTOMLConfigTemplate)
 
 	// replace the config
-	writeSource(t, "multilangconfig.toml", newConfig)
+	writeSource(t, fs, "multilangconfig.toml", newConfig)
 
 	// Watching does not work with in-memory fs, so we trigger a reload manually
 	require.NoError(t, viper.ReadInConfig())
@@ -712,18 +723,19 @@
 	}
 
 	// Default language is now en, so that should now be the "root" language
-	assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Bonjour")
-	assertFileContent(t, "public/sect/doc2/index.html", true, "Single", "Hello")
+	assertFileContent(t, fs, "public/fr/sect/doc1/index.html", true, "Single", "Bonjour")
+	assertFileContent(t, fs, "public/sect/doc2/index.html", true, "Single", "Hello")
 }
 
 func TestTableOfContentsInShortcodes(t *testing.T) {
 	testCommonResetState()
+	fs := hugofs.NewMem()
 
-	sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "en"}, multiSiteTOMLConfigTemplate)
+	writeSource(t, fs, "layouts/shortcodes/toc.html", tocShortcode)
+	writeSource(t, fs, "content/post/simple.en.md", tocPageSimple)
+	writeSource(t, fs, "content/post/withSCInHeading.en.md", tocPageWithShortcodesInHeadings)
 
-	writeSource(t, "layouts/shortcodes/toc.html", tocShortcode)
-	writeSource(t, "content/post/simple.en.md", tocPageSimple)
-	writeSource(t, "content/post/withSCInHeading.en.md", tocPageWithShortcodesInHeadings)
+	sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "en", Fs: fs}, multiSiteTOMLConfigTemplate)
 
 	cfg := BuildCfg{}
 
@@ -733,8 +745,8 @@
 		t.Fatalf("Failed to build sites: %s", err)
 	}
 
-	assertFileContent(t, "public/en/post/simple/index.html", true, tocPageSimpleExpected)
-	assertFileContent(t, "public/en/post/withSCInHeading/index.html", true, tocPageWithShortcodesInHeadingsExpected)
+	assertFileContent(t, fs, "public/en/post/simple/index.html", true, tocPageSimpleExpected)
+	assertFileContent(t, fs, "public/en/post/withSCInHeading/index.html", true, tocPageWithShortcodesInHeadingsExpected)
 }
 
 var tocShortcode = `
@@ -1014,10 +1026,11 @@
 
 func createMultiTestSitesForConfig(t *testing.T, siteConfig testSiteConfig, configTemplate, configSuffix string) *HugoSites {
 
+	depsCfg := deps.DepsCfg{Fs: siteConfig.Fs}
 	configContent := createConfig(t, siteConfig, configTemplate)
 
 	// Add some layouts
-	if err := afero.WriteFile(hugofs.Source(),
+	if err := afero.WriteFile(depsCfg.Fs.Source,
 		filepath.Join("layouts", "_default/single.html"),
 		[]byte("Single: {{ .Title }}|{{ i18n \"hello\" }}|{{.Lang}}|{{ .Content }}"),
 		0755); err != nil {
@@ -1024,7 +1037,7 @@
 		t.Fatalf("Failed to write layout file: %s", err)
 	}
 
-	if err := afero.WriteFile(hugofs.Source(),
+	if err := afero.WriteFile(depsCfg.Fs.Source,
 		filepath.Join("layouts", "_default/list.html"),
 		[]byte("{{ $p := .Paginator }}List Page {{ $p.PageNumber }}: {{ .Title }}|{{ i18n \"hello\" }}|{{ .Permalink }}"),
 		0755); err != nil {
@@ -1031,7 +1044,7 @@
 		t.Fatalf("Failed to write layout file: %s", err)
 	}
 
-	if err := afero.WriteFile(hugofs.Source(),
+	if err := afero.WriteFile(depsCfg.Fs.Source,
 		filepath.Join("layouts", "index.html"),
 		[]byte("{{ $p := .Paginator }}Home Page {{ $p.PageNumber }}: {{ .Title }}|{{ .IsHome }}|{{ i18n \"hello\" }}|{{ .Permalink }}|{{  .Site.Data.hugo.slogan }}"),
 		0755); err != nil {
@@ -1039,7 +1052,7 @@
 	}
 
 	// Add a shortcode
-	if err := afero.WriteFile(hugofs.Source(),
+	if err := afero.WriteFile(depsCfg.Fs.Source,
 		filepath.Join("layouts", "shortcodes", "shortcode.html"),
 		[]byte("Shortcode: {{ i18n \"hello\" }}"),
 		0755); err != nil {
@@ -1047,7 +1060,7 @@
 	}
 
 	// Add some language files
-	if err := afero.WriteFile(hugofs.Source(),
+	if err := afero.WriteFile(depsCfg.Fs.Source,
 		filepath.Join("i18n", "en.yaml"),
 		[]byte(`
 - id: hello
@@ -1056,7 +1069,7 @@
 		0755); err != nil {
 		t.Fatalf("Failed to write language file: %s", err)
 	}
-	if err := afero.WriteFile(hugofs.Source(),
+	if err := afero.WriteFile(depsCfg.Fs.Source,
 		filepath.Join("i18n", "fr.yaml"),
 		[]byte(`
 - id: hello
@@ -1210,7 +1223,10 @@
 	}
 
 	configFile := "multilangconfig." + configSuffix
-	writeSource(t, configFile, configContent)
+	writeSource(t, depsCfg.Fs, configFile, configContent)
+
+	viper.SetFs(depsCfg.Fs.Source)
+
 	if err := LoadGlobalConfig("", configFile); err != nil {
 		t.Fatalf("Failed to load config: %s", err)
 	}
@@ -1218,15 +1234,15 @@
 	// Hugo support using ByteSource's directly (for testing),
 	// but to make it more real, we write them to the mem file system.
 	for _, s := range sources {
-		if err := afero.WriteFile(hugofs.Source(), filepath.Join("content", s.Name), s.Content, 0755); err != nil {
+		if err := afero.WriteFile(depsCfg.Fs.Source, filepath.Join("content", s.Name), s.Content, 0755); err != nil {
 			t.Fatalf("Failed to write file: %s", err)
 		}
 	}
 
 	// Add some data
-	writeSource(t, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
+	writeSource(t, depsCfg.Fs, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
 
-	sites, err := NewHugoSitesFromConfiguration(DepsCfg{})
+	sites, err := NewHugoSitesFromConfiguration(depsCfg)
 
 	if err != nil {
 		t.Fatalf("Failed to create sites: %s", err)
@@ -1239,18 +1255,18 @@
 	return sites
 }
 
-func writeSource(t *testing.T, filename, content string) {
-	if err := afero.WriteFile(hugofs.Source(), filepath.FromSlash(filename), []byte(content), 0755); err != nil {
+func writeSource(t *testing.T, fs *hugofs.Fs, filename, content string) {
+	if err := afero.WriteFile(fs.Source, filepath.FromSlash(filename), []byte(content), 0755); err != nil {
 		t.Fatalf("Failed to write file: %s", err)
 	}
 }
 
-func readDestination(t *testing.T, filename string) string {
-	return readFileFromFs(t, hugofs.Destination(), filename)
+func readDestination(t *testing.T, fs *hugofs.Fs, filename string) string {
+	return readFileFromFs(t, fs.Destination, filename)
 }
 
-func destinationExists(filename string) bool {
-	b, err := helpers.Exists(filename, hugofs.Destination())
+func destinationExists(fs *hugofs.Fs, filename string) bool {
+	b, err := helpers.Exists(filename, fs.Destination)
 	if err != nil {
 		panic(err)
 	}
@@ -1257,8 +1273,8 @@
 	return b
 }
 
-func readSource(t *testing.T, filename string) string {
-	return readFileFromFs(t, hugofs.Source(), filename)
+func readSource(t *testing.T, fs *hugofs.Fs, filename string) string {
+	return readFileFromFs(t, fs.Source, filename)
 }
 
 func readFileFromFs(t *testing.T, fs afero.Fs, filename string) string {
@@ -1291,9 +1307,9 @@
 	return fmt.Sprintf(testPageTemplate, title, date, weight, title)
 }
 
-func writeNewContentFile(t *testing.T, title, date, filename string, weight int) {
+func writeNewContentFile(t *testing.T, fs *hugofs.Fs, title, date, filename string, weight int) {
 	content := newTestPage(title, date, weight)
-	writeSource(t, filename, content)
+	writeSource(t, fs, filename, content)
 }
 
 func createConfig(t *testing.T, config testSiteConfig, configTemplate string) string {
--- a/hugolib/i18n.go
+++ b/hugolib/i18n.go
@@ -19,11 +19,10 @@
 	"github.com/nicksnyder/go-i18n/i18n/bundle"
 	"github.com/spf13/hugo/source"
 	"github.com/spf13/hugo/tpl"
-	jww "github.com/spf13/jwalterweatherman"
 )
 
-func loadI18n(sources []source.Input) error {
-	jww.DEBUG.Printf("Load I18n from %q", sources)
+func (s *Site) loadI18n(sources []source.Input) error {
+	s.Log.DEBUG.Printf("Load I18n from %q", sources)
 
 	i18nBundle := bundle.New()
 
--- a/hugolib/menu_test.go
+++ b/hugolib/menu_test.go
@@ -18,12 +18,14 @@
 	"strings"
 	"testing"
 
+	"github.com/spf13/hugo/deps"
+
 	"github.com/spf13/hugo/helpers"
+	"github.com/spf13/hugo/hugofs"
 
 	"path/filepath"
 
 	toml "github.com/pelletier/go-toml"
-	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/hugo/source"
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/assert"
@@ -677,29 +679,27 @@
 }
 
 func setupMenuTests(t *testing.T, pageSources []source.ByteSource) *Site {
-	s := createTestSite(pageSources)
 
 	setupTestMenuState(t)
-	testSiteSetup(s, t)
 
-	return s
+	fs := hugofs.NewMem()
+
+	for _, src := range pageSources {
+		writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
+
+	}
+
+	return buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+
 }
 
 func createTestSite(pageSources []source.ByteSource) *Site {
-	hugofs.InitMemFs()
 
 	return &Site{
-		deps:     newDeps(DepsCfg{}),
 		Source:   &source.InMemorySource{ByteSource: pageSources},
 		Language: helpers.NewDefaultLanguage(),
 	}
 
-}
-
-func testSiteSetup(s *Site, t *testing.T) {
-	if err := buildSiteSkipRender(s); err != nil {
-		t.Fatalf("Sites build failed: %s", err)
-	}
 }
 
 func tomlToMap(s string) (map[string]interface{}, error) {
--- a/hugolib/node_as_page_test.go
+++ b/hugolib/node_as_page_test.go
@@ -18,8 +18,11 @@
 	"path/filepath"
 	"strings"
 	"testing"
+
 	"time"
 
+	"github.com/spf13/hugo/deps"
+	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/require"
 )
@@ -56,24 +59,28 @@
 	viper.Set("uglyURLs", ugly)
 	viper.Set("preserveTaxonomyNames", preserveTaxonomyNames)
 
-	writeLayoutsForNodeAsPageTests(t)
-	writeNodePagesForNodeAsPageTests("", t)
-
-	writeRegularPagesForNodeAsPageTests(t)
-
 	viper.Set("paginate", 1)
 	viper.Set("title", "Hugo Rocks")
 	viper.Set("rssURI", "customrss.xml")
 
-	s := NewSiteDefaultLang()
+	depsCfg := newTestDepsConfig()
 
-	if err := buildAndRenderSite(s); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	viper.SetFs(depsCfg.Fs.Source)
 
+	writeLayoutsForNodeAsPageTests(t, depsCfg.Fs)
+	writeNodePagesForNodeAsPageTests(t, depsCfg.Fs, "")
+
+	writeRegularPagesForNodeAsPageTests(t, depsCfg.Fs)
+
+	sites, err := NewHugoSitesFromConfiguration(depsCfg)
+
+	require.NoError(t, err)
+
+	require.NoError(t, sites.Build(BuildCfg{}))
+
 	// date order: home, sect1, sect2, cat/hugo, cat/web, categories
 
-	assertFileContent(t, filepath.Join("public", "index.html"), false,
+	assertFileContent(t, depsCfg.Fs, filepath.Join("public", "index.html"), false,
 		"Index Title: Home Sweet Home!",
 		"Home <strong>Content!</strong>",
 		"# Pages: 4",
@@ -82,10 +89,9 @@
 		"GetPage: Section1 ",
 	)
 
-	assertFileContent(t, expectedFilePath(ugly, "public", "sect1", "regular1"), false, "Single Title: Page 01", "Content Page 01")
+	assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "sect1", "regular1"), false, "Single Title: Page 01", "Content Page 01")
 
-	h := s.owner
-	nodes := h.findAllPagesByKindNotIn(KindPage)
+	nodes := sites.findAllPagesByKindNotIn(KindPage)
 
 	require.Len(t, nodes, 7)
 
@@ -99,7 +105,7 @@
 	section2 := nodes[4]
 	require.Equal(t, "Section2", section2.Title)
 
-	pages := h.findAllPagesByKind(KindPage)
+	pages := sites.findAllPagesByKind(KindPage)
 	require.Len(t, pages, 4)
 
 	first := pages[0]
@@ -109,17 +115,17 @@
 	require.True(t, first.IsPage())
 
 	// Check Home paginator
-	assertFileContent(t, expectedFilePath(ugly, "public", "page", "2"), false,
+	assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "page", "2"), false,
 		"Pag: Page 02")
 
 	// Check Sections
-	assertFileContent(t, expectedFilePath(ugly, "public", "sect1"), false,
+	assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "sect1"), false,
 		"Section Title: Section", "Section1 <strong>Content!</strong>",
 		"Date: 2009-01-04",
 		"Lastmod: 2009-01-05",
 	)
 
-	assertFileContent(t, expectedFilePath(ugly, "public", "sect2"), false,
+	assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "sect2"), false,
 		"Section Title: Section", "Section2 <strong>Content!</strong>",
 		"Date: 2009-01-06",
 		"Lastmod: 2009-01-07",
@@ -126,29 +132,31 @@
 	)
 
 	// Check Sections paginator
-	assertFileContent(t, expectedFilePath(ugly, "public", "sect1", "page", "2"), false,
+	assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "sect1", "page", "2"), false,
 		"Pag: Page 02")
 
-	sections := h.findAllPagesByKind(KindSection)
+	sections := sites.findAllPagesByKind(KindSection)
 
 	require.Len(t, sections, 2)
 
 	// Check taxonomy lists
-	assertFileContent(t, expectedFilePath(ugly, "public", "categories", "hugo"), false,
+	assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "categories", "hugo"), false,
 		"Taxonomy Title: Taxonomy Hugo", "Taxonomy Hugo <strong>Content!</strong>",
 		"Date: 2009-01-08",
 		"Lastmod: 2009-01-09",
 	)
 
-	assertFileContent(t, expectedFilePath(ugly, "public", "categories", "hugo-rocks"), false,
+	assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "categories", "hugo-rocks"), false,
 		"Taxonomy Title: Taxonomy Hugo Rocks",
 	)
 
+	s := sites.Sites[0]
+
 	web := s.getPage(KindTaxonomy, "categories", "web")
 	require.NotNil(t, web)
 	require.Len(t, web.Data["Pages"].(Pages), 4)
 
-	assertFileContent(t, expectedFilePath(ugly, "public", "categories", "web"), false,
+	assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "categories", "web"), false,
 		"Taxonomy Title: Taxonomy Web",
 		"Taxonomy Web <strong>Content!</strong>",
 		"Date: 2009-01-10",
@@ -156,12 +164,12 @@
 	)
 
 	// Check taxonomy list paginator
-	assertFileContent(t, expectedFilePath(ugly, "public", "categories", "hugo", "page", "2"), false,
+	assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "categories", "hugo", "page", "2"), false,
 		"Taxonomy Title: Taxonomy Hugo",
 		"Pag: Page 02")
 
 	// Check taxonomy terms
-	assertFileContent(t, expectedFilePath(ugly, "public", "categories"), false,
+	assertFileContent(t, depsCfg.Fs, expectedFilePath(ugly, "public", "categories"), false,
 		"Taxonomy Terms Title: Taxonomy Term Categories", "Taxonomy Term Categories <strong>Content!</strong>", "k/v: hugo",
 		"Date: 2009-01-14",
 		"Lastmod: 2009-01-15",
@@ -170,11 +178,11 @@
 	// There are no pages to paginate over in the taxonomy terms.
 
 	// RSS
-	assertFileContent(t, filepath.Join("public", "customrss.xml"), false, "Recent content in Home Sweet Home! on Hugo Rocks", "<rss")
-	assertFileContent(t, filepath.Join("public", "sect1", "customrss.xml"), false, "Recent content in Section1 on Hugo Rocks", "<rss")
-	assertFileContent(t, filepath.Join("public", "sect2", "customrss.xml"), false, "Recent content in Section2 on Hugo Rocks", "<rss")
-	assertFileContent(t, filepath.Join("public", "categories", "hugo", "customrss.xml"), false, "Recent content in Taxonomy Hugo on Hugo Rocks", "<rss")
-	assertFileContent(t, filepath.Join("public", "categories", "web", "customrss.xml"), false, "Recent content in Taxonomy Web on Hugo Rocks", "<rss")
+	assertFileContent(t, depsCfg.Fs, filepath.Join("public", "customrss.xml"), false, "Recent content in Home Sweet Home! on Hugo Rocks", "<rss")
+	assertFileContent(t, depsCfg.Fs, filepath.Join("public", "sect1", "customrss.xml"), false, "Recent content in Section1 on Hugo Rocks", "<rss")
+	assertFileContent(t, depsCfg.Fs, filepath.Join("public", "sect2", "customrss.xml"), false, "Recent content in Section2 on Hugo Rocks", "<rss")
+	assertFileContent(t, depsCfg.Fs, filepath.Join("public", "categories", "hugo", "customrss.xml"), false, "Recent content in Taxonomy Hugo on Hugo Rocks", "<rss")
+	assertFileContent(t, depsCfg.Fs, filepath.Join("public", "categories", "web", "customrss.xml"), false, "Recent content in Taxonomy Web on Hugo Rocks", "<rss")
 
 }
 
@@ -187,20 +195,24 @@
 func doTestNodesWithNoContentFile(t *testing.T, ugly bool) {
 	testCommonResetState()
 
-	writeLayoutsForNodeAsPageTests(t)
-	writeRegularPagesForNodeAsPageTests(t)
-
 	viper.Set("uglyURLs", ugly)
 	viper.Set("paginate", 1)
 	viper.Set("title", "Hugo Rocks!")
 	viper.Set("rssURI", "customrss.xml")
 
-	s := NewSiteDefaultLang()
+	fs := hugofs.NewMem()
 
-	if err := buildAndRenderSite(s); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	writeLayoutsForNodeAsPageTests(t, fs)
+	writeRegularPagesForNodeAsPageTests(t, fs)
 
+	sites, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
+
+	require.NoError(t, err)
+
+	require.NoError(t, sites.Build(BuildCfg{}))
+
+	s := sites.Sites[0]
+
 	// Home page
 	homePages := s.findPagesByKind(KindHome)
 	require.Len(t, homePages, 1)
@@ -210,7 +222,7 @@
 	require.Len(t, homePage.Pages, 4)
 	require.True(t, homePage.Path() == "")
 
-	assertFileContent(t, filepath.Join("public", "index.html"), false,
+	assertFileContent(t, fs, filepath.Join("public", "index.html"), false,
 		"Index Title: Hugo Rocks!",
 		"Date: 2010-06-12",
 		"Lastmod: 2010-06-13",
@@ -217,7 +229,7 @@
 	)
 
 	// Taxonomy list
-	assertFileContent(t, expectedFilePath(ugly, "public", "categories", "hugo"), false,
+	assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories", "hugo"), false,
 		"Taxonomy Title: Hugo",
 		"Date: 2010-06-12",
 		"Lastmod: 2010-06-13",
@@ -224,7 +236,7 @@
 	)
 
 	// Taxonomy terms
-	assertFileContent(t, expectedFilePath(ugly, "public", "categories"), false,
+	assertFileContent(t, fs, expectedFilePath(ugly, "public", "categories"), false,
 		"Taxonomy Terms Title: Categories",
 	)
 
@@ -232,9 +244,9 @@
 	for _, p := range pages {
 		var want string
 		if ugly {
-			want = "/" + p.Site.pathSpec.URLize(p.Title) + ".html"
+			want = "/" + p.s.PathSpec.URLize(p.Title) + ".html"
 		} else {
-			want = "/" + p.Site.pathSpec.URLize(p.Title) + "/"
+			want = "/" + p.s.PathSpec.URLize(p.Title) + "/"
 		}
 		if p.URL() != want {
 			t.Errorf("Taxonomy term URL mismatch: want %q, got %q", want, p.URL())
@@ -242,13 +254,13 @@
 	}
 
 	// Sections
-	assertFileContent(t, expectedFilePath(ugly, "public", "sect1"), false,
+	assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect1"), false,
 		"Section Title: Sect1s",
 		"Date: 2010-06-12",
 		"Lastmod: 2010-06-13",
 	)
 
-	assertFileContent(t, expectedFilePath(ugly, "public", "sect2"), false,
+	assertFileContent(t, fs, expectedFilePath(ugly, "public", "sect2"), false,
 		"Section Title: Sect2s",
 		"Date: 2008-07-06",
 		"Lastmod: 2008-07-09",
@@ -255,16 +267,16 @@
 	)
 
 	// RSS
-	assertFileContent(t, filepath.Join("public", "customrss.xml"), false, "Hugo Rocks!", "<rss")
-	assertFileContent(t, filepath.Join("public", "sect1", "customrss.xml"), false, "Recent content in Sect1s on Hugo Rocks!", "<rss")
-	assertFileContent(t, filepath.Join("public", "sect2", "customrss.xml"), false, "Recent content in Sect2s on Hugo Rocks!", "<rss")
-	assertFileContent(t, filepath.Join("public", "categories", "hugo", "customrss.xml"), false, "Recent content in Hugo on Hugo Rocks!", "<rss")
-	assertFileContent(t, filepath.Join("public", "categories", "web", "customrss.xml"), false, "Recent content in Web on Hugo Rocks!", "<rss")
+	assertFileContent(t, fs, filepath.Join("public", "customrss.xml"), false, "Hugo Rocks!", "<rss")
+	assertFileContent(t, fs, filepath.Join("public", "sect1", "customrss.xml"), false, "Recent content in Sect1s on Hugo Rocks!", "<rss")
+	assertFileContent(t, fs, filepath.Join("public", "sect2", "customrss.xml"), false, "Recent content in Sect2s on Hugo Rocks!", "<rss")
+	assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "customrss.xml"), false, "Recent content in Hugo on Hugo Rocks!", "<rss")
+	assertFileContent(t, fs, filepath.Join("public", "categories", "web", "customrss.xml"), false, "Recent content in Web on Hugo Rocks!", "<rss")
 
 }
 
 func TestNodesAsPageMultilingual(t *testing.T) {
-	for _, ugly := range []bool{true, false} {
+	for _, ugly := range []bool{false, true} {
 		doTestNodesAsPageMultilingual(t, ugly)
 	}
 }
@@ -273,11 +285,13 @@
 
 	testCommonResetState()
 
+	fs := hugofs.NewMem()
+
 	viper.Set("uglyURLs", ugly)
 
-	writeLayoutsForNodeAsPageTests(t)
+	viper.SetFs(fs.Source)
 
-	writeSource(t, "config.toml",
+	writeSource(t, fs, "config.toml",
 		`
 paginage = 1
 title = "Hugo Multilingual Rocks!"
@@ -303,19 +317,17 @@
 title = "Deutsche Hugo"
 `)
 
+	writeLayoutsForNodeAsPageTests(t, fs)
+
 	for _, lang := range []string{"nn", "en"} {
-		writeRegularPagesForNodeAsPageTestsWithLang(t, lang)
+		writeRegularPagesForNodeAsPageTestsWithLang(t, fs, lang)
 	}
 
-	// Only write node pages for the English and Deutsch
-	writeNodePagesForNodeAsPageTests("en", t)
-	writeNodePagesForNodeAsPageTests("de", t)
-
 	if err := LoadGlobalConfig("", "config.toml"); err != nil {
 		t.Fatalf("Failed to load config: %s", err)
 	}
 
-	sites, err := NewHugoSitesFromConfiguration(DepsCfg{})
+	sites, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
 
 	if err != nil {
 		t.Fatalf("Failed to create sites: %s", err)
@@ -325,6 +337,10 @@
 		t.Fatalf("Got %d sites", len(sites.Sites))
 	}
 
+	// Only write node pages for the English and Deutsch
+	writeNodePagesForNodeAsPageTests(t, fs, "en")
+	writeNodePagesForNodeAsPageTests(t, fs, "de")
+
 	err = sites.Build(BuildCfg{})
 
 	if err != nil {
@@ -356,53 +372,53 @@
 
 	require.Equal(t, expetedPermalink(ugly, "/en/sect1/"), enSect.Permalink())
 
-	assertFileContent(t, filepath.Join("public", "nn", "index.html"), true,
+	assertFileContent(t, fs, filepath.Join("public", "nn", "index.html"), true,
 		"Index Title: Hugo på norsk")
-	assertFileContent(t, filepath.Join("public", "en", "index.html"), true,
+	assertFileContent(t, fs, filepath.Join("public", "en", "index.html"), true,
 		"Index Title: Home Sweet Home!", "<strong>Content!</strong>")
-	assertFileContent(t, filepath.Join("public", "de", "index.html"), true,
+	assertFileContent(t, fs, filepath.Join("public", "de", "index.html"), true,
 		"Index Title: Home Sweet Home!", "<strong>Content!</strong>")
 
 	// Taxonomy list
-	assertFileContent(t, expectedFilePath(ugly, "public", "nn", "categories", "hugo"), true,
+	assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "categories", "hugo"), true,
 		"Taxonomy Title: Hugo")
-	assertFileContent(t, expectedFilePath(ugly, "public", "en", "categories", "hugo"), true,
+	assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "categories", "hugo"), true,
 		"Taxonomy Title: Taxonomy Hugo")
 
 	// Taxonomy terms
-	assertFileContent(t, expectedFilePath(ugly, "public", "nn", "categories"), true,
+	assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "categories"), true,
 		"Taxonomy Terms Title: Categories")
-	assertFileContent(t, expectedFilePath(ugly, "public", "en", "categories"), true,
+	assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "categories"), true,
 		"Taxonomy Terms Title: Taxonomy Term Categories")
 
 	// Sections
-	assertFileContent(t, expectedFilePath(ugly, "public", "nn", "sect1"), true,
+	assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect1"), true,
 		"Section Title: Sect1s")
-	assertFileContent(t, expectedFilePath(ugly, "public", "nn", "sect2"), true,
+	assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect2"), true,
 		"Section Title: Sect2s")
-	assertFileContent(t, expectedFilePath(ugly, "public", "en", "sect1"), true,
+	assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect1"), true,
 		"Section Title: Section1")
-	assertFileContent(t, expectedFilePath(ugly, "public", "en", "sect2"), true,
+	assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect2"), true,
 		"Section Title: Section2")
 
 	// Regular pages
-	assertFileContent(t, expectedFilePath(ugly, "public", "en", "sect1", "regular1"), true,
+	assertFileContent(t, fs, expectedFilePath(ugly, "public", "en", "sect1", "regular1"), true,
 		"Single Title: Page 01")
-	assertFileContent(t, expectedFilePath(ugly, "public", "nn", "sect1", "regular2"), true,
+	assertFileContent(t, fs, expectedFilePath(ugly, "public", "nn", "sect1", "regular2"), true,
 		"Single Title: Page 02")
 
 	// RSS
-	assertFileContent(t, filepath.Join("public", "nn", "customrss.xml"), true, "Hugo på norsk", "<rss")
-	assertFileContent(t, filepath.Join("public", "nn", "sect1", "customrss.xml"), true, "Recent content in Sect1s on Hugo på norsk", "<rss")
-	assertFileContent(t, filepath.Join("public", "nn", "sect2", "customrss.xml"), true, "Recent content in Sect2s on Hugo på norsk", "<rss")
-	assertFileContent(t, filepath.Join("public", "nn", "categories", "hugo", "customrss.xml"), true, "Recent content in Hugo on Hugo på norsk", "<rss")
-	assertFileContent(t, filepath.Join("public", "nn", "categories", "web", "customrss.xml"), true, "Recent content in Web on Hugo på norsk", "<rss")
+	assertFileContent(t, fs, filepath.Join("public", "nn", "customrss.xml"), true, "Hugo på norsk", "<rss")
+	assertFileContent(t, fs, filepath.Join("public", "nn", "sect1", "customrss.xml"), true, "Recent content in Sect1s on Hugo på norsk", "<rss")
+	assertFileContent(t, fs, filepath.Join("public", "nn", "sect2", "customrss.xml"), true, "Recent content in Sect2s on Hugo på norsk", "<rss")
+	assertFileContent(t, fs, filepath.Join("public", "nn", "categories", "hugo", "customrss.xml"), true, "Recent content in Hugo on Hugo på norsk", "<rss")
+	assertFileContent(t, fs, filepath.Join("public", "nn", "categories", "web", "customrss.xml"), true, "Recent content in Web on Hugo på norsk", "<rss")
 
-	assertFileContent(t, filepath.Join("public", "en", "customrss.xml"), true, "Recent content in Home Sweet Home! on Hugo in English", "<rss")
-	assertFileContent(t, filepath.Join("public", "en", "sect1", "customrss.xml"), true, "Recent content in Section1 on Hugo in English", "<rss")
-	assertFileContent(t, filepath.Join("public", "en", "sect2", "customrss.xml"), true, "Recent content in Section2 on Hugo in English", "<rss")
-	assertFileContent(t, filepath.Join("public", "en", "categories", "hugo", "customrss.xml"), true, "Recent content in Taxonomy Hugo on Hugo in English", "<rss")
-	assertFileContent(t, filepath.Join("public", "en", "categories", "web", "customrss.xml"), true, "Recent content in Taxonomy Web on Hugo in English", "<rss")
+	assertFileContent(t, fs, filepath.Join("public", "en", "customrss.xml"), true, "Recent content in Home Sweet Home! on Hugo in English", "<rss")
+	assertFileContent(t, fs, filepath.Join("public", "en", "sect1", "customrss.xml"), true, "Recent content in Section1 on Hugo in English", "<rss")
+	assertFileContent(t, fs, filepath.Join("public", "en", "sect2", "customrss.xml"), true, "Recent content in Section2 on Hugo in English", "<rss")
+	assertFileContent(t, fs, filepath.Join("public", "en", "categories", "hugo", "customrss.xml"), true, "Recent content in Taxonomy Hugo on Hugo in English", "<rss")
+	assertFileContent(t, fs, filepath.Join("public", "en", "categories", "web", "customrss.xml"), true, "Recent content in Taxonomy Web on Hugo in English", "<rss")
 
 }
 
@@ -409,29 +425,31 @@
 func TestNodesWithTaxonomies(t *testing.T) {
 	testCommonResetState()
 
-	writeLayoutsForNodeAsPageTests(t)
-	writeRegularPagesForNodeAsPageTests(t)
+	fs := hugofs.NewMem()
 
-	writeSource(t, filepath.Join("content", "_index.md"), `---
+	viper.Set("paginate", 1)
+	viper.Set("title", "Hugo Rocks!")
+
+	writeLayoutsForNodeAsPageTests(t, fs)
+	writeRegularPagesForNodeAsPageTests(t, fs)
+
+	writeSource(t, fs, filepath.Join("content", "_index.md"), `---
 title: Home With Taxonomies
 categories:  [
-        "Hugo",
+        "Hugo",	
 		"Home"
 ]
 ---
 `)
 
-	viper.Set("paginate", 1)
-	viper.Set("title", "Hugo Rocks!")
+	h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
 
-	s := NewSiteDefaultLang()
+	require.NoError(t, err)
 
-	if err := buildAndRenderSite(s); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	require.NoError(t, h.Build(BuildCfg{}))
 
-	assertFileContent(t, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy Title: Hugo", "# Pages: 5")
-	assertFileContent(t, filepath.Join("public", "categories", "home", "index.html"), true, "Taxonomy Title: Home", "# Pages: 1")
+	assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy Title: Hugo", "# Pages: 5")
+	assertFileContent(t, fs, filepath.Join("public", "categories", "home", "index.html"), true, "Taxonomy Title: Home", "# Pages: 1")
 
 }
 
@@ -438,10 +456,15 @@
 func TestNodesWithMenu(t *testing.T) {
 	testCommonResetState()
 
-	writeLayoutsForNodeAsPageTests(t)
-	writeRegularPagesForNodeAsPageTests(t)
+	viper.Set("paginate", 1)
+	viper.Set("title", "Hugo Rocks!")
 
-	writeSource(t, filepath.Join("content", "_index.md"), `---
+	fs := hugofs.NewMem()
+
+	writeLayoutsForNodeAsPageTests(t, fs)
+	writeRegularPagesForNodeAsPageTests(t, fs)
+
+	writeSource(t, fs, filepath.Join("content", "_index.md"), `---
 title: Home With Menu
 menu:
   mymenu:
@@ -449,7 +472,7 @@
 ---
 `)
 
-	writeSource(t, filepath.Join("content", "sect1", "_index.md"), `---
+	writeSource(t, fs, filepath.Join("content", "sect1", "_index.md"), `---
 title: Sect1 With Menu
 menu:
   mymenu:
@@ -457,7 +480,7 @@
 ---
 `)
 
-	writeSource(t, filepath.Join("content", "categories", "hugo", "_index.md"), `---
+	writeSource(t, fs, filepath.Join("content", "categories", "hugo", "_index.md"), `---
 title: Taxonomy With Menu
 menu:
   mymenu:
@@ -465,18 +488,15 @@
 ---
 `)
 
-	viper.Set("paginate", 1)
-	viper.Set("title", "Hugo Rocks!")
+	h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
 
-	s := NewSiteDefaultLang()
+	require.NoError(t, err)
 
-	if err := buildAndRenderSite(s); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	require.NoError(t, h.Build(BuildCfg{}))
 
-	assertFileContent(t, filepath.Join("public", "index.html"), true, "Home With Menu", "Home Menu Item: Go Home!: /")
-	assertFileContent(t, filepath.Join("public", "sect1", "index.html"), true, "Sect1 With Menu", "Section Menu Item: Go Sect1!: /sect1/")
-	assertFileContent(t, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy With Menu", "Taxonomy Menu Item: Go Tax Hugo!: /categories/hugo/")
+	assertFileContent(t, fs, filepath.Join("public", "index.html"), true, "Home With Menu", "Home Menu Item: Go Home!: /")
+	assertFileContent(t, fs, filepath.Join("public", "sect1", "index.html"), true, "Sect1 With Menu", "Section Menu Item: Go Sect1!: /sect1/")
+	assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy With Menu", "Taxonomy Menu Item: Go Tax Hugo!: /categories/hugo/")
 
 }
 
@@ -483,10 +503,16 @@
 func TestNodesWithAlias(t *testing.T) {
 	testCommonResetState()
 
-	writeLayoutsForNodeAsPageTests(t)
-	writeRegularPagesForNodeAsPageTests(t)
+	fs := hugofs.NewMem()
 
-	writeSource(t, filepath.Join("content", "_index.md"), `---
+	viper.Set("paginate", 1)
+	viper.Set("baseURL", "http://base/")
+	viper.Set("title", "Hugo Rocks!")
+
+	writeLayoutsForNodeAsPageTests(t, fs)
+	writeRegularPagesForNodeAsPageTests(t, fs)
+
+	writeSource(t, fs, filepath.Join("content", "_index.md"), `---
 title: Home With Alias
 aliases:
     - /my/new/home.html
@@ -493,18 +519,14 @@
 ---
 `)
 
-	viper.Set("paginate", 1)
-	viper.Set("baseURL", "http://base/")
-	viper.Set("title", "Hugo Rocks!")
+	h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
 
-	s := NewSiteDefaultLang()
+	require.NoError(t, err)
 
-	if err := buildAndRenderSite(s); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	require.NoError(t, h.Build(BuildCfg{}))
 
-	assertFileContent(t, filepath.Join("public", "index.html"), true, "Home With Alias")
-	assertFileContent(t, filepath.Join("public", "my", "new", "home.html"), true, "content=\"0; url=http://base/")
+	assertFileContent(t, fs, filepath.Join("public", "index.html"), true, "Home With Alias")
+	assertFileContent(t, fs, filepath.Join("public", "my", "new", "home.html"), true, "content=\"0; url=http://base/")
 
 }
 
@@ -511,24 +533,26 @@
 func TestNodesWithSectionWithIndexPageOnly(t *testing.T) {
 	testCommonResetState()
 
-	writeLayoutsForNodeAsPageTests(t)
+	fs := hugofs.NewMem()
 
-	writeSource(t, filepath.Join("content", "sect", "_index.md"), `---
+	viper.Set("paginate", 1)
+	viper.Set("title", "Hugo Rocks!")
+
+	writeLayoutsForNodeAsPageTests(t, fs)
+
+	writeSource(t, fs, filepath.Join("content", "sect", "_index.md"), `---
 title: MySection
 ---
 My Section Content
 `)
 
-	viper.Set("paginate", 1)
-	viper.Set("title", "Hugo Rocks!")
+	h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
 
-	s := NewSiteDefaultLang()
+	require.NoError(t, err)
 
-	if err := buildAndRenderSite(s); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	require.NoError(t, h.Build(BuildCfg{}))
 
-	assertFileContent(t, filepath.Join("public", "sect", "index.html"), true, "My Section")
+	assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), true, "My Section")
 
 }
 
@@ -535,11 +559,16 @@
 func TestNodesWithURLs(t *testing.T) {
 	testCommonResetState()
 
-	writeLayoutsForNodeAsPageTests(t)
+	fs := hugofs.NewMem()
 
-	writeRegularPagesForNodeAsPageTests(t)
+	viper.Set("paginate", 1)
+	viper.Set("title", "Hugo Rocks!")
+	viper.Set("baseURL", "http://bep.is/base/")
 
-	writeSource(t, filepath.Join("content", "sect", "_index.md"), `---
+	writeLayoutsForNodeAsPageTests(t, fs)
+	writeRegularPagesForNodeAsPageTests(t, fs)
+
+	writeSource(t, fs, filepath.Join("content", "sect", "_index.md"), `---
 title: MySection
 url: foo.html
 ---
@@ -546,18 +575,16 @@
 My Section Content
 `)
 
-	viper.Set("paginate", 1)
-	viper.Set("title", "Hugo Rocks!")
-	viper.Set("baseURL", "http://bep.is/base/")
+	h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs})
 
-	s := NewSiteDefaultLang()
+	require.NoError(t, err)
 
-	if err := buildAndRenderSite(s); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	require.NoError(t, h.Build(BuildCfg{}))
 
-	assertFileContent(t, filepath.Join("public", "sect", "index.html"), true, "My Section")
+	assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), true, "My Section")
 
+	s := h.Sites[0]
+
 	p := s.RegularPages[0]
 
 	require.Equal(t, "/base/sect1/regular1/", p.URL())
@@ -573,11 +600,11 @@
 
 }
 
-func writeRegularPagesForNodeAsPageTests(t *testing.T) {
-	writeRegularPagesForNodeAsPageTestsWithLang(t, "")
+func writeRegularPagesForNodeAsPageTests(t *testing.T, fs *hugofs.Fs) {
+	writeRegularPagesForNodeAsPageTestsWithLang(t, fs, "")
 }
 
-func writeRegularPagesForNodeAsPageTestsWithLang(t *testing.T, lang string) {
+func writeRegularPagesForNodeAsPageTestsWithLang(t *testing.T, fs *hugofs.Fs, lang string) {
 	var langStr string
 
 	if lang != "" {
@@ -597,7 +624,7 @@
 
 		}
 		date = date.Add(-24 * time.Duration(i) * time.Hour)
-		writeSource(t, filepath.Join("content", sect, fmt.Sprintf("regular%d.%smd", i, langStr)), fmt.Sprintf(`---
+		writeSource(t, fs, filepath.Join("content", sect, fmt.Sprintf("regular%d.%smd", i, langStr)), fmt.Sprintf(`---
 title: Page %02d
 lastMod : %q
 date : %q
@@ -612,7 +639,7 @@
 	}
 }
 
-func writeNodePagesForNodeAsPageTests(lang string, t *testing.T) {
+func writeNodePagesForNodeAsPageTests(t *testing.T, fs *hugofs.Fs, lang string) {
 
 	filename := "_index.md"
 
@@ -624,7 +651,7 @@
 
 	date, _ := time.Parse(format, "2009-01-01")
 
-	writeSource(t, filepath.Join("content", filename), fmt.Sprintf(`---
+	writeSource(t, fs, filepath.Join("content", filename), fmt.Sprintf(`---
 title: Home Sweet Home!
 date : %q
 lastMod : %q
@@ -632,7 +659,7 @@
 l-%s Home **Content!**
 `, date.Add(1*24*time.Hour).Format(time.RFC822), date.Add(2*24*time.Hour).Format(time.RFC822), lang))
 
-	writeSource(t, filepath.Join("content", "sect1", filename), fmt.Sprintf(`---
+	writeSource(t, fs, filepath.Join("content", "sect1", filename), fmt.Sprintf(`---
 title: Section1
 date : %q
 lastMod : %q
@@ -639,7 +666,7 @@
 ---
 Section1 **Content!**
 `, date.Add(3*24*time.Hour).Format(time.RFC822), date.Add(4*24*time.Hour).Format(time.RFC822)))
-	writeSource(t, filepath.Join("content", "sect2", filename), fmt.Sprintf(`---
+	writeSource(t, fs, filepath.Join("content", "sect2", filename), fmt.Sprintf(`---
 title: Section2
 date : %q
 lastMod : %q
@@ -647,7 +674,7 @@
 Section2 **Content!**
 `, date.Add(5*24*time.Hour).Format(time.RFC822), date.Add(6*24*time.Hour).Format(time.RFC822)))
 
-	writeSource(t, filepath.Join("content", "categories", "hugo", filename), fmt.Sprintf(`---
+	writeSource(t, fs, filepath.Join("content", "categories", "hugo", filename), fmt.Sprintf(`---
 title: Taxonomy Hugo
 date : %q
 lastMod : %q
@@ -655,7 +682,7 @@
 Taxonomy Hugo **Content!**
 `, date.Add(7*24*time.Hour).Format(time.RFC822), date.Add(8*24*time.Hour).Format(time.RFC822)))
 
-	writeSource(t, filepath.Join("content", "categories", "web", filename), fmt.Sprintf(`---
+	writeSource(t, fs, filepath.Join("content", "categories", "web", filename), fmt.Sprintf(`---
 title: Taxonomy Web
 date : %q
 lastMod : %q
@@ -663,7 +690,7 @@
 Taxonomy Web **Content!**
 `, date.Add(9*24*time.Hour).Format(time.RFC822), date.Add(10*24*time.Hour).Format(time.RFC822)))
 
-	writeSource(t, filepath.Join("content", "categories", "hugo-rocks", filename), fmt.Sprintf(`---
+	writeSource(t, fs, filepath.Join("content", "categories", "hugo-rocks", filename), fmt.Sprintf(`---
 title: Taxonomy Hugo Rocks
 date : %q
 lastMod : %q
@@ -671,7 +698,7 @@
 Taxonomy Hugo Rocks **Content!**
 `, date.Add(11*24*time.Hour).Format(time.RFC822), date.Add(12*24*time.Hour).Format(time.RFC822)))
 
-	writeSource(t, filepath.Join("content", "categories", filename), fmt.Sprintf(`---
+	writeSource(t, fs, filepath.Join("content", "categories", filename), fmt.Sprintf(`---
 title: Taxonomy Term Categories
 date : %q
 lastMod : %q
@@ -681,8 +708,8 @@
 
 }
 
-func writeLayoutsForNodeAsPageTests(t *testing.T) {
-	writeSource(t, filepath.Join("layouts", "index.html"), `
+func writeLayoutsForNodeAsPageTests(t *testing.T, fs *hugofs.Fs) {
+	writeSource(t, fs, filepath.Join("layouts", "index.html"), `
 Index Title: {{ .Title }}
 Index Content: {{ .Content }}
 # Pages: {{ len .Data.Pages }}
@@ -699,7 +726,7 @@
 GetPage: {{ with .Site.GetPage "section" "sect1" }}{{ .Title }}{{ end }} 
 `)
 
-	writeSource(t, filepath.Join("layouts", "_default", "single.html"), `
+	writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `
 Single Title: {{ .Title }}
 Single Content: {{ .Content }}
 Date: {{ .Date.Format "2006-01-02" }}
@@ -706,7 +733,7 @@
 Lastmod: {{ .Lastmod.Format "2006-01-02" }}
 `)
 
-	writeSource(t, filepath.Join("layouts", "_default", "section.html"), `
+	writeSource(t, fs, filepath.Join("layouts", "_default", "section.html"), `
 Section Title: {{ .Title }}
 Section Content: {{ .Content }}
 # Pages: {{ len .Data.Pages }}
@@ -723,7 +750,7 @@
 `)
 
 	// Taxonomy lists
-	writeSource(t, filepath.Join("layouts", "_default", "taxonomy.html"), `
+	writeSource(t, fs, filepath.Join("layouts", "_default", "taxonomy.html"), `
 Taxonomy Title: {{ .Title }}
 Taxonomy Content: {{ .Content }}
 # Pages: {{ len .Data.Pages }}
@@ -740,7 +767,7 @@
 `)
 
 	// Taxonomy terms
-	writeSource(t, filepath.Join("layouts", "_default", "terms.html"), `
+	writeSource(t, fs, filepath.Join("layouts", "_default", "terms.html"), `
 Taxonomy Terms Title: {{ .Title }}
 Taxonomy Terms Content: {{ .Content }}
 {{ range $key, $value := .Data.Terms }}
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -38,7 +38,6 @@
 
 	"github.com/spf13/cast"
 	bp "github.com/spf13/hugo/bufferpool"
-	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/hugo/source"
 	"github.com/spf13/viper"
 )
@@ -536,7 +535,7 @@
 		p.renderingConfig = helpers.NewBlackfriday(p.Language())
 
 		if err := mapstructure.Decode(pageParam, p.renderingConfig); err != nil {
-			p.s.log.FATAL.Printf("Failed to get rendering config for %s:\n%s", p.BaseFileName(), err.Error())
+			p.s.Log.FATAL.Printf("Failed to get rendering config for %s:\n%s", p.BaseFileName(), err.Error())
 		}
 
 	})
@@ -556,7 +555,7 @@
 		sections:     sectionsFromFilename(filename),
 	}
 
-	s.log.DEBUG.Println("Reading from", page.File.Path())
+	s.Log.DEBUG.Println("Reading from", page.File.Path())
 	return &page
 }
 
@@ -683,7 +682,7 @@
 func (p *Page) ReadFrom(buf io.Reader) (int64, error) {
 	// Parse for metadata & body
 	if err := p.parse(buf); err != nil {
-		p.s.log.ERROR.Print(err)
+		p.s.Log.ERROR.Print(err)
 		return 0, err
 	}
 
@@ -738,7 +737,7 @@
 	p.pageURLInit.Do(func() {
 		u, err := p.createPermalink()
 		if err != nil {
-			p.s.log.ERROR.Printf("Failed to create permalink for page %q: %s", p.FullFilePath(), err)
+			p.s.Log.ERROR.Printf("Failed to create permalink for page %q: %s", p.FullFilePath(), err)
 			p.permalink = new(url.URL)
 			return
 		}
@@ -759,16 +758,16 @@
 
 	if p.IsNode() {
 		// No permalink config for nodes (currently)
-		pURL := strings.TrimSpace(p.Site.pathSpec.URLize(p.URLPath.URL))
+		pURL := strings.TrimSpace(p.s.PathSpec.URLize(p.URLPath.URL))
 		pURL = p.addLangPathPrefix(pURL)
-		pURL = p.Site.pathSpec.URLPrep(pURL)
+		pURL = p.s.PathSpec.URLPrep(pURL)
 		url := helpers.MakePermalink(baseURL, pURL)
 		return url, nil
 	}
 
-	dir := strings.TrimSpace(p.Site.pathSpec.MakePath(filepath.ToSlash(strings.ToLower(p.Source.Dir()))))
-	pSlug := strings.TrimSpace(p.Site.pathSpec.URLize(p.Slug))
-	pURL := strings.TrimSpace(p.Site.pathSpec.URLize(p.URLPath.URL))
+	dir := strings.TrimSpace(p.s.PathSpec.MakePath(filepath.ToSlash(strings.ToLower(p.Source.Dir()))))
+	pSlug := strings.TrimSpace(p.s.PathSpec.URLize(p.Slug))
+	pURL := strings.TrimSpace(p.s.PathSpec.URLize(p.URLPath.URL))
 	var permalink string
 	var err error
 
@@ -784,10 +783,10 @@
 		}
 	} else {
 		if len(pSlug) > 0 {
-			permalink = p.Site.pathSpec.URLPrep(path.Join(dir, p.Slug+"."+p.Extension()))
+			permalink = p.s.PathSpec.URLPrep(path.Join(dir, p.Slug+"."+p.Extension()))
 		} else {
 			t := p.Source.TranslationBaseName()
-			permalink = p.Site.pathSpec.URLPrep(path.Join(dir, (strings.TrimSpace(t) + "." + p.Extension())))
+			permalink = p.s.PathSpec.URLPrep(path.Join(dir, (strings.TrimSpace(t) + "." + p.Extension())))
 		}
 	}
 
@@ -953,22 +952,22 @@
 		case "date":
 			p.Date, err = cast.ToTimeE(v)
 			if err != nil {
-				p.s.log.ERROR.Printf("Failed to parse date '%v' in page %s", v, p.File.Path())
+				p.s.Log.ERROR.Printf("Failed to parse date '%v' in page %s", v, p.File.Path())
 			}
 		case "lastmod":
 			p.Lastmod, err = cast.ToTimeE(v)
 			if err != nil {
-				p.s.log.ERROR.Printf("Failed to parse lastmod '%v' in page %s", v, p.File.Path())
+				p.s.Log.ERROR.Printf("Failed to parse lastmod '%v' in page %s", v, p.File.Path())
 			}
 		case "publishdate", "pubdate":
 			p.PublishDate, err = cast.ToTimeE(v)
 			if err != nil {
-				p.s.log.ERROR.Printf("Failed to parse publishdate '%v' in page %s", v, p.File.Path())
+				p.s.Log.ERROR.Printf("Failed to parse publishdate '%v' in page %s", v, p.File.Path())
 			}
 		case "expirydate", "unpublishdate":
 			p.ExpiryDate, err = cast.ToTimeE(v)
 			if err != nil {
-				p.s.log.ERROR.Printf("Failed to parse expirydate '%v' in page %s", v, p.File.Path())
+				p.s.Log.ERROR.Printf("Failed to parse expirydate '%v' in page %s", v, p.File.Path())
 			}
 		case "draft":
 			draft = new(bool)
@@ -1040,7 +1039,7 @@
 
 	if draft != nil && published != nil {
 		p.Draft = *draft
-		p.s.log.ERROR.Printf("page %s has both draft and published settings in its frontmatter. Using draft.", p.File.Path())
+		p.s.Log.ERROR.Printf("page %s has both draft and published settings in its frontmatter. Using draft.", p.File.Path())
 		return ErrHasDraftAndPublished
 	} else if draft != nil {
 		p.Draft = *draft
@@ -1049,7 +1048,7 @@
 	}
 
 	if p.Date.IsZero() && viper.GetBool("useModTimeAsFallback") {
-		fi, err := hugofs.Source().Stat(filepath.Join(helpers.AbsPathify(viper.GetString("contentDir")), p.File.Path()))
+		fi, err := p.s.Fs.Source.Stat(filepath.Join(helpers.AbsPathify(viper.GetString("contentDir")), p.File.Path()))
 		if err == nil {
 			p.Date = fi.ModTime()
 		}
@@ -1109,7 +1108,7 @@
 		return v
 	}
 
-	p.s.log.ERROR.Printf("GetParam(\"%s\"): Unknown type %s\n", key, reflect.TypeOf(v))
+	p.s.Log.ERROR.Printf("GetParam(\"%s\"): Unknown type %s\n", key, reflect.TypeOf(v))
 	return nil
 }
 
@@ -1251,16 +1250,16 @@
 			menus, err := cast.ToStringMapE(ms)
 
 			if err != nil {
-				p.s.log.ERROR.Printf("unable to process menus for %q\n", p.Title)
+				p.s.Log.ERROR.Printf("unable to process menus for %q\n", p.Title)
 			}
 
 			for name, menu := range menus {
 				menuEntry := MenuEntry{Name: p.LinkTitle(), URL: link, Weight: p.Weight, Menu: name}
 				if menu != nil {
-					p.s.log.DEBUG.Printf("found menu: %q, in %q\n", name, p.Title)
+					p.s.Log.DEBUG.Printf("found menu: %q, in %q\n", name, p.Title)
 					ime, err := cast.ToStringMapE(menu)
 					if err != nil {
-						p.s.log.ERROR.Printf("unable to process menus for %q: %s", p.Title, err)
+						p.s.Log.ERROR.Printf("unable to process menus for %q: %s", p.Title, err)
 					}
 
 					menuEntry.marshallMap(ime)
@@ -1283,7 +1282,7 @@
 		l = p.layouts()
 	}
 
-	return p.s.tmpl.ExecuteTemplateToHTML(p, l...)
+	return p.s.Tmpl.ExecuteTemplateToHTML(p, l...)
 }
 
 func (p *Page) determineMarkupType() string {
@@ -1311,8 +1310,8 @@
 	meta, err := psr.Metadata()
 	if meta != nil {
 		if err != nil {
-			p.s.log.ERROR.Printf("Error parsing page meta data for %s", p.File.Path())
-			p.s.log.ERROR.Println(err)
+			p.s.Log.ERROR.Printf("Error parsing page meta data for %s", p.File.Path())
+			p.s.Log.ERROR.Println(err)
 			return err
 		}
 		if err = p.update(meta); err != nil {
@@ -1381,12 +1380,12 @@
 	if !filepath.IsAbs(inpath) {
 		inpath = helpers.AbsPathify(inpath)
 	}
-	p.s.log.INFO.Println("creating", inpath)
+	p.s.Log.INFO.Println("creating", inpath)
 
 	if safe {
-		err = helpers.SafeWriteToDisk(inpath, bytes.NewReader(by), hugofs.Source())
+		err = helpers.SafeWriteToDisk(inpath, bytes.NewReader(by), p.s.Fs.Source)
 	} else {
-		err = helpers.WriteToDisk(inpath, bytes.NewReader(by), hugofs.Source())
+		err = helpers.WriteToDisk(inpath, bytes.NewReader(by), p.s.Fs.Source)
 	}
 	if err != nil {
 		return
@@ -1455,7 +1454,7 @@
 	}
 
 	return p.addLangFilepathPrefix(filepath.Join(strings.ToLower(
-		p.Site.pathSpec.MakePath(p.Source.Dir())), strings.TrimSpace(outfile)))
+		p.s.PathSpec.MakePath(p.Source.Dir())), strings.TrimSpace(outfile)))
 }
 
 // Pre render prepare steps
@@ -1466,7 +1465,7 @@
 		var layouts []string
 		if !p.IsRenderable() {
 			self := "__" + p.TargetPath()
-			_, err := p.Site.owner.tmpl.GetClone().New(self).Parse(string(p.Content))
+			_, err := p.Site.owner.Tmpl.GetClone().New(self).Parse(string(p.Content))
 			if err != nil {
 				return err
 			}
@@ -1473,7 +1472,6 @@
 			layouts = append(layouts, self)
 		} else {
 			layouts = append(layouts, p.layouts()...)
-			layouts = append(layouts, "_default/single.html")
 		}
 		p.layoutsCalculated = layouts
 	}
@@ -1707,7 +1705,7 @@
 
 		if language == nil {
 			// It can be a file named stefano.chiodino.md.
-			p.s.log.WARN.Printf("Page language (if it is that) not found in multilang setup: %s.", pageLang)
+			p.s.Log.WARN.Printf("Page language (if it is that) not found in multilang setup: %s.", pageLang)
 			language = ml.DefaultLang
 		}
 
--- a/hugolib/page_permalink_test.go
+++ b/hugolib/page_permalink_test.go
@@ -23,7 +23,8 @@
 	"github.com/spf13/viper"
 )
 
-func TestPermalink(t *testing.T) {
+// TODO(bep) globals test siteinfo
+func _TestPermalink(t *testing.T) {
 	testCommonResetState()
 
 	tests := []struct {
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -26,7 +26,9 @@
 	"time"
 
 	"github.com/spf13/cast"
+	"github.com/spf13/hugo/deps"
 	"github.com/spf13/hugo/helpers"
+	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -465,8 +467,13 @@
 Hi.
 `
 
-var pageTestSite = NewSiteDefaultLang()
+func init() {
+	testCommonResetState()
+	pageTestSite, _ = NewSiteDefaultLang()
+}
 
+var pageTestSite *Site
+
 func checkError(t *testing.T, err error, expected string) {
 	if err == nil {
 		t.Fatalf("err is nil.  Expected: %s", expected)
@@ -606,6 +613,8 @@
 
 		testCommonResetState()
 
+		fs := hugofs.NewMem()
+
 		if settings != nil {
 			for k, v := range settings {
 				viper.Set(k, v)
@@ -625,15 +634,11 @@
 		}
 
 		for i := 0; i < len(fileSourcePairs); i += 2 {
-			writeSource(t, filepath.Join(contentDir, fileSourcePairs[i]), fileSourcePairs[i+1])
+			writeSource(t, fs, filepath.Join(contentDir, fileSourcePairs[i]), fileSourcePairs[i+1])
 		}
 
-		s := NewSiteDefaultLang()
+		s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
 
-		if err := buildSiteSkipRender(s); err != nil {
-			t.Fatalf("Failed to build site: %s", err)
-		}
-
 		require.Len(t, s.RegularPages, len(pageSources))
 
 		assertFunc(t, e.ext, s.RegularPages)
@@ -738,12 +743,15 @@
 
 // Issue #1076
 func TestPageWithDelimiterForMarkdownThatCrossesBorder(t *testing.T) {
-	s := newSiteFromSources("simple.md", simplePageWithSummaryDelimiterAndMarkdownThatCrossesBorder)
 
-	if err := buildSiteSkipRender(s); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	testCommonResetState()
 
+	fs := hugofs.NewMem()
+
+	writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithSummaryDelimiterAndMarkdownThatCrossesBorder)
+
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+
 	require.Len(t, s.RegularPages, 1)
 
 	p := s.RegularPages[0]
@@ -759,16 +767,18 @@
 
 // Issue #2601
 func TestPageRawContent(t *testing.T) {
-	s := newSiteFromSources("raw.md", `---
+	testCommonResetState()
+
+	fs := hugofs.NewMem()
+
+	writeSource(t, fs, filepath.Join("content", "raw.md"), `---
 title: Raw
 ---
 **Raw**`)
 
-	writeSource(t, filepath.Join("layouts", "_default", "single.html"), `{{ .RawContent }}`)
+	writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .RawContent }}`)
 
-	if err := buildSiteSkipRender(s); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
 
 	require.Len(t, s.RegularPages, 1)
 	p := s.RegularPages[0]
@@ -806,12 +816,13 @@
 }
 
 func TestPageWithAdditionalExtension(t *testing.T) {
-	s := newSiteFromSources("simple.md", simplePageWithAdditionalExtension)
 
-	if err := buildSiteSkipRender(s); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	fs := hugofs.NewMem()
 
+	writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithAdditionalExtension)
+
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
+
 	require.Len(t, s.RegularPages, 1)
 
 	p := s.RegularPages[0]
@@ -820,12 +831,13 @@
 }
 
 func TestTableOfContents(t *testing.T) {
-	s := newSiteFromSources("tocpage.md", pageWithToC)
 
-	if err := buildSiteSkipRender(s); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	fs := hugofs.NewMem()
 
+	writeSource(t, fs, filepath.Join("content", "tocpage.md"), pageWithToC)
+
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
+
 	require.Len(t, s.RegularPages, 1)
 
 	p := s.RegularPages[0]
@@ -850,12 +862,12 @@
 }
 
 func TestPageWithDate(t *testing.T) {
-	s := newSiteFromSources("simple.md", simplePageRFC3339Date)
+	fs := hugofs.NewMem()
 
-	if err := buildSiteSkipRender(s); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageRFC3339Date)
 
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
+
 	require.Len(t, s.RegularPages, 1)
 
 	p := s.RegularPages[0]
@@ -1372,11 +1384,11 @@
 func TestChompBOM(t *testing.T) {
 	const utf8BOM = "\xef\xbb\xbf"
 
-	s := newSiteFromSources("simple.md", utf8BOM+simplePage)
+	fs := hugofs.NewMem()
 
-	if err := buildSiteSkipRender(s); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	writeSource(t, fs, filepath.Join("content", "simple.md"), utf8BOM+simplePage)
+
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
 
 	require.Len(t, s.RegularPages, 1)
 
--- a/hugolib/pagination.go
+++ b/hugolib/pagination.go
@@ -279,7 +279,7 @@
 			return
 		}
 
-		pagers, err := paginatePages(p.Data["Pages"], pagerSize, p.sections...)
+		pagers, err := paginatePages(p.s.PathSpec, p.Data["Pages"], pagerSize, p.sections...)
 
 		if err != nil {
 			initError = err
@@ -322,7 +322,7 @@
 		if p.paginator != nil {
 			return
 		}
-		pagers, err := paginatePages(seq, pagerSize, p.sections...)
+		pagers, err := paginatePages(p.s.PathSpec, seq, pagerSize, p.sections...)
 
 		if err != nil {
 			initError = err
@@ -371,13 +371,13 @@
 	return pas, nil
 }
 
-func paginatePages(seq interface{}, pagerSize int, sections ...string) (pagers, error) {
+func paginatePages(pathSpec *helpers.PathSpec, seq interface{}, pagerSize int, sections ...string) (pagers, error) {
 
 	if pagerSize <= 0 {
 		return nil, errors.New("'paginate' configuration setting must be positive to paginate")
 	}
 
-	urlFactory := newPaginationURLFactory(sections...)
+	urlFactory := newPaginationURLFactory(pathSpec, sections...)
 
 	var paginator *paginator
 
@@ -504,8 +504,7 @@
 	return p, nil
 }
 
-func newPaginationURLFactory(pathElements ...string) paginationURLFactory {
-	pathSpec := helpers.CurrentPathSpec()
+func newPaginationURLFactory(pathSpec *helpers.PathSpec, pathElements ...string) paginationURLFactory {
 
 	basePath := path.Join(pathElements...)
 
--- a/hugolib/pagination_test.go
+++ b/hugolib/pagination_test.go
@@ -19,10 +19,13 @@
 	"path/filepath"
 	"testing"
 
+	"github.com/spf13/hugo/deps"
 	"github.com/spf13/hugo/helpers"
+	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/hugo/source"
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestSplitPages(t *testing.T) {
@@ -197,11 +200,22 @@
 	testCommonResetState()
 
 	viper.Set("paginatePath", "zoo")
-	unicode := newPaginationURLFactory("новости проекта")
-	fooBar := newPaginationURLFactory("foo", "bar")
 
+	pathSpec := newTestPathSpec()
+
+	unicode := newPaginationURLFactory(pathSpec, "новости проекта")
+	fooBar := newPaginationURLFactory(pathSpec, "foo", "bar")
+
 	assert.Equal(t, "/foo/bar/", fooBar(1))
 	assert.Equal(t, "/%D0%BD%D0%BE%D0%B2%D0%BE%D1%81%D1%82%D0%B8-%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B0/zoo/4/", unicode(4))
+
+	unicoded := unicode(4)
+	unicodedExpected := "/%D0%BD%D0%BE%D0%B2%D0%BE%D1%81%D1%82%D0%B8-%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B0/zoo/4/"
+
+	if unicoded != unicodedExpected {
+		t.Fatal("Expected\n", unicodedExpected, "\nGot\n", unicoded)
+	}
+
 	assert.Equal(t, "/foo/bar/zoo/12345/", fooBar(12345))
 
 }
@@ -224,13 +238,13 @@
 		viper.Set("paginate", -1)
 	}
 	pages := createTestPages(12)
-	s := NewSiteDefaultLang()
+	s, err := NewSiteDefaultLang()
+	require.NoError(t, err)
 	n1 := s.newHomePage()
 	n2 := s.newHomePage()
 	n1.Data["Pages"] = pages
 
 	var paginator1 *Pager
-	var err error
 
 	if useViper {
 		paginator1, err = n1.Paginator()
@@ -261,9 +275,10 @@
 	testCommonResetState()
 
 	viper.Set("paginate", -1)
-	s := NewSiteDefaultLang()
-	_, err := s.newHomePage().Paginator()
-	assert.NotNil(t, err)
+	s, err := NewSiteDefaultLang()
+	require.NoError(t, err)
+	_, err = s.newHomePage().Paginator()
+	require.Error(t, err)
 }
 
 func TestPaginate(t *testing.T) {
@@ -280,9 +295,11 @@
 	viper.Set("paginate", 2)
 	viper.Set("paginatePath", "testing")
 
+	fs := hugofs.NewMem()
+
 	for i := 0; i < 10; i++ {
 		// Issue #2177, do not double encode URLs
-		writeSource(t, filepath.Join("content", "阅读", fmt.Sprintf("page%d.md", (i+1))),
+		writeSource(t, fs, filepath.Join("content", "阅读", fmt.Sprintf("page%d.md", (i+1))),
 			fmt.Sprintf(`---
 title: Page%d
 ---
@@ -290,8 +307,8 @@
 `, (i+1), i+1))
 
 	}
-	writeSource(t, filepath.Join("layouts", "_default", "single.html"), "<html><body>{{.Content}}</body></html>")
-	writeSource(t, filepath.Join("layouts", "_default", "list.html"),
+	writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), "<html><body>{{.Content}}</body></html>")
+	writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"),
 		`
 <html><body>
 Count: {{ .Paginator.TotalNumberOfElements }}
@@ -301,11 +318,9 @@
 {{ end }}
 </body></html>`)
 
-	if err := buildAndRenderSite(NewSiteDefaultLang()); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
 
-	assertFileContent(t, filepath.Join("public", "阅读", "testing", "2", "index.html"), false, "2: /%E9%98%85%E8%AF%BB/testing/2/")
+	assertFileContent(t, fs, filepath.Join("public", "阅读", "testing", "2", "index.html"), false, "2: /%E9%98%85%E8%AF%BB/testing/2/")
 
 }
 
@@ -318,12 +333,12 @@
 	}
 
 	pages := createTestPages(6)
-	s := NewSiteDefaultLang()
+	s, err := NewSiteDefaultLang()
+	require.NoError(t, err)
 	n1 := s.newHomePage()
 	n2 := s.newHomePage()
 
 	var paginator1, paginator2 *Pager
-	var err error
 
 	if useViper {
 		paginator1, err = n1.Paginate(pages)
@@ -351,9 +366,10 @@
 }
 
 func TestInvalidOptions(t *testing.T) {
-	s := NewSiteDefaultLang()
+	s, err := NewSiteDefaultLang()
+	require.NoError(t, err)
 	n1 := s.newHomePage()
-	_, err := n1.Paginate(createTestPages(1), 1, 2)
+	_, err = n1.Paginate(createTestPages(1), 1, 2)
 	assert.NotNil(t, err)
 	_, err = n1.Paginator(1, 2)
 	assert.NotNil(t, err)
@@ -365,19 +381,22 @@
 	testCommonResetState()
 
 	viper.Set("paginate", -1)
-	s := NewSiteDefaultLang()
-	_, err := s.newHomePage().Paginate(createTestPages(2))
+	s, err := NewSiteDefaultLang()
+	require.NoError(t, err)
+	_, err = s.newHomePage().Paginate(createTestPages(2))
 	assert.NotNil(t, err)
 }
 
 func TestPaginatePages(t *testing.T) {
 	groups, _ := createTestPages(31).GroupBy("Weight", "desc")
+	pathSpec := newTestPathSpec()
+
 	for i, seq := range []interface{}{createTestPages(11), groups, WeightedPages{}, PageGroup{}, &Pages{}} {
-		v, err := paginatePages(seq, 11, "t")
+		v, err := paginatePages(pathSpec, seq, 11, "t")
 		assert.NotNil(t, v, "Val %d", i)
 		assert.Nil(t, err, "Err %d", i)
 	}
-	_, err := paginatePages(Site{}, 11, "t")
+	_, err := paginatePages(pathSpec, Site{}, 11, "t")
 	assert.NotNil(t, err)
 
 }
@@ -387,11 +406,12 @@
 	testCommonResetState()
 
 	viper.Set("paginate", 10)
-	s := NewSiteDefaultLang()
+	s, err := NewSiteDefaultLang()
+	require.NoError(t, err)
 	n1 := s.newHomePage()
 	n2 := s.newHomePage()
 
-	_, err := n1.Paginator()
+	_, err = n1.Paginator()
 	assert.Nil(t, err)
 	_, err = n1.Paginate(createTestPages(2))
 	assert.NotNil(t, err)
@@ -405,7 +425,8 @@
 	testCommonResetState()
 
 	viper.Set("paginate", 10)
-	s := NewSiteDefaultLang()
+	s, err := NewSiteDefaultLang()
+	require.NoError(t, err)
 	n1 := s.newHomePage()
 	n2 := s.newHomePage()
 
@@ -412,7 +433,7 @@
 	p1 := createTestPages(2)
 	p2 := createTestPages(10)
 
-	_, err := n1.Paginate(p1)
+	_, err = n1.Paginate(p1)
 	assert.Nil(t, err)
 
 	_, err = n1.Paginate(p1)
--- a/hugolib/permalinks.go
+++ b/hugolib/permalinks.go
@@ -150,7 +150,7 @@
 func pageToPermalinkTitle(p *Page, _ string) (string, error) {
 	// Page contains Node which has Title
 	// (also contains URLPath which has Slug, sometimes)
-	return p.Site.pathSpec.URLize(p.Title), nil
+	return p.s.PathSpec.URLize(p.Title), nil
 }
 
 // pageToPermalinkFilename returns the URL-safe form of the filename
@@ -157,7 +157,7 @@
 func pageToPermalinkFilename(p *Page, _ string) (string, error) {
 	//var extension = p.Source.Ext
 	//var name = p.Source.Path()[0 : len(p.Source.Path())-len(extension)]
-	return p.Site.pathSpec.URLize(p.Source.TranslationBaseName()), nil
+	return p.s.PathSpec.URLize(p.Source.TranslationBaseName()), nil
 }
 
 // if the page has a slug, return the slug, else return the title
@@ -172,7 +172,7 @@
 		if strings.HasSuffix(p.Slug, "-") {
 			p.Slug = p.Slug[0 : len(p.Slug)-1]
 		}
-		return p.Site.pathSpec.URLize(p.Slug), nil
+		return p.s.PathSpec.URLize(p.Slug), nil
 	}
 	return pageToPermalinkTitle(p, a)
 }
--- a/hugolib/robotstxt_test.go
+++ b/hugolib/robotstxt_test.go
@@ -14,12 +14,12 @@
 package hugolib
 
 import (
-	"bytes"
+	"path/filepath"
 	"testing"
 
-	"github.com/spf13/hugo/helpers"
 	"github.com/spf13/hugo/hugofs"
-	"github.com/spf13/hugo/source"
+
+	"github.com/spf13/hugo/deps"
 	"github.com/spf13/viper"
 )
 
@@ -32,28 +32,16 @@
 func TestRobotsTXTOutput(t *testing.T) {
 	testCommonResetState()
 
-	hugofs.InitMemFs()
-
 	viper.Set("baseURL", "http://auth/bub/")
 	viper.Set("enableRobotsTXT", true)
 
-	s := &Site{
-		Source:   &source.InMemorySource{ByteSource: weightedSources},
-		Language: helpers.NewDefaultLanguage(),
-	}
+	fs := hugofs.NewMem()
 
-	if err := buildAndRenderSite(s, "robots.txt", robotTxtTemplate); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	writeSource(t, fs, filepath.Join("layouts", "robots.txt"), robotTxtTemplate)
+	writeSourcesToSource(t, "content", fs, weightedSources...)
 
-	robotsFile, err := hugofs.Destination().Open("public/robots.txt")
+	buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
 
-	if err != nil {
-		t.Fatalf("Unable to locate: robots.txt")
-	}
+	assertFileContent(t, fs, "public/robots.txt", true, "User-agent: Googlebot")
 
-	robots := helpers.ReaderToBytes(robotsFile)
-	if !bytes.HasPrefix(robots, []byte("User-agent: Googlebot")) {
-		t.Errorf("Robots file should start with 'User-agent: Googlebot'. %s", robots)
-	}
 }
--- a/hugolib/rss_test.go
+++ b/hugolib/rss_test.go
@@ -17,6 +17,8 @@
 	"path/filepath"
 	"testing"
 
+	"github.com/spf13/hugo/deps"
+	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/viper"
 )
 
@@ -28,19 +30,19 @@
 	viper.Set("rssURI", rssURI)
 	viper.Set("title", "RSSTest")
 
-	for _, s := range weightedSources {
-		writeSource(t, filepath.Join("content", "sect", s.Name), string(s.Content))
-	}
+	fs := hugofs.NewMem()
 
-	if err := buildAndRenderSite(NewSiteDefaultLang()); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
+	for _, src := range weightedSources {
+		writeSource(t, fs, filepath.Join("content", "sect", src.Name), string(src.Content))
 	}
 
+	buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+
 	// Home RSS
-	assertFileContent(t, filepath.Join("public", rssURI), true, "<?xml", "rss version", "RSSTest")
+	assertFileContent(t, fs, filepath.Join("public", rssURI), true, "<?xml", "rss version", "RSSTest")
 	// Section RSS
-	assertFileContent(t, filepath.Join("public", "sect", rssURI), true, "<?xml", "rss version", "Sects on RSSTest")
+	assertFileContent(t, fs, filepath.Join("public", "sect", rssURI), true, "<?xml", "rss version", "Sects on RSSTest")
 	// Taxonomy RSS
-	assertFileContent(t, filepath.Join("public", "categories", "hugo", rssURI), true, "<?xml", "rss version", "Hugo on RSSTest")
+	assertFileContent(t, fs, filepath.Join("public", "categories", "hugo", rssURI), true, "<?xml", "rss version", "Hugo on RSSTest")
 
 }
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -26,7 +26,7 @@
 
 	bp "github.com/spf13/hugo/bufferpool"
 	"github.com/spf13/hugo/helpers"
-	"github.com/spf13/hugo/tpl"
+	"github.com/spf13/hugo/tplapi"
 )
 
 // ShortcodeWithPage is the "." context in a shortcode template.
@@ -211,10 +211,10 @@
 const innerCleanupExpand = "$1"
 
 func renderShortcode(sc shortcode, parent *ShortcodeWithPage, p *Page) string {
-	tmpl := getShortcodeTemplate(sc.name, p.s.tmpl)
+	tmpl := getShortcodeTemplate(sc.name, p.s.Tmpl)
 
 	if tmpl == nil {
-		p.s.log.ERROR.Printf("Unable to locate template for shortcode '%s' in page %s", sc.name, p.BaseFileName())
+		p.s.Log.ERROR.Printf("Unable to locate template for shortcode '%s' in page %s", sc.name, p.BaseFileName())
 		return ""
 	}
 
@@ -232,7 +232,7 @@
 			case shortcode:
 				inner += renderShortcode(innerData.(shortcode), data, p)
 			default:
-				p.s.log.ERROR.Printf("Illegal state on shortcode rendering of '%s' in page %s. Illegal type in inner data: %s ",
+				p.s.Log.ERROR.Printf("Illegal state on shortcode rendering of '%s' in page %s. Illegal type in inner data: %s ",
 					sc.name, p.BaseFileName(), reflect.TypeOf(innerData))
 				return ""
 			}
@@ -286,7 +286,7 @@
 
 	if err != nil {
 		//  try to render what we have whilst logging the error
-		p.s.log.ERROR.Println(err.Error())
+		p.s.Log.ERROR.Println(err.Error())
 	}
 
 	// Save for reuse
@@ -398,7 +398,7 @@
 			sc.inner = append(sc.inner, currItem.val)
 		case tScName:
 			sc.name = currItem.val
-			tmpl := getShortcodeTemplate(sc.name, p.s.tmpl)
+			tmpl := getShortcodeTemplate(sc.name, p.s.Tmpl)
 
 			if tmpl == nil {
 				return sc, fmt.Errorf("Unable to locate template for shortcode '%s' in page %s", sc.name, p.BaseFileName())
@@ -566,7 +566,7 @@
 	return source, nil
 }
 
-func getShortcodeTemplate(name string, t tpl.Template) *template.Template {
+func getShortcodeTemplate(name string, t tplapi.Template) *template.Template {
 	if x := t.Lookup("shortcodes/" + name + ".html"); x != nil {
 		return x
 	}
@@ -584,9 +584,8 @@
 	err := tmpl.Execute(buffer, data)
 	isInnerShortcodeCache.RUnlock()
 	if err != nil {
-		// TODO(bep) globals
-		data.Page.s.log.ERROR.Println("error processing shortcode", tmpl.Name(), "\n ERR:", err)
-		data.Page.s.log.WARN.Println(data)
+		data.Page.s.Log.ERROR.Println("error processing shortcode", tmpl.Name(), "\n ERR:", err)
+		data.Page.s.Log.WARN.Println(data)
 	}
 	return buffer.String()
 }
--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -22,32 +22,38 @@
 	"strings"
 	"testing"
 
+	"github.com/spf13/hugo/deps"
 	"github.com/spf13/hugo/helpers"
 	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/hugo/source"
-	"github.com/spf13/hugo/target"
-	"github.com/spf13/hugo/tpl"
+	"github.com/spf13/hugo/tplapi"
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/require"
 )
 
 // TODO(bep) remove
-func pageFromString(in, filename string, withTemplate ...func(templ tpl.Template) error) (*Page, error) {
+func pageFromString(in, filename string, withTemplate ...func(templ tplapi.Template) error) (*Page, error) {
 	s := pageTestSite
 	if len(withTemplate) > 0 {
 		// Have to create a new site
-		s = NewSiteDefaultLang(withTemplate...)
+		var err error
+		s, err = NewSiteDefaultLang(withTemplate...)
+		if err != nil {
+			return nil, err
+		}
 	}
 	return s.NewPageFrom(strings.NewReader(in), filename)
 }
 
-func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error) {
+func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tplapi.Template) error) {
 	CheckShortCodeMatchAndError(t, input, expected, withTemplate, false)
 }
 
-func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error, expectError bool) {
+func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tplapi.Template) error, expectError bool) {
 	testCommonResetState()
 
+	fs := hugofs.NewMem()
+
 	// Need some front matter, see https://github.com/spf13/hugo/issues/2337
 	contentFile := `---
 title: "Title"
@@ -54,18 +60,15 @@
 ---
 ` + input
 
-	writeSource(t, "content/simple.md", contentFile)
+	writeSource(t, fs, "content/simple.md", contentFile)
 
-	h, err := newHugoSitesDefaultLanguage()
+	h, err := NewHugoSitesFromConfiguration(deps.DepsCfg{Fs: fs, WithTemplate: withTemplate})
 
-	if err != nil {
-		t.Fatalf("Failed to create sites: %s", err)
-	}
+	require.NoError(t, err)
+	require.Len(t, h.Sites, 1)
 
-	cfg := BuildCfg{SkipRender: true, withTemplate: withTemplate}
+	err = h.Build(BuildCfg{})
 
-	err = h.Build(cfg)
-
 	if err != nil && !expectError {
 		t.Fatalf("Shortcode rendered error %s.", err)
 	}
@@ -89,7 +92,7 @@
 
 func TestShortcodeGoFuzzReports(t *testing.T) {
 
-	p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.Template) error {
+	p, _ := pageFromString(simplePage, "simple.md", func(templ tplapi.Template) error {
 		return templ.AddInternalShortcode("sc.html", `foo`)
 	})
 
@@ -124,7 +127,7 @@
 
 // Issue #929
 func TestHyphenatedSC(t *testing.T) {
-	wt := func(tem tpl.Template) error {
+	wt := func(tem tplapi.Template) error {
 		tem.AddInternalShortcode("hyphenated-video.html", `Playing Video {{ .Get 0 }}`)
 		return nil
 	}
@@ -134,7 +137,7 @@
 
 // Issue #1753
 func TestNoTrailingNewline(t *testing.T) {
-	wt := func(tem tpl.Template) error {
+	wt := func(tem tplapi.Template) error {
 		tem.AddInternalShortcode("a.html", `{{ .Get 0 }}`)
 		return nil
 	}
@@ -143,7 +146,7 @@
 }
 
 func TestPositionalParamSC(t *testing.T) {
-	wt := func(tem tpl.Template) error {
+	wt := func(tem tplapi.Template) error {
 		tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 0 }}`)
 		return nil
 	}
@@ -156,7 +159,7 @@
 }
 
 func TestPositionalParamIndexOutOfBounds(t *testing.T) {
-	wt := func(tem tpl.Template) error {
+	wt := func(tem tplapi.Template) error {
 		tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 1 }}`)
 		return nil
 	}
@@ -166,7 +169,7 @@
 // some repro issues for panics in Go Fuzz testing
 
 func TestNamedParamSC(t *testing.T) {
-	wt := func(tem tpl.Template) error {
+	wt := func(tem tplapi.Template) error {
 		tem.AddInternalShortcode("img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
 		return nil
 	}
@@ -180,7 +183,7 @@
 
 // Issue #2294
 func TestNestedNamedMissingParam(t *testing.T) {
-	wt := func(tem tpl.Template) error {
+	wt := func(tem tplapi.Template) error {
 		tem.AddInternalShortcode("acc.html", `<div class="acc">{{ .Inner }}</div>`)
 		tem.AddInternalShortcode("div.html", `<div {{with .Get "class"}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
 		tem.AddInternalShortcode("div2.html", `<div {{with .Get 0}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
@@ -192,7 +195,7 @@
 }
 
 func TestIsNamedParamsSC(t *testing.T) {
-	wt := func(tem tpl.Template) error {
+	wt := func(tem tplapi.Template) error {
 		tem.AddInternalShortcode("byposition.html", `<div id="{{ .Get 0 }}">`)
 		tem.AddInternalShortcode("byname.html", `<div id="{{ .Get "id" }}">`)
 		tem.AddInternalShortcode("ifnamedparams.html", `<div id="{{ if .IsNamedParams }}{{ .Get "id" }}{{ else }}{{ .Get 0 }}{{end}}">`)
@@ -207,7 +210,7 @@
 }
 
 func TestInnerSC(t *testing.T) {
-	wt := func(tem tpl.Template) error {
+	wt := func(tem tplapi.Template) error {
 		tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
 		return nil
 	}
@@ -217,7 +220,7 @@
 }
 
 func TestInnerSCWithMarkdown(t *testing.T) {
-	wt := func(tem tpl.Template) error {
+	wt := func(tem tplapi.Template) error {
 		tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
 		return nil
 	}
@@ -230,7 +233,7 @@
 }
 
 func TestInnerSCWithAndWithoutMarkdown(t *testing.T) {
-	wt := func(tem tpl.Template) error {
+	wt := func(tem tplapi.Template) error {
 		tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
 		return nil
 	}
@@ -259,7 +262,7 @@
 }
 
 func TestNestedSC(t *testing.T) {
-	wt := func(tem tpl.Template) error {
+	wt := func(tem tplapi.Template) error {
 		tem.AddInternalShortcode("scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
 		tem.AddInternalShortcode("scn2.html", `<div>SC2</div>`)
 		return nil
@@ -270,7 +273,7 @@
 }
 
 func TestNestedComplexSC(t *testing.T) {
-	wt := func(tem tpl.Template) error {
+	wt := func(tem tplapi.Template) error {
 		tem.AddInternalShortcode("row.html", `-row-{{ .Inner}}-rowStop-`)
 		tem.AddInternalShortcode("column.html", `-col-{{.Inner    }}-colStop-`)
 		tem.AddInternalShortcode("aside.html", `-aside-{{    .Inner  }}-asideStop-`)
@@ -285,7 +288,7 @@
 }
 
 func TestParentShortcode(t *testing.T) {
-	wt := func(tem tpl.Template) error {
+	wt := func(tem tplapi.Template) error {
 		tem.AddInternalShortcode("r1.html", `1: {{ .Get "pr1" }} {{ .Inner }}`)
 		tem.AddInternalShortcode("r2.html", `2: {{ .Parent.Get "pr1" }}{{ .Get "pr2" }} {{ .Inner }}`)
 		tem.AddInternalShortcode("r3.html", `3: {{ .Parent.Parent.Get "pr1" }}{{ .Parent.Get "pr2" }}{{ .Get "pr3" }} {{ .Inner }}`)
@@ -382,7 +385,7 @@
 			fmt.Sprintf("Hello %sworld%s. And that's it.", testScPlaceholderRegexp, testScPlaceholderRegexp), ""},
 	} {
 
-		p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.Template) error {
+		p, _ := pageFromString(simplePage, "simple.md", func(templ tplapi.Template) error {
 			templ.AddInternalShortcode("tag.html", `tag`)
 			templ.AddInternalShortcode("sc1.html", `sc1`)
 			templ.AddInternalShortcode("sc2.html", `sc2`)
@@ -471,7 +474,7 @@
 		expected    string
 	}{
 		{"sect/doc1.md", `a{{< b >}}c`,
-			filepath.FromSlash("sect/doc1/index.html"), "<p>abc</p>\n"},
+			filepath.FromSlash("public/sect/doc1/index.html"), "<p>abc</p>\n"},
 		// Issue #1642: Multiple shortcodes wrapped in P
 		// Deliberately forced to pass even if they maybe shouldn't.
 		{"sect/doc2.md", `a
@@ -481,7 +484,7 @@
 {{< d >}}
 
 e`,
-			filepath.FromSlash("sect/doc2/index.html"),
+			filepath.FromSlash("public/sect/doc2/index.html"),
 			"<p>a</p>\n\n<p>b<br />\nc\nd</p>\n\n<p>e</p>\n"},
 		{"sect/doc3.md", `a
 
@@ -491,7 +494,7 @@
 {{< d >}}
 
 e`,
-			filepath.FromSlash("sect/doc3/index.html"),
+			filepath.FromSlash("public/sect/doc3/index.html"),
 			"<p>a</p>\n\n<p>b<br />\nc</p>\n\nd\n\n<p>e</p>\n"},
 		{"sect/doc4.md", `a
 {{< b >}}
@@ -510,22 +513,22 @@
 
 
 `,
-			filepath.FromSlash("sect/doc4/index.html"),
+			filepath.FromSlash("public/sect/doc4/index.html"),
 			"<p>a\nb\nb\nb\nb\nb</p>\n"},
 		// #2192 #2209: Shortcodes in markdown headers
 		{"sect/doc5.md", `# {{< b >}}	
 ## {{% c %}}`,
-			filepath.FromSlash("sect/doc5/index.html"), "\n\n<h1 id=\"hahahugoshortcode-1hbhb\">b</h1>\n\n<h2 id=\"hahahugoshortcode-2hbhb\">c</h2>\n"},
+			filepath.FromSlash("public/sect/doc5/index.html"), "\n\n<h1 id=\"hahahugoshortcode-1hbhb\">b</h1>\n\n<h2 id=\"hahahugoshortcode-2hbhb\">c</h2>\n"},
 		// #2223 pygments
 		{"sect/doc6.md", "\n```bash\nb: {{< b >}} c: {{% c %}}\n```\n",
-			filepath.FromSlash("sect/doc6/index.html"),
+			filepath.FromSlash("public/sect/doc6/index.html"),
 			"b: b c: c\n</code></pre></div>\n"},
 		// #2249
 		{"sect/doc7.ad", `_Shortcodes:_ *b: {{< b >}} c: {{% c %}}*`,
-			filepath.FromSlash("sect/doc7/index.html"),
+			filepath.FromSlash("public/sect/doc7/index.html"),
 			"<div class=\"paragraph\">\n<p><em>Shortcodes:</em> <strong>b: b c: c</strong></p>\n</div>\n"},
 		{"sect/doc8.rst", `**Shortcodes:** *b: {{< b >}} c: {{% c %}}*`,
-			filepath.FromSlash("sect/doc8/index.html"),
+			filepath.FromSlash("public/sect/doc8/index.html"),
 			"<div class=\"document\">\n\n\n<p><strong>Shortcodes:</strong> <em>b: b c: c</em></p>\n</div>"},
 		{"sect/doc9.mmark", `
 ---
@@ -534,7 +537,7 @@
     parent: 'parent'
 ---
 **Shortcodes:** *b: {{< b >}} c: {{% c %}}*`,
-			filepath.FromSlash("sect/doc9/index.html"),
+			filepath.FromSlash("public/sect/doc9/index.html"),
 			"<p><strong>Shortcodes:</strong> <em>b: b c: c</em></p>\n"},
 		// Issue #1229: Menus not available in shortcode.
 		{"sect/doc10.md", `---
@@ -545,7 +548,7 @@
 - Menu
 ---
 **Menus:** {{< menu >}}`,
-			filepath.FromSlash("sect/doc10/index.html"),
+			filepath.FromSlash("public/sect/doc10/index.html"),
 			"<p><strong>Menus:</strong> 1</p>\n"},
 		// Issue #2323: Taxonomies not available in shortcode.
 		{"sect/doc11.md", `---
@@ -553,7 +556,7 @@
 - Bugs
 ---
 **Tags:** {{< tags >}}`,
-			filepath.FromSlash("sect/doc11/index.html"),
+			filepath.FromSlash("public/sect/doc11/index.html"),
 			"<p><strong>Tags:</strong> 2</p>\n"},
 	}
 
@@ -563,13 +566,7 @@
 		sources[i] = source.ByteSource{Name: filepath.FromSlash(test.contentPath), Content: []byte(test.content)}
 	}
 
-	s := &Site{
-		Source:   &source.InMemorySource{ByteSource: sources},
-		targets:  targetList{page: &target.PagePub{UglyURLs: false}},
-		Language: helpers.NewDefaultLanguage(),
-	}
-
-	addTemplates := func(templ tpl.Template) error {
+	addTemplates := func(templ tplapi.Template) error {
 		templ.AddTemplate("_default/single.html", "{{.Content}}")
 
 		templ.AddInternalShortcode("b.html", `b`)
@@ -582,15 +579,11 @@
 
 	}
 
-	sites, err := newHugoSites(DepsCfg{}, s)
+	fs := hugofs.NewMem()
 
-	if err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	writeSourcesToSource(t, "content", fs, sources...)
 
-	if err = sites.Build(BuildCfg{withTemplate: addTemplates}); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	buildSingleSite(t, deps.DepsCfg{WithTemplate: addTemplates, Fs: fs}, BuildCfg{})
 
 	for _, test := range tests {
 		if strings.HasSuffix(test.contentPath, ".ad") && !helpers.HasAsciidoc() {
@@ -604,17 +597,7 @@
 			continue
 		}
 
-		file, err := hugofs.Destination().Open(test.outFile)
-
-		if err != nil {
-			t.Fatalf("Did not find %s in target: %s", test.outFile, err)
-		}
-
-		content := helpers.ReaderToString(file)
-
-		if !strings.Contains(content, test.expected) {
-			t.Fatalf("%s content expected:\n%q\ngot:\n%q", test.outFile, test.expected, content)
-		}
+		assertFileContent(t, fs, test.outFile, true, test.expected)
 	}
 
 }
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -35,6 +35,7 @@
 	"github.com/spf13/afero"
 	"github.com/spf13/cast"
 	bp "github.com/spf13/hugo/bufferpool"
+	"github.com/spf13/hugo/deps"
 	"github.com/spf13/hugo/helpers"
 	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/hugo/parser"
@@ -41,6 +42,7 @@
 	"github.com/spf13/hugo/source"
 	"github.com/spf13/hugo/target"
 	"github.com/spf13/hugo/tpl"
+	"github.com/spf13/hugo/tplapi"
 	"github.com/spf13/hugo/transform"
 	"github.com/spf13/nitro"
 	"github.com/spf13/viper"
@@ -106,57 +108,81 @@
 	Language       *helpers.Language
 
 	// Logger etc.
-	*deps
+	*deps.Deps `json:"-"`
 }
 
 // reset returns a new Site prepared for rebuild.
 func (s *Site) reset() *Site {
-	return &Site{deps: s.deps, Language: s.Language, owner: s.owner, PageCollections: newPageCollections()}
+	return &Site{Deps: s.Deps, owner: s.owner, PageCollections: newPageCollections()}
 }
 
-// newSite creates a new site in the given language.
-func newSite(lang *helpers.Language, deps *deps, withTemplate ...func(templ tpl.Template) error) *Site {
+// newSite creates a new site with the given configuration.
+func newSite(cfg deps.DepsCfg) (*Site, error) {
 	c := newPageCollections()
-	// TODO(bep) globals
-	viper.Set("currentContentLanguage", lang)
 
-	if deps == nil {
-		depsCfg := DepsCfg{WithTemplate: withTemplate}
-		deps = newDeps(depsCfg)
+	if cfg.Language == nil {
+		cfg.Language = helpers.NewDefaultLanguage()
 	}
 
-	return &Site{deps: deps, Language: lang, PageCollections: c, Info: newSiteInfo(siteBuilderCfg{pageCollections: c, language: lang})}
+	s := &Site{PageCollections: c, Language: cfg.Language}
 
+	s.Info = newSiteInfo(siteBuilderCfg{s: s, pageCollections: c, language: s.Language})
+	return s, nil
+
 }
 
+// NewSite creates a new site with the given dependency configuration.
+// The site will have a template system loaded and ready to use.
+// Note: This is mainly used in single site tests.
+func NewSite(cfg deps.DepsCfg) (*Site, error) {
+	s, err := newSite(cfg)
+
+	if err != nil {
+		return nil, err
+	}
+
+	if err := applyDepsIfNeeded(cfg, s); err != nil {
+		return nil, err
+	}
+
+	return s, nil
+}
+
 // NewSiteDefaultLang creates a new site in the default language.
-func NewSiteDefaultLang(withTemplate ...func(templ tpl.Template) error) *Site {
-	return newSite(helpers.NewDefaultLanguage(), nil, withTemplate...)
+// The site will have a template system loaded and ready to use.
+// Note: This is mainly used in single site tests.
+func NewSiteDefaultLang(withTemplate ...func(templ tplapi.Template) error) (*Site, error) {
+	return newSiteForLang(helpers.NewDefaultLanguage(), withTemplate...)
 }
 
-// Convenience func used in tests.
-func newSiteFromSources(pathContentPairs ...string) *Site {
-	if len(pathContentPairs)%2 != 0 {
-		panic("pathContentPairs must come in pairs")
+// NewSiteDefaultLang creates a new site in the default language.
+// The site will have a template system loaded and ready to use.
+// Note: This is mainly used in single site tests.
+func NewEnglishSite(withTemplate ...func(templ tplapi.Template) error) (*Site, error) {
+	return newSiteForLang(helpers.NewLanguage("en"), withTemplate...)
+}
+
+// NewSiteDefaultLang creates a new site in the default language.
+func newSiteForLang(lang *helpers.Language, withTemplate ...func(templ tplapi.Template) error) (*Site, error) {
+	withTemplates := func(templ tplapi.Template) error {
+		for _, wt := range withTemplate {
+			if err := wt(templ); err != nil {
+				return err
+			}
+		}
+		return nil
 	}
+	cfg := deps.DepsCfg{WithTemplate: withTemplates, Language: lang}
+	s, err := newSite(cfg)
 
-	sources := make([]source.ByteSource, 0)
-
-	for i := 0; i < len(pathContentPairs); i += 2 {
-		path := pathContentPairs[i]
-		content := pathContentPairs[i+1]
-		sources = append(sources, source.ByteSource{Name: filepath.FromSlash(path), Content: []byte(content)})
+	if err != nil {
+		return nil, err
 	}
 
-	lang := helpers.NewDefaultLanguage()
-
-	return &Site{
-		deps:            newDeps(DepsCfg{}),
-		PageCollections: newPageCollections(),
-		Source:          &source.InMemorySource{ByteSource: sources},
-		Language:        lang,
-		Info:            newSiteInfo(siteBuilderCfg{language: lang}),
+	if err := applyDepsIfNeeded(cfg, s); err != nil {
+		return nil, err
 	}
+	return s, nil
 }
 
 type targetList struct {
@@ -202,6 +228,7 @@
 	Data                  *map[string]interface{}
 
 	owner                          *HugoSites
+	s                              *Site
 	multilingual                   *Multilingual
 	Language                       *helpers.Language
 	LanguagePrefix                 string
@@ -208,8 +235,6 @@
 	Languages                      helpers.Languages
 	defaultContentLanguageInSubdir bool
 	sectionPagesMenu               string
-
-	pathSpec *helpers.PathSpec
 }
 
 func (s *SiteInfo) String() string {
@@ -219,15 +244,19 @@
 // Used in tests.
 
 type siteBuilderCfg struct {
-	language        *helpers.Language
+	language *helpers.Language
+	// TOD(bep) globals fs
+	s               *Site
+	fs              *hugofs.Fs
 	pageCollections *PageCollections
 	baseURL         string
 }
 
+// TODO(bep) globals get rid of this
 func newSiteInfo(cfg siteBuilderCfg) SiteInfo {
 	return SiteInfo{
+		s:               cfg.s,
 		BaseURL:         template.URL(cfg.baseURL),
-		pathSpec:        helpers.NewPathSpecFromConfig(cfg.language),
 		multilingual:    newMultiLingualForLanguage(cfg.language),
 		PageCollections: cfg.pageCollections,
 	}
@@ -498,7 +527,7 @@
 // It returns whetever the content source was changed.
 func (s *Site) reProcess(events []fsnotify.Event) (whatChanged, error) {
 
-	s.log.DEBUG.Printf("Rebuild for events %q", events)
+	s.Log.DEBUG.Printf("Rebuild for events %q", events)
 
 	s.timerStep("initialize rebuild")
 
@@ -533,8 +562,25 @@
 	}
 
 	if len(tmplChanged) > 0 {
-		s.prepTemplates(nil)
-		s.owner.tmpl.PrintErrors()
+		sites := s.owner.Sites
+		first := sites[0]
+
+		// TOD(bep) globals clean
+		if err := first.Deps.LoadTemplates(); err != nil {
+			s.Log.ERROR.Println(err)
+		}
+
+		s.Tmpl.PrintErrors()
+
+		for i := 1; i < len(sites); i++ {
+			site := sites[i]
+			var err error
+			site.Deps, err = first.Deps.ForLanguage(site.Language)
+			if err != nil {
+				return whatChanged{}, err
+			}
+		}
+
 		s.timerStep("template prep")
 	}
 
@@ -544,7 +590,7 @@
 
 	if len(i18nChanged) > 0 {
 		if err := s.readI18nSources(); err != nil {
-			s.log.ERROR.Println(err)
+			s.Log.ERROR.Println(err)
 		}
 	}
 
@@ -595,7 +641,7 @@
 		// it's been updated
 		if ev.Op&fsnotify.Rename == fsnotify.Rename {
 			// If the file is still on disk, it's only been updated, if it's not, it's been moved
-			if ex, err := afero.Exists(hugofs.Source(), ev.Name); !ex || err != nil {
+			if ex, err := afero.Exists(s.Fs.Source, ev.Name); !ex || err != nil {
 				path, _ := helpers.GetRelativePath(ev.Name, s.getContentDir(ev.Name))
 				s.removePageByPath(path)
 				continue
@@ -613,7 +659,7 @@
 		file, err := s.reReadFile(ev.Name)
 
 		if err != nil {
-			s.log.ERROR.Println("Error reading file", ev.Name, ";", err)
+			s.Log.ERROR.Println("Error reading file", ev.Name, ";", err)
 		}
 
 		if file != nil {
@@ -647,7 +693,7 @@
 	for i := 0; i < 2; i++ {
 		err := <-errs
 		if err != nil {
-			s.log.ERROR.Println(err)
+			s.Log.ERROR.Println(err)
 		}
 	}
 
@@ -660,29 +706,8 @@
 
 }
 
-func (s *Site) prepTemplates(withTemplate func(templ tpl.Template) error) error {
-
-	wt := func(tmpl tpl.Template) error {
-		// TODO(bep) global error handling
-		tmpl.LoadTemplates(s.absLayoutDir())
-		if s.hasTheme() {
-			tmpl.LoadTemplatesWithPrefix(s.absThemeDir()+"/layouts", "theme")
-		}
-		if withTemplate != nil {
-			if err := withTemplate(tmpl); err != nil {
-				return err
-			}
-		}
-		return nil
-	}
-
-	s.refreshTemplates(wt)
-
-	return nil
-}
-
 func (s *Site) loadData(sources []source.Input) (err error) {
-	s.log.DEBUG.Printf("Load Data from %q", sources)
+	s.Log.DEBUG.Printf("Load Data from %q", sources)
 	s.Data = make(map[string]interface{})
 	var current map[string]interface{}
 	for _, currentSource := range sources {
@@ -717,7 +742,7 @@
 						// this warning could happen if
 						// 1. A theme uses the same key; the main data folder wins
 						// 2. A sub folder uses the same key: the sub folder wins
-						s.log.WARN.Printf("Data for key '%s' in path '%s' is overridden in subfolder", key, r.Path())
+						s.Log.WARN.Printf("Data for key '%s' in path '%s' is overridden in subfolder", key, r.Path())
 					}
 					data[key] = value
 				}
@@ -740,7 +765,7 @@
 	case "toml":
 		return parser.HandleTOMLMetaData(f.Bytes())
 	default:
-		s.log.WARN.Printf("Data not supported for extension '%s'", f.Extension())
+		s.Log.WARN.Printf("Data not supported for extension '%s'", f.Extension())
 		return nil, nil
 	}
 }
@@ -747,14 +772,14 @@
 
 func (s *Site) readI18nSources() error {
 
-	i18nSources := []source.Input{&source.Filesystem{Base: s.absI18nDir()}}
+	i18nSources := []source.Input{source.NewFilesystem(s.Fs, s.absI18nDir())}
 
-	themeI18nDir, err := helpers.GetThemeI18nDirPath()
+	themeI18nDir, err := s.PathSpec.GetThemeI18nDirPath()
 	if err == nil {
-		i18nSources = []source.Input{&source.Filesystem{Base: themeI18nDir}, i18nSources[0]}
+		i18nSources = []source.Input{source.NewFilesystem(s.Fs, themeI18nDir), i18nSources[0]}
 	}
 
-	if err = loadI18n(i18nSources); err != nil {
+	if err = s.loadI18n(i18nSources); err != nil {
 		return err
 	}
 
@@ -763,12 +788,12 @@
 
 func (s *Site) readDataFromSourceFS() error {
 	dataSources := make([]source.Input, 0, 2)
-	dataSources = append(dataSources, &source.Filesystem{Base: s.absDataDir()})
+	dataSources = append(dataSources, source.NewFilesystem(s.Fs, s.absDataDir()))
 
 	// have to be last - duplicate keys in earlier entries will win
-	themeDataDir, err := helpers.GetThemeDataDirPath()
+	themeDataDir, err := s.PathSpec.GetThemeDataDirPath()
 	if err == nil {
-		dataSources = append(dataSources, &source.Filesystem{Base: themeDataDir})
+		dataSources = append(dataSources, source.NewFilesystem(s.Fs, themeDataDir))
 	}
 
 	err = s.loadData(dataSources)
@@ -781,11 +806,8 @@
 	if err = s.initialize(); err != nil {
 		return
 	}
+	s.timerStep("initialize")
 
-	s.prepTemplates(config.withTemplate)
-	s.owner.tmpl.PrintErrors()
-	s.timerStep("initialize & template prep")
-
 	if err = s.readDataFromSourceFS(); err != nil {
 		return
 	}
@@ -817,7 +839,6 @@
 	viper.Set("currentContentLanguage", s.Language)
 	// Cache the current config.
 	helpers.InitConfigProviderForCurrentContentLanguage()
-	s.Info.pathSpec = helpers.CurrentPathSpec()
 	return tpl.SetTranslateLang(s.Language)
 }
 
@@ -873,7 +894,7 @@
 
 	// May be supplied in tests.
 	if s.Source != nil && len(s.Source.Files()) > 0 {
-		s.log.DEBUG.Println("initialize: Source is already set")
+		s.Log.DEBUG.Println("initialize: Source is already set")
 		return
 	}
 
@@ -883,10 +904,7 @@
 
 	staticDir := helpers.AbsPathify(viper.GetString("staticDir") + "/")
 
-	s.Source = &source.Filesystem{
-		AvoidPaths: []string{staticDir},
-		Base:       s.absContentDir(),
-	}
+	s.Source = source.NewFilesystem(s.Fs, s.absContentDir(), staticDir)
 
 	return
 }
@@ -897,7 +915,7 @@
 	if s.IsMultiLingual() {
 		base = s.Language.Lang
 	}
-	return s.pathSpec.AbsURL(base, false)
+	return s.owner.AbsURL(base, false)
 }
 
 // SitemapAbsURL is a convenience method giving the absolute URL to the sitemap.
@@ -966,7 +984,7 @@
 		Permalinks:                     permalinks,
 		Data:                           &s.Data,
 		owner:                          s.owner,
-		pathSpec:                       helpers.NewPathSpecFromConfig(lang),
+		s:                              s,
 	}
 
 	s.Info.RSSLink = s.Info.permalinkStr(lang.GetString("rssURI"))
@@ -1081,11 +1099,11 @@
 		return base
 	}
 
-	realDir, err := helpers.GetRealPath(hugofs.Source(), base)
+	realDir, err := helpers.GetRealPath(s.Fs.Source, base)
 
 	if err != nil {
 		if !os.IsNotExist(err) {
-			s.log.ERROR.Printf("Failed to get real path for %s: %s", path, err)
+			s.Log.ERROR.Printf("Failed to get real path for %s: %s", path, err)
 		}
 		return ""
 	}
@@ -1102,7 +1120,7 @@
 }
 
 func (s *Site) checkDirectories() (err error) {
-	if b, _ := helpers.DirExists(s.absContentDir(), hugofs.Source()); !b {
+	if b, _ := helpers.DirExists(s.absContentDir(), s.Fs.Source); !b {
 		return errors.New("No source directory found, expecting to find it at " + s.absContentDir())
 	}
 	return
@@ -1110,10 +1128,10 @@
 
 // reReadFile resets file to be read from disk again
 func (s *Site) reReadFile(absFilePath string) (*source.File, error) {
-	s.log.INFO.Println("rereading", absFilePath)
+	s.Log.INFO.Println("rereading", absFilePath)
 	var file *source.File
 
-	reader, err := source.NewLazyFileReader(hugofs.Source(), absFilePath)
+	reader, err := source.NewLazyFileReader(s.Fs.Source, absFilePath)
 	if err != nil {
 		return nil, err
 	}
@@ -1131,7 +1149,7 @@
 		panic(fmt.Sprintf("s.Source not set %s", s.absContentDir()))
 	}
 
-	s.log.DEBUG.Printf("Read %d pages from source", len(s.Source.Files()))
+	s.Log.DEBUG.Printf("Read %d pages from source", len(s.Source.Files()))
 
 	errs := make(chan error)
 	if len(s.Source.Files()) < 1 {
@@ -1231,7 +1249,7 @@
 	if h != nil {
 		h.Read(file, s, results)
 	} else {
-		s.log.ERROR.Println("Unsupported File Type", file.Path())
+		s.Log.ERROR.Println("Unsupported File Type", file.Path())
 	}
 }
 
@@ -1372,17 +1390,17 @@
 		for name, menu := range menus {
 			m, err := cast.ToSliceE(menu)
 			if err != nil {
-				s.log.ERROR.Printf("unable to process menus in site config\n")
-				s.log.ERROR.Println(err)
+				s.Log.ERROR.Printf("unable to process menus in site config\n")
+				s.Log.ERROR.Println(err)
 			} else {
 				for _, entry := range m {
-					s.log.DEBUG.Printf("found menu: %q, in site config\n", name)
+					s.Log.DEBUG.Printf("found menu: %q, in site config\n", name)
 
 					menuEntry := MenuEntry{Menu: name}
 					ime, err := cast.ToStringMapE(entry)
 					if err != nil {
-						s.log.ERROR.Printf("unable to process menus in site config\n")
-						s.log.ERROR.Println(err)
+						s.Log.ERROR.Printf("unable to process menus in site config\n")
+						s.Log.ERROR.Println(err)
 					}
 
 					menuEntry.marshallMap(ime)
@@ -1407,7 +1425,7 @@
 	}
 	// make it match the nodes
 	menuEntryURL := in
-	menuEntryURL = helpers.SanitizeURLKeepTrailingSlash(s.pathSpec.URLize(menuEntryURL))
+	menuEntryURL = helpers.SanitizeURLKeepTrailingSlash(s.s.PathSpec.URLize(menuEntryURL))
 	if !s.canonifyURLs {
 		menuEntryURL = helpers.AddContextRoot(string(s.BaseURL), menuEntryURL)
 	}
@@ -1454,7 +1472,7 @@
 
 		for name, me := range p.Menus() {
 			if _, ok := flat[twoD{name, me.KeyName()}]; ok {
-				s.log.ERROR.Printf("Two or more menu items have the same name/identifier in Menu %q: %q.\nRename or set an unique identifier.\n", name, me.KeyName())
+				s.Log.ERROR.Printf("Two or more menu items have the same name/identifier in Menu %q: %q.\nRename or set an unique identifier.\n", name, me.KeyName())
 				continue
 			}
 			flat[twoD{name, me.KeyName()}] = me
@@ -1490,6 +1508,13 @@
 	}
 }
 
+func (s *Site) getTaxonomyKey(key string) string {
+	if s.Info.preserveTaxonomyNames {
+		// Keep as is
+		return key
+	}
+	return s.PathSpec.MakePathSanitized(key)
+}
 func (s *Site) assembleTaxonomies() {
 	s.Taxonomies = make(TaxonomyList)
 	s.taxonomiesPluralSingular = make(map[string]string)
@@ -1497,7 +1522,7 @@
 
 	taxonomies := s.Language.GetStringMapString("taxonomies")
 
-	s.log.INFO.Printf("found taxonomies: %#v\n", taxonomies)
+	s.Log.INFO.Printf("found taxonomies: %#v\n", taxonomies)
 
 	for singular, plural := range taxonomies {
 		s.Taxonomies[plural] = make(Taxonomy)
@@ -1513,21 +1538,21 @@
 				if v, ok := vals.([]string); ok {
 					for _, idx := range v {
 						x := WeightedPage{weight.(int), p}
-						s.Taxonomies[plural].add(idx, x, s.Info.preserveTaxonomyNames)
+						s.Taxonomies[plural].add(s.getTaxonomyKey(idx), x)
 						if s.Info.preserveTaxonomyNames {
 							// Need to track the original
-							s.taxonomiesOrigKey[fmt.Sprintf("%s-%s", plural, kp(idx))] = idx
+							s.taxonomiesOrigKey[fmt.Sprintf("%s-%s", plural, s.PathSpec.MakePathSanitized(idx))] = idx
 						}
 					}
 				} else if v, ok := vals.(string); ok {
 					x := WeightedPage{weight.(int), p}
-					s.Taxonomies[plural].add(v, x, s.Info.preserveTaxonomyNames)
+					s.Taxonomies[plural].add(s.getTaxonomyKey(v), x)
 					if s.Info.preserveTaxonomyNames {
 						// Need to track the original
-						s.taxonomiesOrigKey[fmt.Sprintf("%s-%s", plural, kp(v))] = v
+						s.taxonomiesOrigKey[fmt.Sprintf("%s-%s", plural, s.PathSpec.MakePathSanitized(v))] = v
 					}
 				} else {
-					s.log.ERROR.Printf("Invalid %s in %s\n", plural, p.File.Path())
+					s.Log.ERROR.Printf("Invalid %s in %s\n", plural, p.File.Path())
 				}
 			}
 		}
@@ -1564,7 +1589,7 @@
 	sectionPages := s.findPagesByKind(KindSection)
 
 	for i, p := range regularPages {
-		s.Sections.add(p.Section(), WeightedPage{regularPages[i].Weight, regularPages[i]}, s.Info.preserveTaxonomyNames)
+		s.Sections.add(s.getTaxonomyKey(p.Section()), WeightedPage{regularPages[i].Weight, regularPages[i]})
 	}
 
 	// Add sections without regular pages, but with a content page
@@ -1665,18 +1690,18 @@
 // Stats prints Hugo builds stats to the console.
 // This is what you see after a successful hugo build.
 func (s *Site) Stats() {
-	s.log.FEEDBACK.Printf("Built site for language %s:\n", s.Language.Lang)
-	s.log.FEEDBACK.Println(s.draftStats())
-	s.log.FEEDBACK.Println(s.futureStats())
-	s.log.FEEDBACK.Println(s.expiredStats())
-	s.log.FEEDBACK.Printf("%d regular pages created\n", len(s.RegularPages))
-	s.log.FEEDBACK.Printf("%d other pages created\n", (len(s.Pages) - len(s.RegularPages)))
-	s.log.FEEDBACK.Printf("%d non-page files copied\n", len(s.Files))
-	s.log.FEEDBACK.Printf("%d paginator pages created\n", s.Info.paginationPageCount)
+	s.Log.FEEDBACK.Printf("Built site for language %s:\n", s.Language.Lang)
+	s.Log.FEEDBACK.Println(s.draftStats())
+	s.Log.FEEDBACK.Println(s.futureStats())
+	s.Log.FEEDBACK.Println(s.expiredStats())
+	s.Log.FEEDBACK.Printf("%d regular pages created\n", len(s.RegularPages))
+	s.Log.FEEDBACK.Printf("%d other pages created\n", (len(s.Pages) - len(s.RegularPages)))
+	s.Log.FEEDBACK.Printf("%d non-page files copied\n", len(s.Files))
+	s.Log.FEEDBACK.Printf("%d paginator pages created\n", s.Info.paginationPageCount)
 	taxonomies := s.Language.GetStringMapString("taxonomies")
 
 	for _, pl := range taxonomies {
-		s.log.FEEDBACK.Printf("%d %s created\n", len(s.Taxonomies[pl]), pl)
+		s.Log.FEEDBACK.Printf("%d %s created\n", len(s.Taxonomies[pl]), pl)
 	}
 
 }
@@ -1701,11 +1726,11 @@
 func (s *SiteInfo) permalinkStr(plink string) string {
 	return helpers.MakePermalink(
 		viper.GetString("baseURL"),
-		s.pathSpec.URLizeAndPrep(plink)).String()
+		s.s.PathSpec.URLizeAndPrep(plink)).String()
 }
 
 func (s *Site) renderAndWriteXML(name string, dest string, d interface{}, layouts ...string) error {
-	s.log.DEBUG.Printf("Render XML for %q to %q", name, dest)
+	s.Log.DEBUG.Printf("Render XML for %q to %q", name, dest)
 	renderBuffer := bp.GetBuffer()
 	defer bp.PutBuffer(renderBuffer)
 	renderBuffer.WriteString("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n")
@@ -1797,7 +1822,7 @@
 
 	if outBuffer.Len() == 0 {
 
-		s.log.WARN.Printf("%s is rendered empty\n", dest)
+		s.Log.WARN.Printf("%s is rendered empty\n", dest)
 		if dest == "/" {
 			debugAddend := ""
 			if !viper.GetBool("verbose") {
@@ -1829,7 +1854,8 @@
 func (s *Site) renderForLayouts(name string, d interface{}, w io.Writer, layouts ...string) error {
 	layout, found := s.findFirstLayout(layouts...)
 	if !found {
-		s.log.WARN.Printf("Unable to locate layout for %s: %s\n", name, layouts)
+		s.Log.WARN.Printf("[%s] Unable to locate layout for %s: %s\n", s.Language.Lang, name, layouts)
+
 		return nil
 	}
 
@@ -1850,7 +1876,7 @@
 
 func (s *Site) findFirstLayout(layouts ...string) (string, bool) {
 	for _, layout := range layouts {
-		if s.owner.tmpl.Lookup(layout) != nil {
+		if s.Tmpl.Lookup(layout) != nil {
 			return layout, true
 		}
 	}
@@ -1860,7 +1886,7 @@
 func (s *Site) renderThing(d interface{}, layout string, w io.Writer) error {
 
 	// If the template doesn't exist, then return, but leave the Writer open
-	if templ := s.owner.tmpl.Lookup(layout); templ != nil {
+	if templ := s.Tmpl.Lookup(layout); templ != nil {
 		return templ.Execute(w, d)
 	}
 	return fmt.Errorf("Layout not found: %s", layout)
@@ -1893,6 +1919,9 @@
 }
 
 func (s *Site) initTargetList() {
+	if s.Fs == nil {
+		panic("Must have Fs")
+	}
 	s.targetListInit.Do(func() {
 		langDir := ""
 		if s.Language.Lang != s.Info.multilingual.DefaultLang.Lang || s.Info.defaultContentLanguageInSubdir {
@@ -1900,6 +1929,7 @@
 		}
 		if s.targets.page == nil {
 			s.targets.page = &target.PagePub{
+				Fs:         s.Fs,
 				PublishDir: s.absPublishDir(),
 				UglyURLs:   viper.GetBool("uglyURLs"),
 				LangDir:    langDir,
@@ -1907,6 +1937,7 @@
 		}
 		if s.targets.pageUgly == nil {
 			s.targets.pageUgly = &target.PagePub{
+				Fs:         s.Fs,
 				PublishDir: s.absPublishDir(),
 				UglyURLs:   true,
 				LangDir:    langDir,
@@ -1914,17 +1945,20 @@
 		}
 		if s.targets.file == nil {
 			s.targets.file = &target.Filesystem{
+				Fs:         s.Fs,
 				PublishDir: s.absPublishDir(),
 			}
 		}
 		if s.targets.alias == nil {
 			s.targets.alias = &target.HTMLRedirectAlias{
+				Fs:         s.Fs,
 				PublishDir: s.absPublishDir(),
-				Templates:  s.owner.tmpl.Lookup("alias.html"),
+				Templates:  s.Tmpl.Lookup("alias.html"),
 			}
 		}
 		if s.targets.languageAlias == nil {
 			s.targets.languageAlias = &target.HTMLRedirectAlias{
+				Fs:         s.Fs,
 				PublishDir: s.absPublishDir(),
 				AllowRoot:  true,
 			}
@@ -1933,12 +1967,12 @@
 }
 
 func (s *Site) writeDestFile(path string, reader io.Reader) (err error) {
-	s.log.DEBUG.Println("creating file:", path)
+	s.Log.DEBUG.Println("creating file:", path)
 	return s.fileTarget().Publish(path, reader)
 }
 
 func (s *Site) writeDestPage(path string, publisher target.Publisher, reader io.Reader) (err error) {
-	s.log.DEBUG.Println("creating page:", path)
+	s.Log.DEBUG.Println("creating page:", path)
 	return publisher.Publish(path, reader)
 }
 
@@ -1956,11 +1990,11 @@
 		}
 		permalink, err = helpers.GetRelativePath(permalink, path)
 		if err != nil {
-			s.log.ERROR.Println("Failed to make a RelativeURL alias:", path, "redirecting to", permalink)
+			s.Log.ERROR.Println("Failed to make a RelativeURL alias:", path, "redirecting to", permalink)
 		}
 		permalink = filepath.ToSlash(permalink)
 	}
-	s.log.DEBUG.Println("creating alias:", path, "redirecting to", permalink)
+	s.Log.DEBUG.Println("creating alias:", path, "redirecting to", permalink)
 	return aliasPublisher.Publish(path, permalink, p)
 }
 
@@ -2051,7 +2085,7 @@
 }
 
 func (s *Site) setPageURLs(p *Page, in string) {
-	p.URLPath.URL = s.Info.pathSpec.URLizeAndPrep(in)
+	p.URLPath.URL = s.PathSpec.URLizeAndPrep(in)
 	p.URLPath.Permalink = s.Info.permalink(p.URLPath.URL)
 	p.RSSLink = template.HTML(s.Info.permalink(in + ".xml"))
 }
@@ -2063,7 +2097,7 @@
 	p.sections = []string{plural, key}
 
 	if s.Info.preserveTaxonomyNames {
-		key = s.Info.pathSpec.MakePathSanitized(key)
+		key = s.PathSpec.MakePathSanitized(key)
 	}
 
 	if s.Info.preserveTaxonomyNames {
--- a/hugolib/siteJSONEncode_test.go
+++ b/hugolib/siteJSONEncode_test.go
@@ -16,6 +16,11 @@
 import (
 	"encoding/json"
 	"testing"
+
+	"path/filepath"
+
+	"github.com/spf13/hugo/deps"
+	"github.com/spf13/hugo/hugofs"
 )
 
 // Issue #1123
@@ -23,9 +28,15 @@
 // May be smart to run with: -timeout 4000ms
 func TestEncodePage(t *testing.T) {
 
+	fs := hugofs.NewMem()
+
 	// borrowed from menu_test.go
-	s := createTestSite(menuPageSources)
-	testSiteSetup(s, t)
+	for _, src := range menuPageSources {
+		writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
+
+	}
+
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
 
 	_, err := json.Marshal(s)
 	check(t, err)
--- a/hugolib/site_render.go
+++ b/hugolib/site_render.go
@@ -66,7 +66,7 @@
 	for p := range pages {
 		targetPath := p.TargetPath()
 		layouts := p.layouts()
-		s.log.DEBUG.Printf("Render %s to %q with layouts %q", p.Kind, targetPath, layouts)
+		s.Log.DEBUG.Printf("Render %s to %q with layouts %q", p.Kind, targetPath, layouts)
 
 		if err := s.renderAndWritePage("page "+p.FullFilePath(), targetPath, p, s.appendThemeTemplates(layouts)...); err != nil {
 			results <- err
@@ -88,7 +88,7 @@
 // renderPaginator must be run after the owning Page has been rendered.
 func (s *Site) renderPaginator(p *Page) error {
 	if p.paginator != nil {
-		s.log.DEBUG.Printf("Render paginator for page %q", p.Path())
+		s.Log.DEBUG.Printf("Render paginator for page %q", p.Path())
 		paginatePath := helpers.Config().GetString("paginatePath")
 
 		// write alias for page 1
@@ -267,14 +267,14 @@
 	if s.owner.multilingual.enabled() {
 		mainLang := s.owner.multilingual.DefaultLang.Lang
 		if s.Info.defaultContentLanguageInSubdir {
-			mainLangURL := s.Info.pathSpec.AbsURL(mainLang, false)
-			s.log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
+			mainLangURL := s.PathSpec.AbsURL(mainLang, false)
+			s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
 			if err := s.publishDestAlias(s.languageAliasTarget(), "/", mainLangURL, nil); err != nil {
 				return err
 			}
 		} else {
-			mainLangURL := s.Info.pathSpec.AbsURL("", false)
-			s.log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
+			mainLangURL := s.PathSpec.AbsURL("", false)
+			s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
 			if err := s.publishDestAlias(s.languageAliasTarget(), mainLang, mainLangURL, nil); err != nil {
 				return err
 			}
--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -18,16 +18,15 @@
 	"path/filepath"
 	"strings"
 	"testing"
-	"time"
 
 	"github.com/bep/inflect"
 	jww "github.com/spf13/jwalterweatherman"
 
 	"github.com/spf13/hugo/helpers"
-	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/hugo/source"
 
-	"github.com/spf13/hugo/target"
+	"github.com/spf13/hugo/deps"
+	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -47,37 +46,6 @@
 	testMode = true
 }
 
-// Issue #1797
-func TestReadPagesFromSourceWithEmptySource(t *testing.T) {
-	testCommonResetState()
-
-	viper.Set("defaultExtension", "html")
-	viper.Set("verbose", true)
-	viper.Set("baseURL", "http://auth/bub")
-
-	sources := []source.ByteSource{}
-
-	s := &Site{
-		deps:    newDeps(DepsCfg{}),
-		Source:  &source.InMemorySource{ByteSource: sources},
-		targets: targetList{page: &target.PagePub{UglyURLs: true}},
-	}
-
-	var err error
-	d := time.Second * 2
-	ticker := time.NewTicker(d)
-	select {
-	case err = <-s.readPagesFromSource():
-		break
-	case <-ticker.C:
-		err = fmt.Errorf("ReadPagesFromSource() never returns in %s", d.String())
-	}
-	ticker.Stop()
-	if err != nil {
-		t.Fatalf("Unable to read source: %s", err)
-	}
-}
-
 func pageMust(p *Page, err error) *Page {
 	if err != nil {
 		panic(err)
@@ -86,12 +54,13 @@
 }
 
 func TestDegenerateRenderThingMissingTemplate(t *testing.T) {
-	s := newSiteFromSources("content/a/file.md", pageSimpleTitle)
 
-	if err := buildSiteSkipRender(s); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	fs := hugofs.NewMem()
 
+	writeSource(t, fs, filepath.Join("content", "a", "file.md"), pageSimpleTitle)
+
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+
 	require.Len(t, s.RegularPages, 1)
 
 	p := s.RegularPages[0]
@@ -104,13 +73,16 @@
 
 func TestRenderWithInvalidTemplate(t *testing.T) {
 
-	s := NewSiteDefaultLang()
-	if err := buildAndRenderSite(s, "missing", templateMissingFunc); err != nil {
-		t.Fatalf("Got build error: %s", err)
-	}
+	fs := hugofs.NewMem()
 
-	errCount := s.log.LogCountForLevelsGreaterThanorEqualTo(jww.LevelError)
+	writeSource(t, fs, filepath.Join("content", "foo.md"), "foo")
 
+	withTemplate := createWithTemplateFromNameValues("missing", templateMissingFunc)
+
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, WithTemplate: withTemplate}, BuildCfg{})
+
+	errCount := s.Log.LogCountForLevelsGreaterThanorEqualTo(jww.LevelError)
+
 	// TODO(bep) globals clean up the template error handling
 	// The template errors are stored in a slice etc. so we get 4 log entries
 	// When we should get only 1
@@ -122,7 +94,6 @@
 func TestDraftAndFutureRender(t *testing.T) {
 	testCommonResetState()
 
-	hugofs.InitMemFs()
 	sources := []source.ByteSource{
 		{Name: filepath.FromSlash("sect/doc1.md"), Content: []byte("---\ntitle: doc1\ndraft: true\npublishdate: \"2414-05-29\"\n---\n# doc1\n*some content*")},
 		{Name: filepath.FromSlash("sect/doc2.md"), Content: []byte("---\ntitle: doc2\ndraft: true\npublishdate: \"2012-05-29\"\n---\n# doc2\n*some content*")},
@@ -131,17 +102,14 @@
 	}
 
 	siteSetup := func(t *testing.T) *Site {
-		s := &Site{
-			deps:     newDeps(DepsCfg{}),
-			Source:   &source.InMemorySource{ByteSource: sources},
-			Language: helpers.NewDefaultLanguage(),
-		}
+		fs := hugofs.NewMem()
 
-		if err := buildSiteSkipRender(s); err != nil {
-			t.Fatalf("Failed to build site: %s", err)
+		for _, src := range sources {
+			writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
+
 		}
 
-		return s
+		return buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
 	}
 
 	viper.Set("baseURL", "http://auth/bub")
@@ -183,7 +151,6 @@
 func TestFutureExpirationRender(t *testing.T) {
 	testCommonResetState()
 
-	hugofs.InitMemFs()
 	sources := []source.ByteSource{
 		{Name: filepath.FromSlash("sect/doc3.md"), Content: []byte("---\ntitle: doc1\nexpirydate: \"2400-05-29\"\n---\n# doc1\n*some content*")},
 		{Name: filepath.FromSlash("sect/doc4.md"), Content: []byte("---\ntitle: doc2\nexpirydate: \"2000-05-29\"\n---\n# doc2\n*some content*")},
@@ -190,17 +157,14 @@
 	}
 
 	siteSetup := func(t *testing.T) *Site {
-		s := &Site{
-			deps:     newDeps(DepsCfg{}),
-			Source:   &source.InMemorySource{ByteSource: sources},
-			Language: helpers.NewDefaultLanguage(),
-		}
+		fs := hugofs.NewMem()
 
-		if err := buildSiteSkipRender(s); err != nil {
-			t.Fatalf("Failed to build site: %s", err)
+		for _, src := range sources {
+			writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
+
 		}
 
-		return s
+		return buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
 	}
 
 	viper.Set("baseURL", "http://auth/bub")
@@ -282,17 +246,19 @@
 		},
 	}
 
-	s := &Site{
-		deps:     newDeps(DepsCfg{}),
-		Source:   &source.InMemorySource{ByteSource: sources},
-		targets:  targetList{page: &target.PagePub{UglyURLs: uglyURLs}},
-		Language: helpers.NewDefaultLanguage(),
-	}
+	fs := hugofs.NewMem()
 
-	if err := buildAndRenderSite(s, "_default/single.html", "{{.Content}}"); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
+	for _, src := range sources {
+		writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
 	}
 
+	s := buildSingleSite(
+		t,
+		deps.DepsCfg{
+			Fs:           fs,
+			WithTemplate: createWithTemplateFromNameValues("_default/single.html", "{{.Content}}")},
+		BuildCfg{})
+
 	if len(s.RegularPages) != 3 {
 		t.Fatalf("Expected 3 got %d pages", len(s.AllPages))
 	}
@@ -301,23 +267,14 @@
 		doc      string
 		expected string
 	}{
-		{filepath.FromSlash(fmt.Sprintf("sect/doc1%s", expectedPathSuffix)), fmt.Sprintf("<p>Ref 2: %s/sect/doc2%s</p>\n", expectedBase, expectedURLSuffix)},
-		{filepath.FromSlash(fmt.Sprintf("sect/doc2%s", expectedPathSuffix)), fmt.Sprintf("<p><strong>Ref 1:</strong></p>\n\n%s/sect/doc1%s\n\n<p>THE END.</p>\n", expectedBase, expectedURLSuffix)},
-		{filepath.FromSlash(fmt.Sprintf("sect/doc3%s", expectedPathSuffix)), fmt.Sprintf("<p><strong>Ref 1:</strong>%s/sect/doc3%s.</p>\n", expectedBase, expectedURLSuffix)},
+		{filepath.FromSlash(fmt.Sprintf("public/sect/doc1%s", expectedPathSuffix)), fmt.Sprintf("<p>Ref 2: %s/sect/doc2%s</p>\n", expectedBase, expectedURLSuffix)},
+		{filepath.FromSlash(fmt.Sprintf("public/sect/doc2%s", expectedPathSuffix)), fmt.Sprintf("<p><strong>Ref 1:</strong></p>\n\n%s/sect/doc1%s\n\n<p>THE END.</p>\n", expectedBase, expectedURLSuffix)},
+		{filepath.FromSlash(fmt.Sprintf("public/sect/doc3%s", expectedPathSuffix)), fmt.Sprintf("<p><strong>Ref 1:</strong>%s/sect/doc3%s.</p>\n", expectedBase, expectedURLSuffix)},
 	}
 
 	for _, test := range tests {
-		file, err := hugofs.Destination().Open(test.doc)
+		assertFileContent(t, fs, test.doc, true, test.expected)
 
-		if err != nil {
-			t.Fatalf("Did not find %s in target: %s", test.doc, err)
-		}
-
-		content := helpers.ReaderToString(file)
-
-		if content != test.expected {
-			t.Fatalf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content)
-		}
 	}
 
 }
@@ -350,22 +307,20 @@
 		{Name: filepath.FromSlash("sect/doc2.md"), Content: []byte("---\nurl: /ugly.html\nmarkup: markdown\n---\n# title\ndoc2 *content*")},
 	}
 
-	s := &Site{
-		deps:     newDeps(DepsCfg{}),
-		Source:   &source.InMemorySource{ByteSource: sources},
-		targets:  targetList{page: &target.PagePub{UglyURLs: uglyURLs, PublishDir: "public"}},
-		Language: helpers.NewDefaultLanguage(),
-	}
+	fs := hugofs.NewMem()
 
-	if err := buildAndRenderSite(s,
-		"index.html", "Home Sweet {{ if.IsHome  }}Home{{ end }}.",
-		"_default/single.html", "{{.Content}}{{ if.IsHome  }}This is not home!{{ end }}",
-		"404.html", "Page Not Found.{{ if.IsHome  }}This is not home!{{ end }}",
-		"rss.xml", "<root>RSS</root>",
-		"sitemap.xml", "<root>SITEMAP</root>"); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
+	for _, src := range sources {
+		writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
 	}
 
+	writeSource(t, fs, filepath.Join("layouts", "index.html"), "Home Sweet {{ if.IsHome  }}Home{{ end }}.")
+	writeSource(t, fs, filepath.Join("layouts", "_default/single.html"), "{{.Content}}{{ if.IsHome  }}This is not home!{{ end }}")
+	writeSource(t, fs, filepath.Join("layouts", "404.html"), "Page Not Found.{{ if.IsHome  }}This is not home!{{ end }}")
+	writeSource(t, fs, filepath.Join("layouts", "rss.xml"), "<root>RSS</root>")
+	writeSource(t, fs, filepath.Join("layouts", "sitemap.xml"), "<root>SITEMAP</root>")
+
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+
 	var expectedPagePath string
 	if uglyURLs {
 		expectedPagePath = "public/sect/doc1.html"
@@ -391,7 +346,7 @@
 	}
 
 	for _, test := range tests {
-		content := readDestination(t, test.doc)
+		content := readDestination(t, fs, test.doc)
 
 		if content != test.expected {
 			t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content)
@@ -435,17 +390,16 @@
 		{Name: filepath.FromSlash("ラーメン/doc3.html"), Content: []byte("doc3")},
 	}
 
+	fs := hugofs.NewMem()
+
 	for _, source := range sources {
-		writeSource(t, filepath.Join("content", source.Name), string(source.Content))
+		writeSource(t, fs, filepath.Join("content", source.Name), string(source.Content))
 	}
 
-	s := NewSiteDefaultLang()
+	writeSource(t, fs, filepath.Join("layouts", "_default/single.html"), "{{.Content}}")
+	writeSource(t, fs, filepath.Join("layouts", "_default/list.html"), "{{.Title}}")
 
-	if err := buildAndRenderSite(s,
-		"_default/single.html", "{{.Content}}",
-		"_default/list.html", "{{ .Title }}"); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
 
 	tests := []struct {
 		doc         string
@@ -466,7 +420,7 @@
 			test.expected = inflect.Pluralize(test.expected)
 		}
 
-		assertFileContent(t, filepath.Join("public", test.doc), true, test.expected)
+		assertFileContent(t, fs, filepath.Join("public", test.doc), true, test.expected)
 	}
 
 }
@@ -473,7 +427,6 @@
 func TestSkipRender(t *testing.T) {
 	testCommonResetState()
 
-	hugofs.InitMemFs()
 	sources := []source.ByteSource{
 		{Name: filepath.FromSlash("sect/doc1.html"), Content: []byte("---\nmarkup: markdown\n---\n# title\nsome *content*")},
 		{Name: filepath.FromSlash("sect/doc2.html"), Content: []byte("<!doctype html><html><body>more content</body></html>")},
@@ -488,37 +441,38 @@
 	viper.Set("defaultExtension", "html")
 	viper.Set("verbose", true)
 	viper.Set("canonifyURLs", true)
+	viper.Set("uglyURLs", true)
 	viper.Set("baseURL", "http://auth/bub")
-	s := &Site{
-		deps:     newDeps(DepsCfg{}),
-		Source:   &source.InMemorySource{ByteSource: sources},
-		targets:  targetList{page: &target.PagePub{UglyURLs: true}},
-		Language: helpers.NewDefaultLanguage(),
-	}
 
-	if err := buildAndRenderSite(s,
-		"_default/single.html", "{{.Content}}",
-		"head", "<head><script src=\"script.js\"></script></head>",
-		"head_abs", "<head><script src=\"/script.js\"></script></head>"); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
+	fs := hugofs.NewMem()
+
+	for _, src := range sources {
+		writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
+
 	}
 
+	writeSource(t, fs, filepath.Join("layouts", "_default/single.html"), "{{.Content}}")
+	writeSource(t, fs, filepath.Join("layouts", "head"), "<head><script src=\"script.js\"></script></head>")
+	writeSource(t, fs, filepath.Join("layouts", "head_abs"), "<head><script src=\"/script.js\"></script></head>")
+
+	buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+
 	tests := []struct {
 		doc      string
 		expected string
 	}{
-		{filepath.FromSlash("sect/doc1.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"},
-		{filepath.FromSlash("sect/doc2.html"), "<!doctype html><html><body>more content</body></html>"},
-		{filepath.FromSlash("sect/doc3.html"), "\n\n<h1 id=\"doc3\">doc3</h1>\n\n<p><em>some</em> content</p>\n"},
-		{filepath.FromSlash("sect/doc4.html"), "\n\n<h1 id=\"doc4\">doc4</h1>\n\n<p><em>some content</em></p>\n"},
-		{filepath.FromSlash("sect/doc5.html"), "<!doctype html><html><head><script src=\"script.js\"></script></head><body>body5</body></html>"},
-		{filepath.FromSlash("sect/doc6.html"), "<!doctype html><html><head><script src=\"http://auth/bub/script.js\"></script></head><body>body5</body></html>"},
-		{filepath.FromSlash("doc7.html"), "<html><body>doc7 content</body></html>"},
-		{filepath.FromSlash("sect/doc8.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"},
+		{filepath.FromSlash("public/sect/doc1.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"},
+		{filepath.FromSlash("public/sect/doc2.html"), "<!doctype html><html><body>more content</body></html>"},
+		{filepath.FromSlash("public/sect/doc3.html"), "\n\n<h1 id=\"doc3\">doc3</h1>\n\n<p><em>some</em> content</p>\n"},
+		{filepath.FromSlash("public/sect/doc4.html"), "\n\n<h1 id=\"doc4\">doc4</h1>\n\n<p><em>some content</em></p>\n"},
+		{filepath.FromSlash("public/sect/doc5.html"), "<!doctype html><html><head><script src=\"script.js\"></script></head><body>body5</body></html>"},
+		{filepath.FromSlash("public/sect/doc6.html"), "<!doctype html><html><head><script src=\"http://auth/bub/script.js\"></script></head><body>body5</body></html>"},
+		{filepath.FromSlash("public/doc7.html"), "<html><body>doc7 content</body></html>"},
+		{filepath.FromSlash("public/sect/doc8.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"},
 	}
 
 	for _, test := range tests {
-		file, err := hugofs.Destination().Open(test.doc)
+		file, err := fs.Destination.Open(test.doc)
 		if err != nil {
 			t.Fatalf("Did not find %s in target.", test.doc)
 		}
@@ -535,8 +489,8 @@
 	testCommonResetState()
 
 	viper.Set("defaultExtension", "html")
+	viper.Set("uglyURLs", true)
 
-	hugofs.InitMemFs()
 	sources := []source.ByteSource{
 		{Name: filepath.FromSlash("sect/doc1.html"), Content: []byte("<!doctype html><html><head></head><body><a href=\"#frag1\">link</a></body></html>")},
 		{Name: filepath.FromSlash("blue/doc2.html"), Content: []byte("---\nf: t\n---\n<!doctype html><html><body>more content</body></html>")},
@@ -545,34 +499,27 @@
 		for _, canonify := range []bool{true, false} {
 			viper.Set("canonifyURLs", canonify)
 			viper.Set("baseURL", baseURL)
-			s := &Site{
-				deps:     newDeps(DepsCfg{}),
-				Source:   &source.InMemorySource{ByteSource: sources},
-				targets:  targetList{page: &target.PagePub{UglyURLs: true}},
-				Language: helpers.NewDefaultLanguage(),
-			}
-			t.Logf("Rendering with baseURL %q and canonifyURLs set %v", viper.GetString("baseURL"), canonify)
 
-			if err := buildAndRenderSite(s, "blue/single.html", templateWithURLAbs); err != nil {
-				t.Fatalf("Failed to build site: %s", err)
+			fs := hugofs.NewMem()
+
+			for _, src := range sources {
+				writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
+
 			}
 
+			writeSource(t, fs, filepath.Join("layouts", "blue/single.html"), templateWithURLAbs)
+
+			buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+
 			tests := []struct {
 				file, expected string
 			}{
-				{"blue/doc2.html", "<a href=\"%s/foobar.jpg\">Going</a>"},
-				{"sect/doc1.html", "<!doctype html><html><head></head><body><a href=\"#frag1\">link</a></body></html>"},
+				{"public/blue/doc2.html", "<a href=\"%s/foobar.jpg\">Going</a>"},
+				{"public/sect/doc1.html", "<!doctype html><html><head></head><body><a href=\"#frag1\">link</a></body></html>"},
 			}
 
 			for _, test := range tests {
 
-				file, err := hugofs.Destination().Open(filepath.FromSlash(test.file))
-				if err != nil {
-					t.Fatalf("Unable to locate rendered content: %s", test.file)
-				}
-
-				content := helpers.ReaderToString(file)
-
 				expected := test.expected
 
 				if strings.Contains(expected, "%s") {
@@ -583,9 +530,8 @@
 					expected = strings.Replace(expected, baseURL, "", -1)
 				}
 
-				if content != expected {
-					t.Errorf("AbsURLify with baseURL %q content expected:\n%q\ngot\n%q", baseURL, expected, content)
-				}
+				assertFileContent(t, fs, test.file, true, expected)
+
 			}
 		}
 	}
@@ -639,19 +585,17 @@
 func TestOrderedPages(t *testing.T) {
 	testCommonResetState()
 
-	hugofs.InitMemFs()
-
 	viper.Set("baseURL", "http://auth/bub")
-	s := &Site{
-		deps:     newDeps(DepsCfg{}),
-		Source:   &source.InMemorySource{ByteSource: weightedSources},
-		Language: helpers.NewDefaultLanguage(),
-	}
 
-	if err := buildSiteSkipRender(s); err != nil {
-		t.Fatalf("Failed to process site: %s", err)
+	fs := hugofs.NewMem()
+
+	for _, src := range weightedSources {
+		writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content))
+
 	}
 
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{SkipRender: true})
+
 	if s.Sections["sect"][0].Weight != 2 || s.Sections["sect"][3].Weight != 6 {
 		t.Errorf("Pages in unexpected order. First should be '%d', got '%d'", 2, s.Sections["sect"][0].Weight)
 	}
@@ -709,23 +653,17 @@
 		}
 	}()
 
-	hugofs.InitMemFs()
-
 	viper.Set("baseURL", "http://auth/bub")
-	s := &Site{
-		deps:     newDeps(DepsCfg{}),
-		Source:   &source.InMemorySource{ByteSource: groupedSources},
-		Language: helpers.NewDefaultLanguage(),
-	}
 
-	if err := buildSiteSkipRender(s); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	fs := hugofs.NewMem()
+	writeSourcesToSource(t, "content", fs, groupedSources...)
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
 
 	rbysection, err := s.RegularPages.GroupBy("Section", "desc")
 	if err != nil {
 		t.Fatalf("Unable to make PageGroup array: %s", err)
 	}
+
 	if rbysection[0].Key != "sect3" {
 		t.Errorf("PageGroup array in unexpected order. First group key should be '%s', got '%s'", "sect3", rbysection[0].Key)
 	}
@@ -885,7 +823,6 @@
 func TestWeightedTaxonomies(t *testing.T) {
 	testCommonResetState()
 
-	hugofs.InitMemFs()
 	sources := []source.ByteSource{
 		{Name: filepath.FromSlash("sect/doc1.md"), Content: pageWithWeightedTaxonomies2},
 		{Name: filepath.FromSlash("sect/doc2.md"), Content: pageWithWeightedTaxonomies1},
@@ -898,15 +835,10 @@
 
 	viper.Set("baseURL", "http://auth/bub")
 	viper.Set("taxonomies", taxonomies)
-	s := &Site{
-		deps:     newDeps(DepsCfg{}),
-		Source:   &source.InMemorySource{ByteSource: sources},
-		Language: helpers.NewDefaultLanguage(),
-	}
 
-	if err := buildSiteSkipRender(s); err != nil {
-		t.Fatalf("Failed to process site: %s", err)
-	}
+	fs := hugofs.NewMem()
+	writeSourcesToSource(t, "content", fs, sources...)
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
 
 	if s.Taxonomies["tags"]["a"][0].Page.Title != "foo" {
 		t.Errorf("Pages in unexpected order, 'foo' expected first, got '%v'", s.Taxonomies["tags"]["a"][0].Page.Title)
@@ -935,7 +867,6 @@
 }
 
 func setupLinkingMockSite(t *testing.T) *Site {
-	hugofs.InitMemFs()
 	sources := []source.ByteSource{
 		{Name: filepath.FromSlash("index.md"), Content: []byte("")},
 		{Name: filepath.FromSlash("rootfile.md"), Content: []byte("")},
@@ -968,17 +899,10 @@
 		map[string]interface{}{
 			"sourceRelativeLinksProjectFolder": "/docs"})
 
-	site := &Site{
-		deps:     newDeps(DepsCfg{}),
-		Source:   &source.InMemorySource{ByteSource: sources},
-		Language: helpers.NewDefaultLanguage(),
-	}
+	fs := hugofs.NewMem()
+	writeSourcesToSource(t, "content", fs, sources...)
+	return buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
 
-	if err := buildSiteSkipRender(site); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
-
-	return site
 }
 
 func TestRefLinking(t *testing.T) {
--- a/hugolib/site_url_test.go
+++ b/hugolib/site_url_test.go
@@ -17,13 +17,13 @@
 	"path/filepath"
 	"testing"
 
-	"github.com/spf13/hugo/helpers"
-
 	"html/template"
 
+	"github.com/spf13/hugo/deps"
 	"github.com/spf13/hugo/hugofs"
 	"github.com/spf13/hugo/source"
 	"github.com/spf13/viper"
+	"github.com/stretchr/testify/require"
 )
 
 const slugDoc1 = "---\ntitle: slug doc 1\nslug: slug-doc-1\naliases:\n - sd1/foo/\n - sd2\n - sd3/\n - sd4.html\n---\nslug doc 1 content\n"
@@ -62,7 +62,8 @@
 		{"http://base.com", "http://base.com"}} {
 
 		viper.Set("baseURL", this.in)
-		s := NewSiteDefaultLang()
+		s, err := NewSiteDefaultLang()
+		require.NoError(t, err)
 		s.initializeSiteInfo()
 
 		if s.Info.BaseURL != template.URL(this.expected) {
@@ -74,32 +75,27 @@
 
 func TestPageCount(t *testing.T) {
 	testCommonResetState()
-	hugofs.InitMemFs()
 
 	viper.Set("uglyURLs", false)
 	viper.Set("paginate", 10)
-	s := &Site{
-		deps:     newDeps(DepsCfg{}),
-		Source:   &source.InMemorySource{ByteSource: urlFakeSource},
-		Language: helpers.NewDefaultLanguage(),
-	}
 
-	if err := buildAndRenderSite(s, "indexes/blue.html", indexTemplate); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
-	_, err := hugofs.Destination().Open("public/blue")
+	fs := hugofs.NewMem()
+	writeSourcesToSource(t, "content", fs, urlFakeSource...)
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+
+	_, err := s.Fs.Destination.Open("public/blue")
 	if err != nil {
 		t.Errorf("No indexed rendered.")
 	}
 
-	for _, s := range []string{
+	for _, pth := range []string{
 		"public/sd1/foo/index.html",
 		"public/sd2/index.html",
 		"public/sd3/index.html",
 		"public/sd4.html",
 	} {
-		if _, err := hugofs.Destination().Open(filepath.FromSlash(s)); err != nil {
-			t.Errorf("No alias rendered: %s", s)
+		if _, err := s.Fs.Destination.Open(filepath.FromSlash(pth)); err != nil {
+			t.Errorf("No alias rendered: %s", pth)
 		}
 	}
 }
--- a/hugolib/sitemap_test.go
+++ b/hugolib/sitemap_test.go
@@ -18,8 +18,9 @@
 
 	"reflect"
 
-	"github.com/spf13/hugo/helpers"
-	"github.com/spf13/hugo/source"
+	"github.com/spf13/hugo/deps"
+	"github.com/spf13/hugo/hugofs"
+	"github.com/spf13/hugo/tplapi"
 	"github.com/spf13/viper"
 )
 
@@ -45,24 +46,21 @@
 
 	viper.Set("baseURL", "http://auth/bub/")
 
-	s := &Site{
-		deps:     newDeps(DepsCfg{}),
-		Source:   &source.InMemorySource{ByteSource: weightedSources},
-		Language: helpers.NewDefaultLanguage(),
-	}
+	fs := hugofs.NewMem()
 
-	if internal {
-		if err := buildAndRenderSite(s); err != nil {
-			t.Fatalf("Failed to build site: %s", err)
-		}
+	depsCfg := deps.DepsCfg{Fs: fs}
 
-	} else {
-		if err := buildAndRenderSite(s, "sitemap.xml", sitemapTemplate); err != nil {
-			t.Fatalf("Failed to build site: %s", err)
+	if !internal {
+		depsCfg.WithTemplate = func(templ tplapi.Template) error {
+			templ.AddTemplate("sitemap.xml", sitemapTemplate)
+			return nil
 		}
 	}
 
-	assertFileContent(t, "public/sitemap.xml", true,
+	writeSourcesToSource(t, "content", fs, weightedSources...)
+	s := buildSingleSite(t, depsCfg, BuildCfg{})
+
+	assertFileContent(t, s.Fs, "public/sitemap.xml", true,
 		// Regular page
 		" <loc>http://auth/bub/sect/doc1/</loc>",
 		// Home page
--- a/hugolib/taxonomy.go
+++ b/hugolib/taxonomy.go
@@ -16,8 +16,6 @@
 import (
 	"fmt"
 	"sort"
-
-	"github.com/spf13/hugo/helpers"
 )
 
 // The TaxonomyList is a list of all taxonomies and their values
@@ -59,26 +57,15 @@
 	WeightedPages WeightedPages
 }
 
-// KeyPrep... Taxonomies should be case insensitive. Can make it easily conditional later.
-func kp(in string) string {
-	return helpers.CurrentPathSpec().MakePathSanitized(in)
-}
-
 // Get the weighted pages for the given key.
 func (i Taxonomy) Get(key string) WeightedPages {
-	if val, ok := i[key]; ok {
-		return val
-	}
-	return i[kp(key)]
+	return i[key]
 }
 
 // Count the weighted pages for the given key.
-func (i Taxonomy) Count(key string) int { return len(i[kp(key)]) }
+func (i Taxonomy) Count(key string) int { return len(i[key]) }
 
-func (i Taxonomy) add(key string, w WeightedPage, pretty bool) {
-	if !pretty {
-		key = kp(key)
-	}
+func (i Taxonomy) add(key string, w WeightedPage) {
 	i[key] = append(i[key], w)
 }
 
--- a/hugolib/taxonomy_test.go
+++ b/hugolib/taxonomy_test.go
@@ -18,6 +18,9 @@
 	"reflect"
 	"testing"
 
+	"github.com/spf13/hugo/deps"
+	"github.com/spf13/hugo/hugofs"
+
 	"github.com/spf13/viper"
 )
 
@@ -31,16 +34,14 @@
 
 	viper.Set("taxonomies", taxonomies)
 
-	writeSource(t, filepath.Join("content", "page.md"), pageYamlWithTaxonomiesA)
+	fs := hugofs.NewMem()
 
-	site := NewSiteDefaultLang()
+	writeSource(t, fs, filepath.Join("content", "page.md"), pageYamlWithTaxonomiesA)
 
-	if err := buildSiteSkipRender(site); err != nil {
-		t.Fatalf("Failed to build site: %s", err)
-	}
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
 
 	st := make([]string, 0)
-	for _, t := range site.Taxonomies["tags"].ByCount() {
+	for _, t := range s.Taxonomies["tags"].ByCount() {
 		st = append(st, t.Name)
 	}
 
--- /dev/null
+++ b/hugolib/template_engines_test.go
@@ -1,0 +1,99 @@
+// Copyright 2017 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hugolib
+
+import (
+	"fmt"
+	"path/filepath"
+	"testing"
+
+	"strings"
+
+	"github.com/spf13/viper"
+
+	"github.com/spf13/hugo/deps"
+	"github.com/spf13/hugo/hugofs"
+)
+
+func TestAllTemplateEngines(t *testing.T) {
+	noOp := func(s string) string {
+		return s
+	}
+
+	amberFixer := func(s string) string {
+		fixed := strings.Replace(s, "{{ .Title", "{{ Title", -1)
+		fixed = strings.Replace(fixed, ".Content", "Content", -1)
+		fixed = strings.Replace(fixed, "{{", "#{", -1)
+		fixed = strings.Replace(fixed, "}}", "}", -1)
+		fixed = strings.Replace(fixed, `title "hello world"`, `title("hello world")`, -1)
+
+		return fixed
+	}
+
+	for _, config := range []struct {
+		suffix        string
+		templateFixer func(s string) string
+	}{
+		{"amber", amberFixer},
+		{"html", noOp},
+		{"ace", noOp},
+	} {
+		doTestTemplateEngine(t, config.suffix, config.templateFixer)
+
+	}
+
+}
+
+func doTestTemplateEngine(t *testing.T, suffix string, templateFixer func(s string) string) {
+
+	testCommonResetState()
+
+	fs := hugofs.NewMem()
+	viper.SetFs(fs.Source)
+
+	writeSource(t, fs, filepath.Join("content", "p.md"), `
+---
+title: My Title 
+---
+My Content
+`)
+
+	t.Log("Testing", suffix)
+
+	templTemplate := `
+p
+	|
+	| Page Title: {{ .Title }}
+	br
+	| Page Content: {{ .Content }}
+	br
+	| {{ title "hello world" }}
+
+`
+
+	templ := templateFixer(templTemplate)
+
+	t.Log(templ)
+
+	writeSource(t, fs, filepath.Join("layouts", "_default", fmt.Sprintf("single.%s", suffix)), templ)
+
+	buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
+
+	assertFileContent(t, fs, filepath.Join("public", "p", "index.html"), true,
+		"Page Title: My Title",
+		"My Content",
+		"Hello World",
+	)
+
+}
--- a/hugolib/template_test.go
+++ b/hugolib/template_test.go
@@ -17,16 +17,22 @@
 	"path/filepath"
 	"testing"
 
+	"github.com/spf13/hugo/deps"
+	"github.com/spf13/hugo/hugofs"
+
 	"github.com/spf13/viper"
 )
 
 func TestBaseGoTemplate(t *testing.T) {
+
+	var fs *hugofs.Fs
+
 	// Variants:
 	//   1. <current-path>/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
 	//   2. <current-path>/baseof.<suffix>
 	//   3. _default/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
 	//   4. _default/baseof.<suffix>
-	for i, this := range []struct {
+	for _, this := range []struct {
 		setup  func(t *testing.T)
 		assert func(t *testing.T)
 	}{
@@ -33,45 +39,45 @@
 		{
 			// Variant 1
 			func(t *testing.T) {
-				writeSource(t, filepath.Join("layouts", "section", "sect-baseof.html"), `Base: {{block "main" .}}block{{end}}`)
-				writeSource(t, filepath.Join("layouts", "section", "sect.html"), `{{define "main"}}sect{{ end }}`)
+				writeSource(t, fs, filepath.Join("layouts", "section", "sect-baseof.html"), `Base: {{block "main" .}}block{{end}}`)
+				writeSource(t, fs, filepath.Join("layouts", "section", "sect.html"), `{{define "main"}}sect{{ end }}`)
 
 			},
 			func(t *testing.T) {
-				assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base: sect")
+				assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: sect")
 			},
 		},
 		{
 			// Variant 2
 			func(t *testing.T) {
-				writeSource(t, filepath.Join("layouts", "baseof.html"), `Base: {{block "main" .}}block{{end}}`)
-				writeSource(t, filepath.Join("layouts", "index.html"), `{{define "main"}}index{{ end }}`)
+				writeSource(t, fs, filepath.Join("layouts", "baseof.html"), `Base: {{block "main" .}}block{{end}}`)
+				writeSource(t, fs, filepath.Join("layouts", "index.html"), `{{define "main"}}index{{ end }}`)
 
 			},
 			func(t *testing.T) {
-				assertFileContent(t, filepath.Join("public", "index.html"), false, "Base: index")
+				assertFileContent(t, fs, filepath.Join("public", "index.html"), false, "Base: index")
 			},
 		},
 		{
 			// Variant 3
 			func(t *testing.T) {
-				writeSource(t, filepath.Join("layouts", "_default", "list-baseof.html"), `Base: {{block "main" .}}block{{end}}`)
-				writeSource(t, filepath.Join("layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
+				writeSource(t, fs, filepath.Join("layouts", "_default", "list-baseof.html"), `Base: {{block "main" .}}block{{end}}`)
+				writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
 
 			},
 			func(t *testing.T) {
-				assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base: list")
+				assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: list")
 			},
 		},
 		{
 			// Variant 4
 			func(t *testing.T) {
-				writeSource(t, filepath.Join("layouts", "_default", "baseof.html"), `Base: {{block "main" .}}block{{end}}`)
-				writeSource(t, filepath.Join("layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
+				writeSource(t, fs, filepath.Join("layouts", "_default", "baseof.html"), `Base: {{block "main" .}}block{{end}}`)
+				writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
 
 			},
 			func(t *testing.T) {
-				assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base: list")
+				assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: list")
 			},
 		},
 		{
@@ -78,13 +84,13 @@
 			// Variant 1, theme,  use project's base
 			func(t *testing.T) {
 				viper.Set("theme", "mytheme")
-				writeSource(t, filepath.Join("layouts", "section", "sect-baseof.html"), `Base: {{block "main" .}}block{{end}}`)
-				writeSource(t, filepath.Join("themes", "mytheme", "layouts", "section", "sect-baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
-				writeSource(t, filepath.Join("layouts", "section", "sect.html"), `{{define "main"}}sect{{ end }}`)
+				writeSource(t, fs, filepath.Join("layouts", "section", "sect-baseof.html"), `Base: {{block "main" .}}block{{end}}`)
+				writeSource(t, fs, filepath.Join("themes", "mytheme", "layouts", "section", "sect-baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
+				writeSource(t, fs, filepath.Join("layouts", "section", "sect.html"), `{{define "main"}}sect{{ end }}`)
 
 			},
 			func(t *testing.T) {
-				assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base: sect")
+				assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: sect")
 			},
 		},
 		{
@@ -91,12 +97,12 @@
 			// Variant 1, theme,  use theme's base
 			func(t *testing.T) {
 				viper.Set("theme", "mytheme")
-				writeSource(t, filepath.Join("themes", "mytheme", "layouts", "section", "sect-baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
-				writeSource(t, filepath.Join("layouts", "section", "sect.html"), `{{define "main"}}sect{{ end }}`)
+				writeSource(t, fs, filepath.Join("themes", "mytheme", "layouts", "section", "sect-baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
+				writeSource(t, fs, filepath.Join("layouts", "section", "sect.html"), `{{define "main"}}sect{{ end }}`)
 
 			},
 			func(t *testing.T) {
-				assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base Theme: sect")
+				assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base Theme: sect")
 			},
 		},
 		{
@@ -103,13 +109,13 @@
 			// Variant 4, theme, use project's base
 			func(t *testing.T) {
 				viper.Set("theme", "mytheme")
-				writeSource(t, filepath.Join("layouts", "_default", "baseof.html"), `Base: {{block "main" .}}block{{end}}`)
-				writeSource(t, filepath.Join("themes", "mytheme", "layouts", "_default", "baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
-				writeSource(t, filepath.Join("themes", "mytheme", "layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
+				writeSource(t, fs, filepath.Join("layouts", "_default", "baseof.html"), `Base: {{block "main" .}}block{{end}}`)
+				writeSource(t, fs, filepath.Join("themes", "mytheme", "layouts", "_default", "baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
+				writeSource(t, fs, filepath.Join("themes", "mytheme", "layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
 
 			},
 			func(t *testing.T) {
-				assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base: list")
+				assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base: list")
 			},
 		},
 		{
@@ -116,12 +122,12 @@
 			// Variant 4, theme, use themes's base
 			func(t *testing.T) {
 				viper.Set("theme", "mytheme")
-				writeSource(t, filepath.Join("themes", "mytheme", "layouts", "_default", "baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
-				writeSource(t, filepath.Join("themes", "mytheme", "layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
+				writeSource(t, fs, filepath.Join("themes", "mytheme", "layouts", "_default", "baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
+				writeSource(t, fs, filepath.Join("themes", "mytheme", "layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
 
 			},
 			func(t *testing.T) {
-				assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base Theme: list")
+				assertFileContent(t, fs, filepath.Join("public", "sect", "index.html"), false, "Base Theme: list")
 			},
 		},
 	} {
@@ -128,7 +134,9 @@
 
 		testCommonResetState()
 
-		writeSource(t, filepath.Join("content", "sect", "page.md"), `---
+		fs = hugofs.NewMem()
+
+		writeSource(t, fs, filepath.Join("content", "sect", "page.md"), `---
 title: Template test
 ---
 Some content
@@ -135,9 +143,7 @@
 `)
 		this.setup(t)
 
-		if err := buildAndRenderSite(NewSiteDefaultLang()); err != nil {
-			t.Fatalf("[%d] Failed to build site: %s", i, err)
-		}
+		buildSingleSite(t, deps.DepsCfg{Fs: fs}, BuildCfg{})
 
 		this.assert(t)
 
--- /dev/null
+++ b/hugolib/testhelpers_test.go
@@ -1,0 +1,53 @@
+package hugolib
+
+import (
+	"path/filepath"
+	"testing"
+
+	"github.com/spf13/hugo/deps"
+	"github.com/spf13/hugo/helpers"
+	"github.com/spf13/hugo/hugofs"
+	"github.com/spf13/hugo/source"
+	"github.com/spf13/hugo/tplapi"
+	"github.com/spf13/viper"
+
+	"github.com/stretchr/testify/require"
+)
+
+func newTestDepsConfig() deps.DepsCfg {
+	return deps.DepsCfg{Fs: hugofs.NewMem()}
+}
+
+func newTestPathSpec() *helpers.PathSpec {
+	return helpers.NewPathSpec(hugofs.NewMem(), viper.GetViper())
+}
+
+func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tplapi.Template) error {
+
+	return func(templ tplapi.Template) error {
+		for i := 0; i < len(additionalTemplates); i += 2 {
+			err := templ.AddTemplate(additionalTemplates[i], additionalTemplates[i+1])
+			if err != nil {
+				return err
+			}
+		}
+		return nil
+	}
+}
+
+func buildSingleSite(t *testing.T, depsCfg deps.DepsCfg, buildCfg BuildCfg) *Site {
+	h, err := NewHugoSitesFromConfiguration(depsCfg)
+
+	require.NoError(t, err)
+	require.Len(t, h.Sites, 1)
+
+	require.NoError(t, h.Build(buildCfg))
+
+	return h.Sites[0]
+}
+
+func writeSourcesToSource(t *testing.T, base string, fs *hugofs.Fs, sources ...source.ByteSource) {
+	for _, src := range sources {
+		writeSource(t, fs, filepath.Join(base, src.Name), string(src.Content))
+	}
+}
--- a/source/filesystem.go
+++ b/source/filesystem.go
@@ -38,8 +38,14 @@
 	files      []*File
 	Base       string
 	AvoidPaths []string
+
+	fs *hugofs.Fs
 }
 
+func NewFilesystem(fs *hugofs.Fs, base string, avoidPaths ...string) *Filesystem {
+	return &Filesystem{fs: fs, Base: base, AvoidPaths: avoidPaths}
+}
+
 func (f *Filesystem) FilesByExts(exts ...string) []*File {
 	var newFiles []*File
 
@@ -92,7 +98,7 @@
 			return err
 		}
 		if b {
-			rd, err := NewLazyFileReader(hugofs.Source(), filePath)
+			rd, err := NewLazyFileReader(f.fs.Source, filePath)
 			if err != nil {
 				return err
 			}
@@ -101,7 +107,10 @@
 		return err
 	}
 
-	err := helpers.SymbolicWalk(hugofs.Source(), f.Base, walker)
+	if f.fs == nil {
+		panic("Must have a fs")
+	}
+	err := helpers.SymbolicWalk(f.fs.Source, f.Base, walker)
 
 	if err != nil {
 		jww.ERROR.Println(err)
@@ -119,7 +128,7 @@
 			jww.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", filePath, err)
 			return false, nil
 		}
-		linkfi, err := hugofs.Source().Stat(link)
+		linkfi, err := f.fs.Source.Stat(link)
 		if err != nil {
 			jww.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
 			return false, nil
--- a/source/filesystem_test.go
+++ b/source/filesystem_test.go
@@ -19,10 +19,12 @@
 	"runtime"
 	"strings"
 	"testing"
+
+	"github.com/spf13/hugo/hugofs"
 )
 
 func TestEmptySourceFilesystem(t *testing.T) {
-	src := &Filesystem{Base: "Empty"}
+	src := NewFilesystem(hugofs.NewMem(), "Empty")
 	if len(src.Files()) != 0 {
 		t.Errorf("new filesystem should contain 0 files.")
 	}
@@ -37,13 +39,12 @@
 }
 
 func TestAddFile(t *testing.T) {
+	fs := hugofs.NewMem()
 	tests := platformPaths
 	for _, test := range tests {
 		base := platformBase
-		srcDefault := new(Filesystem)
-		srcWithBase := &Filesystem{
-			Base: base,
-		}
+		srcDefault := NewFilesystem(fs, "")
+		srcWithBase := NewFilesystem(fs, base)
 
 		for _, src := range []*Filesystem{srcDefault, srcWithBase} {
 
@@ -99,8 +100,10 @@
 		{NFC: "é", NFD: "\x65\xcc\x81"},
 	}
 
+	fs := hugofs.NewMem()
+
 	for _, path := range paths {
-		src := new(Filesystem)
+		src := NewFilesystem(fs, "")
 		_ = src.add(path.NFD, strings.NewReader(""))
 		f := src.Files()[0]
 		if f.BaseFileName() != path.NFC {
--- a/target/file.go
+++ b/target/file.go
@@ -41,6 +41,8 @@
 
 type Filesystem struct {
 	PublishDir string
+
+	Fs *hugofs.Fs
 }
 
 func (fs *Filesystem) Publish(path string, r io.Reader) (err error) {
@@ -49,7 +51,7 @@
 		return
 	}
 
-	return helpers.WriteToDisk(translated, r, hugofs.Destination())
+	return helpers.WriteToDisk(translated, r, fs.Fs.Destination)
 }
 
 func (fs *Filesystem) Translate(src string) (dest string, err error) {
--- a/target/htmlredirect.go
+++ b/target/htmlredirect.go
@@ -46,6 +46,8 @@
 	PublishDir string
 	Templates  *template.Template
 	AllowRoot  bool // for the language redirects
+
+	Fs *hugofs.Fs
 }
 
 func (h *HTMLRedirectAlias) Translate(alias string) (aliasPath string, err error) {
@@ -145,5 +147,5 @@
 		return
 	}
 
-	return helpers.WriteToDisk(path, buffer, hugofs.Destination())
+	return helpers.WriteToDisk(path, buffer, h.Fs.Destination)
 }
--- a/target/page.go
+++ b/target/page.go
@@ -35,6 +35,8 @@
 	// LangDir will contain the subdir for the language, i.e. "en", "de" etc.
 	// It will be empty if the site is rendered in root.
 	LangDir string
+
+	Fs *hugofs.Fs
 }
 
 func (pp *PagePub) Publish(path string, r io.Reader) (err error) {
@@ -44,7 +46,7 @@
 		return
 	}
 
-	return helpers.WriteToDisk(translated, r, hugofs.Destination())
+	return helpers.WriteToDisk(translated, r, pp.Fs.Destination)
 }
 
 func (pp *PagePub) Translate(src string) (dest string, err error) {
--- a/target/page_test.go
+++ b/target/page_test.go
@@ -16,9 +16,13 @@
 import (
 	"path/filepath"
 	"testing"
+
+	"github.com/spf13/hugo/hugofs"
 )
 
 func TestPageTranslator(t *testing.T) {
+	fs := hugofs.NewMem()
+
 	tests := []struct {
 		content  string
 		expected string
@@ -37,7 +41,7 @@
 	}
 
 	for _, test := range tests {
-		f := new(PagePub)
+		f := &PagePub{Fs: fs}
 		dest, err := f.Translate(filepath.FromSlash(test.content))
 		expected := filepath.FromSlash(test.expected)
 		if err != nil {
--- /dev/null
+++ b/tpl/amber_compiler.go
@@ -1,0 +1,42 @@
+// Copyright 2017 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tpl
+
+import (
+	"html/template"
+
+	"github.com/eknkc/amber"
+)
+
+func (gt *GoHTMLTemplate) CompileAmberWithTemplate(b []byte, path string, t *template.Template) (*template.Template, error) {
+	c := amber.New()
+
+	if err := c.ParseData(b, path); err != nil {
+		return nil, err
+	}
+
+	data, err := c.CompileString()
+
+	if err != nil {
+		return nil, err
+	}
+
+	tpl, err := t.Funcs(gt.amberFuncMap).Parse(data)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return tpl, nil
+}
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -24,34 +24,13 @@
 	"github.com/eknkc/amber"
 	"github.com/spf13/afero"
 	bp "github.com/spf13/hugo/bufferpool"
+	"github.com/spf13/hugo/deps"
 	"github.com/spf13/hugo/helpers"
-	"github.com/spf13/hugo/hugofs"
-	jww "github.com/spf13/jwalterweatherman"
 	"github.com/yosssi/ace"
 )
 
 // TODO(bep) globals get rid of the rest of the jww.ERR etc.
-//var tmpl *GoHTMLTemplate
 
-// TODO(bep) an interface with hundreds of methods ... remove it.
-// And unexport most of these methods.
-type Template interface {
-	ExecuteTemplate(wr io.Writer, name string, data interface{}) error
-	Lookup(name string) *template.Template
-	Templates() []*template.Template
-	New(name string) *template.Template
-	GetClone() *template.Template
-	LoadTemplates(absPath string)
-	LoadTemplatesWithPrefix(absPath, prefix string)
-	AddTemplate(name, tpl string) error
-	AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error
-	AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error
-	AddInternalTemplate(prefix, name, tpl string) error
-	AddInternalShortcode(name, tpl string) error
-	PrintErrors()
-	Funcs(funcMap template.FuncMap)
-}
-
 type templateErr struct {
 	name string
 	err  error
@@ -70,36 +49,34 @@
 
 	funcster *templateFuncster
 
-	// TODO(bep) globals template
-	log *jww.Notepad
+	amberFuncMap template.FuncMap
+
+	*deps.Deps
 }
 
-// New returns a new Hugo Template System
+type TemplateProvider struct{}
+
+var DefaultTemplateProvider *TemplateProvider
+
+// Update updates the Hugo Template System in the provided Deps.
 // with all the additional features, templates & functions
-func New(logger *jww.Notepad, withTemplate ...func(templ Template) error) *GoHTMLTemplate {
+func (*TemplateProvider) Update(deps *deps.Deps) error {
+	// TODO(bep) check that this isn't called too many times.
 	tmpl := &GoHTMLTemplate{
 		Template: template.New(""),
 		overlays: make(map[string]*template.Template),
 		errors:   make([]*templateErr, 0),
-		log:      logger,
+		Deps:     deps,
 	}
 
-	tmpl.funcster = newTemplateFuncster(tmpl)
+	deps.Tmpl = tmpl
 
-	// The URL funcs in the funcMap is somewhat language dependent,
-	// so we need to wait until the language and site config is loaded.
-	// TODO(bep) globals
-	tmpl.funcster.initFuncMap()
+	tmpl.initFuncs(deps)
 
-	// TODO(bep) globals
-	for k, v := range tmpl.funcster.funcMap {
-		amber.FuncMap[k] = v
-	}
-
 	tmpl.LoadEmbedded()
 
-	for _, wt := range withTemplate {
-		err := wt(tmpl)
+	if deps.WithTemplate != nil {
+		err := deps.WithTemplate(tmpl)
 		if err != nil {
 			tmpl.errors = append(tmpl.errors, &templateErr{"init", err})
 		}
@@ -106,16 +83,71 @@
 
 	}
 
-	tmpl.markReady()
+	tmpl.MarkReady()
 
-	return tmpl
+	return nil
+
 }
 
+// Clone clones
+func (*TemplateProvider) Clone(d *deps.Deps) error {
+
+	t := d.Tmpl.(*GoHTMLTemplate)
+
+	// 1. Clone the clone with new template funcs
+	// 2. Clone any overlays with new template funcs
+
+	tmpl := &GoHTMLTemplate{
+		Template: template.Must(t.Template.Clone()),
+		overlays: make(map[string]*template.Template),
+		errors:   make([]*templateErr, 0),
+		Deps:     d,
+	}
+
+	d.Tmpl = tmpl
+	tmpl.initFuncs(d)
+
+	for k, v := range t.overlays {
+		vc := template.Must(v.Clone())
+		vc.Funcs(tmpl.funcster.funcMap)
+		tmpl.overlays[k] = vc
+	}
+
+	tmpl.MarkReady()
+
+	return nil
+}
+
+func (t *GoHTMLTemplate) initFuncs(d *deps.Deps) {
+
+	t.funcster = newTemplateFuncster(d)
+
+	// The URL funcs in the funcMap is somewhat language dependent,
+	// so we need to wait until the language and site config is loaded.
+	t.funcster.initFuncMap()
+
+	t.amberFuncMap = template.FuncMap{}
+
+	for k, v := range amber.FuncMap {
+		t.amberFuncMap[k] = v
+	}
+
+	for k, v := range t.funcster.funcMap {
+		t.amberFuncMap[k] = v
+		// Hacky, but we need to make sure that the func names are in the global map.
+		amber.FuncMap[k] = func() string {
+			panic("should never be invoked")
+			return ""
+		}
+	}
+
+}
+
 func (t *GoHTMLTemplate) Funcs(funcMap template.FuncMap) {
 	t.Template.Funcs(funcMap)
 }
 
-func (t *GoHTMLTemplate) partial(name string, contextList ...interface{}) template.HTML {
+func (t *GoHTMLTemplate) Partial(name string, contextList ...interface{}) template.HTML {
 	if strings.HasPrefix("partials/", name) {
 		name = name[8:]
 	}
@@ -147,8 +179,8 @@
 		}
 	}
 	if !worked {
-		t.log.ERROR.Println("Unable to render", layouts)
-		t.log.ERROR.Println("Expecting to find a template in either the theme/layouts or /layouts in one of the following relative locations", layouts)
+		t.Log.ERROR.Println("Unable to render", layouts)
+		t.Log.ERROR.Println("Expecting to find a template in either the theme/layouts or /layouts in one of the following relative locations", layouts)
 	}
 }
 
@@ -186,9 +218,9 @@
 	t.EmbedTemplates()
 }
 
-// markReady marks the template as "ready for execution". No changes allowed
+// MarkReady marks the template as "ready for execution". No changes allowed
 // after this is set.
-func (t *GoHTMLTemplate) markReady() {
+func (t *GoHTMLTemplate) MarkReady() {
 	if t.clone == nil {
 		t.clone = template.Must(t.Template.Clone())
 	}
@@ -244,7 +276,7 @@
 	masterTpl := t.Lookup(masterFilename)
 
 	if masterTpl == nil {
-		b, err := afero.ReadFile(hugofs.Source(), masterFilename)
+		b, err := afero.ReadFile(t.Fs.Source, masterFilename)
 		if err != nil {
 			return err
 		}
@@ -257,7 +289,7 @@
 		}
 	}
 
-	b, err := afero.ReadFile(hugofs.Source(), overlayFilename)
+	b, err := afero.ReadFile(t.Fs.Source, overlayFilename)
 	if err != nil {
 		return err
 	}
@@ -315,19 +347,13 @@
 	switch ext {
 	case ".amber":
 		templateName := strings.TrimSuffix(name, filepath.Ext(name)) + ".html"
-		compiler := amber.New()
-		b, err := afero.ReadFile(hugofs.Source(), path)
+		b, err := afero.ReadFile(t.Fs.Source, path)
 
 		if err != nil {
 			return err
 		}
 
-		// Parse the input data
-		if err := compiler.ParseData(b, path); err != nil {
-			return err
-		}
-
-		templ, err := compiler.CompileWithTemplate(t.New(templateName))
+		templ, err := t.CompileAmberWithTemplate(b, path, t.New(templateName))
 		if err != nil {
 			return err
 		}
@@ -335,7 +361,7 @@
 		return applyTemplateTransformers(templ)
 	case ".ace":
 		var innerContent, baseContent []byte
-		innerContent, err := afero.ReadFile(hugofs.Source(), path)
+		innerContent, err := afero.ReadFile(t.Fs.Source, path)
 
 		if err != nil {
 			return err
@@ -342,7 +368,7 @@
 		}
 
 		if baseTemplatePath != "" {
-			baseContent, err = afero.ReadFile(hugofs.Source(), baseTemplatePath)
+			baseContent, err = afero.ReadFile(t.Fs.Source, baseTemplatePath)
 			if err != nil {
 				return err
 			}
@@ -355,13 +381,13 @@
 			return t.AddTemplateFileWithMaster(name, path, baseTemplatePath)
 		}
 
-		b, err := afero.ReadFile(hugofs.Source(), path)
+		b, err := afero.ReadFile(t.Fs.Source, path)
 
 		if err != nil {
 			return err
 		}
 
-		t.log.DEBUG.Printf("Add template file from path %s", path)
+		t.Log.DEBUG.Printf("Add template file from path %s", path)
 
 		return t.AddTemplate(name, string(b))
 	}
@@ -391,25 +417,25 @@
 }
 
 func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) {
-	t.log.DEBUG.Printf("Load templates from path %q prefix %q", absPath, prefix)
+	t.Log.DEBUG.Printf("Load templates from path %q prefix %q", absPath, prefix)
 	walker := func(path string, fi os.FileInfo, err error) error {
 		if err != nil {
 			return nil
 		}
-		t.log.DEBUG.Println("Template path", path)
+		t.Log.DEBUG.Println("Template path", path)
 		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
 			link, err := filepath.EvalSymlinks(absPath)
 			if err != nil {
-				t.log.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", absPath, err)
+				t.Log.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", absPath, err)
 				return nil
 			}
-			linkfi, err := hugofs.Source().Stat(link)
+			linkfi, err := t.Fs.Source.Stat(link)
 			if err != nil {
-				t.log.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
+				t.Log.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
 				return nil
 			}
 			if !linkfi.Mode().IsRegular() {
-				t.log.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", absPath)
+				t.Log.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", absPath)
 			}
 			return nil
 		}
@@ -441,7 +467,7 @@
 
 				// This may be a view that shouldn't have base template
 				// Have to look inside it to make sure
-				needsBase, err := helpers.FileContainsAny(path, innerMarkers, hugofs.Source())
+				needsBase, err := helpers.FileContainsAny(path, innerMarkers, t.Fs.Source)
 				if err != nil {
 					return err
 				}
@@ -482,7 +508,7 @@
 					for _, pair := range pairsToCheck {
 						pathsToCheck := basePathsToCheck(pair, layoutDir, themeDir)
 						for _, pathToCheck := range pathsToCheck {
-							if ok, err := helpers.Exists(pathToCheck, hugofs.Source()); err == nil && ok {
+							if ok, err := helpers.Exists(pathToCheck, t.Fs.Source); err == nil && ok {
 								baseTemplatePath = pathToCheck
 								break Loop
 							}
@@ -492,14 +518,14 @@
 			}
 
 			if err := t.AddTemplateFile(tplName, baseTemplatePath, path); err != nil {
-				t.log.ERROR.Printf("Failed to add template %s in path %s: %s", tplName, path, err)
+				t.Log.ERROR.Printf("Failed to add template %s in path %s: %s", tplName, path, err)
 			}
 
 		}
 		return nil
 	}
-	if err := helpers.SymbolicWalk(hugofs.Source(), absPath, walker); err != nil {
-		t.log.ERROR.Printf("Failed to load templates: %s", err)
+	if err := helpers.SymbolicWalk(t.Fs.Source, absPath, walker); err != nil {
+		t.Log.ERROR.Printf("Failed to load templates: %s", err)
 	}
 }
 
@@ -526,6 +552,6 @@
 
 func (t *GoHTMLTemplate) PrintErrors() {
 	for i, e := range t.errors {
-		t.log.ERROR.Println(i, ":", e.err)
+		t.Log.ERROR.Println(i, ":", e.err)
 	}
 }
--- a/tpl/template_funcs.go
+++ b/tpl/template_funcs.go
@@ -43,8 +43,8 @@
 	"github.com/bep/inflect"
 	"github.com/spf13/afero"
 	"github.com/spf13/cast"
+	"github.com/spf13/hugo/deps"
 	"github.com/spf13/hugo/helpers"
-	"github.com/spf13/hugo/hugofs"
 	jww "github.com/spf13/jwalterweatherman"
 	"github.com/spf13/viper"
 
@@ -56,14 +56,15 @@
 
 // Some of the template funcs are'nt entirely stateless.
 type templateFuncster struct {
-	t              *GoHTMLTemplate
 	funcMap        template.FuncMap
 	cachedPartials partialCache
+
+	*deps.Deps
 }
 
-func newTemplateFuncster(t *GoHTMLTemplate) *templateFuncster {
+func newTemplateFuncster(deps *deps.Deps) *templateFuncster {
 	return &templateFuncster{
-		t:              t,
+		Deps:           deps,
 		cachedPartials: partialCache{p: make(map[string]template.HTML)},
 	}
 }
@@ -424,7 +425,7 @@
 
 // imageConfig returns the image.Config for the specified path relative to the
 // working directory. resetImageConfigCache must be run beforehand.
-func imageConfig(path interface{}) (image.Config, error) {
+func (t *templateFuncster) imageConfig(path interface{}) (image.Config, error) {
 	filename, err := cast.ToStringE(path)
 	if err != nil {
 		return image.Config{}, err
@@ -443,7 +444,7 @@
 		return config, nil
 	}
 
-	f, err := hugofs.WorkingDir().Open(filename)
+	f, err := t.Fs.WorkingDir.Open(filename)
 	if err != nil {
 		return image.Config{}, err
 	}
@@ -1013,7 +1014,7 @@
 }
 
 // apply takes a map, array, or slice and returns a new slice with the function fname applied over it.
-func (tf *templateFuncster) apply(seq interface{}, fname string, args ...interface{}) (interface{}, error) {
+func (t *templateFuncster) apply(seq interface{}, fname string, args ...interface{}) (interface{}, error) {
 	if seq == nil {
 		return make([]interface{}, 0), nil
 	}
@@ -1028,7 +1029,7 @@
 		return nil, errors.New("can't iterate over a nil value")
 	}
 
-	fn, found := tf.funcMap[fname]
+	fn, found := t.funcMap[fname]
 	if !found {
 		return nil, errors.New("can't find function " + fname)
 	}
@@ -1528,26 +1529,27 @@
 // Get retrieves partial output from the cache based upon the partial name.
 // If the partial is not found in the cache, the partial is rendered and added
 // to the cache.
-func (tf *templateFuncster) Get(key, name string, context interface{}) (p template.HTML) {
+func (t *templateFuncster) Get(key, name string, context interface{}) (p template.HTML) {
 	var ok bool
 
-	tf.cachedPartials.RLock()
-	p, ok = tf.cachedPartials.p[key]
-	tf.cachedPartials.RUnlock()
+	t.cachedPartials.RLock()
+	p, ok = t.cachedPartials.p[key]
+	t.cachedPartials.RUnlock()
 
 	if ok {
 		return p
 	}
 
-	tf.cachedPartials.Lock()
-	if p, ok = tf.cachedPartials.p[key]; !ok {
-		tf.cachedPartials.Unlock()
-		p = tf.t.partial(name, context)
+	t.cachedPartials.Lock()
+	if p, ok = t.cachedPartials.p[key]; !ok {
+		t.cachedPartials.Unlock()
+		p = t.Tmpl.Partial(name, context)
 
-		tf.cachedPartials.Lock()
-		tf.cachedPartials.p[key] = p
+		t.cachedPartials.Lock()
+		t.cachedPartials.p[key] = p
+
 	}
-	tf.cachedPartials.Unlock()
+	t.cachedPartials.Unlock()
 
 	return p
 }
@@ -1556,7 +1558,7 @@
 // string parameter (a string slice actually, but be only use a variadic
 // argument to make it optional) can be passed so that a given partial can have
 // multiple uses.  The cache is created with name+variant as the key.
-func (tf *templateFuncster) partialCached(name string, context interface{}, variant ...string) template.HTML {
+func (t *templateFuncster) partialCached(name string, context interface{}, variant ...string) template.HTML {
 	key := name
 	if len(variant) > 0 {
 		for i := 0; i < len(variant); i++ {
@@ -1563,7 +1565,7 @@
 			key += variant[i]
 		}
 	}
-	return tf.Get(key, name, context)
+	return t.Get(key, name, context)
 }
 
 // regexpCache represents a cache of regexp objects protected by a mutex.
@@ -1814,23 +1816,23 @@
 // configured WorkingDir.
 // It returns the contents as a string.
 // There is a upper size limit set at 1 megabytes.
-func readFileFromWorkingDir(i interface{}) (string, error) {
+func (t *templateFuncster) readFileFromWorkingDir(i interface{}) (string, error) {
 	s, err := cast.ToStringE(i)
 	if err != nil {
 		return "", err
 	}
-	return readFile(hugofs.WorkingDir(), s)
+	return readFile(t.Fs.WorkingDir, s)
 }
 
 // readDirFromWorkingDir listst the directory content relative to the
 // configured WorkingDir.
-func readDirFromWorkingDir(i interface{}) ([]os.FileInfo, error) {
+func (t *templateFuncster) readDirFromWorkingDir(i interface{}) ([]os.FileInfo, error) {
 	path, err := cast.ToStringE(i)
 	if err != nil {
 		return nil, err
 	}
 
-	list, err := afero.ReadDir(hugofs.WorkingDir(), path)
+	list, err := afero.ReadDir(t.Fs.WorkingDir, path)
 
 	if err != nil {
 		return nil, fmt.Errorf("Failed to read Directory %s with error message %s", path, err)
@@ -2074,20 +2076,20 @@
 	return html.UnescapeString(conv), nil
 }
 
-func absURL(a interface{}) (template.HTML, error) {
+func (t *templateFuncster) absURL(a interface{}) (template.HTML, error) {
 	s, err := cast.ToStringE(a)
 	if err != nil {
 		return "", nil
 	}
-	return template.HTML(helpers.CurrentPathSpec().AbsURL(s, false)), nil
+	return template.HTML(t.PathSpec.AbsURL(s, false)), nil
 }
 
-func relURL(a interface{}) (template.HTML, error) {
+func (t *templateFuncster) relURL(a interface{}) (template.HTML, error) {
 	s, err := cast.ToStringE(a)
 	if err != nil {
 		return "", nil
 	}
-	return template.HTML(helpers.CurrentPathSpec().RelURL(s, false)), nil
+	return template.HTML(t.PathSpec.RelURL(s, false)), nil
 }
 
 // getenv retrieves the value of the environment variable named by the key.
@@ -2101,19 +2103,19 @@
 	return os.Getenv(skey), nil
 }
 
-func (tf *templateFuncster) initFuncMap() {
+func (t *templateFuncster) initFuncMap() {
 	funcMap := template.FuncMap{
-		"absURL": absURL,
+		"absURL": t.absURL,
 		"absLangURL": func(i interface{}) (template.HTML, error) {
 			s, err := cast.ToStringE(i)
 			if err != nil {
 				return "", err
 			}
-			return template.HTML(helpers.CurrentPathSpec().AbsURL(s, true)), nil
+			return template.HTML(t.PathSpec.AbsURL(s, true)), nil
 		},
 		"add":           func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '+') },
 		"after":         after,
-		"apply":         tf.apply,
+		"apply":         t.apply,
 		"base64Decode":  base64Decode,
 		"base64Encode":  base64Encode,
 		"chomp":         chomp,
@@ -2130,8 +2132,8 @@
 		"findRE":        findRE,
 		"first":         first,
 		"ge":            ge,
-		"getCSV":        getCSV,
-		"getJSON":       getJSON,
+		"getCSV":        t.getCSV,
+		"getJSON":       t.getJSON,
 		"getenv":        getenv,
 		"gt":            gt,
 		"hasPrefix":     hasPrefix,
@@ -2139,7 +2141,7 @@
 		"htmlEscape":    htmlEscape,
 		"htmlUnescape":  htmlUnescape,
 		"humanize":      humanize,
-		"imageConfig":   imageConfig,
+		"imageConfig":   t.imageConfig,
 		"in":            in,
 		"index":         index,
 		"int":           func(v interface{}) (int, error) { return cast.ToIntE(v) },
@@ -2158,21 +2160,21 @@
 		"mul":           func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '*') },
 		"ne":            ne,
 		"now":           func() time.Time { return time.Now() },
-		"partial":       tf.t.partial,
-		"partialCached": tf.partialCached,
+		"partial":       t.Tmpl.Partial,
+		"partialCached": t.partialCached,
 		"plainify":      plainify,
 		"pluralize":     pluralize,
 		"querify":       querify,
-		"readDir":       readDirFromWorkingDir,
-		"readFile":      readFileFromWorkingDir,
+		"readDir":       t.readDirFromWorkingDir,
+		"readFile":      t.readFileFromWorkingDir,
 		"ref":           ref,
-		"relURL":        relURL,
+		"relURL":        t.relURL,
 		"relLangURL": func(i interface{}) (template.HTML, error) {
 			s, err := cast.ToStringE(i)
 			if err != nil {
 				return "", err
 			}
-			return template.HTML(helpers.CurrentPathSpec().RelURL(s, true)), nil
+			return template.HTML(t.PathSpec.RelURL(s, true)), nil
 		},
 		"relref":       relRef,
 		"replace":      replace,
@@ -2201,12 +2203,12 @@
 		"trim":         trim,
 		"truncate":     truncate,
 		"upper":        upper,
-		"urlize":       helpers.CurrentPathSpec().URLize,
+		"urlize":       t.PathSpec.URLize,
 		"where":        where,
 		"i18n":         i18nTranslate,
 		"T":            i18nTranslate,
 	}
 
-	tf.funcMap = funcMap
-	tf.t.Funcs(funcMap)
+	t.funcMap = funcMap
+	t.Tmpl.Funcs(funcMap)
 }
--- a/tpl/template_funcs_test.go
+++ b/tpl/template_funcs_test.go
@@ -31,6 +31,9 @@
 	"testing"
 	"time"
 
+	"github.com/spf13/hugo/tplapi"
+
+	"github.com/spf13/hugo/deps"
 	"github.com/spf13/hugo/helpers"
 
 	"io/ioutil"
@@ -43,9 +46,17 @@
 	jww "github.com/spf13/jwalterweatherman"
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
-var logger = jww.NewNotepad(jww.LevelFatal, jww.LevelFatal, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+var (
+	logger            = jww.NewNotepad(jww.LevelFatal, jww.LevelFatal, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+	defaultDepsConfig = deps.DepsCfg{
+		Language:         helpers.NewLanguage("en"),
+		Logger:           logger,
+		TemplateProvider: DefaultTemplateProvider,
+	}
+)
 
 type tstNoStringer struct {
 }
@@ -80,8 +91,7 @@
 
 func TestFuncsInTemplate(t *testing.T) {
 
-	viper.Reset()
-	defer viper.Reset()
+	testReset()
 
 	workingDir := "/home/hugo"
 
@@ -89,10 +99,9 @@
 	viper.Set("currentContentLanguage", helpers.NewDefaultLanguage())
 	viper.Set("multilingual", true)
 
-	fs := &afero.MemMapFs{}
-	hugofs.InitFs(fs)
+	fs := hugofs.NewMem()
 
-	afero.WriteFile(fs, filepath.Join(workingDir, "README.txt"), []byte("Hugo Rocks!"), 0755)
+	afero.WriteFile(fs.Source, filepath.Join(workingDir, "README.txt"), []byte("Hugo Rocks!"), 0755)
 
 	// Add the examples from the docs: As a smoke test and to make sure the examples work.
 	// TODO(bep): docs: fix title example
@@ -244,7 +253,7 @@
 `
 
 	var b bytes.Buffer
-	templ, err := New(logger).New("test").Parse(in)
+
 	var data struct {
 		Title   string
 		Section string
@@ -259,12 +268,22 @@
 
 	tstInitTemplates()
 
-	if err != nil {
-		t.Fatal("Got error on parse", err)
+	config := defaultDepsConfig
+	config.WithTemplate = func(templ tplapi.Template) error {
+		if _, err := templ.New("test").Parse(in); err != nil {
+			t.Fatal("Got error on parse", err)
+		}
+		return nil
 	}
+	config.Fs = fs
 
-	err = templ.Execute(&b, &data)
+	d := deps.New(config)
+	if err := d.LoadTemplates(); err != nil {
+		t.Fatal(err)
+	}
 
+	err := d.Tmpl.Lookup("test").Execute(&b, &data)
+
 	if err != nil {
 		t.Fatal("Got error on execute", err)
 	}
@@ -624,15 +643,13 @@
 }
 
 func TestImageConfig(t *testing.T) {
-	viper.Reset()
-	defer viper.Reset()
+	testReset()
 
 	workingDir := "/home/hugo"
 
 	viper.Set("workingDir", workingDir)
 
-	fs := &afero.MemMapFs{}
-	hugofs.InitFs(fs)
+	f := newTestFuncster()
 
 	for i, this := range []struct {
 		resetCache bool
@@ -692,13 +709,13 @@
 			},
 		},
 	} {
-		afero.WriteFile(fs, filepath.Join(workingDir, this.path), this.input, 0755)
+		afero.WriteFile(f.Fs.Source, filepath.Join(workingDir, this.path), this.input, 0755)
 
 		if this.resetCache {
 			resetImageConfigCache()
 		}
 
-		result, err := imageConfig(this.path)
+		result, err := f.imageConfig(this.path)
 		if err != nil {
 			t.Errorf("imageConfig returned error: %s", err)
 		}
@@ -712,15 +729,15 @@
 		}
 	}
 
-	if _, err := imageConfig(t); err == nil {
+	if _, err := f.imageConfig(t); err == nil {
 		t.Error("Expected error from imageConfig when passed invalid path")
 	}
 
-	if _, err := imageConfig("non-existent.png"); err == nil {
+	if _, err := f.imageConfig("non-existent.png"); err == nil {
 		t.Error("Expected error from imageConfig when passed non-existent file")
 	}
 
-	if _, err := imageConfig(""); err == nil {
+	if _, err := f.imageConfig(""); err == nil {
 		t.Error("Expected error from imageConfig when passed empty path")
 	}
 
@@ -2381,14 +2398,11 @@
 		{map[string]string{"foo": "dog"}, `{{ default "nope" .foo "extra" }}`, ``, false},
 		{map[string]interface{}{"images": []string{}}, `{{ default "default.jpg" (index .images 0) }}`, `default.jpg`, true},
 	} {
-		tmpl, err := New(logger).New("test").Parse(this.tpl)
-		if err != nil {
-			t.Errorf("[%d] unable to create new html template %q: %s", i, this.tpl, err)
-			continue
-		}
 
+		tmpl := newTestTemplate(t, "test", this.tpl)
+
 		buf := new(bytes.Buffer)
-		err = tmpl.Execute(buf, this.input)
+		err := tmpl.Execute(buf, this.input)
 		if (err == nil) != this.ok {
 			t.Errorf("[%d] execute template returned unexpected error: %s", i, err)
 			continue
@@ -2520,6 +2534,7 @@
 	}
 }
 
+// TODO(bep) what is this? Also look above.
 func TestSafeJS(t *testing.T) {
 	for i, this := range []struct {
 		str                 string
@@ -2560,6 +2575,7 @@
 	}
 }
 
+// TODO(bep) what is this?
 func TestSafeURL(t *testing.T) {
 	for i, this := range []struct {
 		str                 string
@@ -2716,18 +2732,16 @@
 }
 
 func TestReadFile(t *testing.T) {
-	viper.Reset()
-	defer viper.Reset()
+	testReset()
 
 	workingDir := "/home/hugo"
 
 	viper.Set("workingDir", workingDir)
 
-	fs := &afero.MemMapFs{}
-	hugofs.InitFs(fs)
+	f := newTestFuncster()
 
-	afero.WriteFile(fs, filepath.Join(workingDir, "/f/f1.txt"), []byte("f1-content"), 0755)
-	afero.WriteFile(fs, filepath.Join("/home", "f2.txt"), []byte("f2-content"), 0755)
+	afero.WriteFile(f.Fs.Source, filepath.Join(workingDir, "/f/f1.txt"), []byte("f1-content"), 0755)
+	afero.WriteFile(f.Fs.Source, filepath.Join("/home", "f2.txt"), []byte("f2-content"), 0755)
 
 	for i, this := range []struct {
 		filename string
@@ -2739,7 +2753,7 @@
 		{filepath.FromSlash("f/f1.txt"), "f1-content"},
 		{filepath.FromSlash("../f2.txt"), false},
 	} {
-		result, err := readFileFromWorkingDir(this.filename)
+		result, err := f.readFileFromWorkingDir(this.filename)
 		if b, ok := this.expect.(bool); ok && !b {
 			if err == nil {
 				t.Errorf("[%d] readFile didn't return an expected error", i)
@@ -2770,8 +2784,6 @@
 		{"test1", "{{ .Title }} seq: {{ shuffle (seq 1 20) }}", `{{ partialCached "test1" . "%s" }}`, "header"},
 	}
 
-	results := make(map[string]string, len(testCases))
-
 	var data struct {
 		Title   string
 		Section string
@@ -2791,19 +2803,25 @@
 			tmp = tc.tmpl
 		}
 
-		tmpl, err := New(logger).New("testroot").Parse(tmp)
-		if err != nil {
-			t.Fatalf("[%d] unable to create new html template: %s", i, err)
-		}
+		defaultDepsConfig.WithTemplate = func(templ tplapi.Template) error {
+			err := templ.AddTemplate("testroot", tmp)
+			if err != nil {
+				return err
+			}
+			err = templ.AddTemplate("partials/"+tc.name, tc.partial)
+			if err != nil {
+				return err
+			}
 
-		if tmpl == nil {
-			t.Fatalf("[%d] tmpl should not be nil!", i)
+			return nil
 		}
 
-		tmpl.New("partials/" + tc.name).Parse(tc.partial)
+		de := deps.New(defaultDepsConfig)
+		require.NoError(t, de.LoadTemplates())
 
 		buf := new(bytes.Buffer)
-		err = tmpl.Execute(buf, &data)
+		templ := de.Tmpl.Lookup("testroot")
+		err := templ.Execute(buf, &data)
 		if err != nil {
 			t.Fatalf("[%d] error executing template: %s", i, err)
 		}
@@ -2810,7 +2828,7 @@
 
 		for j := 0; j < 10; j++ {
 			buf2 := new(bytes.Buffer)
-			err = tmpl.Execute(buf2, nil)
+			err := templ.Execute(buf2, nil)
 			if err != nil {
 				t.Fatalf("[%d] error executing template 2nd time: %s", i, err)
 			}
@@ -2819,33 +2837,33 @@
 				t.Fatalf("[%d] cached results do not match:\nResult 1:\n%q\nResult 2:\n%q", i, buf, buf2)
 			}
 		}
-
-		// double-check against previous test cases of the same variant
-		previous, ok := results[tc.name+tc.variant]
-		if !ok {
-			results[tc.name+tc.variant] = buf.String()
-		} else {
-			if previous != buf.String() {
-				t.Errorf("[%d] cached variant differs from previous rendering; got:\n%q\nwant:\n%q", i, buf.String(), previous)
-			}
-		}
 	}
 }
 
 func BenchmarkPartial(b *testing.B) {
-	tstInitTemplates()
-	tmpl, err := New(logger).New("testroot").Parse(`{{ partial "bench1" . }}`)
-	if err != nil {
-		b.Fatalf("unable to create new html template: %s", err)
+	defaultDepsConfig.WithTemplate = func(templ tplapi.Template) error {
+		err := templ.AddTemplate("testroot", `{{ partial "bench1" . }}`)
+		if err != nil {
+			return err
+		}
+		err = templ.AddTemplate("partials/bench1", `{{ shuffle (seq 1 10) }}`)
+		if err != nil {
+			return err
+		}
+
+		return nil
 	}
 
-	tmpl.New("partials/bench1").Parse(`{{ shuffle (seq 1 10) }}`)
+	de := deps.New(defaultDepsConfig)
+	require.NoError(b, de.LoadTemplates())
+
 	buf := new(bytes.Buffer)
+	tmpl := de.Tmpl.Lookup("testroot")
 
 	b.ReportAllocs()
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		if err = tmpl.Execute(buf, nil); err != nil {
+		if err := tmpl.Execute(buf, nil); err != nil {
 			b.Fatalf("error executing template: %s", err)
 		}
 		buf.Reset()
@@ -2853,19 +2871,29 @@
 }
 
 func BenchmarkPartialCached(b *testing.B) {
-	tstInitTemplates()
-	tmpl, err := New(logger).New("testroot").Parse(`{{ partialCached "bench1" . }}`)
-	if err != nil {
-		b.Fatalf("unable to create new html template: %s", err)
+	defaultDepsConfig.WithTemplate = func(templ tplapi.Template) error {
+		err := templ.AddTemplate("testroot", `{{ partialCached "bench1" . }}`)
+		if err != nil {
+			return err
+		}
+		err = templ.AddTemplate("partials/bench1", `{{ shuffle (seq 1 10) }}`)
+		if err != nil {
+			return err
+		}
+
+		return nil
 	}
 
-	tmpl.New("partials/bench1").Parse(`{{ shuffle (seq 1 10) }}`)
+	de := deps.New(defaultDepsConfig)
+	require.NoError(b, de.LoadTemplates())
+
 	buf := new(bytes.Buffer)
+	tmpl := de.Tmpl.Lookup("testroot")
 
 	b.ReportAllocs()
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		if err = tmpl.Execute(buf, nil); err != nil {
+		if err := tmpl.Execute(buf, nil); err != nil {
 			b.Fatalf("error executing template: %s", err)
 		}
 		buf.Reset()
@@ -2872,25 +2900,26 @@
 	}
 }
 
-func BenchmarkPartialCachedVariants(b *testing.B) {
-	tmpl, err := New(logger).New("testroot").Parse(`{{ partialCached "bench1" . "header" }}`)
-	if err != nil {
-		b.Fatalf("unable to create new html template: %s", err)
+func newTestFuncster() *templateFuncster {
+	d := deps.New(defaultDepsConfig)
+	if err := d.LoadTemplates(); err != nil {
+		panic(err)
 	}
 
-	tmpl.New("partials/bench1").Parse(`{{ shuffle (seq 1 10) }}`)
-	buf := new(bytes.Buffer)
+	return d.Tmpl.(*GoHTMLTemplate).funcster
+}
 
-	b.ReportAllocs()
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		if err = tmpl.Execute(buf, nil); err != nil {
-			b.Fatalf("error executing template: %s", err)
+func newTestTemplate(t *testing.T, name, template string) *template.Template {
+	defaultDepsConfig.WithTemplate = func(templ tplapi.Template) error {
+		err := templ.AddTemplate(name, template)
+		if err != nil {
+			return err
 		}
-		buf.Reset()
+		return nil
 	}
-}
 
-func newTestFuncster() *templateFuncster {
-	return New(logger).funcster
+	de := deps.New(defaultDepsConfig)
+	require.NoError(t, de.LoadTemplates())
+
+	return de.Tmpl.Lookup(name)
 }
--- a/tpl/template_i18n.go
+++ b/tpl/template_i18n.go
@@ -33,6 +33,7 @@
 	current bundle.TranslateFunc
 }
 
+// TODO(bep) global translator
 var translator *translate
 
 // SetTranslateLang sets the translations language to use during template processing.
--- a/tpl/template_resources.go
+++ b/tpl/template_resources.go
@@ -28,7 +28,6 @@
 
 	"github.com/spf13/afero"
 	"github.com/spf13/hugo/helpers"
-	"github.com/spf13/hugo/hugofs"
 	jww "github.com/spf13/jwalterweatherman"
 	"github.com/spf13/viper"
 )
@@ -165,25 +164,25 @@
 }
 
 // resGetResource loads the content of a local or remote file
-func resGetResource(url string) ([]byte, error) {
+func (t *templateFuncster) resGetResource(url string) ([]byte, error) {
 	if url == "" {
 		return nil, nil
 	}
 	if strings.Contains(url, "://") {
-		return resGetRemote(url, hugofs.Source(), http.DefaultClient)
+		return resGetRemote(url, t.Fs.Source, http.DefaultClient)
 	}
-	return resGetLocal(url, hugofs.Source())
+	return resGetLocal(url, t.Fs.Source)
 }
 
 // getJSON expects one or n-parts of a URL to a resource which can either be a local or a remote one.
 // If you provide multiple parts they will be joined together to the final URL.
 // GetJSON returns nil or parsed JSON to use in a short code.
-func getJSON(urlParts ...string) interface{} {
+func (t *templateFuncster) getJSON(urlParts ...string) interface{} {
 	var v interface{}
 	url := strings.Join(urlParts, "")
 
 	for i := 0; i <= resRetries; i++ {
-		c, err := resGetResource(url)
+		c, err := t.resGetResource(url)
 		if err != nil {
 			jww.ERROR.Printf("Failed to get json resource %s with error message %s", url, err)
 			return nil
@@ -194,7 +193,7 @@
 			jww.ERROR.Printf("Cannot read json from resource %s with error message %s", url, err)
 			jww.ERROR.Printf("Retry #%d for %s and sleeping for %s", i, url, resSleep)
 			time.Sleep(resSleep)
-			resDeleteCache(url, hugofs.Source())
+			resDeleteCache(url, t.Fs.Source)
 			continue
 		}
 		break
@@ -220,7 +219,7 @@
 // The data separator can be a comma, semi-colon, pipe, etc, but only one character.
 // If you provide multiple parts for the URL they will be joined together to the final URL.
 // GetCSV returns nil or a slice slice to use in a short code.
-func getCSV(sep string, urlParts ...string) [][]string {
+func (t *templateFuncster) getCSV(sep string, urlParts ...string) [][]string {
 	var d [][]string
 	url := strings.Join(urlParts, "")
 
@@ -227,11 +226,11 @@
 	var clearCacheSleep = func(i int, u string) {
 		jww.ERROR.Printf("Retry #%d for %s and sleeping for %s", i, url, resSleep)
 		time.Sleep(resSleep)
-		resDeleteCache(url, hugofs.Source())
+		resDeleteCache(url, t.Fs.Source)
 	}
 
 	for i := 0; i <= resRetries; i++ {
-		c, err := resGetResource(url)
+		c, err := t.resGetResource(url)
 
 		if err == nil && !bytes.Contains(c, []byte(sep)) {
 			err = errors.New("Cannot find separator " + sep + " in CSV.")
--- a/tpl/template_resources_test.go
+++ b/tpl/template_resources_test.go
@@ -80,8 +80,10 @@
 }
 
 func TestScpGetLocal(t *testing.T) {
-	fs := new(afero.MemMapFs)
+	testReset()
+	fs := hugofs.NewMem()
 	ps := helpers.FilePathSeparator
+
 	tests := []struct {
 		path    string
 		content []byte
@@ -95,12 +97,12 @@
 
 	for _, test := range tests {
 		r := bytes.NewReader(test.content)
-		err := helpers.WriteToDisk(test.path, r, fs)
+		err := helpers.WriteToDisk(test.path, r, fs.Source)
 		if err != nil {
 			t.Error(err)
 		}
 
-		c, err := resGetLocal(test.path, fs)
+		c, err := resGetLocal(test.path, fs.Source)
 		if err != nil {
 			t.Errorf("Error getting resource content: %s", err)
 		}
@@ -212,9 +214,9 @@
 	Reset func()
 }
 
-func testRetryWhenDone() wd {
+func testRetryWhenDone(f *templateFuncster) wd {
 	cd := viper.GetString("cacheDir")
-	viper.Set("cacheDir", helpers.GetTempDir("", hugofs.Source()))
+	viper.Set("cacheDir", helpers.GetTempDir("", f.Fs.Source))
 	var tmpSleep time.Duration
 	tmpSleep, resSleep = resSleep, time.Millisecond
 	return wd{func() {
@@ -224,8 +226,11 @@
 }
 
 func TestGetJSONFailParse(t *testing.T) {
-	defer testRetryWhenDone().Reset()
 
+	f := newTestFuncster()
+
+	defer testRetryWhenDone(f).Reset()
+
 	reqCount := 0
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		if reqCount > 0 {
@@ -242,7 +247,7 @@
 	defer os.Remove(getCacheFileID(url))
 
 	want := map[string]interface{}{"gomeetup": []interface{}{"Sydney", "San Francisco", "Stockholm"}}
-	have := getJSON(url)
+	have := f.getJSON(url)
 	assert.NotNil(t, have)
 	if have != nil {
 		assert.EqualValues(t, want, have)
@@ -250,8 +255,10 @@
 }
 
 func TestGetCSVFailParseSep(t *testing.T) {
-	defer testRetryWhenDone().Reset()
+	f := newTestFuncster()
 
+	defer testRetryWhenDone(f).Reset()
+
 	reqCount := 0
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		if reqCount > 0 {
@@ -271,7 +278,7 @@
 	defer os.Remove(getCacheFileID(url))
 
 	want := [][]string{{"gomeetup", "city"}, {"yes", "Sydney"}, {"yes", "San Francisco"}, {"yes", "Stockholm"}}
-	have := getCSV(",", url)
+	have := f.getCSV(",", url)
 	assert.NotNil(t, have)
 	if have != nil {
 		assert.EqualValues(t, want, have)
@@ -279,8 +286,11 @@
 }
 
 func TestGetCSVFailParse(t *testing.T) {
-	defer testRetryWhenDone().Reset()
 
+	f := newTestFuncster()
+
+	defer testRetryWhenDone(f).Reset()
+
 	reqCount := 0
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Add("Content-type", "application/json")
@@ -302,7 +312,7 @@
 	defer os.Remove(getCacheFileID(url))
 
 	want := [][]string{{"gomeetup", "city"}, {"yes", "Sydney"}, {"yes", "San Francisco"}, {"yes", "Stockholm"}}
-	have := getCSV(",", url)
+	have := f.getCSV(",", url)
 	assert.NotNil(t, have)
 	if have != nil {
 		assert.EqualValues(t, want, have)
--- a/tpl/template_test.go
+++ b/tpl/template_test.go
@@ -25,9 +25,21 @@
 	"testing"
 
 	"github.com/spf13/afero"
+	"github.com/spf13/hugo/deps"
+	"github.com/spf13/hugo/helpers"
 	"github.com/spf13/hugo/hugofs"
+	"github.com/spf13/hugo/tplapi"
+	"github.com/spf13/viper"
+	"github.com/stretchr/testify/require"
 )
 
+func testReset() {
+	viper.Reset()
+
+	// TODO(bep) viper-globals
+	viper.Set("currentContentLanguage", helpers.NewLanguage("en"))
+}
+
 // Some tests for Issue #1178 -- Ace
 func TestAceTemplates(t *testing.T) {
 
@@ -68,12 +80,20 @@
 
 			d := "DATA"
 
-			templ := New(logger, func(templ Template) error {
+			config := defaultDepsConfig
+			config.WithTemplate = func(templ tplapi.Template) error {
 				return templ.AddAceTemplate("mytemplate.ace", basePath, innerPath,
 					[]byte(this.baseContent), []byte(this.innerContent))
+			}
 
-			})
+			a := deps.New(config)
 
+			if err := a.LoadTemplates(); err != nil {
+				t.Fatal(err)
+			}
+
+			templ := a.Tmpl.(*GoHTMLTemplate)
+
 			if len(templ.errors) > 0 && this.expectErr == 0 {
 				t.Errorf("Test %d with root '%s' errored: %v", i, root, templ.errors)
 			} else if len(templ.errors) == 0 && this.expectErr == 1 {
@@ -81,7 +101,7 @@
 			}
 
 			var buff bytes.Buffer
-			err := templ.ExecuteTemplate(&buff, "mytemplate.html", d)
+			err := a.Tmpl.ExecuteTemplate(&buff, "mytemplate.html", d)
 
 			if err != nil && this.expectErr == 0 {
 				t.Errorf("Test %d with root '%s' errored: %s", i, root, err)
@@ -93,6 +113,7 @@
 					t.Errorf("Test %d  with root '%s' got\n%s\nexpected\n%s", i, root, result, this.expect)
 				}
 			}
+
 		}
 	}
 
@@ -124,53 +145,60 @@
 		{`tpl`, `{{.0.E}}`, 0, false},
 	} {
 
-		hugofs.InitMemFs()
-		templ := New(logger)
 		overlayTplName := "ot"
 		masterTplName := "mt"
 		finalTplName := "tp"
 
-		if this.writeSkipper != 1 {
-			afero.WriteFile(hugofs.Source(), masterTplName, []byte(this.masterTplContent), 0644)
-		}
-		if this.writeSkipper != 2 {
-			afero.WriteFile(hugofs.Source(), overlayTplName, []byte(this.overlayTplContent), 0644)
-		}
+		defaultDepsConfig.WithTemplate = func(templ tplapi.Template) error {
 
-		err := templ.AddTemplateFileWithMaster(finalTplName, overlayTplName, masterTplName)
+			err := templ.AddTemplateFileWithMaster(finalTplName, overlayTplName, masterTplName)
 
-		if b, ok := this.expect.(bool); ok && !b {
-			if err == nil {
-				t.Errorf("[%d] AddTemplateFileWithMaster didn't return an expected error", i)
-			}
-		} else {
+			if b, ok := this.expect.(bool); ok && !b {
+				if err == nil {
+					t.Errorf("[%d] AddTemplateFileWithMaster didn't return an expected error", i)
+				}
+			} else {
 
-			if err != nil {
-				t.Errorf("[%d] AddTemplateFileWithMaster failed: %s", i, err)
-				continue
-			}
+				if err != nil {
+					t.Errorf("[%d] AddTemplateFileWithMaster failed: %s", i, err)
+					return nil
+				}
 
-			resultTpl := templ.Lookup(finalTplName)
+				resultTpl := templ.Lookup(finalTplName)
 
-			if resultTpl == nil {
-				t.Errorf("[%d] AddTemplateFileWithMaster: Result template not found", i)
-				continue
-			}
+				if resultTpl == nil {
+					t.Errorf("[%d] AddTemplateFileWithMaster: Result template not found", i)
+					return nil
+				}
 
-			var b bytes.Buffer
-			err := resultTpl.Execute(&b, nil)
+				var b bytes.Buffer
+				err := resultTpl.Execute(&b, nil)
 
-			if err != nil {
-				t.Errorf("[%d] AddTemplateFileWithMaster execute failed: %s", i, err)
-				continue
-			}
-			resultContent := b.String()
+				if err != nil {
+					t.Errorf("[%d] AddTemplateFileWithMaster execute failed: %s", i, err)
+					return nil
+				}
+				resultContent := b.String()
 
-			if resultContent != this.expect {
-				t.Errorf("[%d] AddTemplateFileWithMaster got \n%s but expected \n%v", i, resultContent, this.expect)
+				if resultContent != this.expect {
+					t.Errorf("[%d] AddTemplateFileWithMaster got \n%s but expected \n%v", i, resultContent, this.expect)
+				}
 			}
+
+			return nil
 		}
 
+		defaultDepsConfig.Fs = hugofs.NewMem()
+
+		if this.writeSkipper != 1 {
+			afero.WriteFile(defaultDepsConfig.Fs.Source, masterTplName, []byte(this.masterTplContent), 0644)
+		}
+		if this.writeSkipper != 2 {
+			afero.WriteFile(defaultDepsConfig.Fs.Source, overlayTplName, []byte(this.overlayTplContent), 0644)
+		}
+
+		deps.New(defaultDepsConfig)
+
 	}
 
 }
@@ -258,23 +286,29 @@
 			H: "a,b,c,d,e,f",
 		}
 
-		templ := New(logger, func(templ Template) error {
+		defaultDepsConfig.WithTemplate = func(templ tplapi.Template) error {
 			return templ.AddTemplate("fuzz", this.data)
+		}
 
-		})
+		de := deps.New(defaultDepsConfig)
+		require.NoError(t, de.LoadTemplates())
 
+		templ := de.Tmpl.(*GoHTMLTemplate)
+
 		if len(templ.errors) > 0 && this.expectErr == 0 {
 			t.Errorf("Test %d errored: %v", i, templ.errors)
 		} else if len(templ.errors) == 0 && this.expectErr == 1 {
 			t.Errorf("#1 Test %d should have errored", i)
 		}
-		err := templ.ExecuteTemplate(ioutil.Discard, "fuzz", d)
 
+		err := de.Tmpl.ExecuteTemplate(ioutil.Discard, "fuzz", d)
+
 		if err != nil && this.expectErr == 0 {
 			t.Fatalf("Test %d errored: %s", i, err)
 		} else if err == nil && this.expectErr == 2 {
 			t.Fatalf("#2 Test %d should have errored", i)
 		}
+
 	}
 }
 
--- /dev/null
+++ b/tplapi/template.go
@@ -1,0 +1,28 @@
+package tplapi
+
+import (
+	"html/template"
+	"io"
+)
+
+// TODO(bep) make smaller
+// TODO(bep) consider putting this into /tpl and the implementation in /tpl/tplimpl or something
+type Template interface {
+	ExecuteTemplate(wr io.Writer, name string, data interface{}) error
+	ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML
+	Lookup(name string) *template.Template
+	Templates() []*template.Template
+	New(name string) *template.Template
+	GetClone() *template.Template
+	LoadTemplates(absPath string)
+	LoadTemplatesWithPrefix(absPath, prefix string)
+	AddTemplate(name, tpl string) error
+	AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error
+	AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error
+	AddInternalTemplate(prefix, name, tpl string) error
+	AddInternalShortcode(name, tpl string) error
+	Partial(name string, contextList ...interface{}) template.HTML
+	PrintErrors()
+	Funcs(funcMap template.FuncMap)
+	MarkReady()
+}