Add an option to output to file or stdout
Problem
It doesn't seem like there is an option to output to stdout or to a file location.
Reasoning for wanting this feature
Trying to use templ with bazel and add a custom rule and want to do something like
genrule(
name = "index_templ",
srcs = ["index.templ"],
outs = ["index_templ.go"],
cmd = "templ generate -f $< -o $@", # or templ generate -f $< > $@
)
If there is another way to do this or something I am missing in the docs please let me know!
Hi, thanks for trying out templ, and for raising the issue and PR.
I've done a review, and it's raised a few things to consider. I'm not familiar with Bazel, so I was wondering if you could explain a bit more, so we get the best solution.
From reading the docs, it looks like it tries to do some stuff around caching build outputs, but the Go tooling does that already for Go packages.
Are you trying to use it to remember to run templ generate if the *.templ files have changed?
Can you explain the use case a bit more please?
Thanks!
Yeah so my use case for Bazel is having a mono repo with multiple build artifacts and Bazel handles the dependency tree + build caching etc.
My reasoning for wanting to put templ generate into Bazel is more of convenience of having a single build command. Right now I'm not checking in my _templ.go files but I could see having my CI incrementally building those too.
I will take another look at the PR. Thank you!
Does PR #198 allow you to export all files to a specific folder of your choice?
So I'm very new to Go (less than 48 hours yay but I like Go finally :eyes:) so I don't really know how packages and import work but I personally find it annoying to have the go files generated next to the source files. And then having to manually delete the generated files when I delete the source file
Does this PR reflect this desire that I have or is what I am asking for an impossible thing in Go xD ?
Yeah it would be able to accomplish that with templ generate -o /path/to/location/dir
To workaround the missing option to specify the output directory for now, the following works for a genrule in a directory called frontend:
genrule(
name = "main_templ",
srcs = ["main.templ"],
outs = ["main_templ.go"],
cmd = "$(location @com_github_a_h_templ//cmd/templ:templ) generate -f $< && cp frontend/main_templ.go $@",
tools = ["@com_github_a_h_templ//cmd/templ"],
)
It might be possible to improve on this. The hardcoded frontend/main_templ.go value would need to be changed if the input file of main.templ is moved/renamed.
+1
I think it will be just better DX to have all the generated files in one place so they don't mix with our code.
So it looks as though the way forward here is to support printing generated files out to stdout, this will allow greater flexibility for those with different use cases.
This could work similarly to the fmt command? templ generate < file.templ
It would be awesome if there was an "output" flag (-o) to generate to a different directory :)
I will mark this to reflect the state, we are looking to add the option to take a templ file from stdin and pipe it to stdout. That way anyone is free to create their own CI pipelines that generate and put their generated files in other folders if that suits them, though this approach would not work with the current tooling around templ so wouldn't be supported out of the box.
FWIW, I'm currently writing a macro to wrap around templ in Bazel.
However, I'm running into the following error:
zsh❯ bazel build -s //lab/lang/go/templ:foo_templ
INFO: Invocation ID: a0aaed13-a38b-4aab-9f46-eb5d4828106c
INFO: Analyzed target //lab/lang/go/templ:foo_templ (1 packages loaded, 2 targets configured).
SUBCOMMAND: # //lab/lang/go/templ:foo_templ [action 'Action lab/lang/go/templ/foo_templ.go', configuration: 9095f31c57e85521371b427894c665203904c9a58a2d69c070d4cb319ba77bfa, execution platform: @@local_config_platform//:host, mnemonic: Action]
(cd /home/yesudeep/.cache/bazel/_bazel_yesudeep/e5f58afa44ddf02c410f9dc20a63651e/execroot/_main && \
exec env - \
bazel-out/k8-opt-exec-ST-13d3ddad9198/bin/external/gazelle~~go_deps~com_github_a_h_templ/cmd/templ/templ_/templ generate -f '$(location lab/lang/go/templ/foo.templ)')
# Configuration: 9095f31c57e85521371b427894c665203904c9a58a2d69c070d4cb319ba77bfa
# Execution platform: @@local_config_platform//:host
WARNING: Remote Cache: Expected output lab/lang/go/templ/foo_templ.go was not created locally.
java.io.IOException: Expected output lab/lang/go/templ/foo_templ.go was not created locally.
at com.google.devtools.build.lib.remote.RemoteExecutionService.lambda$buildUploadManifestAsync$6(RemoteExecutionService.java:1339)
at io.reactivex.rxjava3.internal.operators.single.SingleFromCallable.subscribeActual(SingleFromCallable.java:43)
at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4855)
at io.reactivex.rxjava3.core.Single.blockingGet(Single.java:3644)
at com.google.devtools.build.lib.remote.RemoteExecutionService.buildUploadManifest(RemoteExecutionService.java:1365)
at com.google.devtools.build.lib.remote.RemoteExecutionService.uploadOutputs(RemoteExecutionService.java:1420)
at com.google.devtools.build.lib.remote.RemoteSpawnCache$1.store(RemoteSpawnCache.java:188)
at com.google.devtools.build.lib.exec.AbstractSpawnStrategy.exec(AbstractSpawnStrategy.java:164)
at com.google.devtools.build.lib.exec.AbstractSpawnStrategy.exec(AbstractSpawnStrategy.java:119)
at com.google.devtools.build.lib.exec.SpawnStrategyResolver.exec(SpawnStrategyResolver.java:45)
at com.google.devtools.build.lib.analysis.actions.SpawnAction.execute(SpawnAction.java:261)
at com.google.devtools.build.lib.skyframe.SkyframeActionExecutor$ActionRunner.executeAction(SkyframeActionExecutor.java:1144)
at com.google.devtools.build.lib.skyframe.SkyframeActionExecutor$ActionRunner.run(SkyframeActionExecutor.java:1061)
at com.google.devtools.build.lib.skyframe.ActionExecutionState.runStateMachine(ActionExecutionState.java:165)
at com.google.devtools.build.lib.skyframe.ActionExecutionState.getResultOrDependOnFuture(ActionExecutionState.java:94)
at com.google.devtools.build.lib.skyframe.SkyframeActionExecutor.executeAction(SkyframeActionExecutor.java:558)
at com.google.devtools.build.lib.skyframe.ActionExecutionFunction.checkCacheAndExecuteIfNeeded(ActionExecutionFunction.java:859)
at com.google.devtools.build.lib.skyframe.ActionExecutionFunction.computeInternal(ActionExecutionFunction.java:333)
at com.google.devtools.build.lib.skyframe.ActionExecutionFunction.compute(ActionExecutionFunction.java:171)
at com.google.devtools.build.skyframe.AbstractParallelEvaluator$Evaluate.run(AbstractParallelEvaluator.java:461)
at com.google.devtools.build.lib.concurrent.AbstractQueueVisitor$WrappedRunnable.run(AbstractQueueVisitor.java:414)
at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(Unknown Source)
at java.base/java.util.concurrent.ForkJoinTask.doExec(Unknown Source)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(Unknown Source)
at java.base/java.util.concurrent.ForkJoinPool.scan(Unknown Source)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(Unknown Source)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source)
INFO: From Action lab/lang/go/templ/foo_templ.go:
(!) templ version check: failed to read go.mod file: open /go.mod: no such file or directory
ERROR: /home/yesudeep/code/github.com/yesudeep/foo/lab/lang/go/templ/BUILD.bazel:3:14: output 'lab/lang/go/templ/foo_templ.go' was not created
ERROR: /home/yesudeep/code/github.com/yesudeep/foo/lab/lang/go/templ/BUILD.bazel:3:14: Action lab/lang/go/templ/foo_templ.go failed: not all outputs were created or valid
Target //lab/lang/go/templ:foo_templ failed to build
INFO: Elapsed time: 1.134s, Critical Path: 1.02s
INFO: 2 processes: 1 internal, 1 linux-sandbox.
ERROR: Build did NOT complete successfully
- Why does templ check for a
go.modfile? Can the check be disabled? - What would one need to do so that it behaves more like other tools say:
-o output_file < inputor-o output_file -f input_fileor read from stdin and write to stdout?
The module file is checked because there are 2 dependencies for templ, the binary for generation, and the runtime dependency that is defined in the go.mod, a difference here can sometimes result in undefined behaviour. It should just be a warning rather than a failure though.
In terms of your suggestion 2 @yesudeep that's the goal of this issue, to support a simple stdin stdout option.
To clarify, if the go mod check fails, it's just a message on stdout, it doesn't set a non-zero exit code.
There was a recent PR to add a stdout mode to the templ fmt command. I'd be up for something similar on templ generate for single files. The PR is here, for a reference implementation: https://github.com/a-h/templ/commit/e3085d083da57fcac60961f14bcc1a63ccb8ceac
I'm not interested in setting a different output directory to the directory the Go code is in.
I've implemented a fix for this. I started with the same API as templ fmt wherein if no path is specified the it accepts from stdin, however this can't work, as the generation process requires context such as the filename of the template, so I opted for a stdout flag in combination with the -f flag.
I'm not sure if this is the right approach, but I don't track the templ generated .go files with git and they make it difficult to navigate the directory with the .templ files I want to edit, so I just updated my build process (using air) to move the generated files to a tmp/ directory.
Here's the relevant parts of my .air.toml file:
root = "."
tmp_dir = "tmp"
[build]
bin = "./tmp/myproj serve"
cmd = "templ generate && mv views/*.go tmp/views/. && go build -o ./tmp/myproj ."
Then just update your imports to point to the right place:
import (
"github.com/myorg/myproj/tmp/views"
)
And .gitignore your tmp* directory.
It works, although doing this does seem to make the language server throw errors in the editor that imported components are undefined.