Hotwired ASP.NET Core Web Application (6 Part Series)
Hotwired ASP.NET Core Web Application — Part 1 (Intro)
Hotwired ASP.NET Core Web Application — Part 2 (NPM, Webpack Setup)
Hotwired ASP.NET Core Web Application — Part 3 (Turbo Drive & Frame)
Hotwired ASP.NET Core Web Application — Part 4 (Turbo Stream)
Hotwired ASP.NET Core Web Application — Part 5 (Stimulus)
Hotwired ASP.NET Core Web Application — Part 6 (Quote Editor)
In the first part of this series, I have tried to summarize what Hotwire approach is and its components. Before porting “The Turbo Rails Tutorial” to ASP.NET Core, we should have to set up our development environment to use NPM and webpack. You can find the source code for the final application here.
I will use the latest .NET 6.0 on Visual Studio 2022 and create an ASP.NET Core Razor Pages web application named HotwiredQuoteEditor.
Now, we first have to set up our application workspace by removing unused libraries and adding the necessary ones.
Loading Packages
Instead of Bootsrap, we will use Tailwind CSS. We will also not use the client-side validation implemented with jQuery. So, we can remove the wwwroot\lib
folder which contains the files for these libraries. After removing these folders, we should also remove their references from the Pages\Shared\_Layout.cshtml
file.
Now, to integrate Turbo, Stimulus, Tailwind CSS, or any other outside package to our project, we will use NPM. Open a terminal window and go to the project root. We will create a folder named ClientApp
under the project root and while inside that folder, we will initialize npm with npm init -y
, and this will create an empty package.json
file.
While in the ClientApp
folder, we will install the following basic development dependencies that we’ll use later.
Next, we will install the necessary development packages for Tailwind CSS.
We will run the following command to initialize Tailwind, which will create postcss.config.js
and tailwind.config.js
files.
npx tailwind init -p
Replace the tailwind.config.js
with the following content:
Here the content
section is important: it configures the paths to our HTML templates or any other source files that contain Tailwind class names. In an ASP.NET Razor Pages project, our Razor pages and supporting files can be found either in Pages or in the Areas directories. We also added the recommended @tailwindcss/forms
plugin which provides a basic reset for form elements that are easy to override with utilities.
Also, modify the postcss.config.js
to add the cssnano
plugin which will minimize our CSS for the production build.
Lastly, create an app.css file under ClientApp/src/css
folder. In that file, add the @tailwind
directives for each of the Tailwind’s layers.
We have loaded all the necessary packages. Now, we need to configure our module bundler webpack, to generate the final (bundled, minified, obfuscated) JavaScript and CSS files or to bundle any other resources of our application.
Webpack Configuration
ClientApp
folder already contains our package.json
and tailwind config files (postcss.config.js
and tailwind.config.js
). We will also create our webpack.config.js
file under this folder and replace its content with the following:
Inside webpack.config.js
, we will set the context
(the base directory for resolving entry points and loaders from the configuration) as ClientApp
by context: path.join(__dirname, '.')
. And relative to the context
, we define an entry
object with the app
key pointing to ./src/js/app.js
file. As we have previously created app.css
under the ClientApp/src/css
, now we will create an empty app.js
file under the ClientApp/src/js
. Webpack will use this file to start building the bundle.
With the output
object, we set the path and filename where webpack will output our bundles and assets. Here, we set the output path as wwwroot\dist
and the file name will be resolved as app.js
.
By default, webpack is used to compile JavaScript modules. And other formats can be handled using loaders and connecting those with our directory structure. And under the module
key, we define the rules that specify how each module will be created by specifying loaders or parsers. For example, for CSS file bundling, we apply three loaders and they are evaluated from right to left (last to first): First postcss-loader
, then css-loader
, and then MiniCssExtractPlugin
loader.
Now, we will add scripts to the scripts
section of our package.json
file to start our bundling process with webpack. By default, the scripts
section contains one script named test
. Replace the section with the following:
devbuild
and prodbuild
scripts are for building our modules in development
and production
environments respectively. But during development, we will mostly use the dev
script, which has a watch option specified. By setting the watch, we instruct webpack to “watch” all files within the dependency graph for changes. When a file is updated, the code will be automatically recompiled, so that we won’t need to run the full build manually. However, the browser is not automatically refreshed for us to see the changes. To achieve this, instead of the watch option, we can use webpack-dev-server
. But we will get automatic browser refresh using dotnet watch run
, so using the watch option will be sufficient for us.
Now, before we run the devbuild
script, remove everything from the wwwroot
directory, except the favicon.ico
file. While inside the ClientApp
folder, run npm run devbuild
in the terminal.
As we see from the image, the dist
folder is created and inside it, a bundled app.js
file is added. Also, our logo.png
image is copied from the ClientApp/images
to the dist\images
folder using CopyWebpackPlugin
. But, we do not see an app.css
file under the dist
folder. It’s because, although we have created the ClientApp\src\css\app.css
file, we haven’t used it in our app.js
file. “Using” it means “importing” it from a Javascript file. So, now when we add import '../css/app.css';
line inside our ClientApp\src\js\app.js
file, and run npm run devbuild
again, we will see that the app.css
file is created under the dist
folder with Tailwinds’ base styles. We should now reference these app.js
and app.css
files in the Pages\Shared\_Layout.cshtml
file as in the following image:
We set the defer
attribute on our script
tag, which specifies that it will be downloaded in parallel to parsing the page, and executed after the page has finished parsing.
Now that we have our JavaScript and CSS files bundling set up, it is time to run our project using dotnet watch
which is a development tool that runs a .NET command to compile, run tests, or publish when source files change. Here, we want the project to be rebuilt when there is any change in our .NET related files.
By default, dotnet watch
tracks all files matching the following glob patterns:
**/*.cs
*.csproj
**/*.resx
- Content files:
wwwroot/**
,**/*.config
,**/*.json
Remember, how the watch option wasn’t automatically refreshing the browser. But we see that dotnet watch
is tracking the wwwroot
folder, which is the output folder of our compiled JavaScript and CSS files. So, when we make a change in our source JavaScript and CSS files, they will be automatically compiled and outputted in the wwwroot
folder and dotnet watch
will refresh the browser.
So, everything is finally ready. Open two terminal windows in Visual Studio.
- In the first terminal, go to the project root directory, and run
dotnet watch run
- In the second terminal, go to the
ClientApp
directory and runnpm run dev
Our application will open in our default browser and we will see our rendered Index.cshtml
page. At this point, we didn’t handle cache busting; so, to see the changes immediately, we should check Disable Cache on the Network tab of the Developer Tools window of our browser.
Since the project was using Bootstrap by default, we will not see any styling. Let’s change a few things and see that our hot reloads are working both on the .NET and webpack sides. In the following video, I showcased the webpack watch and its immediate application by adding Tailwind CSS classes to the body
element in the Pages\Shared\_Layout.cshtml
file. Also, removed the footer and Privacy links to trigger .NET hot reloads. You can watch the hot reloads in action in the following video.
Here is the final view of the Index
page, after the changes in _Layout.cshtml
and Index.cshtml
.
Finally, we are ready to work with Turbo in part 3. You can download the code covering this part from here.
References
[1] Introduction to Razor Pages in ASP.NET Core
[2] Webpack Concepts
[3] Tailwind CSS Documentation
[4] Cache busting JavaScript files using webpack and ASP.NET