Assets
Modern web applications require processing and bundling JavaScript and CSS assets. This makes it possible to merge multiple files together, remove unused code and minimize them. The recommended tool to do that when using Bulletcode.NET is Vite.
Styles included in Bulletcode.NET use the .scss
format, so Sass is required to process them. It is also recommended to use PostCSS with the autoprefixer plugin to ensure maximum browser compatibility.
Vite configuration
Refer to the official Vite documentation for detailed information regarding configuring Vite. The information presented here makes it easier to seamlessly integrate Vite with Bulletcode.NET.
It’s assumed that the source .js
and .scss
files are located in the src
subdirectory of the App.Web
project. The bundle will be generated in the assets
subdirectory of the wwwroot
directory, which is used by ASP.NET to store static files. Both the generated .js
and .css
files and the manifest.json
file generated by Vite are placed directly in the output directory.
This corresponds to the following vite.config.js
options:
export default defineConfig( {
base: '/assets/',
publicDir: false,
build: {
outDir: 'App.Web/wwwroot/assets',
assetsDir: '',
manifest: 'manifest.json',
rollupOptions: {
input: [
'App.Web/src/index.js',
'App.Web/src/styles/index.scss',
],
},
},
} );
In this example, the input files for Vite are index.js
and styles/index.scss
, but you can create as many input files as you need. You can also configure Vite to split the output files into multiple chunks.
Make sure to add the assets
directory to .gitignore
:
**/wwwroot/assets/
In order to make it easier to import .js
and .scss
files stored in the Bulletcode.Web.Front package, use the projectAliases()
plugin for Vite, which is part of the bc-tools-dotnet NPM package:
import { projectAliases } from 'bc-tools-dotnet';
export default defineConfig( {
plugins: [
projectAliases( join( process.cwd(), 'App.Web' ), {
'@bc-front': 'Bulletcode.Web.Front',
} );
],
} );
The arguments of projectAliases()
are the absolute path of the project and the object containing aliases and their corresponding libraries.
NOTE
The project must be compiled before running Vite, in order for the plugin to be able to find the Bulletcode.Web.Front library.
The @bc-front
alias can be used for importing JavaScript modules, for example:
import { initialize } from '@bc-front';
import { createClientApp, createRouter } from '@bc-front/client';
It can also be used to import .scss
files:
@import "@bc-front/styles/config";
The alias points to a directory called .deps/@bc-front
, which is automatically created in the solution root directory as a symbolic link to the src
subdirectory inside the Bulletcode.Web.Front
NuGet package, or inside the corresponding project when the development version of Bulletcode.NET is used. The link is necessary for NPM modules to be correctly resolved from the node_modules
directory of the application.
If your application contains multiple source subdirectories, it’s also useful to define an @app
alias to make it easier to import application JavaScript modules and .scss
files. This can be done by adding the following option to vite.config.js
:
export default defineConfig( {
resolve: {
alias: {
'@app': join( rootPath, 'App.Web/src' ),
},
},
} );
The cssCharset()
plugin, defined in the [bc-tools-dotnet] package, automatically adds the @charset "UTF-8"
directive to the generated .css
files if they contain non-ASCII characters.
The bundleTranslations()
plugin can be used when the application scripts contains a lot of translations. By default, they are injected directly into every HTML page, along with locale information. When this plugin is used, translations from .mo
files are converted to JSON format and bundled into separate .js
files for each language. Those language files are then automatically loaded, depending on the language of the page. For example:
export default defineConfig( {
plugins: [
bundleTranslations( 'App.Web', [ 'pl-PL' ], [
'App.Web',
'Bulletcode.Web.Front',
] );
],
} );
The first parameter is the path of the projects for which assets are being built. The second parameter is an array of languages for which translations should be bundled. The last one is an array of projects containing translations to be bundled — they can be either NuGet packages, or projects in the solution.
By default, the domain name is the same as the project name, but an object containing both the domain
and the name
of the project can also be specified. Note that the .js
suffix is automatically appended to the domain name, so only translations extracted from JavaScript source files are included. See the Internationalization chapter for more information about translations.
Scripts and styles
In order to use the scripts and styles provided by the Bulletcode.NET framework, the bc-css-framework and leaner NPM packages must be manually installed as the application’s dependencies.
The index.js
file of the application should, at minimum, call the initialize()
function from the @bc-front
module. This function attaches handlers for all elements containing the data-handler
attributes, and attaches form handlers, responsible for client-side validation, to all <form>
elements. It also mounts components and input components for all elements containing the data-mount
and data-mount-input
attributes.
Handlers are functions which add event listeners to specific target elements, and can handle dynamic behavior, keyboard navigation, etc. Bulletcode.NET contains built-in handlers for forms, post buttons and multi-select dropdown controls (see Tag Helpers), and the ApplicationHeader and Grid components (see View Components). Applications can create their own handlers, as described in the Validation chapter.
Custom handlers should be registered before calling initialize()
:
import { initialize, registerHandler } from '@bc-front';
import * as handlers from './handlers';
for ( const name in handlers )
registerHandler( name, handlers[ name ] );
initialize();
The code above imports all handlers from an index.js
file in the handlers
subdirectory, and registers them using the registerHandler()
function.
Unlike handlers, components are written using Leaner — a lightweight, reactive wrapper library for DOM — and they completely replace the server-side generated elements, which serve as placeholders. Bulletcode.NET uses built-in components for date and passwords fields (see Tag Helpers).
Handlers can be attached to DOM elements by explicitly calling the attach()
function, which is globally available as window.bcFront.attach()
, passing the target element or selector, the name of the registered handler and optional properties, which are passed as the second argument to the handler function. Handlers can also be attached automatically using the data-handler
attribute, which specifies the name of the handler, and optional attributes prefixed with data-prop-
, which are passed as its properties.
The application can also register custom components by calling the registerComponent()
function. The mount()
function creates a component inside the specified container, with optional properties. The mountInput()
function creates a component which is used as a field input control. Both functions are globally available using the window.bcFront
object. Components can also be mounted automatically using the data-mount
or data-mount-input
attribute, which specifies the name of the component, and optional attributes prefixed with data-prop-
, which are passed as its properties.
The structure of the entry script for applications which use client-side views is described in the ClientView chapter.
The style.scss
file of the application should import .scss
files from the @bc-front
package and the bc-css-framework NPM package, on which the Bulletcode.NET styles are based.
A minimal style which includes all built-in styles for Bulletcode.NET applications looks as follows:
@import "@bc-front/styles/config";
@import "bc-css-framework/src/config";
@import "bc-css-framework/src/functions";
@import "@bc-front/styles/variables";
@import "bc-css-framework/src/variables";
@import "@bc-front/styles/mixins";
@import "bc-css-framework/src/mixins";
@import "bc-css-framework/src/normalize";
@import "@bc-front/styles/base";
@import "@bc-front/styles/utils";
@import "@bc-front/styles/components";
The application can redefine the default configuration and variables defined by Bulletcode.NET and the Bulletcode CSS Framework by creating its own config.scss
and variables.scss
files and importing them before the standard ones.
The application can also define styles for its own custom components and views, by following the guidelines defined by the Bulletcode CSS Framework.
Loading assets
Assets processed using Vite should be loaded using the custom asp-entry
attribute for <link>
and <script>
tags:
<link asp-entry="App.Web/src/styles/index.scss" rel="stylesheet">
<script asp-entry="App.Web/src/index.js"></script>
These tags should be placed in the <head>
section of the root layout file. The path of the entry style or script file is relative to the solution’s root directory. It is automatically converted to the appropriate path of the generated file using the manifest.json
file created by Vite.
When a script is loaded using the asp-entry
attribute, the Vite asset manager automatically generates the corresponding <link href="modulepreload">
tags in the page header and the <script type="module">
tags in the page body.
It is also possible to use the asp-entry
attribute for other link types, for example a <link rel="preload">
tag can use it to preload a web font.
The IViteAssetManager
service can be used to programmatically add <link>
and <script>
tags corresponding to assets built using Vite.
When the application runs in development mode, the Vite asset manager watches the manifest.json
file for changes. When the file is modified, and any JavaScript files have been updated, the application is automatically reloaded in the browser. If only CSS files have been modified, they are replaced without reloading the whole page.
NOTE
For the best development experience, it’s recommended to configure the following Visual Studio options in the Projects and Solutions > ASP.NET Core section:
- Auto build and refresh option — Refresh browser after build
- Auto restart Kestrel server after build — True
- CSS Hot Reload — Disabled
During development, Vite can be run in watch mode, so that assets are automatically rebuilt when the source files are modified, and development mode should be configured to disable code minification and other optimizations.
The following commands can be added to the scripts
section of the package.json
file to make it easier to run Vite in both production mode and development mode:
{
"build": "vite build --config build/vite.config.js",
"dev": "vite build --watch --mode development --config build/vite.config.js"
}
Another solution is to use a simple build.js
script to run Vite with specified options.
Bulletcode.NET also supports running Vite as development server. This can speed up recompilation for very large projects, but the disadvantage is that the development server must be manually started before running the application.
In order to use Vite in server mode, set the BC_WEB_FRONT_DEV_SERVER
environment variable to a non-empty value, for example 1
. By default, the Vite server is expected to be listening at http://localhost:5173
, but the URL can be changed by setting the BC_WEB_FRONT_DEV_URL
variable. Both variables can be configured conveniently in the launchSettings.json
file.
Managing assets
The IAssetManager
service makes it possible to load custom .css
and .js
files, which are not processed using Vite, insert inline JavaScript code and variables into the page body, and insert inline CSS code. In order to load .css
and .js
files, use the IViteAssetManager
service or the asp-entry
attribute, as described above.
An example which shows how inline JavaScript can be generated is shown in the View Components chapter.
The IAssetRenderer
service renders the custom <link>
and <script>
tags generated by the asset manager and asset bundles. In order for the asset mechanism to work, the RenderHeadAssets()
and RenderBodyAssets()
methods must be called in the root layout file at the end of the <head>
and at the end of the <body>
:
<!DOCTYPE html>
@RootTags.Html.RenderStartTag()
<head>
<meta charset="utf-8">
@* insert other head tags here *@
@AssetRenderer.RenderHeadAssets()
</head>
@RootTags.Body.RenderStartTag()
@RenderBody()
@AssetRenderer.RenderBodyAssets()
@RootTags.Body.RenderEndTag()
@RootTags.Html.RenderEndTag()
NOTE
The IAssetManager
, IAssetRenderer
and IViteAssetManager
services implement the IViewContextAware
interfaces, which means that they must be contextualized using the current view context before they can be used.
The BaseController
, BaseViewComponent
and BaseRazorPage
classes provide access to the IAssetManager
using the AssetManager
property, ensuring that it’s properly contextualized. The BaseRazorPage
also provides access to IAssetRenderer
using the AssetRenderer
property. See MVC for more information about these base classes.
The IAssetBundle
service can be used to register <link>
and <script>
tags which are rendered globally for the entire application. its two methods, RegisterHeadAssets()
and RegisterBodyAssets()
, can be used to register header and body assets, respectively, using the IAssetManager
. In addition, an asset bundle can also implement the IAssetRenderer
service, which makes it possible to insert arbitrary HTML content into the header and body.
Bulletcode.NET automatically registers the following asset bundles:
I18nAssetBundle
injects locale and translations as thei18nData
global variable, as described in the Internationalization chapter. Translations which are bundled into external.js
files are not included in the inline translations data.ViteAssetBundle
injects<link>
and<script>
tags generated by theIViteScriptProvider
andIViteAssetManager
services.
The IViteScriptProvider
services can be used to insert additional scripts on every page. Bulletcode.NET implements two such services:
ViteClientScriptProvider
adds the client script which handles the hot-reload mechanism during development.ViteI18nScriptProvider
adds the appropriate language file generated by thebundleTranslations()
Vite plugin described above.
Icons
Bulletcode.NET uses icons from the open-source Lucide package. Applications can also use icons using the following HTML markup:
<i class="icon-arrow-down"></i>
The simplest way of using Lucide icons is to directly import the .css
file from the lucide-static NPM package from the application’s index.scss
file:
@import "lucide-static/font/lucide";
However, the pre-built .css
and web font files are large, and most applications only use a small number of icons.
A better solution is to create a custom web font, which contains only the required subset of icons. Such font can be created using the bc-build-icons
tool which is provided by the bc-font-utils NPM package.
In order to generate the font, create a src/fonts/lucide.yml
file in the project’s directory. It should contain a list of icons that will be included:
icons:
- arrow-down
- arrow-up
- calendar
# ...
See the lucide.yml
file in the demo application in the framework’s source repository for a full list of icons used by Bulletcode.Web.Front.
Run the following command to generate the web font:
bc-build-icons App.Web/src/fonts/lucide.yml
This creates lucide.ttf
and lucide.woff
web font files, and a lucide.css
file containing the CSS classes for the icons, in the same location as the lucide.yml
file.
In order to use the web font, create a font.scss
file in the src/styles
subdirectory with the following contents:
@font-face {
font-family: lucide;
font-style: normal;
font-weight: normal;
font-display: block;
src: url( '../fonts/lucide.woff2' ) format( 'woff2' ),
url( '../fonts/lucide.ttf' ) format( 'truetype' );
}
[class^="icon-"], [class*=" icon-"] {
font-family: lucide;
font-size: 1.14em;
line-height: 1;
font-style: normal;
font-weight: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
Finally, import both the lucide.css
and the fonts.scss
file from the main index.scss
file:
@import "icons";
@import "../fonts/lucide";
NOTE
Another advantage of using a custom web font is that bc-build-icons
optimizes the generated fonts using the ttfautohint tool, which improves the appearance of the icons on the screen. The pre-built web fonts provided in the lucide-static package are not optimized in this way.