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:
Родитель
7393fbf4ac
Коммит
6c624ec06c
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче