Skip to content

Commit 65c2618

Browse files
Aerek-Yasaa-h
andauthoredAug 18, 2024··
feat: add support for lazy generation (#874)
Co-authored-by: Adrian Hesketh <adrianhesketh@hushmail.com>
1 parent b5513b9 commit 65c2618

File tree

7 files changed

+39
-9
lines changed

7 files changed

+39
-9
lines changed
 

‎cmd/templ/generatecmd/cmd.go

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ func (cmd Generate) Run(ctx context.Context) (err error) {
9898
cmd.Args.GenerateSourceMapVisualisations,
9999
cmd.Args.KeepOrphanedFiles,
100100
cmd.Args.FileWriter,
101+
cmd.Args.Lazy,
101102
)
102103

103104
// If we're processing a single file, don't bother setting up the channels/multithreaing.
@@ -183,6 +184,7 @@ func (cmd Generate) Run(ctx context.Context) (err error) {
183184
cmd.Args.GenerateSourceMapVisualisations,
184185
cmd.Args.KeepOrphanedFiles,
185186
cmd.Args.FileWriter,
187+
cmd.Args.Lazy,
186188
)
187189
errorCount.Store(0)
188190
if err := watcher.WalkFiles(ctx, cmd.Args.Path, events); err != nil {

‎cmd/templ/generatecmd/eventhandler.go

+27-8
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func NewFSEventHandler(
4545
genSourceMapVis bool,
4646
keepOrphanedFiles bool,
4747
fileWriter FileWriterFunc,
48+
lazy bool,
4849
) *FSEventHandler {
4950
if !path.IsAbs(dir) {
5051
dir, _ = filepath.Abs(dir)
@@ -63,6 +64,7 @@ func NewFSEventHandler(
6364
DevMode: devMode,
6465
keepOrphanedFiles: keepOrphanedFiles,
6566
writer: fileWriter,
67+
lazy: lazy,
6668
}
6769
if devMode {
6870
fseh.genOpts = append(fseh.genOpts, generator.WithExtractStrings())
@@ -86,6 +88,7 @@ type FSEventHandler struct {
8688
Errors []error
8789
keepOrphanedFiles bool
8890
writer func(string, []byte) error
91+
lazy bool
8992
}
9093

9194
func (h *FSEventHandler) HandleEvent(ctx context.Context, event fsnotify.Event) (goUpdated, textUpdated bool, err error) {
@@ -125,10 +128,16 @@ func (h *FSEventHandler) HandleEvent(ctx context.Context, event fsnotify.Event)
125128
}
126129

127130
// If the file hasn't been updated since the last time we processed it, ignore it.
128-
if !h.UpsertLastModTime(event.Name) {
131+
lastModTime, updatedModTime := h.UpsertLastModTime(event.Name)
132+
if !updatedModTime {
129133
h.Log.Debug("Skipping file because it wasn't updated", slog.String("file", event.Name))
130134
return false, false, nil
131135
}
136+
// If the go file is newer than the templ file, skip generation, because it's up-to-date.
137+
if h.lazy && goFileIsUpToDate(event.Name, lastModTime) {
138+
h.Log.Debug("Skipping file because the Go file is up-to-date", slog.String("file", event.Name))
139+
return false, false, nil
140+
}
132141

133142
// Start a processor.
134143
start := time.Now()
@@ -159,6 +168,15 @@ func (h *FSEventHandler) HandleEvent(ctx context.Context, event fsnotify.Event)
159168
return goUpdated, textUpdated, nil
160169
}
161170

171+
func goFileIsUpToDate(templFileName string, templFileLastMod time.Time) (upToDate bool) {
172+
goFileName := strings.TrimSuffix(templFileName, ".templ") + "_templ.go"
173+
goFileInfo, err := os.Stat(goFileName)
174+
if err != nil {
175+
return false
176+
}
177+
return goFileInfo.ModTime().After(templFileLastMod)
178+
}
179+
162180
func (h *FSEventHandler) SetError(fileName string, hasError bool) (previouslyHadError bool, errorCount int) {
163181
h.fileNameToErrorMutex.Lock()
164182
defer h.fileNameToErrorMutex.Unlock()
@@ -170,19 +188,20 @@ func (h *FSEventHandler) SetError(fileName string, hasError bool) (previouslyHad
170188
return previouslyHadError, len(h.fileNameToError)
171189
}
172190

173-
func (h *FSEventHandler) UpsertLastModTime(fileName string) (updated bool) {
191+
func (h *FSEventHandler) UpsertLastModTime(fileName string) (modTime time.Time, updated bool) {
174192
fileInfo, err := os.Stat(fileName)
175193
if err != nil {
176-
return false
194+
return modTime, false
177195
}
178196
h.fileNameToLastModTimeMutex.Lock()
179197
defer h.fileNameToLastModTimeMutex.Unlock()
180-
lastModTime := h.fileNameToLastModTime[fileName]
181-
if !fileInfo.ModTime().After(lastModTime) {
182-
return false
198+
previousModTime := h.fileNameToLastModTime[fileName]
199+
currentModTime := fileInfo.ModTime()
200+
if !currentModTime.After(previousModTime) {
201+
return currentModTime, false
183202
}
184-
h.fileNameToLastModTime[fileName] = fileInfo.ModTime()
185-
return true
203+
h.fileNameToLastModTime[fileName] = currentModTime
204+
return currentModTime, true
186205
}
187206

188207
func (h *FSEventHandler) UpsertHash(fileName string, hash [sha256.Size]byte) (updated bool) {

‎cmd/templ/generatecmd/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type Arguments struct {
2626
// PPROFPort is the port to run the pprof server on.
2727
PPROFPort int
2828
KeepOrphanedFiles bool
29+
Lazy bool
2930
}
3031

3132
func Run(ctx context.Context, log *slog.Logger, args Arguments) (err error) {

‎cmd/templ/generatecmd/test-eventhandler/eventhandler_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func TestErrorLocationMapping(t *testing.T) {
4343

4444
slog := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{}))
4545
var fw generatecmd.FileWriterFunc
46-
fseh := generatecmd.NewFSEventHandler(slog, ".", false, []generator.GenerateOpt{}, false, false, fw)
46+
fseh := generatecmd.NewFSEventHandler(slog, ".", false, []generator.GenerateOpt{}, false, false, fw, false)
4747
for _, test := range tests {
4848
// The raw files cannot end in .templ because they will cause the generator to fail. Instead,
4949
// we create a tmp file that ends in .templ only for the duration of the test.

‎cmd/templ/main.go

+4
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ Args:
169169
If present, the command will issue a reload event to the proxy 127.0.0.1:7331, or use proxyport and proxybind to specify a different address.
170170
-w
171171
Number of workers to use when generating code. (default runtime.NumCPUs)
172+
-lazy
173+
Only generate .go files if the source .templ file is newer.
172174
-pprof
173175
Port to run the pprof server on.
174176
-keep-orphaned-files
@@ -215,6 +217,7 @@ func generateCmd(stdout, stderr io.Writer, args []string) (code int) {
215217
keepOrphanedFilesFlag := cmd.Bool("keep-orphaned-files", false, "")
216218
verboseFlag := cmd.Bool("v", false, "")
217219
logLevelFlag := cmd.String("log-level", "info", "")
220+
lazyFlag := cmd.Bool("lazy", false, "")
218221
helpFlag := cmd.Bool("help", false, "")
219222
err := cmd.Parse(args)
220223
if err != nil {
@@ -259,6 +262,7 @@ func generateCmd(stdout, stderr io.Writer, args []string) (code int) {
259262
IncludeTimestamp: *includeTimestampFlag,
260263
PPROFPort: *pprofPortFlag,
261264
KeepOrphanedFiles: *keepOrphanedFilesFlag,
265+
Lazy: *lazyFlag,
262266
})
263267
if err != nil {
264268
color.New(color.FgRed).Fprint(stderr, "(✗) ")

‎docs/docs/04-core-concepts/02-template-generation.md

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ Args:
6060
If present, the command will issue a reload event to the proxy 127.0.0.1:7331, or use proxyport and proxybind to specify a different address.
6161
-w
6262
Number of workers to use when generating code. (default runtime.NumCPUs)
63+
-lazy
64+
Only generate .go files if the source .templ file is newer.
6365
-pprof
6466
Port to run the pprof server on.
6567
-keep-orphaned-files

‎docs/docs/09-commands-and-tools/01-cli.md

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ Args:
5151
The address the proxy will listen on. (default 127.0.0.1)
5252
-w
5353
Number of workers to use when generating code. (default runtime.NumCPUs)
54+
-lazy
55+
Only generate .go files if the source .templ file is newer.
5456
-pprof
5557
Port to run the pprof server on.
5658
-keep-orphaned-files

0 commit comments

Comments
 (0)
Please sign in to comment.