all: make compatible with Go 1.15

With the help of the backported libraries introduced in the previous CL,
we can make the whole cmd/golangorg site run on Go 1.15 again.
This will let us use App Engine standard in advance of the release
of Go 1.16 on App Engine.

Change-Id: I9d1612de6f366e0774919aa6a94af14aafb248f5
Reviewed-on: https://go-review.googlesource.com/c/website/+/323891
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/cmd/golangorg/go115.go b/cmd/golangorg/go115.go
deleted file mode 100644
index 38174dc..0000000
--- a/cmd/golangorg/go115.go
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2021 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.16
-// +build !go1.16
-
-package main
-
-import "log"
-
-func main() {
-	log.Fatalf("golangorg requires Go 1.16 or later")
-}
diff --git a/cmd/golangorg/godoc.go b/cmd/golangorg/godoc.go
index 2cd1b2a..8ff0960 100644
--- a/cmd/golangorg/godoc.go
+++ b/cmd/golangorg/godoc.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package main
 
 import (
diff --git a/cmd/golangorg/godoc_test.go b/cmd/golangorg/godoc_test.go
index 1bdbb0f..66514a1 100644
--- a/cmd/golangorg/godoc_test.go
+++ b/cmd/golangorg/godoc_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package main_test
 
 import (
diff --git a/cmd/golangorg/handlers.go b/cmd/golangorg/handlers.go
index 85b985d..3287e64 100644
--- a/cmd/golangorg/handlers.go
+++ b/cmd/golangorg/handlers.go
@@ -2,18 +2,15 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package main
 
 import (
 	"encoding/json"
 	"go/format"
-	"io/fs"
 	"net/http"
 	"strings"
 
+	"golang.org/x/website/internal/backport/io/fs"
 	"golang.org/x/website/internal/codewalk"
 	"golang.org/x/website/internal/env"
 	"golang.org/x/website/internal/redirect"
diff --git a/cmd/golangorg/local.go b/cmd/golangorg/local.go
index ba00aaa..3e0d9ab 100644
--- a/cmd/golangorg/local.go
+++ b/cmd/golangorg/local.go
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16 && !prod
-// +build go1.16,!prod
+//go:build !prod
+// +build !prod
 
 package main
 
diff --git a/cmd/golangorg/main.go b/cmd/golangorg/main.go
index 37d9d8b..d0beec5 100644
--- a/cmd/golangorg/main.go
+++ b/cmd/golangorg/main.go
@@ -15,21 +15,19 @@
 //				https://golang.org/pkg/compress/zlib)
 //
 
-//go:build go1.16
-// +build go1.16
-
 package main
 
 import (
 	"flag"
 	"fmt"
-	"io/fs"
 	"log"
 	"net/http"
 	"os"
 	"runtime"
 
 	"golang.org/x/website"
+	"golang.org/x/website/internal/backport/io/fs"
+	"golang.org/x/website/internal/backport/osfs"
 	"golang.org/x/website/internal/web"
 )
 
@@ -72,11 +70,11 @@
 	// Serve files from _content, falling back to GOROOT.
 	var content fs.FS
 	if *templateDir != "" {
-		content = os.DirFS(*templateDir)
+		content = osfs.DirFS(*templateDir)
 	} else {
 		content = website.Content
 	}
-	fsys = unionFS{content, os.DirFS(*goroot)}
+	fsys = unionFS{content, osfs.DirFS(*goroot)}
 
 	var err error
 	site, err = web.NewSite(fsys)
diff --git a/cmd/golangorg/prod.go b/cmd/golangorg/prod.go
index e6e3a54..bc81910 100644
--- a/cmd/golangorg/prod.go
+++ b/cmd/golangorg/prod.go
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16 && prod
-// +build go1.16,prod
+//go:build prod
+// +build prod
 
 package main
 
diff --git a/cmd/golangorg/regtest_test.go b/cmd/golangorg/regtest_test.go
index fd694cd..f89b05c 100644
--- a/cmd/golangorg/regtest_test.go
+++ b/cmd/golangorg/regtest_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 // Regression tests to run against a production instance of golangorg.
 
 package main_test
diff --git a/cmd/golangorg/release_test.go b/cmd/golangorg/release_test.go
index 14917a0..4de944b 100644
--- a/cmd/golangorg/release_test.go
+++ b/cmd/golangorg/release_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package main
 
 import (
diff --git a/cmd/golangorg/x.go b/cmd/golangorg/x.go
index a3c8412..741a2f2 100644
--- a/cmd/golangorg/x.go
+++ b/cmd/golangorg/x.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 // This file contains the handlers that serve go-import redirects for Go
 // sub-repositories. It specifies the mapping from import paths like
 // "golang.org/x/tools" to the actual repository locations.
@@ -12,12 +9,12 @@
 package main
 
 import (
-	"html/template"
 	"log"
 	"net/http"
 	"strings"
 
 	"golang.org/x/build/repos"
+	"golang.org/x/website/internal/backport/html/template"
 )
 
 func xHandler(w http.ResponseWriter, r *http.Request) {
diff --git a/cmd/golangorg/x_test.go b/cmd/golangorg/x_test.go
index aedc2db..538d8ab 100644
--- a/cmd/golangorg/x_test.go
+++ b/cmd/golangorg/x_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package main
 
 import (
diff --git a/cmd/googlegolangorg/main.go b/cmd/googlegolangorg/main.go
index daf3966..92cb524 100644
--- a/cmd/googlegolangorg/main.go
+++ b/cmd/googlegolangorg/main.go
@@ -3,10 +3,11 @@
 
 import (
 	"fmt"
-	"html/template"
 	"net/http"
 	"os"
 	"strings"
+
+	"golang.org/x/website/internal/backport/html/template"
 )
 
 var repoMap = map[string]*repoImport{
diff --git a/content.go b/content.go
index 1e4d26f..28edcb0 100644
--- a/content.go
+++ b/content.go
@@ -2,22 +2,35 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 // Package website exports the static content as an embed.FS.
 package website
 
 import (
-	"embed"
-	"io/fs"
+	"os"
+
+	"golang.org/x/website/internal/backport/io/fs"
+	"golang.org/x/website/internal/backport/osfs"
 )
 
 // Content is the website's static content.
-var Content = subdir(embedded, "_content")
+var Content = findContent()
 
-//go:embed _content
-var embedded embed.FS
+// TODO: Use with Go 1.16 in place of findContent call above.
+// var Content = subdir(embedded, "_content")
+// //go:embed _content
+// var embedded embed.FS
+
+func findContent() fs.FS {
+	// Walk parent directories looking for _content.
+	dir := "_content"
+	for i := 0; i < 10; i++ {
+		if _, err := os.Stat(dir + "/lib/godoc/godocs.js"); err == nil {
+			return osfs.DirFS(dir)
+		}
+		dir = "../" + dir
+	}
+	panic("cannot find _content")
+}
 
 func subdir(fsys fs.FS, path string) fs.FS {
 	s, err := fs.Sub(fsys, path)
diff --git a/internal/api/api.go b/internal/api/api.go
index 7fcb23d..c616bf7 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 // This file caches information about which standard library types, methods,
 // and functions appeared in what version of Go
 
@@ -12,13 +9,14 @@
 
 import (
 	"bufio"
-	"io/fs"
 	"path"
 	"path/filepath"
 	"sort"
 	"strconv"
 	"strings"
 	"unicode"
+
+	"golang.org/x/website/internal/backport/io/fs"
 )
 
 // DB is a map of packages to information about those packages'
diff --git a/internal/api/api_test.go b/internal/api/api_test.go
index ced9aa4..469ab2c 100644
--- a/internal/api/api_test.go
+++ b/internal/api/api_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package api
 
 import (
 	"go/build"
-	"os"
 	"runtime"
 	"testing"
+
+	"golang.org/x/website/internal/backport/osfs"
 )
 
 func TestParseVersionRow(t *testing.T) {
@@ -93,7 +91,7 @@
 }
 
 func TestAPIVersion(t *testing.T) {
-	av, err := Load(os.DirFS(runtime.GOROOT()))
+	av, err := Load(osfs.DirFS(runtime.GOROOT()))
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/internal/codewalk/codewalk.go b/internal/codewalk/codewalk.go
index 532a092..e4cb711 100644
--- a/internal/codewalk/codewalk.go
+++ b/internal/codewalk/codewalk.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 // The /doc/codewalk/ tree is synthesized from codewalk descriptions,
 // files named _content/doc/codewalk/*.xml.
 // For an example and a description of the format, see
@@ -19,9 +16,7 @@
 	"encoding/xml"
 	"errors"
 	"fmt"
-	"html/template"
 	"io"
-	"io/fs"
 	"log"
 	"net/http"
 	"os"
@@ -32,6 +27,8 @@
 	"strings"
 	"unicode/utf8"
 
+	"golang.org/x/website/internal/backport/html/template"
+	"golang.org/x/website/internal/backport/io/fs"
 	"golang.org/x/website/internal/web"
 )
 
diff --git a/internal/dl/dl.go b/internal/dl/dl.go
index 2a3a7da..39a54c1 100644
--- a/internal/dl/dl.go
+++ b/internal/dl/dl.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 // Package dl implements a simple downloads frontend server.
 //
 // It accepts HTTP POST requests to create a new download metadata entity, and
diff --git a/internal/dl/dl_test.go b/internal/dl/dl_test.go
index 1a1e632..1e562b3 100644
--- a/internal/dl/dl_test.go
+++ b/internal/dl/dl_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package dl
 
 import (
diff --git a/internal/dl/server.go b/internal/dl/server.go
index 3f8efd4..43cc116 100644
--- a/internal/dl/server.go
+++ b/internal/dl/server.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package dl
 
 import (
diff --git a/internal/dl/server_test.go b/internal/dl/server_test.go
index 8aa5205..2cff8d7 100644
--- a/internal/dl/server_test.go
+++ b/internal/dl/server_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package dl
 
 import (
diff --git a/internal/history/history.go b/internal/history/history.go
index 327b214..42aabf2 100644
--- a/internal/history/history.go
+++ b/internal/history/history.go
@@ -8,10 +8,11 @@
 import (
 	"fmt"
 	"html"
-	"html/template"
 	"sort"
 	"strings"
 	"time"
+
+	"golang.org/x/website/internal/backport/html/template"
 )
 
 // A Release describes a single Go release.
diff --git a/internal/history/release.go b/internal/history/release.go
index a3deec2..47041a0 100644
--- a/internal/history/release.go
+++ b/internal/history/release.go
@@ -5,7 +5,7 @@
 // Package history stores historical data for the Go project.
 package history
 
-import "html/template"
+import "golang.org/x/website/internal/backport/html/template"
 
 // Releases summarizes the changes between official stable releases of Go.
 // It contains entries for all releases of Go, but releases older than Go 1.9
diff --git a/internal/pkgdoc/dir.go b/internal/pkgdoc/dir.go
index fe995e2..5ff4814 100644
--- a/internal/pkgdoc/dir.go
+++ b/internal/pkgdoc/dir.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 // This file contains the code dealing with package directory trees.
 
 package pkgdoc
@@ -15,11 +12,12 @@
 	"go/doc"
 	"go/parser"
 	"go/token"
-	"io/fs"
 	"log"
 	"path"
 	"sort"
 	"strings"
+
+	"golang.org/x/website/internal/backport/io/fs"
 )
 
 type Dir struct {
diff --git a/internal/pkgdoc/dir_test.go b/internal/pkgdoc/dir_test.go
index db1c782..0faa961 100644
--- a/internal/pkgdoc/dir_test.go
+++ b/internal/pkgdoc/dir_test.go
@@ -2,22 +2,20 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package pkgdoc
 
 import (
 	"go/token"
-	"os"
 	"runtime"
 	"sort"
 	"testing"
-	"testing/fstest"
+
+	"golang.org/x/website/internal/backport/osfs"
+	"golang.org/x/website/internal/backport/testing/fstest"
 )
 
 func TestNewDirTree(t *testing.T) {
-	dir := newDir(os.DirFS(runtime.GOROOT()), token.NewFileSet(), "src")
+	dir := newDir(osfs.DirFS(runtime.GOROOT()), token.NewFileSet(), "src")
 	processDir(t, dir)
 }
 
@@ -57,7 +55,7 @@
 		b.Skip("not running tests requiring large file scan in short mode")
 	}
 
-	fs := os.DirFS(runtime.GOROOT())
+	fs := osfs.DirFS(runtime.GOROOT())
 
 	b.ResetTimer()
 	b.ReportAllocs()
diff --git a/internal/pkgdoc/doc.go b/internal/pkgdoc/doc.go
index 218228e..edb5a9f 100644
--- a/internal/pkgdoc/doc.go
+++ b/internal/pkgdoc/doc.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package pkgdoc
 
 import (
@@ -14,7 +11,6 @@
 	"go/doc"
 	"go/token"
 	"io"
-	"io/fs"
 	"io/ioutil"
 	"log"
 	"os"
@@ -24,6 +20,8 @@
 	"strings"
 	"unicode"
 	"unicode/utf8"
+
+	"golang.org/x/website/internal/backport/io/fs"
 )
 
 type Docs struct {
diff --git a/internal/pkgdoc/doc_test.go b/internal/pkgdoc/doc_test.go
index 1ac4f0a..ca5e23f 100644
--- a/internal/pkgdoc/doc_test.go
+++ b/internal/pkgdoc/doc_test.go
@@ -2,14 +2,12 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package pkgdoc
 
 import (
 	"testing"
-	"testing/fstest"
+
+	"golang.org/x/website/internal/backport/testing/fstest"
 )
 
 // TestIgnoredGoFiles tests the scenario where a folder has no .go or .c files,
diff --git a/internal/redirect/redirect.go b/internal/redirect/redirect.go
index d2d8262..42199e2 100644
--- a/internal/redirect/redirect.go
+++ b/internal/redirect/redirect.go
@@ -10,7 +10,6 @@
 import (
 	"context"
 	"fmt"
-	"html/template"
 	"net/http"
 	"os"
 	"regexp"
@@ -20,6 +19,7 @@
 	"time"
 
 	"golang.org/x/net/context/ctxhttp"
+	"golang.org/x/website/internal/backport/html/template"
 )
 
 // Register registers HTTP handlers that redirect old godoc paths to their new
diff --git a/internal/short/short.go b/internal/short/short.go
index a7a1507..3054b91 100644
--- a/internal/short/short.go
+++ b/internal/short/short.go
@@ -12,7 +12,6 @@
 	"context"
 	"errors"
 	"fmt"
-	"html/template"
 	"log"
 	"net/http"
 	"net/url"
@@ -20,6 +19,7 @@
 	"strings"
 
 	"cloud.google.com/go/datastore"
+	"golang.org/x/website/internal/backport/html/template"
 	"golang.org/x/website/internal/memcache"
 )
 
diff --git a/internal/web/astfuncs.go b/internal/web/astfuncs.go
index 3e7a499..79d193d 100644
--- a/internal/web/astfuncs.go
+++ b/internal/web/astfuncs.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package web
 
 import (
@@ -14,12 +11,12 @@
 	"go/doc"
 	"go/printer"
 	"go/token"
-	"html/template"
 	"io"
 	"log"
 	"unicode"
 
 	"golang.org/x/website/internal/api"
+	"golang.org/x/website/internal/backport/html/template"
 	"golang.org/x/website/internal/pkgdoc"
 	"golang.org/x/website/internal/texthtml"
 )
diff --git a/internal/web/docfuncs.go b/internal/web/docfuncs.go
index 1f7850e..6494cd7 100644
--- a/internal/web/docfuncs.go
+++ b/internal/web/docfuncs.go
@@ -2,21 +2,18 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package web
 
 import (
 	"bytes"
 	"fmt"
-	"html/template"
-	"io/fs"
 	"log"
 	"path"
 	"regexp"
 	"strings"
 
+	"golang.org/x/website/internal/backport/html/template"
+	"golang.org/x/website/internal/backport/io/fs"
 	"golang.org/x/website/internal/history"
 	"golang.org/x/website/internal/texthtml"
 )
diff --git a/internal/web/examplefuncs.go b/internal/web/examplefuncs.go
index bfc88da..ce98d9d 100644
--- a/internal/web/examplefuncs.go
+++ b/internal/web/examplefuncs.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package web
 
 import (
@@ -12,12 +9,12 @@
 	"go/ast"
 	"go/format"
 	"go/printer"
-	"html/template"
 	"log"
 	"regexp"
 	"strings"
 	"unicode/utf8"
 
+	"golang.org/x/website/internal/backport/html/template"
 	"golang.org/x/website/internal/pkgdoc"
 )
 
diff --git a/internal/web/file.go b/internal/web/file.go
index c82ff85..4b83422 100644
--- a/internal/web/file.go
+++ b/internal/web/file.go
@@ -2,18 +2,16 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package web
 
 import (
 	"bytes"
 	"encoding/json"
-	"io/fs"
 	"log"
 	"path"
 	"strings"
+
+	"golang.org/x/website/internal/backport/io/fs"
 )
 
 type file struct {
@@ -45,7 +43,9 @@
 		relpath = strings.TrimSuffix(relpath, "/")
 	}
 
-	files := []string{relpath + ".html", relpath + ".md", path.Join(relpath, "index.html"), path.Join(relpath, "index.md")}
+	// Check md before html to work correctly when x/website is layered atop Go 1.15 goroot during Go 1.15 tests.
+	// Want to find x/website's debugging_with_gdb.md not Go 1.15's debuging_with_gdb.html.
+	files := []string{relpath + ".md", relpath + ".html", path.Join(relpath, "index.md"), path.Join(relpath, "index.html")}
 	var filePath string
 	var b []byte
 	var err error
diff --git a/internal/web/istext.go b/internal/web/istext.go
index c584540..07c667e 100644
--- a/internal/web/istext.go
+++ b/internal/web/istext.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package web
 
 import (
-	"io/fs"
 	"path"
 	"strings"
 	"unicode/utf8"
+
+	"golang.org/x/website/internal/backport/io/fs"
 )
 
 // isText reports whether a significant prefix of s looks like correct UTF-8;
diff --git a/internal/web/markdown.go b/internal/web/markdown.go
index 1ca78c6..1973461 100644
--- a/internal/web/markdown.go
+++ b/internal/web/markdown.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package web
 
 import (
diff --git a/internal/web/pkgdoc.go b/internal/web/pkgdoc.go
index 32949f4..212526d 100644
--- a/internal/web/pkgdoc.go
+++ b/internal/web/pkgdoc.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package web
 
 import (
diff --git a/internal/web/site.go b/internal/web/site.go
index 1ad8948..4383eee 100644
--- a/internal/web/site.go
+++ b/internal/web/site.go
@@ -2,18 +2,13 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package web
 
 import (
 	"bytes"
 	"fmt"
 	"html"
-	"html/template"
 	"io"
-	"io/fs"
 	"log"
 	"net/http"
 	"path"
@@ -23,6 +18,9 @@
 	"strings"
 
 	"golang.org/x/website/internal/api"
+	"golang.org/x/website/internal/backport/html/template"
+	"golang.org/x/website/internal/backport/httpfs"
+	"golang.org/x/website/internal/backport/io/fs"
 	"golang.org/x/website/internal/pkgdoc"
 	"golang.org/x/website/internal/spec"
 	"golang.org/x/website/internal/texthtml"
@@ -59,7 +57,7 @@
 		fs:         fsys,
 		api:        apiDB,
 		mux:        http.NewServeMux(),
-		fileServer: http.FileServer(http.FS(fsys)),
+		fileServer: http.FileServer(httpfs.FS(fsys)),
 	}
 	docs := &docServer{
 		p: p,
diff --git a/internal/web/site_test.go b/internal/web/site_test.go
index 798ac2f..2f3290b 100644
--- a/internal/web/site_test.go
+++ b/internal/web/site_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package web
 
 import (
@@ -13,7 +10,8 @@
 	"net/url"
 	"strings"
 	"testing"
-	"testing/fstest"
+
+	"golang.org/x/website/internal/backport/testing/fstest"
 )
 
 func testServeBody(t *testing.T, p *Site, path, body string) {
diff --git a/internal/web/sitefuncs.go b/internal/web/sitefuncs.go
index 04f1ad7..02d093c 100644
--- a/internal/web/sitefuncs.go
+++ b/internal/web/sitefuncs.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package web
 
 import (
@@ -14,10 +11,10 @@
 	"go/doc"
 	"go/token"
 	"html"
-	"html/template"
 	"path"
 	"strings"
 
+	"golang.org/x/website/internal/backport/html/template"
 	"golang.org/x/website/internal/pkgdoc"
 )
 
diff --git a/internal/web/tab.go b/internal/web/tab.go
index e84aa8e..88cb4f7 100644
--- a/internal/web/tab.go
+++ b/internal/web/tab.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package web
 
 import "io"
diff --git a/internal/web/template_test.go b/internal/web/template_test.go
index 3ca8bbd..c9fbb84 100644
--- a/internal/web/template_test.go
+++ b/internal/web/template_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package web
 
 import (
@@ -12,10 +9,10 @@
 	"fmt"
 	"go/parser"
 	"go/token"
-	"html/template"
 	"strings"
 	"testing"
 
+	"golang.org/x/website/internal/backport/html/template"
 	"golang.org/x/website/internal/pkgdoc"
 )
 
diff --git a/internal/webtest/webtest.go b/internal/webtest/webtest.go
index c16c4bb..8fe9b33 100644
--- a/internal/webtest/webtest.go
+++ b/internal/webtest/webtest.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 // Package webtest implements script-based testing for web servers.
 //
 // The scripts, described below, can be run against http.Handler
@@ -155,10 +152,10 @@
 	"errors"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
-	"os"
 	"path/filepath"
 	"regexp"
 	"strings"
@@ -192,7 +189,7 @@
 	}
 	var buf bytes.Buffer
 	for _, file := range files {
-		data, err := os.ReadFile(file)
+		data, err := ioutil.ReadFile(file)
 		if err != nil {
 			fmt.Fprintf(&buf, "# %s\n%v\n", file, err)
 			continue
@@ -244,7 +241,7 @@
 	}
 	for _, file := range files {
 		t.Run(filepath.Base(file), func(t *testing.T) {
-			data, err := os.ReadFile(file)
+			data, err := ioutil.ReadFile(file)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -345,7 +342,7 @@
 	if err != nil {
 		return fmt.Errorf("%s:%d: %s %s: %s", c.file, c.line, c.method, c.url, err)
 	}
-	body, err := io.ReadAll(resp.Body)
+	body, err := ioutil.ReadAll(resp.Body)
 	resp.Body.Close()
 	if err != nil {
 		return fmt.Errorf("%s:%d: %s %s: reading body: %s", c.file, c.line, c.method, c.url, err)
diff --git a/internal/webtest/webtest_test.go b/internal/webtest/webtest_test.go
index 1705aa1..db23d92 100644
--- a/internal/webtest/webtest_test.go
+++ b/internal/webtest/webtest_test.go
@@ -2,16 +2,13 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.16
-// +build go1.16
-
 package webtest
 
 import (
 	"fmt"
+	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
-	"os"
 	"path/filepath"
 	"strings"
 	"testing"
@@ -54,7 +51,7 @@
 	}
 	for _, file := range files {
 		t.Run(filepath.Base(file), func(t *testing.T) {
-			data, err := os.ReadFile(file)
+			data, err := ioutil.ReadFile(file)
 			if err != nil {
 				t.Fatal(err)
 			}