Thanks to visit codestin.com
Credit goes to github.com

Skip to content

refactor: Migrate from Next.js to pure webpack config #360

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 72 commits into from
Mar 12, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
f87c03e
Remove nextJS as dependency
bryphe-coder Feb 24, 2022
d767aa8
Next js config -> webpack config
bryphe-coder Feb 24, 2022
e1dbec2
Migrate next/link -> react-router-dom Link
bryphe-coder Feb 24, 2022
6f65f1b
Migrate routing to react-router-dom style; get webpack building
bryphe-coder Feb 24, 2022
69010f8
Hook up workspaces route
bryphe-coder Feb 25, 2022
7800a4e
First round of clean-up
bryphe-coder Feb 25, 2022
7158d75
Get SignInForm tests green
bryphe-coder Feb 25, 2022
a58a29d
Get additional tests green
bryphe-coder Feb 25, 2022
bc83df6
Get tests green again
bryphe-coder Feb 25, 2022
3c5895c
HMR / live reload updates from FE variety
bryphe-coder Feb 25, 2022
b657fe6
Remove export from Makefile temporarily
bryphe-coder Feb 25, 2022
75a38d9
Remove export command
bryphe-coder Feb 25, 2022
e4fe1ed
Convert webpack config to typescript
bryphe-coder Feb 25, 2022
d9e9d2d
Split out dev vs prod webpack configs
bryphe-coder Feb 25, 2022
02838a2
Remove nextrouter, port over some functionality to embed
bryphe-coder Feb 25, 2022
d8c983f
Implement content-addressable (and cache-friendly) bundle name
bryphe-coder Feb 26, 2022
396be38
Start setting up new post-nextrouter routing strategy
bryphe-coder Feb 26, 2022
87be7aa
Fix production webpack config
bryphe-coder Feb 26, 2022
3e1d008
Remove _document
bryphe-coder Feb 26, 2022
d1566d3
Remove nested GITKEEP
bryphe-coder Feb 26, 2022
bdd966b
Copy static files over
bryphe-coder Feb 26, 2022
ac064ca
Fix up dependencies
bryphe-coder Feb 26, 2022
c0ca05c
Clean-up
bryphe-coder Feb 26, 2022
b85dcf1
Merge main
bryphe-coder Mar 2, 2022
3a724e4
Merge main
bryphe-coder Mar 8, 2022
40a4e69
Use same entry point as v1
bryphe-coder Mar 8, 2022
7f10110
Initial lint fix
bryphe-coder Mar 8, 2022
a5a09d5
Move index.html -> html_templates
bryphe-coder Mar 8, 2022
5af2c20
Merge branch 'main' into bryphe/experiment/nextjs-to-webpack
bryphe-coder Mar 8, 2022
3898544
Implement 404 page
bryphe-coder Mar 8, 2022
cd07056
Add test case for caching
bryphe-coder Mar 9, 2022
78051ff
Remove now-unused function
bryphe-coder Mar 9, 2022
cf8b4e0
Add plumbing to make CSRF token testable
bryphe-coder Mar 9, 2022
8bd5804
Fix todos
bryphe-coder Mar 9, 2022
427672b
Additional test cases
bryphe-coder Mar 9, 2022
929a3b7
Remove now unnecessary test
bryphe-coder Mar 9, 2022
f822347
Add test + fix template parameters
bryphe-coder Mar 9, 2022
6c00822
Fix up go lint cases
bryphe-coder Mar 9, 2022
12b0e1d
Remove commented and now-unnecessary export step
bryphe-coder Mar 9, 2022
b348b70
Fix Handler -> DefaultHandler
bryphe-coder Mar 9, 2022
07f52d5
Fix remaining lint issues
bryphe-coder Mar 9, 2022
095bcfc
Add webpack-bundle-analyzer + command to run server
bryphe-coder Mar 9, 2022
7b5e982
Better document the webpack files to match the work Grey is doing in v1
bryphe-coder Mar 9, 2022
a3f31c9
Update webpack configs
bryphe-coder Mar 9, 2022
4134392
Port over fileoverview comments
bryphe-coder Mar 9, 2022
90f04ab
Remove accidental addition of test collateral
bryphe-coder Mar 10, 2022
6ffd38b
Use <Navigate> component from react-router-dom instead of custom <Red…
bryphe-coder Mar 10, 2022
7a50bc1
Merge main
bryphe-coder Mar 11, 2022
b4a18e0
Fix lint issues
bryphe-coder Mar 11, 2022
244e305
Remove build:dev to match v1 suite of webpack build commands
bryphe-coder Mar 11, 2022
e65f5af
Remove dev.ts and several references in jest/tsconfig
bryphe-coder Mar 11, 2022
19509bc
Fix copy-paste error with the NotFoundPage
bryphe-coder Mar 11, 2022
f4f7cb3
Add descriptive comment for SWR fetching
bryphe-coder Mar 11, 2022
37295ce
Remove unnecessary inline styles
bryphe-coder Mar 11, 2022
02a8718
Remove custom title in HTMLWebpackPlugin
bryphe-coder Mar 11, 2022
6e168a8
Use default settings for HtmlWebpackPlugin for script injection
bryphe-coder Mar 11, 2022
1a600d1
Remove 'noEmit' flag from tsconfig
bryphe-coder Mar 11, 2022
cab30cd
Remove dev.ts from tsconfig.test.json
bryphe-coder Mar 11, 2022
b725cf8
Format jest file
bryphe-coder Mar 11, 2022
71d076d
Use bundle name as-is for development
bryphe-coder Mar 11, 2022
818fc74
Factor to v1 strategy; get tests passing
bryphe-coder Mar 11, 2022
b61d39e
Fix go lint issues
bryphe-coder Mar 11, 2022
8b67328
Add missed meta tag
bryphe-coder Mar 11, 2022
2a183ce
Port over publicPath, needed for the v1 embed strategy
bryphe-coder Mar 11, 2022
037489f
Fix up formatting
bryphe-coder Mar 11, 2022
0b81ab8
Fix compilation error
bryphe-coder Mar 12, 2022
0839201
Update comment
bryphe-coder Mar 12, 2022
685b8f1
Add progress to build:analyze command
bryphe-coder Mar 12, 2022
807ac3b
Tweak comment
bryphe-coder Mar 12, 2022
2d47d8c
Add more logs to playwright
bryphe-coder Mar 12, 2022
861592a
Port over our playwright utilities for client-side nav
bryphe-coder Mar 12, 2022
c31f594
Merge branch 'main' into bryphe/experiment/nextjs-to-webpack
bryphe-coder Mar 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Additional test cases
  • Loading branch information
