Managing web client assets ... and how Snowpack can help

Copper Contributor

Hello there. I've been wondering what tooling and workflow everyone is using to manage client assets JS/CSS/images etc especially for Blazor and perhaps provide some inspiration in the process (not writing a complete guide, merely a pointer). When trying tools like Libman, Gulp, Webpack, MSBuild it turned out to be either insufficient or too convoluted with lot of complexity and JS code for even basic tasks.

 

My needs were somewhat simple in the head, yet hard to execute:

🟢 Needs to work with CLI/Visual Studio Code ecosystem

🟢 Manage packages with PNPM (faster and more efficient alternative to NPM)

🟢 Self-contained TypeScript source files that would produce fully optimized unbundled importable ESM modules with treeshaking, splitting, minification

🟢 PostCSS with support for nesting, variables, imports. Coupled with TailwindCSS JIT that would automatically generate on-demand minimal CSS styles depending on what is in .razor or even .cshtml & .cs template files.

🟢 All of this would need to happen fast, watcher should rebuild only what is needed every time relevant file is changed and output has to be hot reloaded into running application so work can be done in real-time with current application state.

🟢 Everything must be minimal, automatic, no extraneous declarations and relevant things should sit side-by-side.

 

Only tool that managed to do all this pretty much out of the box is Snowpack lightning-fast frontend build tool, designed for the modern web.

 

Configuration is very simple compared to other tools

 

 

/* snowpack.config.js */
/** @type {import("snowpack").SnowpackUserConfig } */
module.exports = {
  mount: {
    assets: "/", //"assets" folder holds source files for our wwwroot
    pages: "/pages", //tells snowpack to watch for changes in this folder
    shared: "/shared", //tells snowpack to watch for changes in this folder
    "../../node_modules/@fontsource/poppins/files": {
      //Copy some extra files from node_modules to wwwroot/css/files
      //Snowpack doesn't resolve font imports "yet"
      url: "/css/files",
      static: true,
    },
  },
  exclude: ["**/*.cs"], //Don't watch or include C# files in the wwwroot, we'd comment this out if they contained classes for TailwindCSS JIT to pick up
  plugins: [
    "@snowpack/plugin-postcss", //Support for PostCSS ideally in `.pcss` files, needs to be installed separately with PNPM
    [
      "@jadex/snowpack-plugin-exclude", //This plugin makes sure these files are watched for changes, but not included in the final wwwroot 
      {
        paths: ["**/*.razor", "**/*.pcss"],
      },
    ],    
  ],
  buildOptions: {
    clean: process.env.NODE_ENV === "production", //Production build should be cleaned up
    out: "wwwroot",
  },
  optimize: {
    minify: true,
    bundle: false,
    treeshake: true,
    splitting: true,
    target: "es2020",
  },
};

 

 

 

And that's all you need to do with Snowpack, all your assets will build up nicely in the wwwroot, ready to be consumed by the browser.

 

------------------------------------------------------

Couple notes how things need to be organized

 

CSS

 

Create folder assets/css and put there your css files you want to act as bundles to be placed in wwwroot. In them import individual .pcss files or globs with postcss-import & postcss-import-ext-glob plugins.

 

/* assets/css/app.css */
@import "tailwindcss/base.css";
@import "tailwindcss/components.css";
@import-glob "./**/_components/**/*.pcss";
@import-glob "../../{Pages,Shared}/**/*.pcss";
@import "../../App.razor.pcss";
@import "tailwindcss/utilities.css";

 

 In your index.html or _Layout.cshtml include as normal

 

<link href="css/app.css" rel="stylesheet" />

 

Snowpack watcher will know when imported pcss is changed and will rebuild the css that imports it.

 

TypeScript

Create folder assets/js and put your ts files there

 

/* assets/js/map.ts */
import mapboxgl from "mapbox-gl";
import "mapbox-gl/dist/mapbox-gl.css";

export function initMap(accessToken: string) {
  mapboxgl.accessToken = accessToken;
  const map = new mapboxgl.Map({
    container: "map",
    style: "mapbox://styles/mapbox/streets-v11",
    center: [19.62915, 45.3701]
    zoom: 15,
  });
  map.addControl(new mapboxgl.FullscreenControl());
  map.scrollZoom.disable();
}

 

Don't include in index.html or _Layout.cshtml. Import modules as needed on your pages/layouts.

 

var mapModule = await this.Runtime.InvokeAsync<IJSObjectReference>("import", "./js/map.js");
await this.mapModule.InvokeVoidAsync("initMap", "accessToken");

 

 

Other files like Images/Manifests/Favicons

Just include in the root of the assets folder or subfolders assets/img etc. It's possible to use plugins to optimize these files too. For example Sharp can be used to optimize, resize images or generate avif/webp variants.

 

Hope someone finds this helpful, should work for MVC and RCL too. Feel free to share your way to tackle the issue or ask questions.

0 Replies