HtmlWebpackPlugin + ASP.NET Core 3

The idea

My goal with this experiment was to set up a Razor page that was generated from HtmlWebpackPlugin in ASP.NET Core 3.

In other words, I want webpack to generate my controller view (.cshtml file) because it has a better understanding of what the client code should look like. At the same time, I wanted the full power of ASP.NET Core in my Razor files.

I spent far too many hours trying to get this to work to not share my solution with the world.

The full code for this experiment is at https://github.com/alexdresko/htmlwebpackplugin-aspnetcore3

webpack config

The webpack.config.js file is simple enough. Note the template, inject, and filename options for HtmlWebpackPlugin. template is relative to webpack.config.js, and filename is relative to the output folder.

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: { index: './src/index.js' },
    mode: 'development',
    output: {
        filename: '[name].[hash].bundle.js',
        path: path.resolve(__dirname, 'wwwroot/dist'),
        publicPath: '/dist'
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: './Views/Home/Index.ejs',
            filename: '../../Views/Home/Index.cshtml',
            inject: false
        })
    ]
};

The html-webpack-plugin template

The index.ejs file is pretty simple. Notice how I’m using both Razor functionality (setting the layout at the top of the page), and ejs functionality (definitely see the plugin documentation for more on that)

@using System.IO

@{
Layout = "";
}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title><%= htmlWebpackPlugin.options.title || 'Webpack App'%></title>

    <% for (var css in htmlWebpackPlugin.files.css) { %>
    <link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
    <% } %>
</head>

<body>
    <h1>TADA!</h1>
	<p>If you didn't see a popup, it didn't work</p>
	<h2>Expected</h2>
	<ol>
	<% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
	    <li><%= htmlWebpackPlugin.files.chunks[chunk].entry %></li>
    <% } %>
    </ol>

	<h2>Actual</h2>
	<ol>
	    @{
	        foreach (var file in Directory.GetFiles("./wwwroot/dist"))
	        {
	            <li>@file</li>
	        }
	    }
    </ol>

	<% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
    <script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
    <% } %>

    <% if (htmlWebpackPlugin.options.devServer) { %>
    <script src="<%= htmlWebpackPlugin.options.devServer%>/webpack-dev-server.js"></script>
    <% } %>
</body>

</html>

On the server side

This page has some very important information. Namely:

Runtime compilation is enabled using the Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation package. To enable runtime compilation, apps must:

Install the Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation NuGet package.

Update the project’s Startup.ConfigureServices method to include a call to AddRazorRuntimeCompilation

With that information in hand, I added AddRazorRuntimeCompilation()to startup.cs:

services.AddRazorPages()
                .AddRazorRuntimeCompilation();

Justification

To prove why all of this is good, consider the typical approach for including webpack files into a Razor page:

<script type="text/javascript" asp-src-include="~/dist/app*.chunk.js"
                                  asp-src-exclude="~/dist/app*.async*.chunk.js"></script>

Then remember that webpack is going to put new files into the dist folder every time you build your application. By default, webpack will not clean your dist folder, and we’ve configured webpack to output our files as “[name].[hash].bundle.js”. If you’ve built your application 100 times, the <script> tag above is going to insert your javascript files 100 times!

Granted, you could just install and configure clean-webpack-plugin to clean your dist folder as part of the build, but that’s not the point. The point again is that html-webpack-plugin is smarter than ASP.NET about how the client code should look. There’s a rich ecosystem of plugins and options available for html-webpack-plugin, and many front end frameworks go to great lengths to ensure their compatibility.

It works!

Just for fun, the demo in the repo writes to the page the file(s) that webpack created, as well as the files that are in the dist folder.

Caveats

The only negative side effect I can think of with this approach is that it might cause an extra Razor compilation — Basically, when the page first loads, I think it gets compiled first. But before the page fully loads, the ASP.NET UseWebpackDevMiddleware... code kicks in and runs webpack. Our webpack process, however, rewrites the very Razor file that we were trying to load in the first place. My hunch is that this causes the Razor file to be recompiled before being displayed. I’m not sure if any of that is correct, but it’s worth a consideration. If it’s true, the performance hit is minimal.

Am I crazy?

I hope that’s helpful to someone. Please let me know what you think!

Publishing a Visual Studio extension via command line

Microsoft not so recently added support for publishing Visual Studio extensions to the marketplace via a command line interface called vsixpublisher

It’s no coincidence they tweeted me upon releasing the feature, as I’d been pestering them for a very long time to make it happen.

The walk-through I linked to will get you going, but might not get you all the way there. If you run into trouble trying to get it to work, here’s a reference implementation that might help:

I have an appveyor build that runs automatically, anytime someone pushes code to the master branch. Here’s how I configured the build to version, package, and publish the vsix to the marketplace. 

Last, but not least, the end result: https://marketplace.visualstudio.com/items?itemName=thealexdresko.HomeSeerTemplates2

I hope that helps someone who wants to get started using vsixpublisher. let me know if you have any questions, and I’ll try to help. 

What is ASP.NET Core 2.1 API “Problem Details” (RFC 7807)

When ASP.NET Core 2.1 came out, there’s was a relatively brief announcement about new support for “Problem Details” (RFC 7807)

In this release we added support for RFC 7808 – Problem Details for HTTP APIs as a standardized format for returning machine readable error responses from HTTP APIs.

If you just want a better explanation of problem details, fear not. I read the documentation for you.   …Continue reading “What is ASP.NET Core 2.1 API “Problem Details” (RFC 7807)” [What is ASP.NET Core 2.1 API “Problem Details” (RFC 7807)]

Thoughts: The Stack Overflow 2017 Developer Survey Results

I found it useful this year to take some notes as I skimmed through the always interesting Stack Overflow developer survey results. Here’s what I came out with this year:  …Continue reading “Thoughts: The Stack Overflow 2017 Developer Survey Results” [Thoughts: The Stack Overflow 2017 Developer Survey Results]

Toastmasters for the software developer

In this video, I, or rather, RealCoder79, give a short speech at Greenville’s Metro Toastmasters about how Toastmasters can benefit software developers.  

Toastmasters, if you didn’t know, provides “a supportive and positive learning experience in which members are empowered to develop communication and leadership skills, resulting in greater self-confidence and personal growth.”

Being that I killed www.realcoder79.com recently, I thought it’d be a good idea to give some of that old content a new home. 

Enjoy!

Scroll Up