docs: Enable static pre-rendering on the Docs WebAssembly app (#4207)

* Add the "BlazorWasmPreRendering.Build" package reference

* Cut out the service configuration to static method

- it is required for static pre-rendering

* Specify locale for pre-rendering to 'en-US' explicitly

- otherwise, an exception, "JsonException: '<' is an invalid start of a value" will happen.
- because there aren't "*.en.json" files, there are "*.en-US.json" files.

* Avoid doing "NavigateTo" during pre-rendering

- otherwise, the "NavigationException" will happen.

* Move caches of the DemoService to an individual singleton service.

- otherwise, a disposed HttpClient object will cause the "ObjectDisposedException" on a Blazor Server
- because a static field of a scoped service captures the HttpClient object that will disposed of after that scoped service has expired.

* Make it to be pre-rendered the zh-CN locale contents too

* Fix the invalid URL in the FAQ document

- both en-US and zh-CN.
- this caused 404 Not Found and a crash during the pre-rendering process.

* Change the pre-rendering output style to '{path}/{to}.html'.

- from the '{path}/{to}/index.html' style.

* Make the description part of the component pages be included in the static pre-rendering.

- To achieve that, load a DemoComponent object in the `OnIntializedAsync` life cycle method.
- and make sure to avoid invoking `NavigationTo` in the `OnIntializedAsync` life cycle method.
This commit is contained in:
jsakamoto 2024-09-18 01:54:16 +09:00 коммит произвёл GitHub
Родитель 7393fbf4ac
Коммит 6c624ec06c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
9 изменённых файлов: 122 добавлений и 56 удалений

Просмотреть файл

@ -19,7 +19,7 @@ And make sure that `#some-scroll-area` element is `position: relative` or `posit
### How do I modify the default theme of Ant Design?
See: https://ant.design/docs/react/customize-theme .
See: [https://ant.design/docs/react/customize-theme](https://ant.design/docs/react/customize-theme)
### Why does modifying props in mutable way not trigger a component update?

Просмотреть файл

@ -19,7 +19,7 @@ title: 常见问题
### 如何修改 Ant Design 的默认主题?
可以参考[定制主题](/docs/customize-theme)。
可以参考[定制主题](https://ant.design/docs/react/customize-theme)。
### 如何修改 Ant Design 组件的默认样式?

Просмотреть файл

@ -26,6 +26,16 @@
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.0" PrivateAssets="all" />
</ItemGroup>
<PropertyGroup>
<BlazorWasmPrerenderingLocale>en-US,zh-CN</BlazorWasmPrerenderingLocale>
<BlazorWasmPrerenderingUrlPathToExplicitFetch>/en-US;/zh-CN</BlazorWasmPrerenderingUrlPathToExplicitFetch>
<BlazorWasmPrerenderingOutputStyle>AppendHtmlExtension</BlazorWasmPrerenderingOutputStyle>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BlazorWasmPreRendering.Build" Version="4.0.0" />
</ItemGroup>
<ItemGroup>
<EmscriptenEnvVars Include="PYTHONUTF8=1" />
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />

Просмотреть файл

@ -28,9 +28,16 @@ namespace AntDesign.Docs.Wasm
builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddAntDesignDocs();
ConfigureServices(builder.Services, builder.HostEnvironment);
await builder.Build().RunAsync();
}
private static void ConfigureServices(IServiceCollection services, IWebAssemblyHostEnvironment hostEnvironment)
{
services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(hostEnvironment.BaseAddress) });
services.AddAntDesignDocs();
Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", hostEnvironment.Environment);
}
}
}

Просмотреть файл

@ -63,10 +63,11 @@ namespace AntDesign.Docs.Pages
private string _currentPageName;
private string _currentLocale;
protected override void OnInitialized()
protected override async Task OnInitializedAsync()
{
LocalizationService.LanguageChanged += OnLanguageChanged;
NavigationManager.LocationChanged += OnLocationChanged;
await HandleNavigate(onInitialize: true);
}
private void OnLanguageChanged(object sender, CultureInfo args)
@ -100,7 +101,7 @@ namespace AntDesign.Docs.Pages
}
}
private async Task HandleNavigate()
private async Task HandleNavigate(bool onInitialize = false)
{
var fullPageName = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
fullPageName = fullPageName.IndexOf('/') > 0 ? fullPageName.Substring(fullPageName.IndexOf('/') + 1) : fullPageName;
@ -115,21 +116,23 @@ namespace AntDesign.Docs.Pages
return;
}
_currentPageName = fullPageName;
_currentLocale = Locale;
if (fullPageName.Split("/").Length != 2)
{
var menus = await DemoService.GetMenuAsync();
var current = menus.FirstOrDefault(x => x.Url == fullPageName.ToLowerInvariant());
var subPath = current.Children[0].Type == "menuItem" ? current.Children[0].Url : current.Children[0].Children[0].Url;
if (current != null)
if (!onInitialize)
{
NavigationManager.NavigateTo($"{CurrentLanguage}/{subPath}");
var menus = await DemoService.GetMenuAsync();
var current = menus.FirstOrDefault(x => x.Url == fullPageName.ToLowerInvariant());
var subPath = current.Children[0].Type == "menuItem" ? current.Children[0].Url : current.Children[0].Children[0].Url;
if (current != null)
{
NavigationManager.NavigateTo($"{CurrentLanguage}/{subPath}");
}
}
}
else
{
_currentPageName = fullPageName;
_currentLocale = Locale;
_demoComponent = await DemoService.GetComponentAsync($"{fullPageName}");
StateHasChanged();
@ -146,7 +149,7 @@ namespace AntDesign.Docs.Pages
private async Task LoadDemos(string pageName)
{
var showDemos = _demoComponent.DemoList?.Where(x => !x.Debug && !x.Docs.HasValue).OrderBy(x => x.Order) ?? Enumerable.Empty<DemoItem>();
var showDemos = _demoComponent?.DemoList?.Where(x => !x.Debug && !x.Docs.HasValue).OrderBy(x => x.Order) ?? Enumerable.Empty<DemoItem>();
_anchors = showDemos.Select(x => (x.Name, x.Title)).ToArray();
_demos = [];
_filePaths = new() { _filePath };

Просмотреть файл

@ -58,7 +58,11 @@ namespace AntDesign.Docs.Pages
var current = menus.FirstOrDefault(x => x.Url == newUrl);
if (current != null)
{
NavigationManager.NavigateTo($"{CurrentLanguage}/{current.Children[0].Url}");
var hostEnvironment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
if (hostEnvironment != "Prerendering")
{
NavigationManager.NavigateTo($"{CurrentLanguage}/{current.Children[0].Url}");
}
}
}
}

Просмотреть файл

@ -14,6 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection
public static IServiceCollection AddAntDesignDocs(this IServiceCollection services)
{
services.AddAntDesign();
services.AddSingleton<DemoServiceCache>();
services.AddScoped<DemoService>();
services.AddScoped<IconListService>();
services.AddScoped<IPrismHighlighter, PrismHighlighter>();

Просмотреть файл

@ -3,13 +3,11 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Reflection;
using System.Threading.Tasks;
using AntDesign.Docs.Utils;
using AntDesign.Extensions.Localization;
using Microsoft.AspNetCore.Components;
@ -17,23 +15,21 @@ namespace AntDesign.Docs.Services
{
public class DemoService
{
private static ConcurrentCache<string, AsyncLazy<IDictionary<string, DemoComponent>>> _componentCache;
private static ConcurrentCache<string, AsyncLazy<DemoMenuItem[]>> _menuCache;
private static ConcurrentCache<string, AsyncLazy<DemoMenuItem[]>> _demoMenuCache;
private static ConcurrentCache<string, AsyncLazy<DemoMenuItem[]>> _docMenuCache;
private static ConcurrentCache<string, Type> _showCaseCache;
private readonly ILocalizationService _localizationService;
private readonly HttpClient _httpClient;
private readonly DemoServiceCache _cache;
private readonly NavigationManager _navigationManager;
private Uri _baseUrl;
private string CurrentLanguage => _localizationService.CurrentCulture.Name;
public DemoService(ILocalizationService localizationService, HttpClient httpClient, NavigationManager navigationManager)
public DemoService(ILocalizationService localizationService, HttpClient httpClient, DemoServiceCache cache, NavigationManager navigationManager)
{
_localizationService = localizationService;
_httpClient = httpClient;
_cache = cache;
_navigationManager = navigationManager;
_baseUrl = _navigationManager.ToAbsoluteUri(_navigationManager.BaseUri);
Initialize(localizationService.CurrentCulture.Name);
@ -43,33 +39,7 @@ namespace AntDesign.Docs.Services
private void Initialize(string language)
{
_menuCache ??= new();
_menuCache.GetOrAdd(language, (currentLanguage) => new(async () =>
{
var menuItems = await _httpClient.GetFromJsonAsync<DemoMenuItem[]>(new Uri(_baseUrl, $"_content/AntDesign.Docs/meta/menu.{language}.json").ToString());
return menuItems;
}));
_componentCache ??= new();
_componentCache.GetOrAdd(language, (currentLanguage) => new(async () =>
{
var components = await _httpClient.GetFromJsonAsync<DemoComponent[]>(new Uri(_baseUrl, $"_content/AntDesign.Docs/meta/components.{language}.json").ToString());
return components.ToDictionary(x => $"{x.Category.ToLower()}/{x.Title.ToLower()}", x => x);
}));
_demoMenuCache ??= new();
_demoMenuCache.GetOrAdd(language, (currentLanguage) => new(async () =>
{
var menuItems = await _httpClient.GetFromJsonAsync<DemoMenuItem[]>(new Uri(_baseUrl, $"_content/AntDesign.Docs/meta/demos.{language}.json").ToString());
return menuItems;
}));
_docMenuCache ??= new();
_docMenuCache.GetOrAdd(language, (currentLanguage) => new(async () =>
{
var menuItems = await _httpClient.GetFromJsonAsync<DemoMenuItem[]>(new Uri(_baseUrl, $"_content/AntDesign.Docs/meta/docs.{language}.json").ToString());
return menuItems;
}));
_cache.PreFetch(language);
}
//public async Task InitializeDemos()
@ -84,14 +54,12 @@ namespace AntDesign.Docs.Services
public async Task<DemoComponent> GetComponentAsync(string componentName)
{
return _componentCache.TryGetValue(CurrentLanguage, out var component)
? (await component).TryGetValue(componentName.ToLower(), out var demoComponent) ? demoComponent : null
: null;
return await _cache.GetComponentAsync(CurrentLanguage, componentName);
}
public async Task<DemoMenuItem[]> GetMenuAsync()
{
return _menuCache.TryGetValue(CurrentLanguage, out var menuItems) ? await menuItems : Array.Empty<DemoMenuItem>();
return await _cache.GetMenuAsync(CurrentLanguage);
}
public async ValueTask<DemoMenuItem[]> GetCurrentMenuItems()
@ -124,15 +92,15 @@ namespace AntDesign.Docs.Services
if (type.ToLowerInvariant() == "docs")
{
items = _docMenuCache.TryGetValue(CurrentLanguage, out var menuItems) ? (await menuItems).OrderBy(x => x.Order).ToArray() : Array.Empty<DemoMenuItem>();
items = (await _cache.GetDocMenuAsync(CurrentLanguage)).OrderBy(x => x.Order).ToArray();
currentTitle = $"docs/{currentTitle}";
}
else
{
items = _demoMenuCache.TryGetValue(CurrentLanguage, out var menuItems) ? (await menuItems)
items = (await _cache.GetDemoMenuAsync(CurrentLanguage))
.OrderBy(x => x.Order)
.SelectMany(x => x.Children)
.ToArray() : Array.Empty<DemoMenuItem>();
.ToArray();
currentTitle = $"components/{currentTitle}";
}

Просмотреть файл

@ -0,0 +1,73 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using AntDesign.Docs.Utils;
namespace AntDesign.Docs.Services
{
public class DemoServiceCache
{
private readonly ConcurrentCache<string, AsyncLazy<IDictionary<string, DemoComponent>>> _componentCache = new();
private readonly ConcurrentCache<string, AsyncLazy<DemoMenuItem[]>> _menuCache = new();
private readonly ConcurrentCache<string, AsyncLazy<DemoMenuItem[]>> _demoMenuCache = new();
private readonly ConcurrentCache<string, AsyncLazy<DemoMenuItem[]>> _docMenuCache = new();
private readonly HttpClient _httpClient;
public DemoServiceCache(HttpClient httpClient)
{
_httpClient = httpClient;
}
public void PreFetch(string language)
{
PreFetchDemoMenuItems(_menuCache, language, lang => $"_content/AntDesign.Docs/meta/menu.{lang}.json");
PreFetchDemoMenuItems(_demoMenuCache, language, lang => $"_content/AntDesign.Docs/meta/demos.{lang}.json");
PreFetchDemoMenuItems(_docMenuCache, language, lang => $"_content/AntDesign.Docs/meta/docs.{lang}.json");
_componentCache.GetOrAdd(language, (lang) => new(async () =>
{
var components = await _httpClient.GetFromJsonAsync<DemoComponent[]>($"_content/AntDesign.Docs/meta/components.{lang}.json");
return components.ToDictionary(x => $"{x.Category.ToLower()}/{x.Title.ToLower()}", x => x);
}));
}
private async void PreFetchDemoMenuItems(ConcurrentCache<string, AsyncLazy<DemoMenuItem[]>> cache, string language, Func<string, string> srcUrl)
{
cache.GetOrAdd(language, (lang) => new(async () =>
{
var items = await _httpClient.GetFromJsonAsync<DemoMenuItem[]>(srcUrl(lang));
return items;
}));
}
public async Task<DemoMenuItem[]> GetMenuAsync(string language)
{
return _menuCache.TryGetValue(language, out var menuItems) ? await menuItems : Array.Empty<DemoMenuItem>();
}
public async Task<DemoMenuItem[]> GetDemoMenuAsync(string language)
{
return _demoMenuCache.TryGetValue(language, out var menuItems) ? await menuItems : Array.Empty<DemoMenuItem>();
}
public async Task<DemoMenuItem[]> GetDocMenuAsync(string language)
{
return _docMenuCache.TryGetValue(language, out var menuItems) ? await menuItems : Array.Empty<DemoMenuItem>();
}
public async Task<DemoComponent> GetComponentAsync(string language, string componentName)
{
return _componentCache.TryGetValue(language, out var component)
? (await component).TryGetValue(componentName.ToLower(), out var demoComponent) ? demoComponent : null
: null;
}
}
}