bryphe-coder committed Mar 9, 2022
commit 427672bd5d76eceb9def2fed059bb02f53f3b60f
101 changes: 81 additions & 20 deletions site/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"embed"
"fmt"
"html/template"
"io/fs"
"net/http"
"path/filepath"
Expand Down Expand Up @@ -69,10 +70,18 @@ func Handler(filesystem fs.FS, logger slog.Logger, templateFunc HTMLTemplateHand
}

func serveFiles(fileSystem fs.FS, logger slog.Logger) (http.HandlerFunc, error) {
// htmlFileToTemplate is a map of html files -> template
// We need to use templates in order to inject parameters from `HtmlState`
// (like CSRF token and CSP nonce)
htmlFileToTemplate := map[string]*template.Template{}

fileNameToBytes := map[string][]byte{}
var indexBytes []byte
indexBytes = nil
// nonHtmlFileToTemplate is a map of files -> byte contents
// This is used for any non-HTML file
nonHtmlFileToTemplate := map[string][]byte{}

// fallbackHtmlTemplate is used as the 'default' template if
// the path requested doesn't match anything on the file systme.
var fallbackHtmlTemplate *template.Template

files, err := fs.ReadDir(fileSystem, ".")
if err != nil {
Expand All @@ -93,19 +102,37 @@ func serveFiles(fileSystem fs.FS, logger slog.Logger) (http.HandlerFunc, error)
continue
}

fileNameToBytes[normalizedName] = fileBytes
if normalizedName == "index.html" {
indexBytes = fileBytes
isHtml := isHtmlFile(normalizedName)
if isHtml {
// For HTML files, we need to parse and store the template.
// If its index.html, we need to keep a reference to it as well.
template, err := template.New("").Parse(string(fileBytes))
if err != nil {
logger.Warn(context.Background(), "Unable to parse html template", slog.F("fileName", normalizedName))
continue
}

htmlFileToTemplate[normalizedName] = template
// If this is the index page, use it as the fallback template
if strings.HasPrefix(normalizedName, "index.") {
fallbackHtmlTemplate = template
}
} else {
// Non HTML files are easy - just cache the bytes
nonHtmlFileToTemplate[normalizedName] = fileBytes
}

continue
}

// If we reached here, there was something on the file system (most likely a directory)
// that we were unable to handle in the current code - so log a warning.
logger.Warn(context.Background(), "Serving from nested directories is not implemented", slog.F("name", name))
}

