Repository: a-h/templ

Issue:
question: Does/Should javascript blocks be formatted with `templ fmt` command?
This is just a question as per the title. I am not sure if it should work.

example file with weird indentations:

```bash
$ templ fmt < ./siteexp.templ
package templates

script graph(data string) {
        const chart = LightweightCharts.createChart(document.body, { width: 400, height: 300 });
                const lineSeries = chart.addLineSeries();
        lineSeries.setData(data);
    if (true) {
    console.log("yes");
    } else {
    console.log("no");
    }
}

templ body() {
        <script>
                const chart = LightweightCharts.createChart(document.body, { width: 400, height: 300 });
                const lineSeries = chart.addLineSeries();
                lineSeries.setData([
                                { time: '2019-04-11', value: 80.01 },
                                { time: '2019-04-12', value: 96.63 },
                                        { time: '2019-04-13', value: 76.64 },
                                { time: '2019-04-14', value: 81.89 },
                                { time: '2019-04-15', value: 74.43 },
                                        { time: '2019-04-16', value: 80.01 },
                                { time: '2019-04-17', value: 96.63 },
                                { time: '2019-04-18', value: 76.64 },
                                { time: '2019-04-19', value: 81.89 },
                                        { time: '2019-04-20', value: 74.43 },
                ]);
        </script>
        <body>
                <div>
                        hello
                </div>
        </body>
}

```

The html 'body' section at the end does format, however nothing in either a 'script' block or tags gets formatted.

Discussion:
@joerdav: Hi @jmarais to answer your question of if templ "does" any formatting, it doesn't currently no.

Whether it should is a harder question, there are a lot of differing opinions on how formatting should be done in terms of JS, so my suggestion would be to encourage separate tools that allow users to configure preferences, rather than having these baked in to templ.@jmarais: Thanks. I had some problem formatting the javascript inside a templ file. I don't know of any formatters that will handle this embedded language situation. Since formatters just overwrite the whole file they essentially compete with each other.
Do you currently have a method to handle this? Or is the current suggestion to just keep the 'script' blocks formatted by hand?@a-h: Interesting idea. I think that templ could detect the presence of a .prettierrc and, if present, and the prettier executable is on the path, could call out and format the JS.

If a .prettierrc was found, but there's no prettier executable, then it could be a warning.@JonnyWhitney: If you are using Neovim, you can run your formatters with https://github.com/stevearc/conform.nvim, which has injected language support through tree sitter. I use it to run biome on the JavaScript portions of my code. @jmarais: @JonnyLoughlin, Thanks. I will give conform a shot.

edit: @JonnyLoughlin  I played around with conform. It doesnt seem to detect the javascript blocks as injected languages in my above example file. I might be messing up the config, do you have a config I can take a look at?
I think I got it with the ```["*"] = { "injected" },``` config in the `formatters_by_ft``` section. conform can fallback to the lsp and format injected langues. 
It still feels weird using external formatters while the templ tool provides a 'fmt' command.@JonnyWhitney: ```
return {
  "stevearc/conform.nvim",
  event = { "BufWritePre" },
  cmd = { "ConformInfo" },
  opts = {
    formatters_by_ft = {
      lua = { "stylua" },

      go = { "goimports", "gofumpt" },
      templ = { "templ", "injected" },

      javascript = { "biome" },
      typescript = { "biome" },
      typescriptreact = { "biome" },
      json = { "biome" },

      sh = { "beautysh" },
      zsh = { "beautysh" },
    },
    format_on_save = {
      lsp_fallback = false,
      timeout_ms = 1000,
    },
    log_level = vim.log.levels.INFO,
    notify_on_error = true,
  },
}
```
That is my config.  There seems to be a bit of an issue with indenting with this method, but it gets fixed by wrapping the js code.  For example:
```
script graph(data string) {
  {
        const chart = LightweightCharts.createChart(document.body, { width: 400, height: 300 });
                const lineSeries = chart.addLineSeries();
        lineSeries.setData(data);
    if (true) {
    console.log("yes");
    } else {
    console.log("no");
    }

  }
}

templ body() {
        <script>
        {

                const chart = LightweightCharts.createChart(document.body, { width: 400, height: 300 });
                const lineSeries = chart.addLineSeries();
                lineSeries.setData([
                                { time: '2019-04-11', value: 80.01 },
                                { time: '2019-04-12', value: 96.63 },
                                        { time: '2019-04-13', value: 76.64 },
                                { time: '2019-04-14', value: 81.89 },
                                { time: '2019-04-15', value: 74.43 },
                                        { time: '2019-04-16', value: 80.01 },
                                { time: '2019-04-17', value: 96.63 },
                                { time: '2019-04-18', value: 76.64 },
                                { time: '2019-04-19', value: 81.89 },
                                        { time: '2019-04-20', value: 74.43 },
                ]);
        }
        </script>
        <body>
                <div>
                        hello
                </div>
        </body>
}
```
formats to 
```
script graph(data string) {
  {
	const chart = LightweightCharts.createChart(document.body, {
		width: 400,
		height: 300,
	});
	const lineSeries = chart.addLineSeries();
	lineSeries.setData(data);
	if (true) {
		console.log("yes");
	} else {
		console.log("no");
	}
}
}

templ body() {
	<script>
        {
	const chart = LightweightCharts.createChart(document.body, {
		width: 400,
		height: 300,
	});
	const lineSeries = chart.addLineSeries();
	lineSeries.setData([
		{ time: "2019-04-11", value: 80.01 },
		{ time: "2019-04-12", value: 96.63 },
		{ time: "2019-04-13", value: 76.64 },
		{ time: "2019-04-14", value: 81.89 },
		{ time: "2019-04-15", value: 74.43 },
		{ time: "2019-04-16", value: 80.01 },
		{ time: "2019-04-17", value: 96.63 },
		{ time: "2019-04-18", value: 76.64 },
		{ time: "2019-04-19", value: 81.89 },
		{ time: "2019-04-20", value: 74.43 },
	]);
}</script>
	<body>
		<div>
			hello
		</div>
	</body>
}
```
for me with my config.  I do intend on figuring out why the extra brackets are needed.  I just haven't had time to dive in.@jmarais: Thanks for the reference. The brackets are a neat trick.

Something else I tried was just using the javascript functions in the `.templ` file, but defining all the javascript logic in a `.js` file. I just run the templ formatter over the `.templ` file and can then use the javascript related tools (tsserver lsp and prettier) in the `.js` files without worrying about embedded language formatting.@a-h: I had a play around with this earlier in the week.

I was able to update the `parser/v2/raw.go` file to get it to format its content when it writes itself out. There's no popular JS / CSS formatter in Go, so I had to stick with prettier. Since it's in JS, I had to pipe the content over to the prettier command, and pass prettier a file name that's appropriate to the contents of the script or style element (scripts can contain JSON, not JS etc.).

It worked, but then I realised that any HTML LSP that you had would also do the same, and that the prettier formatting and LSP formatting might clash.

I was also concerned about the performance of starting up lots of processes (one per script or style tag in your code).

I noted that if the JS is invalid in some way, prettier bombs out with some log entries on stdout. That gave me the idea of testing the JS early, using the `tdewolff` JS parser - that worked really well as a diagnostic, which could be plugged into the templ LSP... but again, what's the point if the design in https://github.com/a-h/templ/issues/498 goes ahead.

```go
func invalidScriptElementContentsDiagnoser(n Node) ([]Diagnostic, error) {
	e, ok := n.(RawElement)
	if !ok || e.Name != "script" {
		return nil, nil
	}
	_, err := js.Parse(parse.NewInputString(e.Contents), js.Options{})
	if err != nil {
		pe, isParseError := err.(*parse.Error)
		if !isParseError {
			return []Diagnostic{{
				Message: fmt.Sprintf("invalid content in script element: %v", err),
				Range:   e.ContentsRange,
			}}, nil
		}
		// Calculate the offset from the start of the script element to the error.
		from := e.ContentsRange.From
		from.Line += uint32(pe.Line)
		from.Col += uint32(pe.Column)
		for li, line := range strings.Split(e.Contents, "\n") {
			if li == pe.Line {
				from.Index += int64(pe.Column)
				break
			}
			from.Index += int64(len(line)) + 1
		}
		return []Diagnostic{{
			Message: fmt.Sprintf("invalid content in script element: %v", pe.Message),
			Range: Range{
				From: from,
				To:   from,
			},
		}}, nil
	}
	return nil, nil
}
```

So, basically, I think that we'll get more benefit from implementing https://github.com/a-h/templ/issues/498 than from doing this, so I'm going to pause my experiment on this, and focus on getting multiple LSPs in place.

Then, you can add a prettier LSP into your dev workflow which would handle this. At that point, `templ fmt` could also be updated to use 3rd party LSPs to complete formatting work.@jmarais: > It worked, but then I realised that any HTML LSP that you had would also do the same, and that the prettier formatting and LSP formatting might clash.

I played around with prettier with the LSPs and ended at the same place.

>So, basically, I think that we'll get more benefit from implementing https://github.com/a-h/templ/issues/498 than from doing this, so I'm going to pause my experiment on this, and focus on getting multiple LSPs in place.
Then, you can add a prettier LSP into your dev workflow which would handle this. At that point, templ fmt could also be updated to use 3rd party LSPs to complete formatting work.

That could help.@muhrizqiardi: I took a look at the docs for developing Prettier plugin [1] [2] and there is a method for formatting languages within language--in other words, embedded language, for example JavaScript within HTML, CSS-in-JS, etc

This made me wonder--is it possible to develop a Prettier plugin for Templ? 

So that it's possible to do `templ fmt && prettier` or something like that

[1] https://prettier.io/docs/en/plugins#optional-embed
[2] https://prettier.io/docs/en/options.html#embedded-language-formatting@therealparmesh: @muhrizqiardi Here's an attempt: https://github.com/therealparmesh/prettier-plugin-templ-script@a-h: The linked PR uses prettier to format `style` and `script` elements. Feel free to give it a try out.@dimmerz92: I know this is closed but perhaps just my 2c if it's welcome.

templ could follow Go's philosophy of just providing an opinionated default format for JS and if people don't like it, they can provide an override that templ could check for and disable it's own JS formatting?

Only if there is an appetite for it, that is.

Solution patch:
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 7c51020..ad77e22 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -1,5 +1,10 @@
 # Coding standards
 
+## Behaviour
+
+* Always run `go fmt` after making changes to Go code.
+* Always run unit tests after making changes to Go code.
+
 ## Environment setup
 
 * Ensure that the user has direnv installed, and that it is set up correctly in their shell. See https://direnv.net/docs/installation.html
@@ -25,13 +30,14 @@ The most useful tasks for local development are:
 * `xc install-snapshot` - builds the templ CLI and installs it into `~/bin`. Ensure that this is in your path.
 * `xc generate` - generates Go code from the templ files in the project.
 * `xc test` - regenerates all templates, and runs the unit tests.
+* `xc test-short` - runs shorter tests, avoiding long running tests for filesystem watchers etc.
 * `xc fmt` - runs `gofmt` to format all Go code.
 * `xc lint` - run the same linting as run in the CI process.
 * `xc docs-run` - run the Docusaurus documentation site.
 
 templ has a code generation step, this is automatically carried out using `xc test`.
 
-Don't install templ globally using `xc install-snapshot` or `go install`. Use the `xc generate` or `xc test` tasks to generate the code, which will also run the tests.
+Don't install templ globally using `xc install-snapshot` or `go install`. Use the `xc generate` or `xc test-short` tasks to generate the code, which will also run the tests.
 
 ## Commit messages
 
@@ -63,6 +69,7 @@ Examples:
 * Use the `xc fmt` and `xc lint` build tasks to format and lint code before committing.
 * Don't use unnecessary comments that explain what the code does.
 * If comments are used, ensure that they are full sentences, and use proper punctuation, including ending with a full stop.
+* Don't write comments after the end of keywords, e.g. `continue // Only process pairs`
 
 ## Tests
 
