Skip to content

Commit

Permalink
que
Browse files Browse the repository at this point in the history
  • Loading branch information
McNerdius committed Mar 30, 2024
1 parent 1c9f1cf commit 719b73a
Show file tree
Hide file tree
Showing 17 changed files with 6 additions and 1,100 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"editor.formatOnPaste": true,
"editor.formatOnSave": true,
"editor.autoIndent": "advanced",
"editor.wordBasedSuggestions": false,
"editor.wordBasedSuggestions": "off",
"editor.semanticHighlighting.enabled": true,
},
"omnisharp.enableEditorConfigSupport": true,
Expand Down
7 changes: 0 additions & 7 deletions LICENSE

This file was deleted.

185 changes: 1 addition & 184 deletions content/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,188 +16,5 @@

:::: content

# Tailwind Incremental Builds, and maybe Hot Reload {#intro}
# Tailwind Incremental Builds, and maybe

So far we've put four more-or-less boilerplate files on disk and installed a single package from `npm` to add Tailwind CSS to the `blazorwasm` template. Not too shabby. Getting build & watch set up is pretty easy too, but unfortunately Hot Reload support is inconsistent between .NET project types.

On the Tailwind side of things, nothing fancy is happening - just a fresh CSS file being output to `wwwroot` as needed. For some project types, Hot Reload doesn't refresh the browser when it sees these changes (yet) - hopefully a fix for this seemingly-trivial issue will come soon. This [GitHub Issue](/~https://github.com/dotnet/aspnetcore/issues/37496){target="_blank"} shows a script that reloads the CSS file on a timer. I think it'd be interesting to make that into a Component - definitely on the todo list.

Ideally Hot Reload would ensure you're seeing latest version of both your Components and CSS. For some project types this is the case, but some are... less Hot Reloady than others. Having to do a full rebuild or browser refresh to see updates for some project types is unfortunate, but also outside the scope of integrating Tailwind CSS into those projects. Hopefully Hot Reload improves in that regard.

## Define `npm` helper scripts {#helpers}

In the default `package.json` you'll see the following:

```json:package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
```

Swap the `"test"` line for the following:

```json:package.json
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
+ "build": "npx tailwindcss --config tailwind.config.js --postcss postcss.config.js -i site.css -o ./wwwroot/site.min.css",
+ "watch": "npx tailwindcss --config tailwind.config.js --postcss postcss.config.js -i site.css -o ./wwwroot/site.min.css --watch",
+ "publish": "npx tailwindcss --config tailwind.config.js --postcss postcss.config.js -i site.css -o ./wwwroot/site.min.css --minify"
}
```

_(Yes, that is `npx` not `npm`)_

Connecting the dots here: point the `tailwindcss` CLI at the relevant config, input, and output files. `npm run build` will do a one-off build, `npm run watch` is what gives us the quick incremental builds akin to Hot Reload, and `npm run publish` will do a one-off build, plus `cssnano` minification.

::: info
Finally ! Having created `site.css` and done the initial configuration, running `npm run build` will run `tailwindcss`, using `site.css` and markup specified in `tailwind.config.js`'s `content` section to generate a vanilla `site.min.css`.
:::

---

## Automate `npm` with MSBuild Targets {#targets}

Next, let's set it up so that `dotnet` CLI or your IDE can care of some of the `npm` stuff for us.

To keep the `*.csproj` clean, i use a special MSBuild "Targets" file, cleverly named `tailwindcss.targets`. Here's an example file:

```xml:tailwindcss.targets
<Project>
<PropertyGroup>
<TailwindBuild>true</TailwindBuild>
</PropertyGroup>
<Target Name="NpmInstallCheck" BeforeTargets="TailwindCSS" Inputs="./package.json" Outputs="./node_modules/.package-lock.json">
<Message Text="NpmInstallCheck Starting..." Importance="high"></Message>
<Exec Command="npm -v" ContinueOnError="true" StandardOutputImportance="low">
<Output TaskParameter="ExitCode" PropertyName="error" />
</Exec>
<Error Condition="'$(error)' != '0'" Text="install node.js please !" />
<Exec Command="npm install" />
<Message Text="NpmInstallCheck Finished !" Importance="high"></Message>
</Target>
<Target Name="TailwindCSS" AfterTargets="AfterBuild" Condition="'$(TailwindBuild)' == 'true'">
<Message Text="TailwindCSS Starting..." Importance="high"></Message>
<Exec Command="npm run build" Condition="'$(Configuration)' == 'Debug'"/>
<Exec Command="npm run publish" Condition="'$(Configuration)' == 'Release'"/>
<Message Text="TailwindCSS Finished !" Importance="high"></Message>
</Target>
</Project>
```