if indexBytes == nil {
return nil, xerrors.Errorf("No index.html available")
// If we don't have a default template, then there's not much to do!
if fallbackHtmlTemplate == nil {
return nil, xerrors.Errorf("No index.html found")
}

serveFunc := func(writer http.ResponseWriter, request *http.Request) {
Expand All @@ -116,29 +143,63 @@ func serveFiles(fileSystem fs.FS, logger slog.Logger) (http.HandlerFunc, error)
normalizedFileName = "index.html"
}

isCacheable := !strings.HasSuffix(normalizedFileName, ".html") && !strings.HasSuffix(normalizedFileName, ".htm")

fileBytes, ok := fileNameToBytes[normalizedFileName]
if !ok {
logger.Warn(request.Context(), "Unable to find request file", slog.F("fileName", normalizedFileName))
fileBytes = indexBytes
isCacheable = false
normalizedFileName = "index.html"
}

if isCacheable {
// First, let's look at our non-HTML files to see if this matches
fileBytes, ok := nonHtmlFileToTemplate[normalizedFileName]
if ok {
// All our assets - JavaScript, CSS, images - should be cached.
// For cases like JavaScript, we rely on a cache-busting strategy whenever
// there is a new version (this is handled in our webpack config).
writer.Header().Add("Cache-Control", "public, max-age=31536000, immutable")
http.ServeContent(writer, request, normalizedFileName, time.Time{}, bytes.NewReader(fileBytes))
return
}

http.ServeContent(writer, request, normalizedFileName, time.Time{}, bytes.NewReader(fileBytes))
var buf bytes.Buffer
// TODO: Fix this
templateData := HtmlState{
CSRFToken: "TODO",
CSPNonce: "TODO",
}

// Next, lets try and load from our HTML templates
template, ok := htmlFileToTemplate[normalizedFileName]
if ok {
logger.Debug(context.Background(), "Applying template parameters", slog.F("fileName", normalizedFileName), slog.F("templateData", templateData))
err := template.ExecuteTemplate(&buf, "", templateData)

if err != nil {
logger.Error(request.Context(), "Error executing template", slog.F("templateData", templateData))
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}

http.ServeContent(writer, request, normalizedFileName, time.Time{}, bytes.NewReader(buf.Bytes()))
return
}

// Finally... the path didn't match any file that we had cached.
// This is expected, because any nested path is going to hit this case.
// For that, we'll serve the fallback
logger.Debug(context.Background(), "Applying template parameters", slog.F("fileName", normalizedFileName), slog.F("templateData", templateData))
err := fallbackHtmlTemplate.ExecuteTemplate(&buf, "", templateData)

if err != nil {
logger.Error(request.Context(), "Error executing template", slog.F("templateData", templateData))
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}

http.ServeContent(writer, request, normalizedFileName, time.Time{}, bytes.NewReader(buf.Bytes()))

}

return serveFunc, nil
}

func isHtmlFile(fileName string) bool {
return strings.HasSuffix(fileName, ".html") || strings.HasSuffix(fileName, ".htm")
}

type HtmlState struct {
CSPNonce string
CSRFToken string
Expand Down
63 changes: 54 additions & 9 deletions site/embed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,48 @@ func TestNestedPathsRenderIndex(t *testing.T) {
"index.html": &fstest.MapFile{
Data: []byte("index-test-file"),
},
"favicon.ico": &fstest.MapFile{
Data: []byte("favicon-bytes"),
},
}

var nestedPathTests = []struct {
path string
expected string
}{
// HTML cases
{"/index.html", "index-test-file"},
{"/", "index-test-file"},
{"/nested/index.html", "index-test-file"},
{"/nested", "index-test-file"},
{"/nested/", "index-test-file"},
{"/double/nested/index.html", "index-test-file"},
{"/double/nested", "index-test-file"},
{"/double/nested/", "index-test-file"},

// Other file cases
{"/favicon.ico", "favicon-bytes"},
// Ensure that nested still picks up the 'top-level' file
{"/nested/favicon.ico", "favicon-bytes"},
{"/double/nested/favicon.ico", "favicon-bytes"},
}

srv := httptest.NewServer(site.Handler(rootFS, slog.Logger{}, defaultTemplateFunc))

path := srv.URL + "/some/nested/path"
for _, testCase := range nestedPathTests {
path := srv.URL + testCase.path

req, err := http.NewRequestWithContext(context.Background(), "GET", path, nil)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err, "get index")
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
require.Equal(t, string(data), "index-test-file")
req, err := http.NewRequestWithContext(context.Background(), "GET", path, nil)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err, "get index")
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
require.Equal(t, string(data), testCase.expected)
}
}

func TestCacheHeaderseAreCorrect(t *testing.T) {
func TestCacheHeadersAreCorrect(t *testing.T) {
rootFS := fstest.MapFS{
"index.html": &fstest.MapFile{
Data: []byte("index-test-file"),
Expand Down Expand Up @@ -120,7 +146,26 @@ func TestCacheHeaderseAreCorrect(t *testing.T) {
require.Emptyf(t, cache, "expected path %q to be un-cacheable", path)
require.NoError(t, resp.Body.Close(), "closing response")
}
}

func TestReturnsErrorIfNoIndex(t *testing.T) {
rootFS := fstest.MapFS{
// No index.html - so our router will have no fallback!
"favicon.ico": &fstest.MapFile{
Data: []byte("favicon-bytes"),
},
"bundle.js": &fstest.MapFile{
Data: []byte("bundle-js-bytes"),
},
"icon.svg": &fstest.MapFile{
Data: []byte("svg-bytes"),
},
}

// When no index.html is available, the site handler should panic
require.Panics(t, func() {
site.Handler(rootFS, slog.Logger{}, defaultTemplateFunc)
})
}

func defaultTemplateFunc(r *http.Request) site.HtmlState {
Expand Down