templ icon indicating copy to clipboard operation
templ copied to clipboard

Add an option to output to file or stdout

Open rowan-gud opened this issue 2 years ago • 5 comments

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!

rowan-gud avatar Oct 02 '23 21:10 rowan-gud

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!

a-h avatar Oct 03 '23 07:10 a-h

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!

rowan-gud avatar Oct 03 '23 15:10 rowan-gud

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 ?

gungun974 avatar Oct 03 '23 16:10 gungun974

Yeah it would be able to accomplish that with templ generate -o /path/to/location/dir

rowan-gud avatar Oct 03 '23 17:10 rowan-gud

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.

ReneHollander avatar Nov 04 '23 08:11 ReneHollander

+1

moshe5745 avatar Jan 28 '24 20:01 moshe5745

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.

moshe5745 avatar Jan 28 '24 20:01 moshe5745

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

joerdav avatar Jan 30 '24 16:01 joerdav

It would be awesome if there was an "output" flag (-o) to generate to a different directory :)

jimafisk avatar Feb 26 '24 19:02 jimafisk

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.

joerdav avatar Feb 27 '24 10:02 joerdav

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
  1. Why does templ check for a go.mod file? Can the check be disabled?
  2. What would one need to do so that it behaves more like other tools say: -o output_file < input or -o output_file -f input_file or read from stdin and write to stdout?

yesudeep avatar Mar 17 '24 07:03 yesudeep

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.

joerdav avatar Mar 18 '24 09:03 joerdav

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.

a-h avatar Mar 18 '24 12:03 a-h

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.

joerdav avatar Mar 19 '24 08:03 joerdav

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.

jimafisk avatar May 10 '24 20:05 jimafisk