To actually make use of this in your Blazor project, add it to your `*.csproj`, top-level:

```xml:site.csproj
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
+ <Import Project="tailwindcss.targets" />
</Project>
```

---

### The TailwindCSS Target { #build }

This is the essential section:

```xml:tailwindcss.targets-p2
<Target Name="TailwindCSS" AfterTargets="AfterBuild" Condition="'$(TailwindBuild)' == 'true'">
<Message Text="TailwindCSS Starting..." Importance="high"></Message>
<Exec Command="npm run build" Condition="'$(Configuration)' == 'Debug'"/>
<Exec Command="npm run publish" Condition="'$(Configuration)' == 'Release'"/>
<Message Text="TailwindCSS Finished !" Importance="high"></Message>
</Target>
```

`AfterBuild` is a standard [MSBuild Target](https://docs.microsoft.com/en-us/visualstudio/msbuild/target-element-msbuild){target="_blank"}. This section defines our own `TailwindCSS` Target which runs after `AfterBuild`. Building the Blazor project in `Debug` mode does a one-off `tailwindcss` build, and `Release` mode results in a `cssnano`-minified build, thanks to their respective `build` and `publish` scripts found in `package.json`.


### The NpmInstallCheck Target { #install }

The next (kinda optional) snippet actually runs in-between `AfterBuild` and `TailwindCSS` from above, doing a sanity check on `npm` stuff:

```xml:tailwindcss.targets-p3
<Target Name="NpmInstallCheck" BeforeTargets="TailwindCSS" Inputs="./package.json" Outputs="./node_modules/.package-lock.json">
<Message Text="NpmInstallCheck Starting..." Importance="high"></Message>
<Exec Command="npm --version" ContinueOnError="true" StandardOutputImportance="low">
<Output TaskParameter="ExitCode" PropertyName="error" />
</Exec>
<Error Condition="'$(error)' != '0'" Text="install node.js please ! https://nodejs.org/" />
<Exec Command="npm install" />
<Message Text="NpmInstallCheck Finished !" Importance="high"></Message>
</Target>
```

The `npm --version` command is there to verify Node.js/npm is installed: if it's not, the command will fail and you'll get a build error prompting to install node.js. Otherwise `npm install` runs. (When used without parameters `npm install` is akin to a `dotnet restore`, installing/updating dependencies listed in `package.json`, if needed.)

The `Inputs` & `Outputs` are for MSBuild [incremental build](https://docs.microsoft.com/en-us/visualstudio/msbuild/how-to-build-incrementally?view=vs-2022){target="_blank"} magic, preventing this from running **every. single. build.** If `Inputs` (`package.json`) is newer than `Outputs` (`.package-lock.json`), or `Outputs` doesn't yet exist, the Target is run, otherwise it will be skipped.

See [here](https://stackoverflow.com/questions/35435041/run-npm-install-only-when-needed-and-or-partially?answertab=active#tab-top){target="_blank"} for the StackOverflow-sauce, and [here](/~https://github.com/McNerdius/TailBlazor/discussions/107){target="_blank"} for some notes on the changes made to the StackOverflow version.

Assuming no errors, things will continue with the `TailwindCSS` target.

### Opting out { #optout }

The top section just defines an MSBuild property we can use like so: `dotnet build -p:TailwindBuild=false` to let us opt out of running the Tailwind build. Note its use in the next section.

```xml:tailwindcss.targets-p1
<PropertyGroup>
<TailwindBuild>true</TailwindBuild>
</PropertyGroup>
```

---

## Using `dotnet watch` & PowerShell {#cli}

A simple, sanity-checks-included PowerShell script:

```powershell:watch.ps1
dotnet build -p:TailwindBuild=false
start "dotnet" -ArgumentList "watch"
while (!(Test-Path "./node_modules/.package-lock.json")) { sleep -ms 100 }
npm run watch
```

Breaking it down: I've found `dotnet watch` without a proper `dotnet build` beforehand can do strange things sometimes. It's only once, so whatever. Using `start` launches `dotnet watch` in its own process. Unlike the first line in the script, this will include the `tailwind build` Target, possibly doing an `npm install` for the first time. Only after `.package-lock.json` provides evidence of an `npm install` is it OK to kick off `tailwindcss` via `npm run watch`. At this point, both .NET's Hot Reload and Tailwind's incremental build mode are watching for relevant changes.

---

## Using VS Code {#vsc}

See the [tailblazor-templates](/~https://github.com/McNerdius/TailBlazor-Templates/tree/main/Templates/SingleProject/TailBlazorWasm/.vscode){target="_blank"} repo to see launch tasks/configs for various project types. It's a bit more robust than the `watch.ps1` script but pretty involved. More to come on this.

---

## Using Visual Studio {#vs}

The best option i've found to integrate `tailwindcss --watch` with Visual Studio UI is to use the [NPM Task Runner](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.NpmTaskRunner64){target="_blank"}, and bind the relevant `watch` script to "Project Open". (Not "After Build", see below.)

::: info
One-off `tailwindcss` builds are not ideal. The long-running `tailwindcss --watch` is the only way to take advantage of Tailwind's super-fast incremental builds.
:::

### Other approaches {#vs-other}

- **Using a "Post Build Event" in Visual Studio's project properties.**

This places an MSBuild Target in the `.csproj` - `<Target Name="PostBuild" AfterTargets="PostBuildEvent">`. **This is a no-go for long-running tasks:** this and similar simple MSBuild Target based approaches do one of two things: terminate immediately or (more likely) hang the build.

- **More advanced uses of MSBuild Targets**

One example is using [Inline Tasks](https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-inline-tasks) - essentially embedding code within the `.csproj` (or more ideally, the `tailwindcss.targets` it points to) to kick off the npm task. It works but it's a bit ugly IMO. Visit [See Also](/also) for a project using this approach.

- **Using Visual Studio Folders View's "Configure Tasks" / `tasks.vs.json`**

Visual Studio [supports tasks](https://docs.microsoft.com/en-us/visualstudio/ide/customize-build-and-debug-tasks-in-visual-studio?view=vs-2022#define-tasks-with-tasksvsjson) much like VS Code which can be used to expose the needed scripts in a right-click menu in the Folder View. A few issues: You have to be in Folder View; You still have to kick it off manually; **I couldn't get it to work.** Tried feeding it various combinations of working directories, no dice.

::: info
All in all, using the NPM Task Runner with Visual Studio takes little effort and Just Works, without injecting long-running tasks into the `.csproj`/`.targets` build files.
:::


---

::: {.text-xl .italic .light .text-right .pr-6 }
[next: tidy css](/tidy_css)
:::

::::
20 changes: 0 additions & 20 deletions content/notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,26 +125,6 @@ While it's not *always* necessary to use `@import` syntax versus `@tailwind` syn

---

# `npm` Alternatives {#NPM}

I'm not a web developer, and the JS ecosystem is a bit churny and disjointed for me to keep up with. Using vanilla CSS wouldn't require any of that, but it is my biggest source of frustration in learning front-end development. Tailwind CSS just "clicks" for me.

The way i've shown how to set things up on this site requires installation of Node.js, `tailwindcss` itself, maintaining a few config files, etc. Several steps but it really is one "real" install (`tailwindcss` CLI being more of a dependency) and boilerplate. Once it's in place, you'll rarely have to think about `npm` or other JS tooling - `MSBuild` takes care of all that for us behind the scenes. That said - is there a *better*, Node.js-free alternative ? Emphasis on *better*.

## Tailwind Standalone CLI {#CLI}

Tailwind 3+ offers a [standalone CLI](https://tailwindcss.com/blog/standalone-cli){target="_blank"} - not to be confused with the ordinary `tailwindcss` CLI. At the time of writing, the singular advantage is that Node.js isn't required. Unfortunately, third party PostCSS or Tailwind plugins (such as `debug-screens`) can't be used with it, nor can the `npm` build scripts (`npm run build` etc).

Even when/if third-party plugins are supported, the standalone CLI and plugins will have to be acquired/installed somehow. Will this be a better developer experience than doing so via `npm` ? Consider continuous deployment - most if not all virtual machines will have Node.js preinstalled.

---

## Tailwind CDN {#CDN}

- Old: Don't use it
- [New](https://www.youtube.com/watch?v=mSC6GwizOag){target="_blank"}: Nifty, but not for production.

---

# Visual Studio {#VS}

Expand Down
49 changes: 0 additions & 49 deletions content/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,48 +18,6 @@

Getting the best out of both takes a couple `csproj` tweaks and a bit of config. {.font-semibold .italic .mx-4 .!indent-0}


::: info

Keep an eye on [notes](notes) as i shift to .NET 7 & Tailwind CSS 3.2.

:::

---


**[Blazor](https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor){target="_blank" .text-[150%] .italic}** is a fresh take on the [well-estabished](https://weblogs.asp.net/scottgu/introducing-razor){target="_blank"} Razor/C# combo for building UI components. It pairs quite well with **[Tailwind CSS](https://tailwindcss.com/){target="_blank" .text-[150%] .italic}**, which will significantly reduce the amount of custom CSS you have to write, keep styling local to your Razor components, while also allowing for things like themes, variables, and nesting. Rather than a set of static utility classes, it is effectively a CSS generator, building classes on demand based on an overridable and extendable base set of values and utilities. The [documentation](https://tailwindcss.com/docs/utility-first){target="_blank"} is great as well. (Random example: [position](https://tailwindcss.com/docs/position){target="_blank"}.)

---

## "Inner Loop" goodies {#innerloop}

Blazor's [Hot Reload](https://docs.microsoft.com/en-us/aspnet/core/test/hot-reload?view=aspnetcore-6.0){target="_blank"} allows edits to be applied immediately without needing to pause or restart a running app. This includes edits to code as well as markup and CSS. It's still newish and not fully supported for all project types or code edits.

Similar to .NET's Hot Reload, Tailwind's [Just-In-Time](https://tailwindcss.com/blog/tailwindcss-v3#just-in-time-all-the-time){target="_blank"} CSS generation is the primary goodness Tailwind CSS 3 brings. Initially it generates only what CSS is relevant to your markup and CSS, subsequently performing much faster incremental builds when changes are spotted. Also newish, it works well until it doesn't - for me it gets "stuck" during a build and runs out of memory once every 2-3 hours.

Hot Reload and `tailwindcss --watch` do their work independently - what's needed from us is to point `tailwindcss` at our input markup/CSS, and our Blazor project at the `tailwindcss` output.

---

## Tidy CSS {#tidy}

Blazor's take on [CSS Isolation](https://docs.microsoft.com/en-us/aspnet/core/blazor/components/css-isolation?view=aspnetcore-6.0){target="_blank"} is a simple way to limit the scope of styles to a particular Razor Component: by convention, styles defined in `.razor.css` (Scoped CSS) files will be rewritten at build time so they only apply to their associated `.razor` Component.

In addition to generating classes for us, Tailwind CSS offers an [`@apply` directive](https://tailwindcss.com/docs/functions-and-directives#apply){target="_blank"}, and built-in support for Sass-like nesting is easily enabled.

To use Tailwind CSS features within your Scoped CSS files takes as little as three lines of code and is well worth it, IMO.

---

# Prerequisites {#prerequisites}

You'll need the .NET 6+ SDK, Node.JS, and of course a development environment — _PowerShell optional_

::: info
TailBlazor is geared toward .NET 6+ and Tailwind 3+. Older versions would work too, but the build steps and config would be a bit different. I'm currently rehashing this site and the associated templates, updating them for .NET 7. The only incompatibilities i forsee will be usage of .NET 7's "empty" (bootstrap-free) templates, and the `--blazor-load-progress` variables.
:::

## Development Environment: {#de}

- [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/){target="_blank"} with "ASP.NET and web development" workload; [NPM Task Runner](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.NpmTaskRunner64){target="_blank"} is handy.
Expand All @@ -80,13 +38,6 @@ TailBlazor is geared toward .NET 6+ and Tailwind 3+. Older versions would work t

---

# Let's set things up !

Hopefully, i won't miss any steps or be overly verbose in this howto. It'll be more detailed regarding the JS tooling side of things - i'm adding Tailwind CSS to a C# project, after all. Feel free to [submit](/~https://github.com/McNerdius/TailBlazor/issues){target="_blank"} any clarification requests or suggestions.

To fast forward through this howto and see what it looks like "on disk", see the the `tailblazor-wasm` template found in my [tailblazor-templates repository](https://www.github.com/McNerdius/tailblazor-templates){target="_blank"}. It's hosted at [templates.tailblazor.dev](https://templates.tailblazor.dev/){target="_blank"} and the content is essentially a tl;dr of this site.

---

::: {.text-xl .italic .light .text-right .pr-6 }
[next: setup](/setup)
Expand Down
Loading

0 comments on commit 719b73a

Please sign in to comment.