Używanie pakietów npm w aplikacji Blazor
Blazor jest świetnym frameworkiem do UI, dzięki któremu możemy używać C# na frontendzie. Są jednak przypadki, w których nadal trzeba korzystać z nowoczesnych bibliotek JavaScript. Zwłaszcza tych dostępnych na npm
.
Jak więc korzystać z modułów JS dostarczanych przez npm
?
Wymagania wstępne:
- zainstalowanie .NET Core 3.0
- zainstalowanie Node.JS
Zbudujmy aplikację Blazor od zera:
$ dotnet new blazorserverside --auth Individual
To polecenie utworzy nową aplikację Blazor.
Spróbujmy uruchomić:
$ dotnet run
Działa zgodnie z oczekiwaniami.
W przypadku modułu npm
, którego będziemy używać, wykorzystamy moment.js
, który jest prostym modułem do obsługi czasu w JS.
Utwórzmy folder o nazwie JsLib
w naszej aplikacji Blazor:
Teraz zainicjujemy nowy projekt npm
w folderze JsLib
:
$ cd JsLib
$ npm init
Po prostu zostaw pustą odpowiedź. Nie będziemy się teraz na tym skupiać.
Utwórz nowy katalog src
w folderze JsLib
i nowy plik index.js
:
Plik index.js
będzie służył jako „root” z którego będziemy wystawiać biblioteki, których używamy.
Teraz dodamy moment.js
za pośrednictwem npm. Wykonamy to w katalogu JsLib
:
$ npm install moment
Plik package.json powinien teraz zawierać następujące elementy:
Następnie utworzymy nowy plik o nazwie time_lib.js
w folderze src
:
Dodaj następujący kod:
import moment from 'moment';
export function getCurrentTime() {
return moment().format();
}
Wywołamy to teraz w index.js
i wystawimy funkcję, którą może wywołać aplikacja Blazor
:
import { getCurrentTime } from './time_lib';
export function GetCurrentTime() {
return getCurrentTime();
}
W powyższym przykładzie JsInterop
dla Blazor
wywoła funkcję GetCurrentTime()
.
Teraz użyjemy pakietu webpack
, aby spakować naszą bibliotekę i umieścić wynik wewnątrz wwwroot/js
w naszej aplikacji Blazor
.
Teraz w folderze JsLib
wywołamy:
$ npm install webpack webpack-cli
To doda zależności, których użyjemy o stworzenia paczki z naszą biblioteką do webpack
i webpack-cli
. Folder zależności powinien teraz zawierać webpack
i webpack-cli
:
Teraz utworzymy webpack.config.js
w folderze JsLib
, który będzie obsługiwał konfigurację webpack
:
Dodaj to do webpack.config.js
:
const path = require("path");
module.exports = {
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]
},
output: {
path: path.resolve(__dirname, '../wwwroot/js'),
filename: "my_lib.js",
library: "MyLib"
}
};
Powyższa konfiguracja każe webpackowi wykonać następujące czynności:
- Użyć babel-loader, ponieważ używamy składni ES6.
- Nazwać dołączoną bibliotekę
my_lib.js
i ustawić bibliotekę główną, która zostanie dołączona dowindow
(po stronie klienta) i nazwie jąMyLib
. Więcej na ten temat później. - Umieścić dołączoną bibliotekę na
wwwroot/js
.
Teraz zaktualizujemy package.json
, aby dodać build
, który wywoła webpack
.
Dodaj to do sekcji „scripts” w pliku package.json
:
"build": "webpack --mode production"
Plik package.json
będzie teraz wyglądał następująco:
Ponieważ użyliśmy składni ES6
, musimy dodać zależności babel
. Uruchomimy polecenie w folderze JsLib
:
$ npm install babel-loader @babel/core --save-dev
Spowoduje to dodanie potrzebnych zależności babel dla webpacka, by stworzyć poprawną paczkę z naszą biblioteką. Teraz pakiet.json
będzie wyglądać tak:
Zbudujmy teraz naszą bibliotekę za pomocą polecenia:
$ npm run build
To polecenie zbuduje bibliotekę i nasz pakiet do folderu wwwroot/js
:
Jak widać, nasz plik my_lib.js
znajduje się teraz w folderze wwwroot/js
w naszej aplikacji Blazor.
Skorzystajmy teraz z naszej biblioteki w aplikacji Blazor
.
Zaczniemy od dodania naszej biblioteki do plików JS używanych przez naszą aplikację Blazor
. W _Host.cshtml
dodaj następujący wiersz:
<script src="js/my_lib.js"></script>
Sekcja body _Host.cshtml powinna wyglądać mniej więcej tak:
Najpierw uruchommy naszą aplikację Blazor
i przekonajmy się, że nasza biblioteka faktycznie tam jest. Po uruchomieniu otwórz przeglądarkę (moja to Chrome) i sprawdź, czy nasza biblioteka została wczytana:
Powodem, dla którego window
faktycznie zawiera MyLib
, jest właściwość library
dodana w output
w webpack.config.js
.
Ponieważ jest to teraz część elementu window
, możemy teraz wywołać to w naszej aplikacji Blazor
:)
Utwórz nową stronę w katalogu Pages
i nazwij ją Time.razor
:
Time.razor
będzie zawierał następujący kod:
@page "/time"
@inject IJSRuntime JsRunTime
<button class="btn btn-primary" @onclick="@GetCurrentTime">Get Current Time</button>
@CurrentTime
@code {
private MarkupString CurrentTime = new MarkupString("");
async void GetCurrentTime()
{
var dateTime = await JsRunTime.InvokeAsync<string>("MyLib.GetCurrentTime");
CurrentTime = new MarkupString(dateTime);
StateHasChanged();
}
}
Powyższy kod używa JsInterop
Blazora i wywołuje MyLib.GetCurrentTime
z naszej biblioteki.
Uruchom aplikację Blazor i przejdź do /time
:
Naciśnij przycisk, a otrzymasz wynik z biblioteki, którą utworzyliśmy.
Jak widzisz, możemy teraz używać modułów npm
i wystawić je jako biblioteki naszej aplikacji Blazor
:)
Bonus
Integracja naszego npm run build
z dotnet build
.
Zmodyfikuj plik .csproj
na podobny do poniższego:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<LangVersion>7.3</LangVersion>
<UserSecretsId>aspnet-BlazorNpmLib-3AB59FEC-F75E-4E24-9EC0-874FBF1316F3</UserSecretsId>
<JsLibRoot>JsLib\</JsLibRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(JsLibRoot)node_modules\**</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<Content Remove="$(JsLibRoot)**" />
<None Remove="$(JsLibRoot)**" />
<None Include="$(JsLibRoot)**" Exclude="$(JsLibRoot)node_modules\**" />
</ItemGroup>
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(JsLibRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(JsLibRoot)" Command="npm install" />
<Exec WorkingDirectory="$(JsLibRoot)" Command="npm run build" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(JsLibRoot)" Command="npm install" />
<Exec WorkingDirectory="$(JsLibRoot)" Command="npm run build" />
</Target>
<ItemGroup>
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="3.0.0-preview6.19307.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.0.0-preview6.19307.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.0.0-preview6.19307.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.0-preview6.19304.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.0.0-preview6.19304.10" />
</ItemGroup>
</Project>
Pomysł ten został skopiowany z szablonu react-redux
. Krótkie podsumowanie jest takie, że wywołania npm
są zintegrowane z poleceniem kompilacji dotnet.
Po opublikowaniu, biblioteka jest kopiowana do folderu wwwroot/js
, więc nie trzeba nic konfigurować przy releasach.
Oto przykładowy folder dist
po uruchomieniu dotnet publish -o dist
:
Jak widać, plik my_lib.js
jest najpierw kopiowany do katalogu źródłowego, aby po skompilowaniu go przez dotnet, już tam istniał.
Zauważ, że wciąż można tu dużo naprawić. Ale teraz przynajmniej widzimy, że moduły npm
mogą być używane jako biblioteki dla aplikacji Blazora :)
Oryginał tekstu w języku angielskim przeczytasz tutaj.