diff --git a/.version b/.version
index bc93d73..aa556d4 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-0.3.935
\ No newline at end of file
+0.3.935
diff --git a/cmd/templ/fmtcmd/main.go b/cmd/templ/fmtcmd/main.go
index b52db83..0fa5c19 100644
--- a/cmd/templ/fmtcmd/main.go
+++ b/cmd/templ/fmtcmd/main.go
@@ -8,37 +8,66 @@ import (
 	"log/slog"
 	"os"
 	"runtime"
-	"sync"
 	"time"
 
-	"github.com/a-h/templ/cmd/templ/imports"
 	"github.com/a-h/templ/cmd/templ/processor"
-	parser "github.com/a-h/templ/parser/v2"
+	"github.com/a-h/templ/internal/format"
 	"github.com/natefinch/atomic"
 )
 
 type Arguments struct {
-	FailIfChanged bool
-	ToStdout      bool
-	StdinFilepath string
-	Files         []string
-	WorkerCount   int
+	FailIfChanged    bool
+	ToStdout         bool
+	StdinFilepath    string
+	Files            []string
+	WorkerCount      int
+	PrettierCommand  string
+	PrettierRequired bool
 }
 
 func Run(log *slog.Logger, stdin io.Reader, stdout io.Writer, args Arguments) (err error) {
 	// If no files are provided, read from stdin and write to stdout.
+	formatterConfig := format.Config{
+		PrettierCommand:  args.PrettierCommand,
+		PrettierRequired: args.PrettierRequired,
+	}
 	if len(args.Files) == 0 {
-		out, _ := format(writeToWriter(stdout), readFromReader(stdin, args.StdinFilepath), true)
-		return out
+		src, err := io.ReadAll(stdin)
+		if err != nil {
+			return fmt.Errorf("failed to read from stdin: %w", err)
+		}
+		formatted, _, err := format.Templ(src, args.StdinFilepath, formatterConfig)
+		if err != nil {
+			return fmt.Errorf("failed to format stdin: %w", err)
+		}
+		if _, err = stdout.Write(formatted); err != nil {
+			return fmt.Errorf("failed to write to stdout: %w", err)
+		}
+		return nil
 	}
+	// If files are provided, process each file.
 	process := func(fileName string) (error, bool) {
-		read := readFromFile(fileName)
-		write := writeToFile
+		src, err := os.ReadFile(fileName)
+		if err != nil {
+			return fmt.Errorf("failed to read file %q: %w", fileName, err), false
+		}
+		formatted, changed, err := format.Templ(src, fileName, formatterConfig)
+		if err != nil {
+			return fmt.Errorf("failed to format file %q: %w", fileName, err), false
+		}
+		if !changed && !args.ToStdout {
+			return nil, false
+		}
 		if args.ToStdout {
-			write = writeToWriter(stdout)
+			if _, err := stdout.Write(formatted); err != nil {
+				return fmt.Errorf("failed to write to stdout: %w", err), false
+			}
+			return nil, true
+		}
+		if err := atomic.WriteFile(fileName, bytes.NewBuffer(formatted)); err != nil {
+			return fmt.Errorf("failed to write file %q: %w", fileName, err), false
 		}
-		writeIfUnchanged := args.ToStdout
-		return format(write, read, writeIfUnchanged)
+		return nil, true
 	}
 	dir := args.Files[0]
 	return NewFormatter(log, dir, process, args.WorkerCount, args.FailIfChanged).Run()
@@ -101,69 +130,3 @@ func (f *Formatter) Run() (err error) {
 
 	return nil
 }
-
-type reader func() (fileName, src string, err error)
-
-func readFromReader(r io.Reader, stdinFilepath string) func() (fileName, src string, err error) {
-	return func() (fileName, src string, err error) {
-		b, err := io.ReadAll(r)
-		if err != nil {
-			return "", "", fmt.Errorf("failed to read stdin: %w", err)
-		}
-		return stdinFilepath, string(b), nil
-	}
-}
-
-func readFromFile(name string) reader {
-	return func() (fileName, src string, err error) {
-		b, err := os.ReadFile(name)
-		if err != nil {
-			return "", "", fmt.Errorf("failed to read file %q: %w", fileName, err)
-		}
-		return name, string(b), nil
-	}
-}
-
-type writer func(fileName, tgt string) error
-
-var mu sync.Mutex
-
-func writeToWriter(w io.Writer) func(fileName, tgt string) error {
-	return func(fileName, tgt string) error {
-		mu.Lock()
-		defer mu.Unlock()
-		_, err := w.Write([]byte(tgt))
-		return err
-	}
-}
-
-func writeToFile(fileName, tgt string) error {
-	return atomic.WriteFile(fileName, bytes.NewBufferString(tgt))
-}
-
-func format(write writer, read reader, writeIfUnchanged bool) (err error, fileChanged bool) {
-	fileName, src, err := read()
-	if err != nil {
-		return err, false
-	}
-	t, err := parser.ParseString(src)
-	if err != nil {
-		return err, false
-	}
-	t.Filepath = fileName
-	t, err = imports.Process(t)
-	if err != nil {
-		return err, false
-	}
-	w := new(bytes.Buffer)
-	if err = t.Write(w); err != nil {
-		return fmt.Errorf("formatting error: %w", err), false
-	}
-
-	fileChanged = (src != w.String())
-
-	if !writeIfUnchanged && !fileChanged {
-		return nil, fileChanged
-	}
-	return write(fileName, w.String()), fileChanged
-}
diff --git a/cmd/templ/lspcmd/proxy/server.go b/cmd/templ/lspcmd/proxy/server.go
index 37e5eb8..d600e63 100644
--- a/cmd/templ/lspcmd/proxy/server.go
+++ b/cmd/templ/lspcmd/proxy/server.go
@@ -10,12 +10,12 @@ import (
 	"strings"
 
 	"github.com/a-h/parse"
+	"github.com/a-h/templ/internal/imports"
 	"github.com/a-h/templ/internal/lazyloader"
 	lsp "github.com/a-h/templ/lsp/protocol"
 	"github.com/a-h/templ/lsp/uri"
 
 	"github.com/a-h/templ"
-	"github.com/a-h/templ/cmd/templ/imports"
 	"github.com/a-h/templ/generator"
 	"github.com/a-h/templ/parser/v2"
 )
diff --git a/cmd/templ/main.go b/cmd/templ/main.go
index ab35c0a..3f15d24 100644
--- a/cmd/templ/main.go
+++ b/cmd/templ/main.go
@@ -172,6 +172,10 @@ Args:
     Set log verbosity level. (default "info", options: "debug", "info", "warn", "error")
   -w
     Number of workers to use when formatting code. (default runtime.NumCPUs).
+  -prettier-command
+    Set the command to use for formatting HTML, CSS, and JS blocks. Default is "prettier --stdin-filepath $TEMPL_PRETTIER_FILENAME".
+  -prettier-required
+    Set to true to return an error the prettier command is not available. Default is false.
   -fail
     Fails with exit code 1 if files are changed. (e.g. in CI)
   -help
@@ -185,6 +189,8 @@ func fmtCmd(stdin io.Reader, stdout, stderr io.Writer, args []string) (code int)
 	verboseFlag := cmd.Bool("v", false, "")
 	logLevelFlag := cmd.String("log-level", "info", "")
 	failIfChanged := cmd.Bool("fail", false, "")
+	prettierCommand := cmd.String("prettier-command", "", "")
+	prettierRequired := cmd.Bool("prettier-required", false, "")
 	stdoutFlag := cmd.Bool("stdout", false, "")
 	stdinFilepath := cmd.String("stdin-filepath", "", "")
 	err := cmd.Parse(args)
@@ -200,11 +206,13 @@ func fmtCmd(stdin io.Reader, stdout, stderr io.Writer, args []string) (code int)
 	log := sloghandler.NewLogger(*logLevelFlag, *verboseFlag, stderr)
 
 	err = fmtcmd.Run(log, stdin, stdout, fmtcmd.Arguments{
-		ToStdout:      *stdoutFlag,
-		Files:         cmd.Args(),
-		WorkerCount:   *workerCountFlag,
-		StdinFilepath: *stdinFilepath,
-		FailIfChanged: *failIfChanged,
+		ToStdout:         *stdoutFlag,
+		Files:            cmd.Args(),
+		WorkerCount:      *workerCountFlag,
+		StdinFilepath:    *stdinFilepath,
+		FailIfChanged:    *failIfChanged,
+		PrettierCommand:  *prettierCommand,
+		PrettierRequired: *prettierRequired,
 	})
 	if err != nil {
 		return 1
diff --git a/cmd/templ/visualize/sourcemapvisualisation.templ b/cmd/templ/visualize/sourcemapvisualisation.templ
index 6d4d5d1..c0c6159 100644
--- a/cmd/templ/visualize/sourcemapvisualisation.templ
+++ b/cmd/templ/visualize/sourcemapvisualisation.templ
@@ -19,8 +19,12 @@ templ combine(templFileName string, left, right templ.Component) {
 		<head>
 			<title>{ templFileName }- Source Map Visualisation</title>
 			<style type="text/css">
-				.mapped { background-color: green }
-				.highlighted { background-color: yellow }
+				.mapped {
+					background-color: green;
+				}
+				.highlighted {
+					background-color: yellow;
+				}
 			</style>
 		</head>
 		<body>
diff --git a/cmd/templ/visualize/sourcemapvisualisation_templ.go b/cmd/templ/visualize/sourcemapvisualisation_templ.go
index 35b25eb..5021399 100644
--- a/cmd/templ/visualize/sourcemapvisualisation_templ.go
+++ b/cmd/templ/visualize/sourcemapvisualisation_templ.go
@@ -73,14 +73,14 @@ func combine(templFileName string, left, right templ.Component) templ.Component
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "- Source Map Visualisation</title><style type=\"text/css\">\n\t\t\t\t.mapped { background-color: green }\n\t\t\t\t.highlighted { background-color: yellow }\n\t\t\t</style></head><body><h1>")
+		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "- Source Map Visualisation</title><style type=\"text/css\">\n\t\t\t\t.mapped {\n\t\t\t\t\tbackground-color: green;\n\t\t\t\t}\n\t\t\t\t.highlighted {\n\t\t\t\t\tbackground-color: yellow;\n\t\t\t\t}\n\t\t\t</style></head><body><h1>")
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
 		var templ_7745c5c3_Var3 string
 		templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templFileName)
 		if templ_7745c5c3_Err != nil {
-			return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/templ/visualize/sourcemapvisualisation.templ`, Line: 27, Col: 22}
+			return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/templ/visualize/sourcemapvisualisation.templ`, Line: 31, Col: 22}
 		}
 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
 		if templ_7745c5c3_Err != nil {
@@ -278,7 +278,7 @@ func mappedCharacter(s string, sourceID, targetID string) templ.Component {
 		var templ_7745c5c3_Var15 string
 		templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(s)
 		if templ_7745c5c3_Err != nil {
-			return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/templ/visualize/sourcemapvisualisation.templ`, Line: 63, Col: 200}
+			return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/templ/visualize/sourcemapvisualisation.templ`, Line: 67, Col: 200}
 		}
 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
 		if templ_7745c5c3_Err != nil {
diff --git a/docs/docs/03-syntax-and-usage/13-script-templates.md b/docs/docs/03-syntax-and-usage/13-script-templates.md
index da6e1b2..d770bec 100644
--- a/docs/docs/03-syntax-and-usage/13-script-templates.md
+++ b/docs/docs/03-syntax-and-usage/13-script-templates.md
@@ -15,6 +15,8 @@ templ body() {
 }
 ```
 
+If you have `prettierd`, `prettier` or `npx` on your `PATH`, `templ` will use it to format the `<script>` tag contents.
+
 :::tip
 To ensure that a `<script>` tag within a templ component is only rendered once per HTTP response (or context), use a [templ.OnceHandle](18-render-once.md).
 
diff --git a/docs/docs/09-developer-tools/01-cli.md b/docs/docs/09-developer-tools/01-cli.md
index 24c4846..0002147 100644
--- a/docs/docs/09-developer-tools/01-cli.md
+++ b/docs/docs/09-developer-tools/01-cli.md
@@ -94,6 +94,8 @@ to exit with unix error-code `1` if any templates needed to be modified.
 templ fmt -fail .
 ```
 
+If `prettierd`, `prettier` or `npx` is found in your `PATH`, `templ fmt` will use prettier to format `script` and `style` elements in files.
+
 ## Language Server for IDE integration
 
 `templ lsp` provides a Language Server Protocol (LSP) implementation to support IDE integrations.
diff --git a/docs/docs/09-developer-tools/02-ide-support.md b/docs/docs/09-developer-tools/02-ide-support.md
index a84da31..cd804d0 100644
--- a/docs/docs/09-developer-tools/02-ide-support.md
+++ b/docs/docs/09-developer-tools/02-ide-support.md
@@ -145,7 +145,6 @@ module.exports = {
 
 With the templ LSP installed and configured, you can use the following code snippet to format on save:
 
-
 ```lua
 vim.api.nvim_create_autocmd({ "BufWritePre" }, { pattern = { "*.templ" }, callback = vim.lsp.buf.format })
 ```
@@ -211,6 +210,12 @@ local templ_format = function()
 end
 ```
 
+:::note
+Formatting `script` and `style` elements in templ files is handed off to prettier.
+
+If you don't have `prettierd`, `prettier` or `npx` on your path, formatting will not be applied to those elements.
+:::
+
 ### Troubleshooting
 
 If you cannot run `:TSInstall templ`, ensure you have an up-to-date version of [tree-sitter](https://github.com/nvim-treesitter/nvim-treesitter). The [package for templ](https://github.com/vrischmann/tree-sitter-templ) was [added to the main tree-sitter repository](https://github.com/nvim-treesitter/nvim-treesitter/pull/5667) so you shouldn't need to install a separate plugin for it.
diff --git a/examples/integration-react/components.templ b/examples/integration-react/components.templ
index 54a7aa1..40339c4 100644
--- a/examples/integration-react/components.templ
+++ b/examples/integration-react/components.templ
@@ -4,7 +4,7 @@ templ Hello(name string) {
 	<div data-name={ name }>
 		<script>
 			// Place the React component into the parent div.
-			bundle.renderHello(document.currentScript.closest('div'));
+			bundle.renderHello(document.currentScript.closest("div"));
 		</script>
 	</div>
 }
diff --git a/examples/integration-react/components_templ.go b/examples/integration-react/components_templ.go
index 44efd85..0e6b479 100644
--- a/examples/integration-react/components_templ.go
+++ b/examples/integration-react/components_templ.go
@@ -41,7 +41,7 @@ func Hello(name string) templ.Component {
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"><script>\n\t\t\t// Place the React component into the parent div.\n\t\t\tbundle.renderHello(document.currentScript.closest('div'));\n\t\t</script></div>")
+		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"><script>\n\t\t\t// Place the React component into the parent div.\n\t\t\tbundle.renderHello(document.currentScript.closest(\"div\"));\n\t\t</script></div>")
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
diff --git a/flake.nix b/flake.nix
index a82cc08..1c18f12 100644
--- a/flake.nix
+++ b/flake.nix
@@ -53,7 +53,7 @@
             name = "templ";
             subPackages = [ "cmd/templ" ];
             src = gitignore.lib.gitignoreSource ./.;
-            vendorHash = "sha256-eJV0q+qG1QETydYtE6hipuxyp+P649RzF36Jc4qe8e4=";
+            vendorHash = "sha256-pVZjZCXT/xhBCMyZdR7kEmB9jqhTwRISFp63bQf6w5A=";
             env = {
               CGO_ENABLED = 0;
             };
@@ -81,6 +81,7 @@
             pkgs.gotestsum
             pkgs.ko # Used to build Docker images.
             pkgs.nodejs # Used to build templ-docs.
+            pkgs.nodePackages.prettier # Used for formatting JS and CSS.
             pkgs.version
             pkgs.xc
           ];
diff --git a/generator/htmldiff/diff.go b/generator/htmldiff/diff.go
index e79a9d7..648f0e7 100644
--- a/generator/htmldiff/diff.go
+++ b/generator/htmldiff/diff.go
@@ -2,87 +2,54 @@ package htmldiff
 
 import (
 	"context"
-	"errors"
 	"fmt"
-	"io"
 	"strings"
 
-	"github.com/a-h/htmlformat"
 	"github.com/a-h/templ"
+	"github.com/a-h/templ/internal/prettier"
 	"github.com/google/go-cmp/cmp"
 	"golang.org/x/sync/errgroup"
 )
 
-func DiffStrings(expected, actual string) (diff string, err error) {
+func DiffStrings(expected, actual string) (output, diff string, err error) {
 	// Format both strings.
 	var wg errgroup.Group
 
 	// Format expected.
-	wg.Go(func() error {
-		e := new(strings.Builder)
-		err := htmlformat.Fragment(e, strings.NewReader(expected))
+	wg.Go(func() (err error) {
+		expected, err = prettier.Run(expected, "expected.html", prettier.DefaultCommand)
 		if err != nil {
-			return fmt.Errorf("expected html formatting error: %w", err)
+			return err
 		}
-		expected = e.String()
 		return nil
 	})
 
 	// Format actual.
-	wg.Go(func() error {
-		a := new(strings.Builder)
-		err := htmlformat.Fragment(a, strings.NewReader(actual))
+	wg.Go(func() (err error) {
+		actual, err = prettier.Run(actual, "actual.html", prettier.DefaultCommand)
 		if err != nil {
 			return fmt.Errorf("actual html formatting error: %w", err)
 		}
-		actual = a.String()
 		return nil
 	})
 
 	// Wait for processing.
 	if err = wg.Wait(); err != nil {
-		return "", err
+		return "", "", err
 	}
 
-	return cmp.Diff(expected, actual), nil
+	return actual, cmp.Diff(expected, actual), nil
 }
 
-func Diff(input templ.Component, expected string) (diff string, err error) {
-	_, diff, err = DiffCtx(context.Background(), input, expected)
-	return diff, err
+func Diff(input templ.Component, expected string) (actual, diff string, err error) {
+	return DiffCtx(context.Background(), input, expected)
 }
 
-func DiffCtx(ctx context.Context, input templ.Component, expected string) (formattedInput, diff string, err error) {
-	var wg errgroup.Group
-
-	// Format the expected value.
-	wg.Go(func() error {
-		e := new(strings.Builder)
-		err := htmlformat.Fragment(e, strings.NewReader(expected))
-		if err != nil {
-			return fmt.Errorf("expected html formatting error: %w", err)
-		}
-		expected = e.String()
-		return nil
-	})
-
-	// Pipe via the HTML formatter.
-	actual := new(strings.Builder)
-	r, w := io.Pipe()
-	wg.Go(func() error {
-		err := htmlformat.Fragment(actual, r)
-		if err != nil {
-			return fmt.Errorf("actual html formatting error: %w", err)
-		}
-		return nil
-	})
-
-	// Render the component.
-	renderErr := input.Render(ctx, w)
-	closeErr := w.Close()
-
-	// Wait for processing.
-	processingErr := wg.Wait()
-
-	return actual.String(), cmp.Diff(expected, actual.String()), errors.Join(renderErr, closeErr, processingErr)
+func DiffCtx(ctx context.Context, input templ.Component, expected string) (actual, diff string, err error) {
+	var a strings.Builder
+	err = input.Render(ctx, &a)
+	if err != nil {
+		return "", "", fmt.Errorf("failed to render input: %w", err)
+	}
+	return DiffStrings(expected, a.String())
 }
diff --git a/generator/test-a-href/expected.html b/generator/test-a-href/expected.html
index 8599861..e48c01c 100644
--- a/generator/test-a-href/expected.html
+++ b/generator/test-a-href/expected.html
@@ -1,3 +1,3 @@
-<a href="javascript:alert(&#39;unaffected&#39;);">Ignored</a>
+<a href="javascript:alert('unaffected');">Ignored</a>
 <a href="about:invalid#TemplFailedSanitizationURL">Sanitized</a>
 <a href="javascript:alert(&#39;should not be sanitized&#39;)">Unsanitized</a>
diff --git a/generator/test-attribute-errors/expected.html b/generator/test-attribute-errors/expected.html
index 79be24c..387fcb7 100644
--- a/generator/test-attribute-errors/expected.html
+++ b/generator/test-attribute-errors/expected.html
@@ -1,5 +1,5 @@
 <ul>
-    <li data-attr="raw"></li>
-    <li data-attr="OK"></li>
-    <li data-attr="OK2"></li>
+	<li data-attr="raw"></li>
+	<li data-attr="OK"></li>
+	<li data-attr="OK2"></li>
 </ul>
diff --git a/generator/test-attribute-escaping/expected.html b/generator/test-attribute-escaping/expected.html
index fcf154d..aef5604 100644
--- a/generator/test-attribute-escaping/expected.html
+++ b/generator/test-attribute-escaping/expected.html
@@ -1,6 +1,6 @@
+<div><a href="about:invalid#TemplFailedSanitizationURL">text</a></div>
 <div>
-	<a href="about:invalid#TemplFailedSanitizationURL">text</a>
-</div>
-<div>
-	<button hx-post="/click" hx-trigger="click" hx-vals='{"val":"Value"}'>Click</button>
+  <button hx-post="/click" hx-trigger="click" hx-vals='{"val":"Value"}'>
+    Click
+  </button>
 </div>
diff --git a/generator/test-call/expected.html b/generator/test-call/expected.html
index bfa1ff9..6185e01 100644
--- a/generator/test-call/expected.html
+++ b/generator/test-call/expected.html
@@ -3,6 +3,4 @@
 <div>C</div>
 <div>Legacy call style</div>
 <div>e</div>
-<div id="wrapper">
-				<div>Child content</div>
-</div>
+<div id="wrapper"><div>Child content</div></div>
diff --git a/generator/test-complex-attributes/expected.html b/generator/test-complex-attributes/expected.html
index 2a2be3d..9d595c3 100644
--- a/generator/test-complex-attributes/expected.html
+++ b/generator/test-complex-attributes/expected.html
@@ -1,17 +1,11 @@
 <div
-  x-data="{darkMode: localStorage.getItem('darkMode') || localStorage.setItem('darkMode', 'system')}"
-  x-init="$watch('darkMode', val => localStorage.setItem('darkMode', val))"
-  :class="{'dark': darkMode === 'dark' || (darkMode === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)}"
+	x-data="{darkMode: localStorage.getItem('darkMode') || localStorage.setItem('darkMode', 'system')}"
+	x-init="$watch('darkMode', val => localStorage.setItem('darkMode', val))"
+	:class="{'dark': darkMode === 'dark' || (darkMode === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)}"
 ></div>
 <div x-data="{ count: 0 }">
-  <button
-    x-on:click="count++"
-  >Increment</button>
-  <span x-text="count"></span>
+	<button x-on:click="count++">Increment</button> <span x-text="count"></span>
 </div>
 <div x-data="{ count: 0 }">
-  <button
-    @click="count++"
-  >Increment</button>
-  <span x-text="count"></span>
+	<button @click="count++">Increment</button> <span x-text="count"></span>
 </div>
diff --git a/generator/test-constant-attribute-escaping/expected.html b/generator/test-constant-attribute-escaping/expected.html
index 54d5171..2213c22 100644
--- a/generator/test-constant-attribute-escaping/expected.html
+++ b/generator/test-constant-attribute-escaping/expected.html
@@ -1,17 +1,10 @@
 <div>
-    <!-- valid go escape sequences -->
-    <input pattern="\a"/>
-    <input pattern="\b"/>
-    <input pattern="\f"/>
-    <input pattern="\n"/>
-    <input pattern="\r"/>
-    <input pattern="\t"/>
-    <input pattern="\v"/>
-    <input pattern="\\"/>
-    <input pattern="\777"/>
-    <input pattern="\xFF"/>
-    <input pattern="\u00FF"/>
-    <input pattern="\u00FF\u00FF\u00FF"/>
-    <!-- invalid go escape sequences -->
-    <input pattern="\s"/>
+	<!-- valid go escape sequences --><input pattern="\a" />
+	<input pattern="\b" /> <input pattern="\f" /> <input pattern="\n" />
+	<input pattern="\r" /> <input pattern="\t" /> <input pattern="\v" />
+	<input pattern="\\" /> <input pattern="\777" /> <input pattern="\xFF" />
+	<input pattern="\u00FF" />
+	<input
+		pattern="\u00FF\u00FF\u00FF"
+	/><!-- invalid go escape sequences --><input pattern="\s" />
 </div>
diff --git a/generator/test-context/expected.html b/generator/test-context/expected.html
index 7a2f653..0312b6c 100644
--- a/generator/test-context/expected.html
+++ b/generator/test-context/expected.html
@@ -1,5 +1,5 @@
 <ul>
-				<li>test</li>
-				<li>the if passed</li>
-				<li>the else if passed</li>
+	<li>test</li>
+	<li>the if passed</li>
+	<li>the else if passed</li>
 </ul>
diff --git a/generator/test-css-middleware/expected.html b/generator/test-css-middleware/expected.html
index df6b148..d95f97f 100644
--- a/generator/test-css-middleware/expected.html
+++ b/generator/test-css-middleware/expected.html
@@ -1,3 +1 @@
-<div class="red_050e5e03">
-	Red text
-</div>
+<div class="red_050e5e03">Red text</div>
diff --git a/generator/test-css-usage/expected.html b/generator/test-css-usage/expected.html
index d488fa1..17c6edd 100644
--- a/generator/test-css-usage/expected.html
+++ b/generator/test-css-usage/expected.html
@@ -2,35 +2,58 @@
 	.test {
 		color: #ff0000;
 	}
-	</style>
+</style>
 <div class="test">Style tags are supported</div>
-<style type="text/css">.cssComponentGreen_58d2872e{color:#00ff00;}</style>
+<style type="text/css">
+	.cssComponentGreen_58d2872e {
+		color: #00ff00;
+	}
+</style>
 <div class="cssComponentGreen_58d2872e">CSS components are supported</div>
-<div class="cssComponentGreen_58d2872e classA &amp;&amp;&amp;classB classC d e" type="button">Both CSS components and constants are supported</div>
-<div class="cssComponentGreen_58d2872e classA &amp;&amp;&amp;classB classC d e" type="button">Both CSS components and constants are supported</div>
-<div class="a c">Maps can be used to determine if a class should be added or not.</div>
-<style type="text/css">.e_739d4573{font-size:14pt;}</style>
+<div
+	class="cssComponentGreen_58d2872e classA &amp;&amp;&amp;classB classC d e"
+	type="button"
+>
+	Both CSS components and constants are supported
+</div>
+<div
+	class="cssComponentGreen_58d2872e classA &amp;&amp;&amp;classB classC d e"
+	type="button"
+>
+	Both CSS components and constants are supported
+</div>
+<div class="a c">
+	Maps can be used to determine if a class should be added or not.
+</div>
+<style type="text/css">
+	.e_739d4573 {
+		font-size: 14pt;
+	}
+</style>
 <div class="a c e_739d4573">KV can be used to conditionally set classes.</div>
-<div class="bg-violet-500 hover:bg-red-600 hover:bg-sky-700 text-[#50d71e] w-[calc(100%-4rem)">Pseudo attributes and complex class names are supported.</div>
+<div
+	class="bg-violet-500 hover:bg-red-600 hover:bg-sky-700 text-[#50d71e] w-[calc(100%-4rem)"
+>
+	Pseudo attributes and complex class names are supported.
+</div>
 <div class="a&#34; onClick=&#34;alert(&#39;hello&#39;)&#34;">
 	Class names are HTML escaped.
 </div>
 <style type="text/css">
- .loading_a3cc3f08{width:50%;}
+	.loading_a3cc3f08 {
+		width: 50%;
+	}
 </style>
-<div class="loading_a3cc3f08">
- CSS components can be used with arguments.
-</div>
+<div class="loading_a3cc3f08">CSS components can be used with arguments.</div>
 <style type="text/css">
- .loading_9ccc4ca9{width:100%;}
+	.loading_9ccc4ca9 {
+		width: 100%;
+	}
 </style>
-<div class="loading_9ccc4ca9">
- CSS components can be used with arguments.
-</div>
+<div class="loading_9ccc4ca9">CSS components can be used with arguments.</div>
 <style type="text/css">
-  .windVaneRotation_b68b990e{transform:rotate(45deg);}
+	.windVaneRotation_b68b990e {
+		transform: rotate(45deg);
+	}
 </style>
-<div class="windVaneRotation_b68b990e">
-  Rotate
-</div>
-
+<div class="windVaneRotation_b68b990e">Rotate</div>
diff --git a/generator/test-css-usage/template.templ b/generator/test-css-usage/template.templ
index 0af1f19..5757652 100644
--- a/generator/test-css-usage/template.templ
+++ b/generator/test-css-usage/template.templ
@@ -7,9 +7,9 @@ import (
 
 templ StyleTagsAreSupported() {
 	<style>
-	.test {
-		color: #ff0000;
-	}
+		.test {
+			color: #ff0000;
+		}
 	</style>
 	<div class="test">Style tags are supported</div>
 }
diff --git a/generator/test-doctype-html4/expected.html b/generator/test-doctype-html4/expected.html
index b18eace..d142ed1 100644
--- a/generator/test-doctype-html4/expected.html
+++ b/generator/test-doctype-html4/expected.html
@@ -1,10 +1,12 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<!doctype html PUBLIC "http://www.w3.org/TR/html4/loose.dtd">
 <html lang="en">
-  <head>
-    <meta charset="UTF-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>title</title>
-  </head>
-  <body>content</body>
+	<head>
+		<meta charset="UTF-8" />
+		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
+		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+		<title>title</title>
+	</head>
+	<body>
+		content
+	</body>
 </html>
diff --git a/generator/test-doctype/expected.html b/generator/test-doctype/expected.html
index a15bac3..73a7ec6 100644
--- a/generator/test-doctype/expected.html
+++ b/generator/test-doctype/expected.html
@@ -1,10 +1,12 @@
 <!doctype html>
 <html lang="en">
-<head>
-<meta charset="UTF-8">
-<meta http-equiv="X-UA-Compatible" content="IE=edge">
-<meta name="viewport" content="width=device-width, initial-scale=1.0">
-<title>title</title>
-</head>
-<body>content</body>
+	<head>
+		<meta charset="UTF-8" />
+		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
+		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+		<title>title</title>
+	</head>
+	<body>
+		content
+	</body>
 </html>
diff --git a/generator/test-element-attributes/expected.html b/generator/test-element-attributes/expected.html
index 99a4d1e..464c9e5 100644
--- a/generator/test-element-attributes/expected.html
+++ b/generator/test-element-attributes/expected.html
@@ -1,33 +1,31 @@
 <style type="text/css">
- .important_2ed176fc{width:100;}
+	.important_2ed176fc {
+		width: 100;
+	}
 </style>
-<div style="width: 100;">
- Important
-</div>
+<div style="width: 100">Important</div>
 <style type="text/css">
- .unimportant_900aeb18{width:50;}
+	.unimportant_900aeb18 {
+		width: 50;
+	}
 </style>
-<div style="width: 100;" class="unimportant_900aeb18">
- Unimportant
-</div>
-<div style="width: 100;" class="unimportant_900aeb18">
-  Else
- </div>
-<div data-script="on click
+<div style="width: 100" class="unimportant_900aeb18">Unimportant</div>
+<div style="width: 100" class="unimportant_900aeb18">Else</div>
+<div
+	data-script="on click
                 do something
-             end"></div>
-
+             end"
+></div>
 <h2>HTMX Wildcard attribute</h2>
-
 <form
- 	dynamic-attr-key="hello world"
-        dynamic-const-key="hello world"
-        my-string-attr
-        bool-attr
- 	hx-post="/api/secret/unlock"
- 	hx-target="#secret"
- 	hx-target-*="#errors"
- 	hx-indicator="#loading-indicator"
+	dynamic-attr-key="hello world"
+	dynamic-const-key="hello world"
+	my-string-attr
+	bool-attr
+	hx-post="/api/secret/unlock"
+	hx-target="#secret"
+	hx-target-*="#errors"
+	hx-indicator="#loading-indicator"
 >
-  <input type="button" value="Unlock"/>
+	<input type="button" value="Unlock" />
 </form>
diff --git a/generator/test-elseif/expected.html b/generator/test-elseif/expected.html
index 722abff..e561228 100644
--- a/generator/test-elseif/expected.html
+++ b/generator/test-elseif/expected.html
@@ -1,9 +1,3 @@
-<div>
-				False
-</div>
-<div>
-				ElseIf
-</div>
-<div>
-				OK
-</div>
+<div>False</div>
+<div>ElseIf</div>
+<div>OK</div>
diff --git a/generator/test-form-action/expected.html b/generator/test-form-action/expected.html
index 6961867..3b34abb 100644
--- a/generator/test-form-action/expected.html
+++ b/generator/test-form-action/expected.html
@@ -1,5 +1,11 @@
-<form action="javascript:alert(&#39;unaffected&#39;);">Ignored</form>
+<form action="javascript:alert('unaffected');">Ignored</form>
 <form action="about:invalid#TemplFailedSanitizationURL">Sanitized</form>
-<form action="javascript:alert(&#39;should not be sanitized&#39;)">Unsanitized</form>
-<form action="javascript:alert(&#39;should not be sanitized&#39;)">with error unsanitized</form>
-<form action="about:invalid#TemplFailedSanitizationURL">with error sanitized</form>
+<form action="javascript:alert(&#39;should not be sanitized&#39;)">
+	Unsanitized
+</form>
+<form action="javascript:alert(&#39;should not be sanitized&#39;)">
+	with error unsanitized
+</form>
+<form action="about:invalid#TemplFailedSanitizationURL">
+	with error sanitized
+</form>
diff --git a/generator/test-fragment/complete.html b/generator/test-fragment/complete.html
index e7462f8..bc0cc56 100644
--- a/generator/test-fragment/complete.html
+++ b/generator/test-fragment/complete.html
@@ -4,4 +4,4 @@
 <div>Outer Fragment Start</div>
 <div>Inner Fragment Content</div>
 <div>Outer Fragment End</div>
-<div>Page Footer</div>
\ No newline at end of file
+<div>Page Footer</div>
diff --git a/generator/test-go-comments/expected.html b/generator/test-go-comments/expected.html
index c46a177..8e08dbc 100644
--- a/generator/test-go-comments/expected.html
+++ b/generator/test-go-comments/expected.html
@@ -1 +1 @@
-	<p>sample content</p>
+<p>sample content</p>
diff --git a/generator/test-go-template-in-templ/expected.html b/generator/test-go-template-in-templ/expected.html
index cf99512..2fa7e0a 100644
--- a/generator/test-go-template-in-templ/expected.html
+++ b/generator/test-go-template-in-templ/expected.html
@@ -1,6 +1,6 @@
-<!DOCTYPE html>
+<!doctype html>
 <html>
-        <body>
-                <div>Hello, World!</div>
+	<body>
+		<div>Hello, World!</div>
 	</body>
 </html>
diff --git a/generator/test-html-comment/expected.html b/generator/test-html-comment/expected.html
index e4c421b..ab89f34 100644
--- a/generator/test-html-comment/expected.html
+++ b/generator/test-html-comment/expected.html
@@ -1,14 +1,13 @@
-	<!-- simple html comment -->
-	<p>sample content</p>
-	<!--
+<!-- simple html comment -->
+<p>sample content</p>
+<!--
 		multiline
 		comment
 	-->
-	<p>second paragraph</p>
-	<!--
+<p>second paragraph</p>
+<!--
 		@paragraph("commented out composed element")
 	-->
-	<p>third paragraph</p>
-	<!-- commented out string expression: { content } -->
-	<span>sample content</span>
-	<!-- <div>comment with html</div> -->
+<p>third paragraph</p>
+<!-- commented out string expression: { content } --><span>sample content</span
+><!-- <div>comment with html</div> -->
diff --git a/generator/test-html/expected.html b/generator/test-html/expected.html
index a04251d..fd8675b 100644
--- a/generator/test-html/expected.html
+++ b/generator/test-html/expected.html
@@ -1,11 +1,14 @@
 <div>
 	<h1>Luiz Bonfa</h1>
-	<div style="font-family: &#39;sans-serif&#39;" id="test" data-contents="something with &#34;quotes&#34; and a &lt;tag&gt;">
-		<div>email:<a href="mailto: luiz@example.com">luiz@example.com</a>
-		</div>
+	<div
+		style="font-family: &quot;sans-serif&quot;"
+		id="test"
+		data-contents="something with &#34;quotes&#34; and a &lt;tag&gt;"
+	>
+		<div>email:<a href="mailto: luiz@example.com">luiz@example.com</a></div>
 	</div>
 </div>
-<hr noshade>
-<hr optionA optionB optionC="other">
-<hr noshade>
-<input name="test">Text</input>
+<hr noshade />
+<hr optionA optionB optionC="other" />
+<hr noshade />
+<input name="test" />Text
diff --git a/generator/test-import/expected.html b/generator/test-import/expected.html
index 6f88dc9..333535f 100644
--- a/generator/test-import/expected.html
+++ b/generator/test-import/expected.html
@@ -1,5 +1,5 @@
 <ul>
-    <li><u>Item 1</u></li>
-    <li><u>Item 2</u></li>
-    <li><u>Item 3</u></li>
-</ul>
\ No newline at end of file
+	<li><u>Item 1</u></li>
+	<li><u>Item 2</u></li>
+	<li><u>Item 3</u></li>
+</ul>
diff --git a/generator/test-js-unsafe-usage/expected.html b/generator/test-js-unsafe-usage/expected.html
index d41f233..e8c9c7d 100644
--- a/generator/test-js-unsafe-usage/expected.html
+++ b/generator/test-js-unsafe-usage/expected.html
@@ -1,6 +1,4 @@
-<button onClick="anythingILike('blah')">
- Click me
-</button>
+<button onClick="anythingILike(&#39;blah&#39;)">Click me</button>
 <script>
- // Arbitrary JS code
+	// Arbitrary JS code
 </script>
diff --git a/generator/test-js-usage/expected.html b/generator/test-js-usage/expected.html
index fd433e5..370e19a 100644
--- a/generator/test-js-usage/expected.html
+++ b/generator/test-js-usage/expected.html
@@ -1,27 +1,29 @@
-<button onclick="alert(&#34;Hello, World!&#34;)">
- Click me
-</button>
+<button onClick="alert(&#34;Hello, World!&#34;)">Click me</button>
 <script>
- function customAlert(msg, date) {
-				alert(msg + " " + date);
-			}
+	function customAlert(msg, date) {
+		alert(msg + " " + date);
+	}
 </script>
-<button onclick="customAlert(&#34;Hello, custom alert 1: &#34;,&#34;2020-01-01T00:00:00Z&#34;)">
- Click me
+<button
+	onClick="customAlert(&#34;Hello, custom alert 1: &#34;,&#34;2020-01-01T00:00:00Z&#34;)"
+>
+	Click me
 </button>
-<button onclick="customAlert(&#34;Hello, custom alert 2: &#34;,&#34;2020-01-01T00:00:00Z&#34;)">
- Click me
+<button
+	onClick="customAlert(&#34;Hello, custom alert 2: &#34;,&#34;2020-01-01T00:00:00Z&#34;)"
+>
+	Click me
 </button>
 <script>
- customAlert("Runs on page load","2020-01-01T00:00:00Z")
+	customAlert("Runs on page load", "2020-01-01T00:00:00Z");
 </script>
 <script>
- function onClickEventHandler(event, data) {
-			alert(event.type);
-			alert(data)
-			event.preventDefault();
-		}
+	function onClickEventHandler(event, data) {
+		alert(event.type);
+		alert(data);
+		event.preventDefault();
+	}
 </script>
 <button onclick="onClickEventHandler(event,&#34;1234&#34;)">
- Pass event handler
+	Pass event handler
 </button>
diff --git a/generator/test-js-usage/template.templ b/generator/test-js-usage/template.templ
index da2340d..40c1e19 100644
--- a/generator/test-js-usage/template.templ
+++ b/generator/test-js-usage/template.templ
@@ -19,7 +19,7 @@ templ TestComponent() {
 	<script>
 		function onClickEventHandler(event, data) {
 			alert(event.type);
-			alert(data)
+			alert(data);
 			event.preventDefault();
 		}
 	</script>
diff --git a/generator/test-once/expected.html b/generator/test-once/expected.html
index 84eaf7d..d74a741 100644
--- a/generator/test-once/expected.html
+++ b/generator/test-once/expected.html
@@ -1,7 +1,16 @@
 <script>
-			function hello(name) {
-				alert('Hello, ' + name + '!');
-			}
+	function hello(name) {
+		alert("Hello, " + name + "!");
+	}
 </script>
-<input type="button" value="Hello User" data-name="user" onclick="hello(this.getAttribute('data-name'))">
-<input type="button" value="Hello World" data-name="world" onclick="hello(this.getAttribute('data-name'))">
+<input
+	type="button"
+	value="Hello User"
+	data-name="user"
+	onclick="hello(this.getAttribute('data-name'))"
+/><input
+	type="button"
+	value="Hello World"
+	data-name="world"
+	onclick="hello(this.getAttribute('data-name'))"
+/>
diff --git a/generator/test-once/template.templ b/generator/test-once/template.templ
index 0486047..a05c685 100644
--- a/generator/test-once/template.templ
+++ b/generator/test-once/template.templ
@@ -6,7 +6,7 @@ templ hello(label, name string) {
 	@helloHandle.Once() {
 		<script>
 			function hello(name) {
-				alert('Hello, ' + name + '!');
+				alert("Hello, " + name + "!");
 			}
 		</script>
 	}
diff --git a/generator/test-only-scripts/expected.html b/generator/test-only-scripts/expected.html
index 5072ab8..bee69df 100644
--- a/generator/test-only-scripts/expected.html
+++ b/generator/test-only-scripts/expected.html
@@ -1,7 +1,8 @@
 <script>
-  function __templ_withParameters_1056(a, b, c){console.log(a, b, c);
-}
+	function __templ_withParameters_1056(a, b, c) {
+		console.log(a, b, c);
+	}
 </script>
 <script>
-  __templ_withParameters_1056("hello","world",42069)
-</script>
\ No newline at end of file
+	__templ_withParameters_1056("hello", "world", 42069);
+</script>
diff --git a/generator/test-primitives/expected.html b/generator/test-primitives/expected.html
index d0d5a1f..3a02a8e 100644
--- a/generator/test-primitives/expected.html
+++ b/generator/test-primitives/expected.html
@@ -1,20 +1,20 @@
 <ul>
-  <li>1</li>
-  <li>1</li>
-  <li>1</li>
-  <li>1</li>
-  <li>1</li>
-  <li>1</li>
-  <li>1</li>
-  <li>1</li>
-  <li>1</li>
-  <li>1</li>
-  <li>1</li>
-  <li>1</li>
-  <li>true</li>
-  <li>false</li>
-  <li>(10+11i)</li>
-  <li>(10+11i)</li>
-  <li>string value</li>
-  <li>stringish value</li>
+	<li>1</li>
+	<li>1</li>
+	<li>1</li>
+	<li>1</li>
+	<li>1</li>
+	<li>1</li>
+	<li>1</li>
+	<li>1</li>
+	<li>1</li>
+	<li>1</li>
+	<li>1</li>
+	<li>1</li>
+	<li>true</li>
+	<li>false</li>
+	<li>(10+11i)</li>
+	<li>(10+11i)</li>
+	<li>string value</li>
+	<li>stringish value</li>
 </ul>
diff --git a/generator/test-raw-elements/expected.html b/generator/test-raw-elements/expected.html
index 5b41f2a..06a4503 100644
--- a/generator/test-raw-elements/expected.html
+++ b/generator/test-raw-elements/expected.html
@@ -1,19 +1,20 @@
 <html>
 	<head></head>
 	<body>
-		<style><!-- Some stuff --></style>
 		<style>
-        .customClass {
-          border: 1px solid black;
-        }
-    </style>
+			<!-- Some stuff --></style
+		><style>
+			.customClass {
+				border: 1px solid black;
+			}
+		</style>
 		<script>
-        $("div").marquee();
-        function test() {
-              window.open("https://example.com")
-        }
-    </script>
+			$("div").marquee();
+			function test() {
+				window.open("https://example.com");
+			}
+		</script>
 		<h1>Hello</h1>
-                <div>World</div>
+		<div>World</div>
 	</body>
 </html>
diff --git a/generator/test-raw-elements/template.templ b/generator/test-raw-elements/template.templ
index 9073f2c..1b35db1 100644
--- a/generator/test-raw-elements/template.templ
+++ b/generator/test-raw-elements/template.templ
@@ -4,18 +4,20 @@ templ Example() {
 	<html>
 		<head></head>
 		<body>
-			<style><!-- Some stuff --></style>
 			<style>
-        .customClass {
-          border: 1px solid black;
-        }
-      </style>
+				<!-- Some stuff -->
+			</style>
+			<style>
+				.customClass {
+					border: 1px solid black;
+				}
+			</style>
 			<script>
-        $("div").marquee();
-        function test() {
-              window.open("https://example.com")
-        }
-      </script>
+				$("div").marquee();
+				function test() {
+					window.open("https://example.com");
+				}
+			</script>
 			<h1>Hello</h1>
 			@templ.Raw("<div>World</div>")
 		</body>
diff --git a/generator/test-script-expressions/expected.html b/generator/test-script-expressions/expected.html
index 767a7be..e9f8a06 100644
--- a/generator/test-script-expressions/expected.html
+++ b/generator/test-script-expressions/expected.html
@@ -1,70 +1,70 @@
 <h1>string data</h1>
 <script>
-		var a = "hello"
-		var b = "hello"
-		var c = 'hello'
-		var d = `hello`
+	var a = "hello";
+	var b = "hello";
+	var c = "hello";
+	var d = `hello`;
 </script>
 <script>
-		console.log("hello")
+	console.log("hello");
 </script>
 <h1>string data with quotes</h1>
 <script>
-		var a = "hello 'world'"
-		var b = "hello \u0027world\u0027"
-		var c = 'hello \u0027world\u0027'
-		var d = `hello \u0027world\u0027`
+	var a = "hello 'world'";
+	var b = "hello \u0027world\u0027";
+	var c = "hello \u0027world\u0027";
+	var d = `hello \u0027world\u0027`;
 </script>
 <script>
-		console.log("hello 'world'")
+	console.log("hello 'world'");
 </script>
 <h1>numeric data</h1>
 <script>
-		var a = 123
-		var b = "123"
-		var c = '123'
-		var d = `123`
+	var a = 123;
+	var b = "123";
+	var c = "123";
+	var d = `123`;
 </script>
 <script>
-		console.log(123)
+	console.log(123);
 </script>
 <h1>boolean data</h1>
 <script>
-		var a = true
-		var b = "true"
-		var c = 'true'
-		var d = `true`
+	var a = true;
+	var b = "true";
+	var c = "true";
+	var d = `true`;
 </script>
 <script>
-		console.log(true)
+	console.log(true);
 </script>
 <h1>array data</h1>
 <script>
-		var a = [1,2,3]
-		var b = "[1,2,3]"
-		var c = '[1,2,3]'
-		var d = `[1,2,3]`
+	var a = [1, 2, 3];
+	var b = "[1,2,3]";
+	var c = "[1,2,3]";
+	var d = `[1,2,3]`;
 </script>
 <script>
-		console.log([1,2,3])
+	console.log([1, 2, 3]);
 </script>
 <h1>object data</h1>
 <script>
-		var a = {"Name":"Alice","Age":30}
-		var b = "{\u0022Name\u0022:\u0022Alice\u0022,\u0022Age\u0022:30}"
-		var c = '{\u0022Name\u0022:\u0022Alice\u0022,\u0022Age\u0022:30}'
-		var d = `{\u0022Name\u0022:\u0022Alice\u0022,\u0022Age\u0022:30}`
+	var a = { Name: "Alice", Age: 30 };
+	var b = "{\u0022Name\u0022:\u0022Alice\u0022,\u0022Age\u0022:30}";
+	var c = "{\u0022Name\u0022:\u0022Alice\u0022,\u0022Age\u0022:30}";
+	var d = `{\u0022Name\u0022:\u0022Alice\u0022,\u0022Age\u0022:30}`;
 </script>
 <script>
-		console.log({"Name":"Alice","Age":30})
+	console.log({ Name: "Alice", Age: 30 });
 </script>
 <h1>null data</h1>
 <script>
-		var a = null
-		var b = "null"
-		var c = 'null'
-		var d = `null`
+	var a = null;
+	var b = "null";
+	var c = "null";
+	var d = `null`;
 </script>
 <script>
-		console.log(null)
+	console.log(null);
 </script>
diff --git a/generator/test-script-expressions/template.templ b/generator/test-script-expressions/template.templ
index 22862c2..6b0dd49 100644
--- a/generator/test-script-expressions/template.templ
+++ b/generator/test-script-expressions/template.templ
@@ -9,7 +9,7 @@ templ Script[T any](name string, data T) {
 		var d = `{{ data }}`
 	</script>
 	<script>
-		console.log({{ data }})
+		console.log({{ data }});
 	</script>
 }
 
diff --git a/generator/test-script-inline/expected.html b/generator/test-script-inline/expected.html
index 8892088..4f22804 100644
--- a/generator/test-script-inline/expected.html
+++ b/generator/test-script-inline/expected.html
@@ -1,20 +1,22 @@
 <script>
-	function __templ_withoutParameters_6bbf(){alert("hello");
-}
+	function __templ_withoutParameters_6bbf() {
+		alert("hello");
+	}
 </script>
 <script>
-  __templ_withoutParameters_6bbf()
+	__templ_withoutParameters_6bbf();
 </script>
 <script>
-  function __templ_withParameters_1056(a, b, c){console.log(a, b, c);
-}
+	function __templ_withParameters_1056(a, b, c) {
+		console.log(a, b, c);
+	}
 </script>
 <script>
-  __templ_withParameters_1056("injected","test",123)
+	__templ_withParameters_1056("injected", "test", 123);
 </script>
 <script>
-  __templ_withoutParameters_6bbf()
+	__templ_withoutParameters_6bbf();
 </script>
 <script>
-  __templ_withParameters_1056("injected","test",123)
+	__templ_withParameters_1056("injected", "test", 123);
 </script>
diff --git a/generator/test-script-usage-nonce/expected.html b/generator/test-script-usage-nonce/expected.html
index b896518..049568d 100644
--- a/generator/test-script-usage-nonce/expected.html
+++ b/generator/test-script-usage-nonce/expected.html
@@ -1,19 +1,38 @@
 <script nonce="nonce1">
-	function __templ_withParameters_1056(a, b, c){console.log(a, b, c);
-}function __templ_withoutParameters_6bbf(){alert("hello");
-}
+	function __templ_withParameters_1056(a, b, c) {
+		console.log(a, b, c);
+	}
+	function __templ_withoutParameters_6bbf() {
+		alert("hello");
+	}
 </script>
-<button onClick="__templ_withParameters_1056(&#34;test&#34;,&#34;A&#34;,123)" onMouseover="__templ_withoutParameters_6bbf()" type="button">A</button>
-<button onClick="__templ_withParameters_1056(&#34;test&#34;,&#34;B&#34;,123)" onMouseover="__templ_withoutParameters_6bbf()" type="button">B</button>
-<button onMouseover="console.log(&#39;mouseover&#39;)" type="button">Button C</button>
+<button
+	onClick="__templ_withParameters_1056(&#34;test&#34;,&#34;A&#34;,123)"
+	onMouseover="__templ_withoutParameters_6bbf()"
+	type="button"
+>
+	A</button
+><button
+	onClick="__templ_withParameters_1056(&#34;test&#34;,&#34;B&#34;,123)"
+	onMouseover="__templ_withoutParameters_6bbf()"
+	type="button"
+>
+	B</button
+><button onMouseover="console.log('mouseover')" type="button">Button C</button>
 <button hx-on::click="alert('clicked inline')" type="button">Button D</button>
 <script nonce="nonce1">
-  function __templ_onClick_657d(){alert("clicked");
-}
+	function __templ_onClick_657d() {
+		alert("clicked");
+	}
 </script>
 <button hx-on::click="__templ_onClick_657d()" type="button">Button E</button>
 <script nonce="nonce1">
-  function __templ_conditionalScript_de41(){alert("conditional");
-}
+	function __templ_conditionalScript_de41() {
+		alert("conditional");
+	}
 </script>
-<input type="button" value="Click me" onclick="__templ_conditionalScript_de41()" />
+<input
+	type="button"
+	value="Click me"
+	onclick="__templ_conditionalScript_de41()"
+/>
diff --git a/generator/test-script-usage/expected.html b/generator/test-script-usage/expected.html
index b0dec32..6e219c8 100644
--- a/generator/test-script-usage/expected.html
+++ b/generator/test-script-usage/expected.html
@@ -1,29 +1,55 @@
 <script>
-	function __templ_withParameters_1056(a, b, c){console.log(a, b, c);
-}function __templ_withoutParameters_6bbf(){alert("hello");
-}
+	function __templ_withParameters_1056(a, b, c) {
+		console.log(a, b, c);
+	}
+	function __templ_withoutParameters_6bbf() {
+		alert("hello");
+	}
 </script>
-<button onClick="__templ_withParameters_1056(&#34;test&#34;,&#34;A&#34;,123)" onMouseover="__templ_withoutParameters_6bbf()" type="button">A</button>
-<button onClick="__templ_withParameters_1056(&#34;test&#34;,&#34;B&#34;,123)" onMouseover="__templ_withoutParameters_6bbf()" type="button">B</button>
-<button onMouseover="console.log(&#39;mouseover&#39;)" type="button">Button C</button>
+<button
+	onClick="__templ_withParameters_1056(&#34;test&#34;,&#34;A&#34;,123)"
+	onMouseover="__templ_withoutParameters_6bbf()"
+	type="button"
+>
+	A</button
+><button
+	onClick="__templ_withParameters_1056(&#34;test&#34;,&#34;B&#34;,123)"
+	onMouseover="__templ_withoutParameters_6bbf()"
+	type="button"
+>
+	B</button
+><button onMouseover="console.log('mouseover')" type="button">Button C</button>
 <button hx-on::click="alert('clicked inline')" type="button">Button D</button>
 <script>
-  function __templ_onClick_657d(){alert("clicked");
-}
+	function __templ_onClick_657d() {
+		alert("clicked");
+	}
 </script>
 <button hx-on::click="__templ_onClick_657d()" type="button">Button E</button>
 <script>
-  function __templ_whenButtonIsClicked_253e(event){console.log(event.target)
-}
+	function __templ_whenButtonIsClicked_253e(event) {
+		console.log(event.target);
+	}
 </script>
 <button onclick="__templ_whenButtonIsClicked_253e(event)">Button F</button>
 <script>
-  function __templ_conditionalScript_de41(){alert("conditional");
-}
+	function __templ_conditionalScript_de41() {
+		alert("conditional");
+	}
 </script>
-<input type="button" value="Click me" onclick="__templ_conditionalScript_de41()" />
+<input
+	type="button"
+	value="Click me"
+	onclick="__templ_conditionalScript_de41()"
+/>
 <script>
-  function __templ_alertTest_eadf(){alert('testing');
-}
+	function __templ_alertTest_eadf() {
+		alert("testing");
+	}
 </script>
-<script async crossorigin="true" onload="__templ_alertTest_eadf()" src="url.to.some.script"></script>
+<script
+	async
+	crossorigin="true"
+	onload="__templ_alertTest_eadf()"
+	src="url.to.some.script"
+></script>
diff --git a/generator/test-spread-attributes/expected.html b/generator/test-spread-attributes/expected.html
index 6c34ff3..b257137 100644
--- a/generator/test-spread-attributes/expected.html
+++ b/generator/test-spread-attributes/expected.html
@@ -1,11 +1,25 @@
 <div>
-<a bool data-attr="value" data-attr-bool dateid="my-custom-id" hx-get="/page" id="test" nonshade optional-from-func-true text="lorem">
-  text
-</a>
-<div bool data-attr="value" data-attr-bool dateid="my-custom-id" hx-get="/page" id="test" nonshade optional-from-func-true text="lorem">
-  text2
-</div>
-<div>
-  text3
-</div>
+  <a
+    bool
+    data-attr="value"
+    data-attr-bool
+    dateId="my-custom-id"
+    hx-get="/page"
+    id="test"
+    nonshade
+    optional-from-func-true
+    text="lorem"
+  >text</a>
+  <div
+    bool
+    data-attr="value"
+    data-attr-bool
+    dateId="my-custom-id"
+    hx-get="/page"
+    id="test"
+    nonshade
+    optional-from-func-true
+    text="lorem"
+  >text2</div>
+  <div>text3</div>
 </div>
diff --git a/generator/test-spread-attributes/expected_numeric_attributes.html b/generator/test-spread-attributes/expected_numeric_attributes.html
index 822ec8b..6c8cc14 100644
--- a/generator/test-spread-attributes/expected_numeric_attributes.html
+++ b/generator/test-spread-attributes/expected_numeric_attributes.html
@@ -1,5 +1,24 @@
 <div>
-	<a bool-true complex-value="(1+2i)" float-value="3.14" int-value="42" int64-value="9223372036854775807" string-value="text" uint-value="100">text</a>
-	<div bool-true complex-value="(1+2i)" float-value="3.14" int-value="42" int64-value="9223372036854775807" string-value="text" uint-value="100">text2</div>
+	<a
+		bool-true
+		complex-value="(1+2i)"
+		float-value="3.14"
+		int-value="42"
+		int64-value="9223372036854775807"
+		string-value="text"
+		uint-value="100"
+		>text</a
+	>
+	<div
+		bool-true
+		complex-value="(1+2i)"
+		float-value="3.14"
+		int-value="42"
+		int64-value="9223372036854775807"
+		string-value="text"
+		uint-value="100"
+	>
+		text2
+	</div>
 	<div>text3</div>
 </div>
diff --git a/generator/test-spread-attributes/expected_ordered_attributes.html b/generator/test-spread-attributes/expected_ordered_attributes.html
index d89dda1..ef6167f 100644
--- a/generator/test-spread-attributes/expected_ordered_attributes.html
+++ b/generator/test-spread-attributes/expected_ordered_attributes.html
@@ -1,12 +1,28 @@
 <div>
-    <a bool data-attr="value" data-attr-bool dateid="my-custom-id" hx-get="/page" id="test" nonshade text="lorem" optional-from-func-true>
-      text
-    </a>
-    <div bool data-attr="value" data-attr-bool dateid="my-custom-id" hx-get="/page" id="test" nonshade text="lorem" optional-from-func-true>
-      text2
-    </div>
-    <div>
-      text3
-    </div>
-    </div>
-    
\ No newline at end of file
+	<a
+		bool
+		data-attr="value"
+		data-attr-bool
+		dateId="my-custom-id"
+		hx-get="/page"
+		id="test"
+		nonshade
+		text="lorem"
+		optional-from-func-true
+		>text</a
+	>
+	<div
+		bool
+		data-attr="value"
+		data-attr-bool
+		dateId="my-custom-id"
+		hx-get="/page"
+		id="test"
+		nonshade
+		text="lorem"
+		optional-from-func-true
+	>
+		text2
+	</div>
+	<div>text3</div>
+</div>
diff --git a/generator/test-string/expected.html b/generator/test-string/expected.html
index 55e0258..6e9285f 100644
--- a/generator/test-string/expected.html
+++ b/generator/test-string/expected.html
@@ -1,5 +1,8 @@
 <ul>
-				<li></li>
-				<li>Strings are HTML escaped. So ampersands (&amp;), greater than (&gt;), and less than symbols (&lt;) are converted.</li>
-				<li>Spaces are preserved.</li>
+	<li></li>
+	<li>
+		Strings are HTML escaped. So ampersands (&amp;), greater than (&gt;), and
+		less than symbols (&lt;) are converted.
+	</li>
+	<li>Spaces are preserved.</li>
 </ul>
diff --git a/generator/test-style-attribute/expected.html b/generator/test-style-attribute/expected.html
index 479949a..2c8728c 100644
--- a/generator/test-style-attribute/expected.html
+++ b/generator/test-style-attribute/expected.html
@@ -1,2 +1,2 @@
-<button style="background-color:blue;color:red;">Click me</button>
-<button style="background-color: red;">Click me</button>
+<button style="background-color: blue; color: red">Click me</button>
+<button style="background-color: red">Click me</button>
diff --git a/generator/test-templ-element/expected.html b/generator/test-templ-element/expected.html
index e662601..f777eee 100644
--- a/generator/test-templ-element/expected.html
+++ b/generator/test-templ-element/expected.html
@@ -1 +1,10 @@
-<div id="1">child1 <div id="2">child2 <div id="3">child3 <div id="4"></div></div></div></div>
+<div id="1">
+	child1
+	<div id="2">
+		child2
+		<div id="3">
+			child3
+			<div id="4"></div>
+		</div>
+	</div>
+</div>
diff --git a/generator/test-templ-in-go-template/expected.html b/generator/test-templ-in-go-template/expected.html
index cf99512..2fa7e0a 100644
--- a/generator/test-templ-in-go-template/expected.html
+++ b/generator/test-templ-in-go-template/expected.html
@@ -1,6 +1,6 @@
-<!DOCTYPE html>
+<!doctype html>
 <html>
-        <body>
-                <div>Hello, World!</div>
+	<body>
+		<div>Hello, World!</div>
 	</body>
 </html>
diff --git a/generator/test-void/expected.html b/generator/test-void/expected.html
index 909b967..dca4e3e 100644
--- a/generator/test-void/expected.html
+++ b/generator/test-void/expected.html
@@ -1,4 +1 @@
-<br>
-<img src="https://example.com/image.png">
-<br>
-<br>
+<br /><img src="https://example.com/image.png" /><br /><br />
diff --git a/go.mod b/go.mod
index caa255c..1d378b5 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,6 @@ go 1.23.0
 toolchain go1.23.6
 
 require (
-	github.com/a-h/htmlformat v0.0.0-20250209131833-673be874c677
 	github.com/a-h/parse v0.0.0-20250122154542-74294addb73e
 	github.com/andybalholm/brotli v1.1.0
 	github.com/cenkalti/backoff/v4 v4.3.0
diff --git a/go.sum b/go.sum
index ba9c963..93fd43f 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,3 @@
-github.com/a-h/htmlformat v0.0.0-20250209131833-673be874c677 h1:H1EoxMpNo/TnCZEgSaKsomi+dQ05dXiVSKDN86Lap9A=
-github.com/a-h/htmlformat v0.0.0-20250209131833-673be874c677/go.mod h1:FMIm5afKmEfarNbIXOaPHFY8X7fo+fRQB6I9MPG2nB0=
 github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo=
 github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ=
 github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
diff --git a/internal/format/scriptelement.go b/internal/format/scriptelement.go
new file mode 100644
index 0000000..16c6aa0
--- /dev/null
+++ b/internal/format/scriptelement.go
@@ -0,0 +1,96 @@
+package format
+
+import (
+	"strings"
+
+	"github.com/a-h/templ/internal/prettier"
+	"github.com/a-h/templ/parser/v2"
+)
+
+const templScriptPlaceholder = "templ_go_expression_7331"
+
+// ScriptElement formats a ScriptElement node, replacing Go expressions with placeholders for formatting.
+// After formatting, it updates the GoCode expressions and their ranges.
+func ScriptElement(se *parser.ScriptElement, depth int, prettierCommand string) (err error) {
+	// Skip empty script elements, as they don't need formatting.
+	if len(se.Contents) == 0 {
+		return nil
+	}
+
+	// ScriptElements may contain Go expressions in {{ }} blocks. Prettier has no idea how to handle
+	// that, so we replace them with a placeholder, format the script, and then replace the placeholders
+	// with the original Go expressions.
+	var placeholderContent []parser.ScriptContents
+	var scriptWithPlaceholders strings.Builder
+	for _, part := range se.Contents {
+		if part.Value != nil {
+			scriptWithPlaceholders.WriteString(*part.Value)
+			continue
+		}
+		if part.GoCode != nil {
+			scriptWithPlaceholders.WriteString(templScriptPlaceholder)
+			placeholderContent = append(placeholderContent, part)
+			continue
+		}
+	}
+
+	// <script> elements can have a type attribute that specifies the script type. This is important
+	// to avoid formatting errors, e.g. attempting to format <script type="text/hyperscript"> with a
+	// JavaScript formatter.
+	var scriptType string
+loop:
+	for _, attr := range se.Attributes {
+		switch attr := attr.(type) {
+		case *parser.ConstantAttribute:
+			if attr.Key.String() == "type" {
+				scriptType = attr.Value
+			}
+			break loop
+		}
+	}
+
+	// Use the prettifyElement function to format the script contents.
+	after, err := prettier.Element("script", scriptType, scriptWithPlaceholders.String(), depth, prettierCommand)
+	if err != nil {
+		return err
+	}
+
+	// If there were no placeholders, set the contents to the formatted script.
+	if len(placeholderContent) == 0 {
+		se.Contents = []parser.ScriptContents{
+			{
+				Value:               &after,
+				GoCode:              nil,
+				InsideStringLiteral: false,
+			},
+		}
+		return nil
+	}
+
+	split := strings.Split(after, templScriptPlaceholder)
+	var appliedPlaceholderCount int
+	var newContents []parser.ScriptContents
+	for _, part := range split {
+		if part == "" {
+			continue
+		}
+		newContents = append(newContents, parser.ScriptContents{
+			Value:               &part,
+			GoCode:              nil,
+			InsideStringLiteral: false,
+		})
+		// If we had a GoCode part, we need to add it back in.
+		if appliedPlaceholderCount < len(placeholderContent) {
+			placeholder := placeholderContent[appliedPlaceholderCount]
+			// Trim horizontal space from the GoCode trailing space, as prettier will add its own.
+			if placeholder.GoCode != nil && strings.Contains(string(placeholder.GoCode.TrailingSpace), " ") {
+				placeholder.GoCode.TrailingSpace = parser.SpaceNone
+			}
+			newContents = append(newContents, placeholder)
+			appliedPlaceholderCount++
+		}
+	}
+	se.Contents = newContents
+
+	return nil
+}
diff --git a/internal/format/styleelement.go b/internal/format/styleelement.go
new file mode 100644
index 0000000..dbc56b8
--- /dev/null
+++ b/internal/format/styleelement.go
@@ -0,0 +1,25 @@
+package format
+
+import (
+	"github.com/a-h/templ/internal/prettier"
+	"github.com/a-h/templ/parser/v2"
+)
+
+func StyleElement(se *parser.RawElement, depth int, prettierCommand string) (err error) {
+	if se.Name != "style" {
+		return nil
+	}
+
+	// Skip empty style elements, as they don't need formatting.
+	if len(se.Contents) == 0 {
+		return nil
+	}
+
+	// Prettyify the style contents.
+	se.Contents, err = prettier.Element("style", "text/css", se.Contents, depth, prettierCommand)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/internal/format/templ.go b/internal/format/templ.go
new file mode 100644
index 0000000..807bfe6
--- /dev/null
+++ b/internal/format/templ.go
@@ -0,0 +1,98 @@
+package format
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/a-h/templ/internal/imports"
+	"github.com/a-h/templ/internal/prettier"
+	parser "github.com/a-h/templ/parser/v2"
+	"github.com/a-h/templ/parser/v2/visitor"
+)
+
+type Config struct {
+	// PrettierCommand is the command to run to format the content of script and style elements.
+	// If empty, the default is "prettier --use-tabs --stdin-filepath $TEMPL_PRETTIER_FILENAME".
+	PrettierCommand string
+	// PrettierRequired indicates that formatting using Prettier must be applied.
+	PrettierRequired bool
+}
+
+// Templ formats templ source, returning the formatted output, whether it changed, and an error if any.
+// The fileName is used for Go import processing, use an empty name if the source is not from a file.
+func Templ(src []byte, fileName string, config Config) (output []byte, changed bool, err error) {
+	t, err := parser.ParseString(string(src))
+	if err != nil {
+		return nil, false, err
+	}
+	t.Filepath = fileName
+	t, err = imports.Process(t)
+	if err != nil {
+		return nil, false, err
+	}
+
+	if err = applyPrettier(t, src, config); err != nil {
+		return nil, false, err
+	}
+
+	w := new(bytes.Buffer)
+	if err = t.Write(w); err != nil {
+		return nil, false, fmt.Errorf("formatting error: %w", err)
+	}
+	out := w.Bytes()
+	changed = !bytes.Equal(src, out)
+	return out, changed, nil
+}
+
+func applyPrettier(t *parser.TemplateFile, src []byte, config Config) (err error) {
+	// Check to see if prettier can be run.
+	if config.PrettierCommand == "" {
+		config.PrettierCommand = prettier.DefaultCommand
+	}
+	if config.PrettierRequired && !prettier.IsAvailable(config.PrettierCommand) {
+		return fmt.Errorf("prettier command is not available, please install it or set a different command using the -prettier-command flag")
+	}
+
+	nodeFormatter := visitor.New()
+	// Calculate the depth of each ScriptElement and RawElement in the tree so that the formatting is properly indented.
+	nodeToDepth := make(map[parser.Node]int)
+	nodeFormatter.HTMLTemplate = func(n *parser.HTMLTemplate) error {
+		// Visit the children first to calculate their depth.
+		for _, child := range n.Children {
+			calculateNodeDepth(child, nodeToDepth, 1)
+		}
+		// Now that we have the depth of each node, we can format them.
+		for _, child := range n.Children {
+			if err := child.Visit(nodeFormatter); err != nil {
+				return err
+			}
+		}
+		return nil
+	}
+	nodeFormatter.ScriptElement = func(se *parser.ScriptElement) error {
+		depth := nodeToDepth[se]
+		return ScriptElement(se, depth, config.PrettierCommand)
+	}
+	nodeFormatter.RawElement = func(re *parser.RawElement) error {
+		depth := nodeToDepth[re]
+		if re.Name != "style" {
+			return nil
+		}
+		return StyleElement(re, depth, config.PrettierCommand)
+	}
+
+	return nodeFormatter.VisitTemplateFile(t)
+}
+
+func calculateNodeDepth(e parser.Node, nodeToDepth map[parser.Node]int, depth int) {
+	switch e := e.(type) {
+	case *parser.ScriptElement:
+		nodeToDepth[e] = depth
+	case *parser.RawElement:
+		nodeToDepth[e] = depth
+	case parser.CompositeNode:
+		for _, child := range e.ChildNodes() {
+			calculateNodeDepth(child, nodeToDepth, depth+1)
+		}
+	}
+}
diff --git a/cmd/templ/imports/process.go b/internal/imports/process.go
similarity index 100%
rename from cmd/templ/imports/process.go
rename to internal/imports/process.go
diff --git a/internal/prettier/prettier.go b/internal/prettier/prettier.go
new file mode 100644
index 0000000..131d2eb
--- /dev/null
+++ b/internal/prettier/prettier.go
@@ -0,0 +1,116 @@
+package prettier
+
+import (
+	"fmt"
+	"html"
+	"os"
+	"os/exec"
+	"runtime"
+	"strings"
+
+	"github.com/a-h/templ/internal/htmlfind"
+)
+
+const DefaultCommand = "prettier --use-tabs --stdin-filepath $TEMPL_PRETTIER_FILENAME"
+
+func IsAvailable(command string) bool {
+	executable := strings.Fields(command)[0]
+	_, err := exec.LookPath(executable)
+	return err == nil
+}
+
+// Run the prettier command with the given input and file name.
+// $TEMPL_PRETTIER_FILENAME is set to the file name being formatted.
+// To format blocks inside templ files a fake name is provided, e.g. format.html, format.js, format.css etc.
+// The command is run in a shell, so it can be a complex command with pipes and redirections.
+//
+// Examples:
+//
+//	prettier --use-tabs --stdin-filepath $TEMPL_PRETTIER_FILENAME
+//	prettierd --use-tabs --stdin-filepath $TEMPL_PRETTIER_FILENAME
+//	npx prettier --use-tabs --stdin-filepath $TEMPL_PRETTIER_FILENAME
+//	prettier --config ./frontend/.prettierrc --use-tabs --stdin-filepath $TEMPL_PRETTIER_FILENAME
+func Run(input, fileName, command string) (formatted string, err error) {
+	cmd := getCommand(runtime.GOOS, command)
+	cmd.Env = append(os.Environ(), fmt.Sprintf("TEMPL_PRETTIER_FILENAME=%s", fileName))
+	cmd.Stdin = strings.NewReader(input)
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		return "", fmt.Errorf("unable to format with command %q, output: %q, error: %v", cmd.Args, string(output), err)
+	}
+	return string(output), nil
+}
+
+func getCommand(goos, command string) *exec.Cmd {
+	if goos == "windows" {
+		return exec.Command("cmd.exe", "/C", command)
+	}
+	shell := os.Getenv("SHELL")
+	if shell == "" {
+		shell = "/bin/sh"
+	}
+	return exec.Command(shell, "-c", command)
+}
+
+func Element(name string, typeAttrValue string, content string, depth int, prettierCommand string) (after string, err error) {
+	var indentationWrapper strings.Builder
+
+	// Add divs to the start and end of the script to ensure that prettier formats the content with
+	// correct indentation.
+	for i := range depth {
+		indentationWrapper.WriteString(fmt.Sprintf("<div data-templ-depth=\"%d\">", i))
+	}
+
+	// Write start tag with type attribute if present.
+	indentationWrapper.WriteString("<")
+	indentationWrapper.WriteString(name)
+	if typeAttrValue != "" {
+		indentationWrapper.WriteString(" type=\"")
+		indentationWrapper.WriteString(html.EscapeString(typeAttrValue))
+		indentationWrapper.WriteString("\"")
+	}
+	indentationWrapper.WriteString(">")
+
+	// Write contents.
+	indentationWrapper.WriteString(content)
+
+	// Write end tag.
+	indentationWrapper.WriteString("</")
+	indentationWrapper.WriteString(name)
+	indentationWrapper.WriteString(">")
+
+	for range depth {
+		indentationWrapper.WriteString("</div>")
+	}
+
+	before := indentationWrapper.String()
+	after, err = Run(before, "templ_content.html", prettierCommand)
+	if err != nil {
+		return "", fmt.Errorf("prettier error: %w", err)
+	}
+	if before == after {
+		return before, nil
+	}
+
+	// Chop off the start and end divs we added to get prettier to format the content with correct
+	// indentation.
+	matcher := htmlfind.Element(name)
+	nodes, err := htmlfind.AllReader(strings.NewReader(after), matcher)
+	if err != nil {
+		return before, fmt.Errorf("htmlfind error: %w", err)
+	}
+	if len(nodes) != 1 {
+		return before, fmt.Errorf("expected 1 %q node, got %d", name, len(nodes))
+	}
+	scriptNode := nodes[0]
+	if scriptNode.FirstChild == nil {
+		return before, fmt.Errorf("%q node has no children", name)
+	}
+	var sb strings.Builder
+	for node := range scriptNode.ChildNodes() {
+		sb.WriteString(node.Data)
+	}
+	after = strings.TrimRight(sb.String(), " \t\r\n") + "\n" + strings.Repeat("\t", depth)
+
+	return after, nil
+}
diff --git a/internal/prettier/testdata.txtar b/internal/prettier/testdata.txtar
new file mode 100644
index 0000000..609d22a
--- /dev/null
+++ b/internal/prettier/testdata.txtar
@@ -0,0 +1,12 @@
+-- js-1.js --
+function foo(){return 1;}
+-- js-1.js --
+function foo() {
+	return 1;
+}
+-- css-1.css --
+body{color:red;}
+-- css-1.css --
+body {
+	color: red;
+}
diff --git a/parser/v2/visitor/visitor.go b/parser/v2/visitor/visitor.go
index 88c50e8..e836c08 100644
--- a/parser/v2/visitor/visitor.go
+++ b/parser/v2/visitor/visitor.go
@@ -2,12 +2,12 @@ package visitor
 
 import "github.com/a-h/templ/parser/v2"
 
-// New creates a default visitor. Each of the visitor functions can be
+// New returns a default Visitor. Each function in the Visitor struct can be
 // overridden to provide custom behavior when visiting nodes in the parse tree.
 func New() *Visitor {
 	v := &Visitor{}
 
-	// Default implementations of the visitor functions
+	// Set default implementations for all visitor functions.
 	v.TemplateFile = func(n *parser.TemplateFile) error {
 		for _, header := range n.Header {
 			if err := v.VisitTemplateFileGoExpression(header); err != nil {
@@ -192,10 +192,8 @@ func New() *Visitor {
 	return v
 }
 
-// Visitor is a struct that implements the parser.Visitor interface. Each
-// function in the Visitor struct corresponds to a node type in the parse tree.
-// The functions can be overridden to provide custom behavior when visiting
-// nodes in the parse tree.
+// Visitor implements the parser.Visitor interface. Each function corresponds to a node type in the parse tree.
+// Override these functions to provide custom behavior when visiting nodes.
 type Visitor struct {
 	TemplateFile             func(n *parser.TemplateFile) error
 	TemplateFileGoExpression func(n *parser.TemplateFileGoExpression) error
diff --git a/runtime/fuzzing/fuzz.templ b/runtime/fuzzing/fuzz.templ
index 282a50a..6d0b118 100644
--- a/runtime/fuzzing/fuzz.templ
+++ b/runtime/fuzzing/fuzz.templ
@@ -3,8 +3,8 @@ package fuzzing
 templ String(v any) {
 	<script>
 		function logValue() {
-			console.log("{{ v }}")
-			return "result_ok"
+			console.log("{{ v }}");
+			return "result_ok";
 		}
 	</script>
 }
@@ -12,8 +12,8 @@ templ String(v any) {
 templ Any(v any) {
 	<script>
 		function logValue() {
-			console.log({{ v }})
-			return "result_ok"
+			console.log({{ v }});
+			return "result_ok";
 		}
 	</script>
 }
diff --git a/runtime/fuzzing/fuzz_templ.go b/runtime/fuzzing/fuzz_templ.go
index 4619b1a..72c883b 100644
--- a/runtime/fuzzing/fuzz_templ.go
+++ b/runtime/fuzzing/fuzz_templ.go
@@ -40,7 +40,7 @@ func String(v any) templ.Component {
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\")\n\t\t\treturn \"result_ok\"\n\t\t}\n\t</script>")
+		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\");\n\t\t\treturn \"result_ok\";\n\t\t}\n\t</script>")
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
@@ -81,7 +81,7 @@ func Any(v any) templ.Component {
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, ")\n\t\t\treturn \"result_ok\"\n\t\t}\n\t</script>")
+		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, ");\n\t\t\treturn \"result_ok\";\n\t\t}\n\t</script>")
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}



Test patch:
diff --git a/generator/test-a-href/render_test.go b/generator/test-a-href/render_test.go
index f757924..0966e28 100644
--- a/generator/test-a-href/render_test.go
+++ b/generator/test-a-href/render_test.go
@@ -2,6 +2,7 @@ package testahref
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := render()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-attribute-errors/render_test.go b/generator/test-attribute-errors/render_test.go
index 1dfbad1..191595b 100644
--- a/generator/test-attribute-errors/render_test.go
+++ b/generator/test-attribute-errors/render_test.go
@@ -15,10 +15,11 @@ import (
 var expected string
 
 func Test(t *testing.T) {
+	t.Parallel()
 	t.Run("can render without error", func(t *testing.T) {
 		component := TestComponent(nil)
 
-		_, err := htmldiff.Diff(component, expected)
+		_, _, err := htmldiff.Diff(component, expected)
 		if err != nil {
 			t.Fatalf("unexpected error: %v", err)
 		}
diff --git a/generator/test-attribute-escaping/render_test.go b/generator/test-attribute-escaping/render_test.go
index bb1fb67..13e2f9e 100644
--- a/generator/test-attribute-escaping/render_test.go
+++ b/generator/test-attribute-escaping/render_test.go
@@ -4,6 +4,8 @@ import (
 	_ "embed"
 	"testing"
 
+	"os"
+
 	"github.com/a-h/templ/generator/htmldiff"
 )
 
@@ -11,13 +13,17 @@ import (
 var expected string
 
 func Test(t *testing.T) {
+	t.Parallel()
 	component := BasicTemplate(`javascript: alert("xss");`)
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-call/render_test.go b/generator/test-call/render_test.go
index adf27fe..6b03567 100644
--- a/generator/test-call/render_test.go
+++ b/generator/test-call/render_test.go
@@ -2,6 +2,7 @@ package testcall
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := showAll()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-complex-attributes/render_test.go b/generator/test-complex-attributes/render_test.go
index e899493..761e3fa 100644
--- a/generator/test-complex-attributes/render_test.go
+++ b/generator/test-complex-attributes/render_test.go
@@ -4,6 +4,8 @@ import (
 	_ "embed"
 	"testing"
 
+	"os"
+
 	"github.com/a-h/templ/generator/htmldiff"
 )
 
@@ -13,11 +15,14 @@ var expected string
 func Test(t *testing.T) {
 	component := ComplexAttributes()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-constant-attribute-escaping/render_test.go b/generator/test-constant-attribute-escaping/render_test.go
index 2fb2a80..6bd8a83 100644
--- a/generator/test-constant-attribute-escaping/render_test.go
+++ b/generator/test-constant-attribute-escaping/render_test.go
@@ -2,6 +2,7 @@ package testconstantattributeescaping
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := BasicTemplate()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-context/render_test.go b/generator/test-context/render_test.go
index c662e51..2e677d6 100644
--- a/generator/test-context/render_test.go
+++ b/generator/test-context/render_test.go
@@ -5,6 +5,8 @@ import (
 	_ "embed"
 	"testing"
 
+	"os"
+
 	"github.com/a-h/templ/generator/htmldiff"
 )
 
@@ -16,11 +18,14 @@ func Test(t *testing.T) {
 
 	ctx := context.WithValue(context.Background(), contextKeyName, "test")
 
-	_, diff, err := htmldiff.DiffCtx(ctx, component, expected)
+	actual, diff, err := htmldiff.DiffCtx(ctx, component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-css-middleware/render_test.go b/generator/test-css-middleware/render_test.go
index 631857f..c95313a 100644
--- a/generator/test-css-middleware/render_test.go
+++ b/generator/test-css-middleware/render_test.go
@@ -2,34 +2,32 @@ package testcssmiddleware
 
 import (
 	_ "embed"
-	"fmt"
 	"net/http/httptest"
-	"strings"
 	"testing"
 
-	"github.com/a-h/htmlformat"
+	"os"
+
 	"github.com/a-h/templ"
-	"github.com/google/go-cmp/cmp"
+	"github.com/a-h/templ/generator/htmldiff"
+	"github.com/a-h/templ/internal/prettier"
 	"golang.org/x/sync/errgroup"
 )
 
 //go:embed expected.html
 var expected string
 
-var expectedCSS = `.red_050e5e03{color:red;}
+var expectedCSS = `.red_050e5e03 { color: red; }
 `
 
 func Test(t *testing.T) {
 	var wg errgroup.Group
 
 	// Format the expected value.
-	wg.Go(func() error {
-		e := new(strings.Builder)
-		err := htmlformat.Fragment(e, strings.NewReader(expected))
+	wg.Go(func() (err error) {
+		expected, err = prettier.Run(expected, "expected.html", prettier.DefaultCommand)
 		if err != nil {
-			return fmt.Errorf("expected html formatting error: %w", err)
+			return err
 		}
-		expected = e.String()
 		return nil
 	})
 
@@ -39,40 +37,48 @@ func Test(t *testing.T) {
 
 	// Create the actual value.
 	var actual string
-	wg.Go(func() error {
+	wg.Go(func() (err error) {
 		w := httptest.NewRecorder()
 		cssmw.ServeHTTP(w, httptest.NewRequest("GET", "/", nil))
-
-		a := new(strings.Builder)
-		err := htmlformat.Fragment(a, w.Body)
+		actual, err = prettier.Run(w.Body.String(), "actual.html", prettier.DefaultCommand)
 		if err != nil {
-			return fmt.Errorf("actual html formatting error: %w", err)
+			return err
 		}
-		actual = a.String()
 		return nil
 	})
 
 	var actualCSS string
-	wg.Go(func() error {
+	wg.Go(func() (err error) {
 		w := httptest.NewRecorder()
 		cssmw.ServeHTTP(w, httptest.NewRequest("GET", "/styles/templ.css", nil))
-
-		a := new(strings.Builder)
-		err := htmlformat.Fragment(a, w.Body)
+		actualCSS, err = prettier.Run(w.Body.String(), "actual.css", prettier.DefaultCommand)
 		if err != nil {
-			return fmt.Errorf("actual html formatting error: %w", err)
+			return err
 		}
-		actualCSS = a.String()
 		return nil
 	})
 
 	if err := wg.Wait(); err != nil {
 		t.Error(err)
 	}
-	if diff := cmp.Diff(expected, actual); diff != "" {
-		t.Error(diff)
+	actualHTML, diffHTML, err := htmldiff.DiffStrings(expected, actual)
+	if err != nil {
+		t.Error(err)
 	}
-	if diff := cmp.Diff(expectedCSS, actualCSS); diff != "" {
-		t.Error(diff)
+	if diffHTML != "" {
+		if err := os.WriteFile("actual.html", []byte(actualHTML), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
+		t.Error(diffHTML)
+	}
+	actualCSSOut, diffCSS, err := htmldiff.DiffStrings(expectedCSS, actualCSS)
+	if err != nil {
+		t.Error(err)
+	}
+	if diffCSS != "" {
+		if err := os.WriteFile("actual_css.html", []byte(actualCSSOut), 0644); err != nil {
+			t.Errorf("failed to write actual_css.html: %v", err)
+		}
+		t.Error(diffCSS)
 	}
 }
diff --git a/generator/test-css-usage/render_test.go b/generator/test-css-usage/render_test.go
index 342f692..09b543a 100644
--- a/generator/test-css-usage/render_test.go
+++ b/generator/test-css-usage/render_test.go
@@ -2,6 +2,7 @@ package testcssusage
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := TestComponent()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-css-usage/template_templ.go b/generator/test-css-usage/template_templ.go
index f93f3f2..5313f44 100644
--- a/generator/test-css-usage/template_templ.go
+++ b/generator/test-css-usage/template_templ.go
@@ -33,7 +33,7 @@ func StyleTagsAreSupported() templ.Component {
 			templ_7745c5c3_Var1 = templ.NopComponent
 		}
 		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<style>\n\t.test {\n\t\tcolor: #ff0000;\n\t}\n\t</style><div class=\"test\">Style tags are supported</div>")
+		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<style>\n\t\t.test {\n\t\t\tcolor: #ff0000;\n\t\t}\n\t</style><div class=\"test\">Style tags are supported</div>")
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
diff --git a/generator/test-doctype-html4/render_test.go b/generator/test-doctype-html4/render_test.go
index e124306..c6acac2 100644
--- a/generator/test-doctype-html4/render_test.go
+++ b/generator/test-doctype-html4/render_test.go
@@ -2,6 +2,7 @@ package testdoctype
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := Layout("title", "content")
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-doctype/render_test.go b/generator/test-doctype/render_test.go
index e124306..c6acac2 100644
--- a/generator/test-doctype/render_test.go
+++ b/generator/test-doctype/render_test.go
@@ -2,6 +2,7 @@ package testdoctype
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := Layout("title", "content")
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-element-attributes/render_test.go b/generator/test-element-attributes/render_test.go
index e4cc34c..63b714b 100644
--- a/generator/test-element-attributes/render_test.go
+++ b/generator/test-element-attributes/render_test.go
@@ -2,6 +2,7 @@ package testelementattributes
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -16,11 +17,14 @@ func Test(t *testing.T) {
 		email: "luiz@example.com",
 	})
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-elseif/render_test.go b/generator/test-elseif/render_test.go
index 45b81db..6781a01 100644
--- a/generator/test-elseif/render_test.go
+++ b/generator/test-elseif/render_test.go
@@ -2,6 +2,7 @@ package elseif
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := render(data{})
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-for/render_test.go b/generator/test-for/render_test.go
index 5935ba0..784bcf2 100644
--- a/generator/test-for/render_test.go
+++ b/generator/test-for/render_test.go
@@ -2,6 +2,7 @@ package testfor
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := render([]string{"a", "b", "c"})
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-form-action/render_test.go b/generator/test-form-action/render_test.go
index f757924..0966e28 100644
--- a/generator/test-form-action/render_test.go
+++ b/generator/test-form-action/render_test.go
@@ -2,6 +2,7 @@ package testahref
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := render()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-fragment/render_test.go b/generator/test-fragment/render_test.go
index 706d7ba..a05936b 100644
--- a/generator/test-fragment/render_test.go
+++ b/generator/test-fragment/render_test.go
@@ -7,6 +7,8 @@ import (
 	"net/http/httptest"
 	"testing"
 
+	"os"
+
 	"github.com/a-h/templ"
 	"github.com/a-h/templ/generator/htmldiff"
 )
@@ -68,11 +70,14 @@ func Test(t *testing.T) {
 				t.Errorf("failed to read body: %v", err)
 			}
 
-			diff, err := htmldiff.DiffStrings(tt.expected, string(body))
+			actual, diff, err := htmldiff.DiffStrings(tt.expected, string(body))
 			if err != nil {
 				t.Fatalf("failed to diff: %v", err)
 			}
 			if diff != "" {
+				if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+					t.Errorf("failed to write actual.html: %v", err)
+				}
 				t.Error(diff)
 			}
 		})
diff --git a/generator/test-go-comments/render_test.go b/generator/test-go-comments/render_test.go
index b324b85..658adfd 100644
--- a/generator/test-go-comments/render_test.go
+++ b/generator/test-go-comments/render_test.go
@@ -2,6 +2,7 @@ package testcomment
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := render("sample content")
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-go-template-in-templ/render_test.go b/generator/test-go-template-in-templ/render_test.go
index 36614f9..920d0bc 100644
--- a/generator/test-go-template-in-templ/render_test.go
+++ b/generator/test-go-template-in-templ/render_test.go
@@ -2,6 +2,7 @@ package testgotemplates
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -12,11 +13,14 @@ var expected string
 
 func TestExample(t *testing.T) {
 	component := Example()
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-html-comment/render_test.go b/generator/test-html-comment/render_test.go
index b324b85..658adfd 100644
--- a/generator/test-html-comment/render_test.go
+++ b/generator/test-html-comment/render_test.go
@@ -2,6 +2,7 @@ package testcomment
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := render("sample content")
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-html/render_test.go b/generator/test-html/render_test.go
index d5d2528..90ae64a 100644
--- a/generator/test-html/render_test.go
+++ b/generator/test-html/render_test.go
@@ -2,6 +2,7 @@ package testhtml
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -16,11 +17,14 @@ func Test(t *testing.T) {
 		email: "luiz@example.com",
 	})
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-if/render_test.go b/generator/test-if/render_test.go
index 08f9635..181b5c7 100644
--- a/generator/test-if/render_test.go
+++ b/generator/test-if/render_test.go
@@ -2,6 +2,7 @@ package testif
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := render(data{})
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-ifelse/render_test.go b/generator/test-ifelse/render_test.go
index 97fe1ee..8575e61 100644
--- a/generator/test-ifelse/render_test.go
+++ b/generator/test-ifelse/render_test.go
@@ -2,6 +2,7 @@ package ifelse
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := render(data{})
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-import/render_test.go b/generator/test-import/render_test.go
index ff0f1c8..daebf67 100644
--- a/generator/test-import/render_test.go
+++ b/generator/test-import/render_test.go
@@ -2,6 +2,7 @@ package testimport
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := main()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-js-unsafe-usage/render_test.go b/generator/test-js-unsafe-usage/render_test.go
index ca8ec51..c07732e 100644
--- a/generator/test-js-unsafe-usage/render_test.go
+++ b/generator/test-js-unsafe-usage/render_test.go
@@ -2,6 +2,7 @@ package testjsunsafeusage
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := TestComponent()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-js-usage/render_test.go b/generator/test-js-usage/render_test.go
index ed03f71..426e7da 100644
--- a/generator/test-js-usage/render_test.go
+++ b/generator/test-js-usage/render_test.go
@@ -2,6 +2,7 @@ package testjsusage
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := TestComponent()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-js-usage/template_templ.go b/generator/test-js-usage/template_templ.go
index 1e450df..69f4a60 100644
--- a/generator/test-js-usage/template_templ.go
+++ b/generator/test-js-usage/template_templ.go
@@ -109,7 +109,7 @@ func TestComponent() templ.Component {
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<script>\n\t\tfunction onClickEventHandler(event, data) {\n\t\t\talert(event.type);\n\t\t\talert(data)\n\t\t\tevent.preventDefault();\n\t\t}\n\t</script>")
+		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<script>\n\t\tfunction onClickEventHandler(event, data) {\n\t\t\talert(event.type);\n\t\t\talert(data);\n\t\t\tevent.preventDefault();\n\t\t}\n\t</script>")
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
diff --git a/generator/test-method/render_test.go b/generator/test-method/render_test.go
index 09951e8..dfd0b6e 100644
--- a/generator/test-method/render_test.go
+++ b/generator/test-method/render_test.go
@@ -2,6 +2,7 @@ package testmethod
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -16,11 +17,14 @@ func Test(t *testing.T) {
 	}
 	component := d.Method()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-once/render_test.go b/generator/test-once/render_test.go
index 3d1f237..82a24e5 100644
--- a/generator/test-once/render_test.go
+++ b/generator/test-once/render_test.go
@@ -2,6 +2,7 @@ package once
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := render()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-once/template_templ.go b/generator/test-once/template_templ.go
index ff55bc5..6a8ef5c 100644
--- a/generator/test-once/template_templ.go
+++ b/generator/test-once/template_templ.go
@@ -42,7 +42,7 @@ func hello(label, name string) templ.Component {
 				}()
 			}
 			ctx = templ.InitializeContext(ctx)
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<script>\n\t\t\tfunction hello(name) {\n\t\t\t\talert('Hello, ' + name + '!');\n\t\t\t}\n\t\t</script>")
+			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<script>\n\t\t\tfunction hello(name) {\n\t\t\t\talert(\"Hello, \" + name + \"!\");\n\t\t\t}\n\t\t</script>")
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
diff --git a/generator/test-only-scripts/render_test.go b/generator/test-only-scripts/render_test.go
index 3b712b4..5ced918 100644
--- a/generator/test-only-scripts/render_test.go
+++ b/generator/test-only-scripts/render_test.go
@@ -2,6 +2,7 @@ package onlyscripts
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	script := withParameters("hello", "world", 42069)
 
-	diff, err := htmldiff.Diff(script, expected)
+	actual, diff, err := htmldiff.Diff(script, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-primitives/render_test.go b/generator/test-primitives/render_test.go
index c9986d1..0ecec5a 100644
--- a/generator/test-primitives/render_test.go
+++ b/generator/test-primitives/render_test.go
@@ -2,6 +2,7 @@ package testprimitives
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := render()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-raw-elements/render_test.go b/generator/test-raw-elements/render_test.go
index bbb5ecb..5908467 100644
--- a/generator/test-raw-elements/render_test.go
+++ b/generator/test-raw-elements/render_test.go
@@ -2,6 +2,7 @@ package testrawelements
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -12,11 +13,14 @@ var expected string
 
 func Test(t *testing.T) {
 	component := Example()
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-raw-elements/template_templ.go b/generator/test-raw-elements/template_templ.go
index ab22de8..be01a2a 100644
--- a/generator/test-raw-elements/template_templ.go
+++ b/generator/test-raw-elements/template_templ.go
@@ -28,7 +28,7 @@ func Example() templ.Component {
 			templ_7745c5c3_Var1 = templ.NopComponent
 		}
 		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<html><head></head><body><style><!-- Some stuff --></style><style>\n        .customClass {\n          border: 1px solid black;\n        }\n      </style><script>\n        $(\"div\").marquee();\n        function test() {\n              window.open(\"https://example.com\")\n        }\n      </script><h1>Hello</h1>")
+		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<html><head></head><body><style>\n\t\t\t\t<!-- Some stuff -->\n\t\t\t</style><style>\n\t\t\t\t.customClass {\n\t\t\t\t\tborder: 1px solid black;\n\t\t\t\t}\n\t\t\t</style><script>\n\t\t\t\t$(\"div\").marquee();\n\t\t\t\tfunction test() {\n\t\t\t\t\twindow.open(\"https://example.com\");\n\t\t\t\t}\n\t\t\t</script><h1>Hello</h1>")
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
diff --git a/generator/test-script-expressions/render_test.go b/generator/test-script-expressions/render_test.go
index e699a7a..00313cb 100644
--- a/generator/test-script-expressions/render_test.go
+++ b/generator/test-script-expressions/render_test.go
@@ -2,6 +2,7 @@ package testscriptexpressions
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := AllTests()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-script-expressions/template_templ.go b/generator/test-script-expressions/template_templ.go
index 60cea56..8adee59 100644
--- a/generator/test-script-expressions/template_templ.go
+++ b/generator/test-script-expressions/template_templ.go
@@ -101,7 +101,7 @@ func Script[T any](name string, data T) templ.Component {
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, ")\n\t</script>")
+		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, ");\n\t</script>")
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
diff --git a/generator/test-script-inline/render_test.go b/generator/test-script-inline/render_test.go
index 0507e44..08192ca 100644
--- a/generator/test-script-inline/render_test.go
+++ b/generator/test-script-inline/render_test.go
@@ -2,6 +2,7 @@ package testscriptinline
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := InlineJavascript("injected")
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-script-usage/render_test.go b/generator/test-script-usage/render_test.go
index 9aea4ba..780780e 100644
--- a/generator/test-script-usage/render_test.go
+++ b/generator/test-script-usage/render_test.go
@@ -2,6 +2,7 @@ package testscriptusage
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := ThreeButtons()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-spread-attributes/render_test.go b/generator/test-spread-attributes/render_test.go
index 5eda11d..54d5b2b 100644
--- a/generator/test-spread-attributes/render_test.go
+++ b/generator/test-spread-attributes/render_test.go
@@ -4,6 +4,8 @@ import (
 	_ "embed"
 	"testing"
 
+	"os"
+
 	"github.com/a-h/templ"
 	"github.com/a-h/templ/generator/htmldiff"
 )
@@ -47,11 +49,14 @@ func Test(t *testing.T) {
 		"optional-from-func-true": func() bool { return true },
 	})
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
@@ -95,11 +100,14 @@ func TestOrderedAttributes(t *testing.T) {
 		{Key: "optional-from-func-true", Value: func() bool { return true }},
 	})
 
-	diff, err := htmldiff.Diff(component, expectedOrderedAttributes)
+	actual, diff, err := htmldiff.Diff(component, expectedOrderedAttributes)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
@@ -128,11 +136,14 @@ func TestNumericAttributeTypes(t *testing.T) {
 		"bool-false":    false,
 	})
 
-	diff, err := htmldiff.Diff(component, expectedNumericAttributes)
+	actual, diff, err := htmldiff.Diff(component, expectedNumericAttributes)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-string-errors/render_test.go b/generator/test-string-errors/render_test.go
index d279881..c12974e 100644
--- a/generator/test-string-errors/render_test.go
+++ b/generator/test-string-errors/render_test.go
@@ -18,7 +18,7 @@ func Test(t *testing.T) {
 	t.Run("can render without error", func(t *testing.T) {
 		component := TestComponent(nil)
 
-		_, err := htmldiff.Diff(component, expected)
+		_, _, err := htmldiff.Diff(component, expected)
 		if err != nil {
 			t.Fatalf("unexpected error: %v", err)
 		}
diff --git a/generator/test-string/render_test.go b/generator/test-string/render_test.go
index 7774362..cb278ff 100644
--- a/generator/test-string/render_test.go
+++ b/generator/test-string/render_test.go
@@ -2,6 +2,7 @@ package teststring
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := render(`Strings are HTML escaped. So ampersands (&), greater than (>), and less than symbols (<) are converted.`)
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-style-attribute/render_test.go b/generator/test-style-attribute/render_test.go
index 3f5a741..6002f3a 100644
--- a/generator/test-style-attribute/render_test.go
+++ b/generator/test-style-attribute/render_test.go
@@ -3,6 +3,7 @@ package teststyleattribute
 import (
 	_ "embed"
 	"fmt"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ"
@@ -52,11 +53,14 @@ func Test(t *testing.T) {
 		t.Run(fmt.Sprintf("%T", test), func(t *testing.T) {
 			component := Button(test, "Click me")
 
-			diff, err := htmldiff.Diff(component, expected)
+			actual, diff, err := htmldiff.Diff(component, expected)
 			if err != nil {
 				t.Fatal(err)
 			}
 			if diff != "" {
+				if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+					t.Errorf("failed to write actual.html: %v", err)
+				}
 				t.Error(diff)
 			}
 		})
diff --git a/generator/test-templ-element/render_test.go b/generator/test-templ-element/render_test.go
index a3c1109..37cc89a 100644
--- a/generator/test-templ-element/render_test.go
+++ b/generator/test-templ-element/render_test.go
@@ -1,6 +1,7 @@
 package testtemplelement
 
 import (
+	"os"
 	"testing"
 
 	_ "embed"
@@ -14,11 +15,14 @@ var expected string
 func Test(t *testing.T) {
 	component := template()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-templ-in-go-template/render_test.go b/generator/test-templ-in-go-template/render_test.go
index 9fa7337..de62b52 100644
--- a/generator/test-templ-in-go-template/render_test.go
+++ b/generator/test-templ-in-go-template/render_test.go
@@ -3,6 +3,7 @@ package testgotemplates
 import (
 	"context"
 	_ "embed"
+	"os"
 	"strings"
 	"testing"
 
@@ -29,11 +30,14 @@ func TestExample(t *testing.T) {
 	}
 
 	// Compare the output with the expected.
-	diff, err := htmldiff.DiffStrings(expected, b.String())
+	actual, diff, err := htmldiff.DiffStrings(expected, b.String())
 	if err != nil {
 		t.Fatalf("failed to diff strings: %v", err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-text/render_test.go b/generator/test-text/render_test.go
index f2eb457..431b8f0 100644
--- a/generator/test-text/render_test.go
+++ b/generator/test-text/render_test.go
@@ -1,6 +1,7 @@
 package testtext
 
 import (
+	"os"
 	"testing"
 
 	_ "embed"
@@ -14,11 +15,14 @@ var expected string
 func Test(t *testing.T) {
 	component := BasicTemplate("Luiz Bonfa")
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/generator/test-void/render_test.go b/generator/test-void/render_test.go
index e91f5db..6b3a138 100644
--- a/generator/test-void/render_test.go
+++ b/generator/test-void/render_test.go
@@ -2,6 +2,7 @@ package testvoid
 
 import (
 	_ "embed"
+	"os"
 	"testing"
 
 	"github.com/a-h/templ/generator/htmldiff"
@@ -13,11 +14,14 @@ var expected string
 func Test(t *testing.T) {
 	component := render()
 
-	diff, err := htmldiff.Diff(component, expected)
+	actual, diff, err := htmldiff.Diff(component, expected)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if diff != "" {
+		if err := os.WriteFile("actual.html", []byte(actual), 0644); err != nil {
+			t.Errorf("failed to write actual.html: %v", err)
+		}
 		t.Error(diff)
 	}
 }
diff --git a/internal/format/format_test.go b/internal/format/format_test.go
new file mode 100644
index 0000000..df33cc9
--- /dev/null
+++ b/internal/format/format_test.go
@@ -0,0 +1,50 @@
+package format
+
+import (
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"golang.org/x/tools/txtar"
+)
+
+func TestFormatting(t *testing.T) {
+	files, _ := filepath.Glob("testdata/*.txt")
+	if len(files) == 0 {
+		t.Errorf("no test files found")
+	}
+	for _, file := range files {
+		t.Run(filepath.Base(file), func(t *testing.T) {
+			t.Parallel()
+			a, err := txtar.ParseFile(file)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if len(a.Files) != 2 {
+				t.Fatalf("expected 2 files, got %d", len(a.Files))
+			}
+			actual, _, err := Templ(a.Files[0].Data, "", Config{PrettierRequired: true})
+			if err != nil {
+				t.Fatalf("failed to format input: %v", err)
+			}
+			expected := string(a.Files[1].Data)
+			if diff := cmp.Diff(expected, string(actual)); diff != "" {
+				t.Errorf("Expected:\n%s\nActual:\n%s\n", showWhitespace(expected), showWhitespace(string(actual)))
+
+				expectedLines := strings.Split(expected, "\n")
+				actualLines := strings.Split(string(actual), "\n")
+				if len(expectedLines) != len(actualLines) {
+					t.Errorf("Expected %d lines, got %d lines", len(expectedLines), len(actualLines))
+				}
+			}
+		})
+	}
+}
+
+func showWhitespace(s string) string {
+	s = strings.ReplaceAll(s, "\n", "⏎\n")
+	s = strings.ReplaceAll(s, "\t", "→")
+	s = strings.ReplaceAll(s, " ", "·")
+	return s
+}
diff --git a/parser/v2/formattestdata/all_children_indented__with_nested_indentation__when_close_tag_is_on_new_line.txt b/internal/format/testdata/all_children_indented__with_nested_indentation__when_close_tag_is_on_new_line.txt
similarity index 100%
rename from parser/v2/formattestdata/all_children_indented__with_nested_indentation__when_close_tag_is_on_new_line.txt
rename to internal/format/testdata/all_children_indented__with_nested_indentation__when_close_tag_is_on_new_line.txt
diff --git a/parser/v2/formattestdata/all_children_indented__with_nested_indentation__when_close_tag_is_on_same_line.txt b/internal/format/testdata/all_children_indented__with_nested_indentation__when_close_tag_is_on_same_line.txt
similarity index 100%
rename from parser/v2/formattestdata/all_children_indented__with_nested_indentation__when_close_tag_is_on_same_line.txt
rename to internal/format/testdata/all_children_indented__with_nested_indentation__when_close_tag_is_on_same_line.txt
diff --git a/parser/v2/formattestdata/br_and_hr_all_on_one_line_are_not_placed_on_new_lines.txt b/internal/format/testdata/br_and_hr_all_on_one_line_are_not_placed_on_new_lines.txt
similarity index 100%
rename from parser/v2/formattestdata/br_and_hr_all_on_one_line_are_not_placed_on_new_lines.txt
rename to internal/format/testdata/br_and_hr_all_on_one_line_are_not_placed_on_new_lines.txt
diff --git a/parser/v2/formattestdata/br_elements_are_placed_on_new_lines.txt b/internal/format/testdata/br_elements_are_placed_on_new_lines.txt
similarity index 100%
rename from parser/v2/formattestdata/br_elements_are_placed_on_new_lines.txt
rename to internal/format/testdata/br_elements_are_placed_on_new_lines.txt
diff --git a/parser/v2/formattestdata/children_indented__closing_elm.txt b/internal/format/testdata/children_indented__closing_elm.txt
similarity index 100%
rename from parser/v2/formattestdata/children_indented__closing_elm.txt
rename to internal/format/testdata/children_indented__closing_elm.txt
diff --git a/parser/v2/formattestdata/children_indented__first_child.txt b/internal/format/testdata/children_indented__first_child.txt
similarity index 100%
rename from parser/v2/formattestdata/children_indented__first_child.txt
rename to internal/format/testdata/children_indented__first_child.txt
diff --git a/parser/v2/formattestdata/comments_are_preserved.txt b/internal/format/testdata/comments_are_preserved.txt
similarity index 100%
rename from parser/v2/formattestdata/comments_are_preserved.txt
rename to internal/format/testdata/comments_are_preserved.txt
diff --git a/parser/v2/formattestdata/conditional_expressions_have_the_same_child_indentation_rules_as_regular_elements.txt b/internal/format/testdata/conditional_expressions_have_the_same_child_indentation_rules_as_regular_elements.txt
similarity index 100%
rename from parser/v2/formattestdata/conditional_expressions_have_the_same_child_indentation_rules_as_regular_elements.txt
rename to internal/format/testdata/conditional_expressions_have_the_same_child_indentation_rules_as_regular_elements.txt
diff --git a/parser/v2/formattestdata/conditional_expressions_result_in_all_attrs_indented.txt b/internal/format/testdata/conditional_expressions_result_in_all_attrs_indented.txt
similarity index 100%
rename from parser/v2/formattestdata/conditional_expressions_result_in_all_attrs_indented.txt
rename to internal/format/testdata/conditional_expressions_result_in_all_attrs_indented.txt
diff --git a/parser/v2/formattestdata/conditional_expressions_result_in_all_attrs_indented__2.txt b/internal/format/testdata/conditional_expressions_result_in_all_attrs_indented__2.txt
similarity index 100%
rename from parser/v2/formattestdata/conditional_expressions_result_in_all_attrs_indented__2.txt
rename to internal/format/testdata/conditional_expressions_result_in_all_attrs_indented__2.txt
diff --git a/parser/v2/formattestdata/conditional_expressions_with_else_blocks_are_also_formatted.txt b/internal/format/testdata/conditional_expressions_with_else_blocks_are_also_formatted.txt
similarity index 100%
rename from parser/v2/formattestdata/conditional_expressions_with_else_blocks_are_also_formatted.txt
rename to internal/format/testdata/conditional_expressions_with_else_blocks_are_also_formatted.txt
diff --git a/parser/v2/formattestdata/constant_attributes_prerfer_double_quotes__but_use_single_quotes_if_required.txt b/internal/format/testdata/constant_attributes_prerfer_double_quotes__but_use_single_quotes_if_required.txt
similarity index 100%
rename from parser/v2/formattestdata/constant_attributes_prerfer_double_quotes__but_use_single_quotes_if_required.txt
rename to internal/format/testdata/constant_attributes_prerfer_double_quotes__but_use_single_quotes_if_required.txt
diff --git a/parser/v2/formattestdata/css_is_indented_by_one_level.txt b/internal/format/testdata/css_is_indented_by_one_level.txt
similarity index 100%
rename from parser/v2/formattestdata/css_is_indented_by_one_level.txt
rename to internal/format/testdata/css_is_indented_by_one_level.txt
diff --git a/parser/v2/formattestdata/css_whitespace_is_tidied.txt b/internal/format/testdata/css_whitespace_is_tidied.txt
similarity index 100%
rename from parser/v2/formattestdata/css_whitespace_is_tidied.txt
rename to internal/format/testdata/css_whitespace_is_tidied.txt
diff --git a/parser/v2/formattestdata/cssarguments_multiline.txt b/internal/format/testdata/cssarguments_multiline.txt
similarity index 100%
rename from parser/v2/formattestdata/cssarguments_multiline.txt
rename to internal/format/testdata/cssarguments_multiline.txt
diff --git a/parser/v2/formattestdata/empty_elements_stay_on_the_same_line.txt b/internal/format/testdata/empty_elements_stay_on_the_same_line.txt
similarity index 100%
rename from parser/v2/formattestdata/empty_elements_stay_on_the_same_line.txt
rename to internal/format/testdata/empty_elements_stay_on_the_same_line.txt
diff --git a/parser/v2/formattestdata/for_loops_are_placed_on_a_new_line.txt b/internal/format/testdata/for_loops_are_placed_on_a_new_line.txt
similarity index 100%
rename from parser/v2/formattestdata/for_loops_are_placed_on_a_new_line.txt
rename to internal/format/testdata/for_loops_are_placed_on_a_new_line.txt
diff --git a/parser/v2/formattestdata/formatting_does_not_alter_whitespace.txt b/internal/format/testdata/formatting_does_not_alter_whitespace.txt
similarity index 100%
rename from parser/v2/formattestdata/formatting_does_not_alter_whitespace.txt
rename to internal/format/testdata/formatting_does_not_alter_whitespace.txt
diff --git a/parser/v2/formattestdata/go_expressions_are_formatted_by_the_go_formatter.txt b/internal/format/testdata/go_expressions_are_formatted_by_the_go_formatter.txt
similarity index 100%
rename from parser/v2/formattestdata/go_expressions_are_formatted_by_the_go_formatter.txt
rename to internal/format/testdata/go_expressions_are_formatted_by_the_go_formatter.txt
diff --git a/parser/v2/formattestdata/godoc_comments_are_preserved.txt b/internal/format/testdata/godoc_comments_are_preserved.txt
similarity index 100%
rename from parser/v2/formattestdata/godoc_comments_are_preserved.txt
rename to internal/format/testdata/godoc_comments_are_preserved.txt
diff --git a/parser/v2/formattestdata/if_statements_are_placed_on_a_new_line.txt b/internal/format/testdata/if_statements_are_placed_on_a_new_line.txt
similarity index 100%
rename from parser/v2/formattestdata/if_statements_are_placed_on_a_new_line.txt
rename to internal/format/testdata/if_statements_are_placed_on_a_new_line.txt
diff --git a/parser/v2/formattestdata/inline_elements_are_not_placed_on_a_new_line.txt b/internal/format/testdata/inline_elements_are_not_placed_on_a_new_line.txt
similarity index 100%
rename from parser/v2/formattestdata/inline_elements_are_not_placed_on_a_new_line.txt
rename to internal/format/testdata/inline_elements_are_not_placed_on_a_new_line.txt
diff --git a/parser/v2/formattestdata/multiline_string_literal_indentation_preserved.txt b/internal/format/testdata/multiline_string_literal_indentation_preserved.txt
similarity index 100%
rename from parser/v2/formattestdata/multiline_string_literal_indentation_preserved.txt
rename to internal/format/testdata/multiline_string_literal_indentation_preserved.txt
diff --git a/parser/v2/formattestdata/non_empty_elements_with_children_that_are_all_on_the_same_line_are_not_split_into_multiple_lines.txt b/internal/format/testdata/non_empty_elements_with_children_that_are_all_on_the_same_line_are_not_split_into_multiple_lines.txt
similarity index 100%
rename from parser/v2/formattestdata/non_empty_elements_with_children_that_are_all_on_the_same_line_are_not_split_into_multiple_lines.txt
rename to internal/format/testdata/non_empty_elements_with_children_that_are_all_on_the_same_line_are_not_split_into_multiple_lines.txt
diff --git a/parser/v2/formattestdata/raw_go_is_formatted.txt b/internal/format/testdata/raw_go_is_formatted.txt
similarity index 100%
rename from parser/v2/formattestdata/raw_go_is_formatted.txt
rename to internal/format/testdata/raw_go_is_formatted.txt
diff --git a/parser/v2/formattestdata/script_tags_are_not_converted_to_self_closing_elements.txt b/internal/format/testdata/script_tags_are_not_converted_to_self_closing_elements.txt
similarity index 100%
rename from parser/v2/formattestdata/script_tags_are_not_converted_to_self_closing_elements.txt
rename to internal/format/testdata/script_tags_are_not_converted_to_self_closing_elements.txt
diff --git a/parser/v2/formattestdata/scriptarguments_multiline.txt b/internal/format/testdata/scriptarguments_multiline.txt
similarity index 100%
rename from parser/v2/formattestdata/scriptarguments_multiline.txt
rename to internal/format/testdata/scriptarguments_multiline.txt
diff --git a/internal/format/testdata/scriptelement_contents_are_formatted.txt b/internal/format/testdata/scriptelement_contents_are_formatted.txt
new file mode 100644
index 0000000..88bf10c
--- /dev/null
+++ b/internal/format/testdata/scriptelement_contents_are_formatted.txt
@@ -0,0 +1,21 @@
+-- in --
+package test
+
+templ script(data string) {
+	<script>
+  function log(  something)     {
+	  console.log(something   );
+		}
+	</script>
+}
+
+-- out --
+package test
+
+templ script(data string) {
+	<script>
+		function log(something) {
+			console.log(something);
+		}
+	</script>
+}
diff --git a/internal/format/testdata/scriptelements_hyperscript_is_ignored.txt b/internal/format/testdata/scriptelements_hyperscript_is_ignored.txt
new file mode 100644
index 0000000..54d05cd
--- /dev/null
+++ b/internal/format/testdata/scriptelements_hyperscript_is_ignored.txt
@@ -0,0 +1,29 @@
+-- in --
+package test
+
+templ script(data string) {
+	<script type="text/hyperscript">
+  on mousedown
+    halt the event -- prevent text selection...
+    -- do other stuff...
+  end
+	</script>
+	<script>
+	  var x = "abc";
+	</script>
+}
+
+-- out --
+package test
+
+templ script(data string) {
+	<script type="text/hyperscript">
+		on mousedown
+		  halt the event -- prevent text selection...
+		  -- do other stuff...
+		end
+	</script>
+	<script>
+		var x = "abc";
+	</script>
+}
diff --git a/internal/format/testdata/scriptelements_with_go_code_are_formatted.txt b/internal/format/testdata/scriptelements_with_go_code_are_formatted.txt
new file mode 100644
index 0000000..5e10c40
--- /dev/null
+++ b/internal/format/testdata/scriptelements_with_go_code_are_formatted.txt
@@ -0,0 +1,21 @@
+-- in --
+package test
+
+templ script(data string) {
+	<script>
+  function log(  {{ data }})     {
+	  console.log({{data}}   );
+		}
+	</script>
+}
+
+-- out --
+package test
+
+templ script(data string) {
+	<script>
+		function log({{ data }}) {
+			console.log({{ data }});
+		}
+	</script>
+}
diff --git a/internal/format/testdata/scriptelements_with_multiple_go_code_sections_are_formatted.txt b/internal/format/testdata/scriptelements_with_multiple_go_code_sections_are_formatted.txt
new file mode 100644
index 0000000..059326a
--- /dev/null
+++ b/internal/format/testdata/scriptelements_with_multiple_go_code_sections_are_formatted.txt
@@ -0,0 +1,23 @@
+-- in --
+package test
+
+templ script(data string) {
+	<script>
+		var a = {{ data }}
+		var b = "{{ data }}"
+		var c = '{{ data }}'
+		var d = `{{ data }}`
+	</script>
+}
+
+-- out --
+package test
+
+templ script(data string) {
+	<script>
+		var a = {{ data }}
+		var b = "{{ data }}"
+		var c = '{{ data }}'
+		var d = `{{ data }}`
+	</script>
+}
diff --git a/internal/format/testdata/scriptelements_within_templ_expressions_are_formatted.txt b/internal/format/testdata/scriptelements_within_templ_expressions_are_formatted.txt
new file mode 100644
index 0000000..1c1d713
--- /dev/null
+++ b/internal/format/testdata/scriptelements_within_templ_expressions_are_formatted.txt
@@ -0,0 +1,27 @@
+-- in --
+package test
+
+templ script(c templ.Component) {
+	@c() {
+		<script>
+			var a = {{ data }}
+			var b = "{{ data }}"
+			var c = '{{ data }}'
+			var d = `{{ data }}`
+		</script>
+	}
+}
+
+-- out --
+package test
+
+templ script(c templ.Component) {
+	@c() {
+		<script>
+			var a = {{ data }}
+			var b = "{{ data }}"
+			var c = '{{ data }}'
+			var d = `{{ data }}`
+		</script>
+	}
+}
diff --git a/parser/v2/formattestdata/spacing_between_string_expressions_is_kept.txt b/internal/format/testdata/spacing_between_string_expressions_is_kept.txt
similarity index 100%
rename from parser/v2/formattestdata/spacing_between_string_expressions_is_kept.txt
rename to internal/format/testdata/spacing_between_string_expressions_is_kept.txt
diff --git a/parser/v2/formattestdata/spacing_between_string_expressions_is_not_magically_added.txt b/internal/format/testdata/spacing_between_string_expressions_is_not_magically_added.txt
similarity index 100%
rename from parser/v2/formattestdata/spacing_between_string_expressions_is_not_magically_added.txt
rename to internal/format/testdata/spacing_between_string_expressions_is_not_magically_added.txt
diff --git a/parser/v2/formattestdata/spacing_between_string_spreads_attributes_is_kept.txt b/internal/format/testdata/spacing_between_string_spreads_attributes_is_kept.txt
similarity index 100%
rename from parser/v2/formattestdata/spacing_between_string_spreads_attributes_is_kept.txt
rename to internal/format/testdata/spacing_between_string_spreads_attributes_is_kept.txt
diff --git a/internal/format/testdata/styleelements_are_formatted.txt b/internal/format/testdata/styleelements_are_formatted.txt
new file mode 100644
index 0000000..8e26d86
--- /dev/null
+++ b/internal/format/testdata/styleelements_are_formatted.txt
@@ -0,0 +1,27 @@
+-- in --
+package test
+
+templ style(data string) {
+	<style>
+   body   {
+  margin: 0;
+   font-family: system-ui, sans-serif;
+		background-color: #f4f4f4;
+  color: #333;
+}
+	</style>
+}
+
+-- out --
+package test
+
+templ style(data string) {
+	<style>
+		body {
+			margin: 0;
+			font-family: system-ui, sans-serif;
+			background-color: #f4f4f4;
+			color: #333;
+		}
+	</style>
+}
diff --git a/parser/v2/formattestdata/switch_statements_are_placed_on_a_new_line.txt b/internal/format/testdata/switch_statements_are_placed_on_a_new_line.txt
similarity index 100%
rename from parser/v2/formattestdata/switch_statements_are_placed_on_a_new_line.txt
rename to internal/format/testdata/switch_statements_are_placed_on_a_new_line.txt
diff --git a/parser/v2/formattestdata/tables_are_formatted_well.txt b/internal/format/testdata/tables_are_formatted_well.txt
similarity index 100%
rename from parser/v2/formattestdata/tables_are_formatted_well.txt
rename to internal/format/testdata/tables_are_formatted_well.txt
diff --git a/parser/v2/formattestdata/templ_expression_attributes_are_formatted_correctly_when_multiline.txt b/internal/format/testdata/templ_expression_attributes_are_formatted_correctly_when_multiline.txt
similarity index 100%
rename from parser/v2/formattestdata/templ_expression_attributes_are_formatted_correctly_when_multiline.txt
rename to internal/format/testdata/templ_expression_attributes_are_formatted_correctly_when_multiline.txt
diff --git a/parser/v2/formattestdata/templ_expression_elements_are_formatted_the_same_as_other_elements.txt b/internal/format/testdata/templ_expression_elements_are_formatted_the_same_as_other_elements.txt
similarity index 100%
rename from parser/v2/formattestdata/templ_expression_elements_are_formatted_the_same_as_other_elements.txt
rename to internal/format/testdata/templ_expression_elements_are_formatted_the_same_as_other_elements.txt
diff --git a/parser/v2/formattestdata/templatearguments_multiline_with_generics.txt b/internal/format/testdata/templatearguments_multiline_with_generics.txt
similarity index 100%
rename from parser/v2/formattestdata/templatearguments_multiline_with_generics.txt
rename to internal/format/testdata/templatearguments_multiline_with_generics.txt
diff --git a/parser/v2/formattestdata/templatefile_can_be_round_tripped.txt b/internal/format/testdata/templatefile_can_be_round_tripped.txt
similarity index 100%
rename from parser/v2/formattestdata/templatefile_can_be_round_tripped.txt
rename to internal/format/testdata/templatefile_can_be_round_tripped.txt
diff --git a/parser/v2/formattestdata/templatefile_can_start_with_comments.txt b/internal/format/testdata/templatefile_can_start_with_comments.txt
similarity index 100%
rename from parser/v2/formattestdata/templatefile_can_start_with_comments.txt
rename to internal/format/testdata/templatefile_can_start_with_comments.txt
diff --git a/parser/v2/formattestdata/templatefile_can_start_with_comments_and_whitespace.txt b/internal/format/testdata/templatefile_can_start_with_comments_and_whitespace.txt
similarity index 100%
rename from parser/v2/formattestdata/templatefile_can_start_with_comments_and_whitespace.txt
rename to internal/format/testdata/templatefile_can_start_with_comments_and_whitespace.txt
diff --git a/parser/v2/formattestdata/templatefile_can_start_with_multiline_comments_and_whitespace.txt b/internal/format/testdata/templatefile_can_start_with_multiline_comments_and_whitespace.txt
similarity index 100%
rename from parser/v2/formattestdata/templatefile_can_start_with_multiline_comments_and_whitespace.txt
rename to internal/format/testdata/templatefile_can_start_with_multiline_comments_and_whitespace.txt
diff --git a/parser/v2/formattestdata/templatefile_can_start_with_multiple_comments_and_whitespace.txt b/internal/format/testdata/templatefile_can_start_with_multiple_comments_and_whitespace.txt
similarity index 100%
rename from parser/v2/formattestdata/templatefile_can_start_with_multiple_comments_and_whitespace.txt
rename to internal/format/testdata/templatefile_can_start_with_multiple_comments_and_whitespace.txt
diff --git a/parser/v2/formattestdata/templateheader_with_build_tags.txt b/internal/format/testdata/templateheader_with_build_tags.txt
similarity index 100%
rename from parser/v2/formattestdata/templateheader_with_build_tags.txt
rename to internal/format/testdata/templateheader_with_build_tags.txt
diff --git a/parser/v2/formattestdata/templelement_multiline_block_containing_multiline_block.txt b/internal/format/testdata/templelement_multiline_block_containing_multiline_block.txt
similarity index 100%
rename from parser/v2/formattestdata/templelement_multiline_block_containing_multiline_block.txt
rename to internal/format/testdata/templelement_multiline_block_containing_multiline_block.txt
diff --git a/parser/v2/formattestdata/templelement_multiline_block_indentation.txt b/internal/format/testdata/templelement_multiline_block_indentation.txt
similarity index 100%
rename from parser/v2/formattestdata/templelement_multiline_block_indentation.txt
rename to internal/format/testdata/templelement_multiline_block_indentation.txt
diff --git a/parser/v2/formattestdata/templelement_multiline_in_div.txt b/internal/format/testdata/templelement_multiline_in_div.txt
similarity index 100%
rename from parser/v2/formattestdata/templelement_multiline_in_div.txt
rename to internal/format/testdata/templelement_multiline_in_div.txt
diff --git a/parser/v2/formattestdata/templelement_param_spacing.txt b/internal/format/testdata/templelement_param_spacing.txt
similarity index 100%
rename from parser/v2/formattestdata/templelement_param_spacing.txt
rename to internal/format/testdata/templelement_param_spacing.txt
diff --git a/parser/v2/formattestdata/templelement_simple_block_indentation.txt b/internal/format/testdata/templelement_simple_block_indentation.txt
similarity index 100%
rename from parser/v2/formattestdata/templelement_simple_block_indentation.txt
rename to internal/format/testdata/templelement_simple_block_indentation.txt
diff --git a/parser/v2/formattestdata/templelement_simple_in_div.txt b/internal/format/testdata/templelement_simple_in_div.txt
similarity index 100%
rename from parser/v2/formattestdata/templelement_simple_in_div.txt
rename to internal/format/testdata/templelement_simple_in_div.txt
diff --git a/parser/v2/formattestdata/templelement_simple_no_change.txt b/internal/format/testdata/templelement_simple_no_change.txt
similarity index 100%
rename from parser/v2/formattestdata/templelement_simple_no_change.txt
rename to internal/format/testdata/templelement_simple_no_change.txt
diff --git a/parser/v2/formattestdata/void_elements_are_converted_to_self_closing_elements.txt b/internal/format/testdata/void_elements_are_converted_to_self_closing_elements.txt
similarity index 100%
rename from parser/v2/formattestdata/void_elements_are_converted_to_self_closing_elements.txt
rename to internal/format/testdata/void_elements_are_converted_to_self_closing_elements.txt
diff --git a/parser/v2/formattestdata/when_an_element_contains_children_that_are_on_new_lines__the_children_are_indented.txt b/internal/format/testdata/when_an_element_contains_children_that_are_on_new_lines__the_children_are_indented.txt
similarity index 100%
rename from parser/v2/formattestdata/when_an_element_contains_children_that_are_on_new_lines__the_children_are_indented.txt
rename to internal/format/testdata/when_an_element_contains_children_that_are_on_new_lines__the_children_are_indented.txt
diff --git a/internal/htmlfind/htmlfind_test.go b/internal/htmlfind/htmlfind_test.go
index 12c40f1..d23b67e 100644
--- a/internal/htmlfind/htmlfind_test.go
+++ b/internal/htmlfind/htmlfind_test.go
@@ -88,6 +88,7 @@ func TestFind(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
 			r := strings.NewReader(tt.input)
 			results, err := htmlfind.AllReader(r, tt.find)
 			if err != nil {
diff --git a/cmd/templ/imports/process_test.go b/internal/imports/process_test.go
similarity index 74%
rename from cmd/templ/imports/process_test.go
rename to internal/imports/process_test.go
index 33deb63..05f6b5e 100644
--- a/cmd/templ/imports/process_test.go
+++ b/internal/imports/process_test.go
@@ -117,42 +117,45 @@ templ Page(count int) {
 	}
 
 	for _, test := range tests {
-		// Create test project.
-		dir, err := testproject.Create("github.com/a-h/templ/cmd/templ/testproject")
-		if err != nil {
-			t.Fatalf("failed to create test project: %v", err)
-		}
-		defer func() {
-			if err = os.RemoveAll(dir); err != nil {
-				t.Errorf("failed to remove test project directory: %v", err)
+		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
+			// Create test project.
+			dir, err := testproject.Create("github.com/a-h/templ/cmd/templ/testproject")
+			if err != nil {
+				t.Fatalf("failed to create test project: %v", err)
+			}
+			defer func() {
+				if err = os.RemoveAll(dir); err != nil {
+					t.Errorf("failed to remove test project directory: %v", err)
+				}
+			}()
+
+			// Load the templates.templ file.
+			filePath := path.Join(dir, "templates.templ")
+			err = os.WriteFile(filePath, []byte(test.src), 0660)
+			if err != nil {
+				t.Fatalf("failed to write file: %v", err)
+			}
+
+			// Parse the new file.
+			template, err := parser.Parse(filePath)
+			if err != nil {
+				t.Fatalf("failed to parse %v", err)
 			}
-		}()
-
-		// Load the templates.templ file.
-		filePath := path.Join(dir, "templates.templ")
-		err = os.WriteFile(filePath, []byte(test.src), 0660)
-		if err != nil {
-			t.Fatalf("failed to write file: %v", err)
-		}
-
-		// Parse the new file.
-		template, err := parser.Parse(filePath)
-		if err != nil {
-			t.Fatalf("failed to parse %v", err)
-		}
-		template.Filepath = filePath
-		tf, err := Process(template)
-		if err != nil {
-			t.Fatalf("failed to process file: %v", err)
-		}
-
-		// Write it back out after processing.
-		buf := new(strings.Builder)
-		if err := tf.Write(buf); err != nil {
-			t.Fatalf("failed to write template file: %v", err)
-		}
-
-		// Assert.
-		test.assertions(t, buf.String())
+			template.Filepath = filePath
+			tf, err := Process(template)
+			if err != nil {
+				t.Fatalf("failed to process file: %v", err)
+			}
+
+			// Write it back out after processing.
+			buf := new(strings.Builder)
+			if err := tf.Write(buf); err != nil {
+				t.Fatalf("failed to write template file: %v", err)
+			}
+
+			// Assert.
+			test.assertions(t, buf.String())
+		})
 	}
 }
diff --git a/cmd/templ/imports/testdata/comments.txtar b/internal/imports/testdata/comments.txtar
similarity index 100%
rename from cmd/templ/imports/testdata/comments.txtar
rename to internal/imports/testdata/comments.txtar
diff --git a/cmd/templ/imports/testdata/commentsbeforepackage.txtar b/internal/imports/testdata/commentsbeforepackage.txtar
similarity index 100%
rename from cmd/templ/imports/testdata/commentsbeforepackage.txtar
rename to internal/imports/testdata/commentsbeforepackage.txtar
diff --git a/cmd/templ/imports/testdata/deleteimports.txtar b/internal/imports/testdata/deleteimports.txtar
similarity index 100%
rename from cmd/templ/imports/testdata/deleteimports.txtar
rename to internal/imports/testdata/deleteimports.txtar
diff --git a/cmd/templ/imports/testdata/extraspace.txtar b/internal/imports/testdata/extraspace.txtar
similarity index 100%
rename from cmd/templ/imports/testdata/extraspace.txtar
rename to internal/imports/testdata/extraspace.txtar
diff --git a/cmd/templ/imports/testdata/groups.txtar b/internal/imports/testdata/groups.txtar
similarity index 100%
rename from cmd/templ/imports/testdata/groups.txtar
rename to internal/imports/testdata/groups.txtar
diff --git a/cmd/templ/imports/testdata/groupsmanynewlines.txtar b/internal/imports/testdata/groupsmanynewlines.txtar
similarity index 100%
rename from cmd/templ/imports/testdata/groupsmanynewlines.txtar
rename to internal/imports/testdata/groupsmanynewlines.txtar
diff --git a/cmd/templ/imports/testdata/header.txtar b/internal/imports/testdata/header.txtar
similarity index 100%
rename from cmd/templ/imports/testdata/header.txtar
rename to internal/imports/testdata/header.txtar
diff --git a/cmd/templ/imports/testdata/namedimportsadd.txtar b/internal/imports/testdata/namedimportsadd.txtar
similarity index 100%
rename from cmd/templ/imports/testdata/namedimportsadd.txtar
rename to internal/imports/testdata/namedimportsadd.txtar
diff --git a/cmd/templ/imports/testdata/namedimportsremoved.txtar b/internal/imports/testdata/namedimportsremoved.txtar
similarity index 100%
rename from cmd/templ/imports/testdata/namedimportsremoved.txtar
rename to internal/imports/testdata/namedimportsremoved.txtar
diff --git a/cmd/templ/imports/testdata/noimports.txtar b/internal/imports/testdata/noimports.txtar
similarity index 100%
rename from cmd/templ/imports/testdata/noimports.txtar
rename to internal/imports/testdata/noimports.txtar
diff --git a/cmd/templ/imports/testdata/noimportscode.txtar b/internal/imports/testdata/noimportscode.txtar
similarity index 100%
rename from cmd/templ/imports/testdata/noimportscode.txtar
rename to internal/imports/testdata/noimportscode.txtar
diff --git a/cmd/templ/imports/testdata/stringexp.txtar b/internal/imports/testdata/stringexp.txtar
similarity index 100%
rename from cmd/templ/imports/testdata/stringexp.txtar
rename to internal/imports/testdata/stringexp.txtar
diff --git a/cmd/templ/imports/testdata/twoimports.txtar b/internal/imports/testdata/twoimports.txtar
similarity index 100%
rename from cmd/templ/imports/testdata/twoimports.txtar
rename to internal/imports/testdata/twoimports.txtar
diff --git a/internal/lazyloader/docheaderparser_test.go b/internal/lazyloader/docheaderparser_test.go
index 0f0a255..2b72e49 100644
--- a/internal/lazyloader/docheaderparser_test.go
+++ b/internal/lazyloader/docheaderparser_test.go
@@ -88,6 +88,7 @@ func TestGoDocHeaderParserParse(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
 			got := tt.parser.parse(tt.filename)
 			assert.IsType(t, &goDocHeader{}, got)
 			h := got.(*goDocHeader)
diff --git a/internal/lazyloader/pkgloader_test.go b/internal/lazyloader/pkgloader_test.go
index 7d789e1..5bedd67 100644
--- a/internal/lazyloader/pkgloader_test.go
+++ b/internal/lazyloader/pkgloader_test.go
@@ -81,6 +81,7 @@ func TestGoPkgLoaderLoad(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
 			gotPkg, err := tt.loader.load(tt.filename)
 
 			if tt.wantErrContains != "" {
diff --git a/internal/lazyloader/pkgtraverser_test.go b/internal/lazyloader/pkgtraverser_test.go
index 69e33de..c0304fa 100644
--- a/internal/lazyloader/pkgtraverser_test.go
+++ b/internal/lazyloader/pkgtraverser_test.go
@@ -660,6 +660,7 @@ func TestTemplPkgTraverserCloseTopologically(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
 			err := tt.traverser.closeTopologically(context.Background(), tt.pkg)
 
 			if tt.wantErrContains != "" {
diff --git a/internal/lazyloader/templdoclazyloader_test.go b/internal/lazyloader/templdoclazyloader_test.go
index b78d510..a4fd678 100644
--- a/internal/lazyloader/templdoclazyloader_test.go
+++ b/internal/lazyloader/templdoclazyloader_test.go
@@ -544,6 +544,7 @@ func TestTemplDocLazyLoaderSync(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
 			err := tt.loader.Sync(context.Background(), tt.params)
 
 			if tt.wantErrContains != "" {
@@ -666,6 +667,7 @@ func TestTemplDocLazyLoaderUnload(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
 			err := tt.loader.Unload(context.Background(), tt.params)
 
 			if tt.wantErrContains != "" {
@@ -724,6 +726,7 @@ func TestTemplDocLazyLoaderHasLoaded(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
 			assert.Equal(t, tt.expected, tt.loader.HasLoaded(tt.doc))
 		})
 	}
diff --git a/internal/prettier/prettier_test.go b/internal/prettier/prettier_test.go
new file mode 100644
index 0000000..f45aa3d
--- /dev/null
+++ b/internal/prettier/prettier_test.go
@@ -0,0 +1,33 @@
+package prettier
+
+import (
+	"strings"
+	"testing"
+
+	"golang.org/x/tools/txtar"
+)
+
+func Test(t *testing.T) {
+	archive, err := txtar.ParseFile("testdata.txtar")
+	if err != nil {
+		t.Fatalf("failed to read testdata.txtar: %v", err)
+	}
+	for i := 0; i < len(archive.Files)-1; i += 2 {
+		if archive.Files[i].Name != archive.Files[i+1].Name {
+			t.Fatalf("test archive is not in the expected format: file pair at index %d do not match: %q vs %q", i, archive.Files[i].Name, archive.Files[i+1].Name)
+		}
+		t.Run(archive.Files[i].Name, func(t *testing.T) {
+			inputData := archive.Files[i].Data
+			expectedData := archive.Files[i+1].Data
+			input := strings.TrimSpace(string(inputData))
+			expected := strings.TrimSpace(string(expectedData))
+			actual, err := Run(input, archive.Files[i].Name, DefaultCommand)
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+			if strings.TrimSpace(actual) != expected {
+				t.Errorf("Actual:\n%s\nExpected:\n%s", actual, expected)
+			}
+		})
+	}
+}
diff --git a/internal/skipdir/skipdir_test.go b/internal/skipdir/skipdir_test.go
index 5235f0a..f6a2ff7 100644
--- a/internal/skipdir/skipdir_test.go
+++ b/internal/skipdir/skipdir_test.go
@@ -51,6 +51,7 @@ func TestSkipDir(t *testing.T) {
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			actual := ShouldSkip(test.dir)
 			if test.expected != actual {
 				t.Errorf("expected %v, got %v", test.expected, actual)
diff --git a/internal/syncmap/map_test.go b/internal/syncmap/map_test.go
index e96999b..678d6e1 100644
--- a/internal/syncmap/map_test.go
+++ b/internal/syncmap/map_test.go
@@ -3,6 +3,7 @@ package syncmap
 import "testing"
 
 func TestMap(t *testing.T) {
+	t.Parallel()
 	t.Run("Can Set and Get values", func(t *testing.T) {
 		m := New[string, int]()
 		m.Set("key1", 42)
diff --git a/internal/syncset/set_test.go b/internal/syncset/set_test.go
index ee0a222..49263b8 100644
--- a/internal/syncset/set_test.go
+++ b/internal/syncset/set_test.go
@@ -3,6 +3,7 @@ package syncset
 import "testing"
 
 func TestSet(t *testing.T) {
+	t.Parallel()
 	t.Run("Get", func(t *testing.T) {
 		t.Run("Returns false for non-existent items", func(t *testing.T) {
 			s := New[string]()
diff --git a/parser/v2/format_test.go b/parser/v2/format_test.go
deleted file mode 100644
index 3c6ee1e..0000000
--- a/parser/v2/format_test.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"path/filepath"
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-	"golang.org/x/tools/txtar"
-)
-
-func TestFormatting(t *testing.T) {
-	files, _ := filepath.Glob("formattestdata/*.txt")
-	if len(files) == 0 {
-		t.Errorf("no test files found")
-	}
-	for _, file := range files {
-		t.Run(filepath.Base(file), func(t *testing.T) {
-			a, err := txtar.ParseFile(file)
-			if err != nil {
-				t.Fatal(err)
-			}
-			if len(a.Files) != 2 {
-				t.Fatalf("expected 2 files, got %d", len(a.Files))
-			}
-			tem, err := ParseString(clean(a.Files[0].Data))
-			if err != nil {
-				t.Fatal(err)
-			}
-			var actual bytes.Buffer
-			if err := tem.Write(&actual); err != nil {
-				t.Fatalf("unexpected error: %v", err)
-			}
-			if diff := cmp.Diff(string(a.Files[1].Data), actual.String()); diff != "" {
-				t.Fatalf("%s:\n%s", file, diff)
-			}
-		})
-	}
-}
-
-func clean(b []byte) string {
-	b = bytes.ReplaceAll(b, []byte("$\n"), []byte("\n"))
-	b = bytes.TrimSuffix(b, []byte("\n"))
-	return string(b)
-}
diff --git a/parser/v2/raw_test.go b/parser/v2/raw_test.go
index 6e59312..a928fee 100644
--- a/parser/v2/raw_test.go
+++ b/parser/v2/raw_test.go
@@ -68,7 +68,6 @@ func TestRawElementParser(t *testing.T) {
 		},
 	}
 	for _, tt := range tests {
-		tt := tt
 		t.Run(tt.name, func(t *testing.T) {
 			input := parse.NewInput(tt.input)
 			actual, ok, err := rawElements.Parse(input)
diff --git a/parser/v2/scriptparser_test.go b/parser/v2/scriptparser_test.go
index 29b269e..14aa841 100644
--- a/parser/v2/scriptparser_test.go
+++ b/parser/v2/scriptparser_test.go
@@ -1,6 +1,7 @@
 package parser
 
 import (
+	"bytes"
 	"path/filepath"
 	"strings"
 	"testing"
@@ -421,3 +422,9 @@ func FuzzScriptParser(f *testing.F) {
 		_, _, _ = scriptElement.Parse(parse.NewInput(input))
 	})
 }
+
+func clean(b []byte) string {
+	b = bytes.ReplaceAll(b, []byte("$\n"), []byte("\n"))
+	b = bytes.TrimSuffix(b, []byte("\n"))
+	return string(b)
+}
diff --git a/parser/v2/visitor/visitor_test.go b/parser/v2/visitor/visitor_test.go
index f165296..e5227e4 100644
--- a/parser/v2/visitor/visitor_test.go
+++ b/parser/v2/visitor/visitor_test.go
@@ -14,7 +14,7 @@ func assetRewriter(rewrite func(path string) string) parser.Visitor {
 	inSrcElement := false
 	inHrefElement := false
 
-	// Visit elements
+	// Save the original Element visitor to allow chaining.
 	visitElement := ar.Element
 	ar.Element = func(e *parser.Element) error {
 		switch e.Name {
@@ -23,7 +23,7 @@ func assetRewriter(rewrite func(path string) string) parser.Visitor {
 		case "img":
 			inSrcElement = true
 		}
-		// Visit children
+		// Visit child elements.
 		if err := visitElement(e); err != nil {
 			return err
 		}
@@ -32,11 +32,11 @@ func assetRewriter(rewrite func(path string) string) parser.Visitor {
 		return nil
 	}
 
-	// Script elements
+	// Save the original ScriptElement visitor to allow chaining.
 	visitScriptElement := ar.ScriptElement
 	ar.ScriptElement = func(e *parser.ScriptElement) error {
 		inSrcElement = true
-		// Visit children
+		// Visit child script elements.
 		if err := visitScriptElement(e); err != nil {
 			return err
 		}
@@ -44,7 +44,7 @@ func assetRewriter(rewrite func(path string) string) parser.Visitor {
 		return nil
 	}
 
-	// Rewrite attributes
+	// Save the original ConstantAttribute visitor to allow chaining.
 	visitConstantAttribute := ar.ConstantAttribute
 	ar.ConstantAttribute = func(n *parser.ConstantAttribute) error {
 		if inSrcElement && n.Key.String() == "src" {