fix, feat, doc: massive build system refactorion, bug fixes and apex documentation (#19)
This commit is contained in:
1 parent
e8b42c60c2
commit
91f5245823
165 files changed
+8598
-1205
No files matched your search
@@ -0,0 +1,19 @@
|
||||
import { defineCli, env, s3SyncCommand } from '@ermnvldmr/cli';
|
||||
|
||||
export default defineCli({
|
||||
commands: [
|
||||
s3SyncCommand({
|
||||
name: 'sync:static',
|
||||
description: 'Sync static assets to S3',
|
||||
targetPrefix: 'www',
|
||||
bucket: env('COM_ERMNVLDMR_STATIC_S3_BUCKET'),
|
||||
region: env('COM_ERMNVLDMR_STATIC_S3_REGION'),
|
||||
endpoint: env('COM_ERMNVLDMR_STATIC_S3_ENDPOINT_URL'),
|
||||
accessKeyId: env('COM_ERMNVLDMR_STATIC_AWS_ACCESS_KEY_ID'),
|
||||
secretAccessKey: env('COM_ERMNVLDMR_STATIC_AWS_SECRET_ACCESS_KEY'),
|
||||
hooks: {
|
||||
beforeSync: 'bash ../../ci/scripts/optimize-images.sh',
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -4,6 +4,8 @@
|
||||
"description": "www.ermnvldmr.com website",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"cli": "ermnvldmr-cli",
|
||||
"sync:static": "pnpm cli sync:static",
|
||||
"dev": "rsbuild dev",
|
||||
"build": "rsbuild build",
|
||||
"build:en": "LOCALE=en rsbuild build",
|
||||
@@ -27,6 +29,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ermnvldmr/i18n": "workspace:*",
|
||||
"@ermnvldmr/ssg": "workspace:*",
|
||||
"@ermnvldmr/stl": "workspace:*",
|
||||
"@ermnvldmr/ui": "workspace:*",
|
||||
"lucide-react": "^0.503.0",
|
||||
@@ -35,6 +38,7 @@
|
||||
"tailwindcss": "^4.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ermnvldmr/cli": "workspace:*",
|
||||
"@ermnvldmr/eslint-config": "workspace:*",
|
||||
"@ermnvldmr/rsbuild-config": "workspace:*",
|
||||
"@ermnvldmr/storybook-config": "workspace:*",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { defineServiceConfig, discoverEntries, mergeConfig } from '@ermnvldmr/rsbuild-config/dev';
|
||||
import { createReadStream, existsSync } from 'node:fs';
|
||||
import { extname, join, sep } from 'node:path';
|
||||
import { defineSSGServiceConfig, discoverEntries, mergeConfig } from '@ermnvldmr/ssg/dev';
|
||||
import { THEME_INIT_SCRIPT, THEME_INIT_STYLES } from '@ermnvldmr/ui/dev';
|
||||
import { localeRsbuildConfig } from '@ermnvldmr/i18n/dev';
|
||||
import { RsdoctorRspackPlugin } from '@rsdoctor/rspack-plugin';
|
||||
@@ -6,11 +8,56 @@ import { pluginMdx } from '@rsbuild/plugin-mdx';
|
||||
import remarkFrontmatter from 'remark-frontmatter';
|
||||
import remarkMdxFrontmatter from 'remark-mdx-frontmatter';
|
||||
|
||||
const MIME: Record<string, string> = {
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.png': 'image/png',
|
||||
'.gif': 'image/gif',
|
||||
'.webp': 'image/webp',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.avif': 'image/avif',
|
||||
'.ico': 'image/x-icon',
|
||||
'.woff': 'font/woff',
|
||||
'.woff2': 'font/woff2',
|
||||
'.ttf': 'font/ttf',
|
||||
'.css': 'text/css',
|
||||
'.js': 'application/javascript',
|
||||
'.json': 'application/json',
|
||||
'.txt': 'text/plain',
|
||||
};
|
||||
|
||||
const locale = process.env.LOCALE ?? 'en';
|
||||
const fontBase = `/${locale}/static/font`;
|
||||
|
||||
export default defineServiceConfig(
|
||||
export default defineSSGServiceConfig(
|
||||
mergeConfig(localeRsbuildConfig(), {
|
||||
dev: {
|
||||
// Serve public/static/* at /static/* directly, bypassing server.base.
|
||||
// Without this, server.base (e.g. /en) would push the public dir to
|
||||
// /en/static/*, making /static/* unreachable in dev.
|
||||
setupMiddlewares: [
|
||||
(middlewares) => {
|
||||
middlewares.unshift((req, res, next) => {
|
||||
const url = (req as { url?: string }).url ?? '';
|
||||
if (!url.startsWith('/static/')) return next();
|
||||
|
||||
// Strip the leading slash so the '/static/...' segment is treated as
|
||||
// relative to public/ (an absolute segment would otherwise let a
|
||||
// resolved path escape it), then confine the result to public/ to
|
||||
// block '..' path traversal.
|
||||
const publicDir = join(import.meta.dirname, 'public');
|
||||
const requestedPath = (url.split('?')[0] ?? url).replace(/^\/+/, '');
|
||||
const filePath = join(publicDir, requestedPath);
|
||||
if (!filePath.startsWith(publicDir + sep) || !existsSync(filePath)) return next();
|
||||
|
||||
const mime = MIME[extname(filePath)] ?? 'application/octet-stream';
|
||||
res.setHeader('Content-Type', mime);
|
||||
createReadStream(filePath).pipe(res as import('node:stream').Writable);
|
||||
});
|
||||
return middlewares;
|
||||
},
|
||||
],
|
||||
},
|
||||
tools: {
|
||||
rspack: {
|
||||
plugins: [
|
||||
@@ -31,6 +78,7 @@ export default defineServiceConfig(
|
||||
],
|
||||
source: {
|
||||
entry: discoverEntries(import.meta.dirname, './src/app/**/*.tsx'),
|
||||
preEntry: ['./src/static/global.css'],
|
||||
},
|
||||
html: {
|
||||
tags: [
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { localePath } from '@ermnvldmr/i18n';
|
||||
import { createPage } from '@ermnvldmr/ssg';
|
||||
import { MARKDOWN_COMPONENTS } from '@ermnvldmr/ui';
|
||||
import React from 'react';
|
||||
|
||||
import { articles } from '../../../../content/registry';
|
||||
import { ArticleLayout } from '../../../components/Layout/ArticleLayout';
|
||||
import { createPage } from '../../../lib/core/createPage';
|
||||
|
||||
const article = articles['joplin-web-deployment-guide'];
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { localePath } from '@ermnvldmr/i18n';
|
||||
import { createPage } from '@ermnvldmr/ssg';
|
||||
import { MARKDOWN_COMPONENTS } from '@ermnvldmr/ui';
|
||||
import React from 'react';
|
||||
|
||||
import { articles } from '../../../../content/registry';
|
||||
import { ArticleLayout } from '../../../components/Layout/ArticleLayout';
|
||||
import { createPage } from '../../../lib/core/createPage';
|
||||
|
||||
const article = articles['matrix-server-deployment-guide'];
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { localePath } from '@ermnvldmr/i18n';
|
||||
import { createPage } from '@ermnvldmr/ssg';
|
||||
import { MARKDOWN_COMPONENTS } from '@ermnvldmr/ui';
|
||||
import React from 'react';
|
||||
|
||||
import { articles } from '../../../../content/registry';
|
||||
import { ArticleLayout } from '../../../components/Layout/ArticleLayout';
|
||||
import { createPage } from '../../../lib/core/createPage';
|
||||
|
||||
const article = articles['test-article'];
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { localePath } from '@ermnvldmr/i18n';
|
||||
import { createPage } from '@ermnvldmr/ssg';
|
||||
import { MARKDOWN_COMPONENTS } from '@ermnvldmr/ui';
|
||||
import React from 'react';
|
||||
|
||||
import { articles } from '../../../../content/registry';
|
||||
import { ArticleLayout } from '../../../components/Layout/ArticleLayout';
|
||||
import { createPage } from '../../../lib/core/createPage';
|
||||
|
||||
const article = articles['xray-core-subnet-mimicry'];
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { LOCALE, localePath } from '@ermnvldmr/i18n';
|
||||
import { Article, Breadcrumbs } from '@ermnvldmr/ui';
|
||||
import { createPage } from '@ermnvldmr/ssg';
|
||||
import { BentoGrid, Breadcrumbs, HStack, Separator, Text, Time, VStack } from '@ermnvldmr/ui';
|
||||
import React from 'react';
|
||||
|
||||
import { t } from './index.i18n';
|
||||
import { sortedArticles } from '../../../content/registry';
|
||||
import { DefaultLayout } from '../../components/Layout/DefaultLayout';
|
||||
import { createPage } from '../../lib/core/createPage';
|
||||
import { SITE_TITLE } from '../../lib/shared/constants';
|
||||
|
||||
const DATE_LOCALE_MAP: Record<string, string> = { en: 'en-US', ru: 'ru-RU' };
|
||||
@@ -35,28 +35,37 @@ function ArticlesList(): React.JSX.Element {
|
||||
paddingY="medium"
|
||||
title={t('Articles')}
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-2">
|
||||
{sortedArticles.map((article) => (
|
||||
<React.Fragment key={article.slug}>
|
||||
<Article
|
||||
additionalText={article.tags?.join(' • ')}
|
||||
className="h-full"
|
||||
headline={article.title}
|
||||
href={localePath(`/articles/${article.slug}`)}
|
||||
subHeadline={article.createdDate.toLocaleDateString(
|
||||
DATE_LOCALE_MAP[LOCALE] ?? 'en-US',
|
||||
{
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
}
|
||||
)}
|
||||
>
|
||||
{article.description}
|
||||
</Article>
|
||||
</React.Fragment>
|
||||
<BentoGrid>
|
||||
{sortedArticles.map((article, index) => (
|
||||
<BentoGrid.InfoCard
|
||||
key={article.slug}
|
||||
colSpan={index === 0 ? 2 : 1}
|
||||
description={article.description}
|
||||
footer={
|
||||
<VStack align="stretch" gap={2}>
|
||||
<Separator thinned="thinned-end" type="single" />
|
||||
<HStack align="center" gap={4} justify="between" wrap="wrap">
|
||||
<Time
|
||||
color="muted"
|
||||
date={article.createdDate}
|
||||
locale={DATE_LOCALE_MAP[LOCALE] ?? 'en-US'}
|
||||
size="s"
|
||||
type="label"
|
||||
/>
|
||||
{article.tags?.length ? (
|
||||
<Text italic color="muted" size="s" type="label">
|
||||
{article.tags.join(' • ')}
|
||||
</Text>
|
||||
) : null}
|
||||
</HStack>
|
||||
</VStack>
|
||||
}
|
||||
href={localePath(`/articles/${article.slug}`)}
|
||||
rowSpan={index === 0 ? 2 : 1}
|
||||
title={article.title}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</BentoGrid>
|
||||
</DefaultLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { localePath } from '@ermnvldmr/i18n';
|
||||
import { createPage } from '@ermnvldmr/ssg';
|
||||
import { Paragraph, Link, VStack, Header } from '@ermnvldmr/ui';
|
||||
import React from 'react';
|
||||
|
||||
import { t, tRaw } from './index.i18n';
|
||||
import { DefaultLayout } from '../components/Layout/DefaultLayout';
|
||||
import { createPage } from '../lib/core/createPage';
|
||||
import { SITE_TITLE } from '../lib/shared/constants';
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,38 +5,43 @@ export const { t } = createKeyset({
|
||||
en: 'Wanted',
|
||||
ru: 'Разыскивается',
|
||||
},
|
||||
Home: {
|
||||
en: 'Home',
|
||||
ru: 'Главная'
|
||||
},
|
||||
'A quiet log of things I am planning to pick up eventually. It serves mostly as a personal reference, but also as a zero-pressure guide for friends.': {
|
||||
en: 'A quiet log of things I am planning to pick up eventually. It serves mostly as a personal reference, but also as a zero-pressure guide for friends.',
|
||||
ru: 'Список вещей, которые я планирую со временем приобрести. В основном это личная заметка, но также и ненавязчивый вишлист.',
|
||||
Home: {
|
||||
en: 'Home',
|
||||
ru: 'Главная',
|
||||
},
|
||||
'A quiet log of things I am planning to pick up eventually. It serves mostly as a personal reference, but also as a zero-pressure guide for friends.':
|
||||
{
|
||||
en: 'A quiet log of things I am planning to pick up eventually. It serves mostly as a personal reference, but also as a zero-pressure guide for friends.',
|
||||
ru: 'Список вещей, которые я планирую со временем приобрести. В основном это личная заметка, но также и ненавязчивый вишлист.',
|
||||
},
|
||||
'How this works': {
|
||||
en: 'How this works',
|
||||
ru: 'Как это работает',
|
||||
},
|
||||
'Think of this as a living list of things I plan to acquire over time. If you ever feel inspired to contribute, I would be genuinely thrilled, but please know it is never expected.': {
|
||||
en: 'Think of this as a living list of things I plan to acquire over time. If you ever feel inspired to contribute, I would be genuinely thrilled, but please know it is never expected.',
|
||||
ru: 'Считайте это живым списком вещей, которые я планирую приобрести. Если у вас когда-нибудь появится желание поучаствовать — я буду искренне рад, но, пожалуйста, знайте, что это абсолютно необязательно.',
|
||||
},
|
||||
'When it comes to the items themselves, authenticity and quality matter a lot to me. I would always prefer to have nothing at all rather than a replica or a knock-off.': {
|
||||
en: 'When it comes to the items themselves, authenticity and quality matter a lot to me. I would always prefer to have nothing at all rather than a replica or a knock-off.',
|
||||
ru: 'Когда дело доходит до самих вещей, подлинность и качество значат для меня очень много. Я всегда предпочту не иметь ничего, чем реплику или подделку.',
|
||||
},
|
||||
'And of course, if you are ever unsure about a specific edition or detail, we can always just talk about it.': {
|
||||
en: 'And of course, if you are ever unsure about a specific edition or detail, we can always just talk about it.',
|
||||
ru: 'И, конечно, если вы не уверены в каком-то конкретном издании или деталях, можно всегда это обсудить.',
|
||||
},
|
||||
'Figurines': {
|
||||
'Think of this as a living list of things I plan to acquire over time. If you ever feel inspired to contribute, I would be genuinely thrilled, but please know it is never expected.':
|
||||
{
|
||||
en: 'Think of this as a living list of things I plan to acquire over time. If you ever feel inspired to contribute, I would be genuinely thrilled, but please know it is never expected.',
|
||||
ru: 'Считайте это живым списком вещей, которые я планирую приобрести. Если у вас когда-нибудь появится желание поучаствовать — я буду искренне рад, но, пожалуйста, знайте, что это абсолютно необязательно.',
|
||||
},
|
||||
'When it comes to the items themselves, authenticity and quality matter a lot to me. I would always prefer to have nothing at all rather than a replica or a knock-off.':
|
||||
{
|
||||
en: 'When it comes to the items themselves, authenticity and quality matter a lot to me. I would always prefer to have nothing at all rather than a replica or a knock-off.',
|
||||
ru: 'Когда дело доходит до самих вещей, подлинность и качество значат для меня очень много. Я всегда предпочту не иметь ничего, чем реплику или подделку.',
|
||||
},
|
||||
'And of course, if you are ever unsure about a specific edition or detail, we can always just talk about it.':
|
||||
{
|
||||
en: 'And of course, if you are ever unsure about a specific edition or detail, we can always just talk about it.',
|
||||
ru: 'И, конечно, если вы не уверены в каком-то конкретном издании или деталях, можно всегда это обсудить.',
|
||||
},
|
||||
Figurines: {
|
||||
en: 'Figurines',
|
||||
ru: 'Фигурки',
|
||||
},
|
||||
'The specifics of the edition and its authenticity are quite important here. It is usually best to know the exact details to get it right.': {
|
||||
en: 'The specifics of the edition and its authenticity are quite important here. It is usually best to know the exact details to get it right.',
|
||||
ru: 'Специфика издания и его подлинность здесь довольно важны. Обычно лучше знать точные детали, чтобы не ошибиться.',
|
||||
},
|
||||
'The specifics of the edition and its authenticity are quite important here. It is usually best to know the exact details to get it right.':
|
||||
{
|
||||
en: 'The specifics of the edition and its authenticity are quite important here. It is usually best to know the exact details to get it right.',
|
||||
ru: 'Специфика издания и его подлинность здесь довольно важны. Обычно лучше знать точные детали, чтобы не ошибиться.',
|
||||
},
|
||||
'Looking for': {
|
||||
en: 'Looking for',
|
||||
ru: 'Ищу',
|
||||
@@ -61,10 +66,11 @@ export const { t } = createKeyset({
|
||||
en: 'LEGO Sets',
|
||||
ru: 'Наборы LEGO',
|
||||
},
|
||||
'I appreciate sets that focus on engineering and make for good display pieces, like Architecture, Technic, or the Botanical collection. I generally lean away from traditional play-sets.': {
|
||||
en: 'I appreciate sets that focus on engineering and make for good display pieces, like Architecture, Technic, or the Botanical collection. I generally lean away from traditional play-sets.',
|
||||
ru: 'Я ценю наборы, ориентированные на инженерию, которые хорошо смотрятся на полке — например, Architecture, Technic или Botanical. Обычно я избегаю традиционных игровых наборов (friends, city, и другие подобные).',
|
||||
},
|
||||
'I appreciate sets that focus on engineering and make for good display pieces, like Architecture, Technic, or the Botanical collection. I generally lean away from traditional play-sets.':
|
||||
{
|
||||
en: 'I appreciate sets that focus on engineering and make for good display pieces, like Architecture, Technic, or the Botanical collection. I generally lean away from traditional play-sets.',
|
||||
ru: 'Я ценю наборы, ориентированные на инженерию, которые хорошо смотрятся на полке — например, Architecture, Technic или Botanical. Обычно я избегаю традиционных игровых наборов (friends, city, и другие подобные).',
|
||||
},
|
||||
'Architectural landmarks or skylines': {
|
||||
en: 'Architectural landmarks or skylines',
|
||||
ru: 'Архитектурные наборы',
|
||||
@@ -93,10 +99,11 @@ export const { t } = createKeyset({
|
||||
en: 'Die-cast Models',
|
||||
ru: 'Дайкаст модели',
|
||||
},
|
||||
'I enjoy high-quality pre-assembled die-cast models. I am not much into plastic kits that require glue or paint to assemble.': {
|
||||
en: 'I enjoy high-quality pre-assembled die-cast models. I am not much into plastic kits that require glue or paint to assemble.',
|
||||
ru: 'Мне нравятся качественные предварительно собранные дайкаст модели. Я не особо увлекаюсь пластиковыми наборами, которые нужно собирать с помощью клея и красок.',
|
||||
},
|
||||
'I enjoy high-quality pre-assembled die-cast models. I am not much into plastic kits that require glue or paint to assemble.':
|
||||
{
|
||||
en: 'I enjoy high-quality pre-assembled die-cast models. I am not much into plastic kits that require glue or paint to assemble.',
|
||||
ru: 'Мне нравятся качественные предварительно собранные дайкаст модели. Я не особо увлекаюсь пластиковыми наборами, которые нужно собирать с помощью клея и красок.',
|
||||
},
|
||||
'Spacecraft scale replicas (SpaceX, NASA)': {
|
||||
en: 'Spacecraft scale replicas (SpaceX, NASA)',
|
||||
ru: 'Масштабные модели космических аппаратов (SpaceX, NASA)',
|
||||
@@ -117,11 +124,12 @@ export const { t } = createKeyset({
|
||||
en: 'Coffee gear',
|
||||
ru: 'Всё для кофе',
|
||||
},
|
||||
'My current espresso setup uses a 51mm portafilter, so any accessories for it need to match that size perfectly.': {
|
||||
en: 'My current espresso setup uses a 51mm portafilter, so any accessories for it need to match that size perfectly.',
|
||||
ru: 'Мой текущий сетап для эспрессо использует 51-миллиметровый портафильтр, поэтому любые аксессуары для него должны идеально подходить по размеру.',
|
||||
},
|
||||
'AeroPress': {
|
||||
'My current espresso setup uses a 51mm portafilter, so any accessories for it need to match that size perfectly.':
|
||||
{
|
||||
en: 'My current espresso setup uses a 51mm portafilter, so any accessories for it need to match that size perfectly.',
|
||||
ru: 'Мой текущий сетап для эспрессо использует 51-миллиметровый портафильтр, поэтому любые аксессуары для него должны идеально подходить по размеру.',
|
||||
},
|
||||
AeroPress: {
|
||||
en: 'AeroPress',
|
||||
ru: 'AeroPress',
|
||||
},
|
||||
@@ -153,7 +161,7 @@ export const { t } = createKeyset({
|
||||
en: 'French Press',
|
||||
ru: 'Френч-пресс',
|
||||
},
|
||||
'Hobby': {
|
||||
Hobby: {
|
||||
en: 'Hobby',
|
||||
ru: 'Хобби',
|
||||
},
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { localePath } from '@ermnvldmr/i18n';
|
||||
import { createPage } from '@ermnvldmr/ssg';
|
||||
import { BentoGrid, Header, Paragraph, VStack, Breadcrumbs } from '@ermnvldmr/ui';
|
||||
import { Ghost, Coffee, Plane, Check, Gift, Cuboid, BoxSelect, Cpu } from 'lucide-react';
|
||||
|
||||
import { t } from './index.i18n';
|
||||
import { DefaultLayout } from '../../components/Layout/DefaultLayout';
|
||||
import { createPage } from '../../lib/core/createPage';
|
||||
|
||||
/**
|
||||
* Wanted page component.
|
||||
@@ -189,9 +189,7 @@ function WantedPage(): React.JSX.Element {
|
||||
value: 'wanted',
|
||||
label: t('Looking for'),
|
||||
labelIcon: <Gift size={18} />,
|
||||
items: [
|
||||
t('Alientek T90 Soldering Iron (or a similar compact, smart iron)'),
|
||||
],
|
||||
items: [t('Alientek T90 Soldering Iron (or a similar compact, smart iron)')],
|
||||
},
|
||||
]}
|
||||
title={t('Hobby')}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import React from 'react';
|
||||
import { createRoot, hydrateRoot } from 'react-dom/client';
|
||||
import '../../static/global.css'; // Ensure global styles are available
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface PageOptions {
|
||||
title?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a React page component with hydration support.
|
||||
*
|
||||
* @param Component - The React component to render.
|
||||
* @param options - Page options like title.
|
||||
* @returns The page component.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* createPage(<App />);
|
||||
* ```
|
||||
*/
|
||||
export function createPage(Component: React.ComponentType, options: PageOptions = {}): void {
|
||||
if (typeof window === 'undefined') return; // SSG build context: skip DOM mounting
|
||||
const rootElement = document.getElementById('root');
|
||||
|
||||
if (options.title) {
|
||||
document.title = options.title;
|
||||
}
|
||||
|
||||
if (options.description) {
|
||||
let metaDescription = document.querySelector('meta[name="description"]');
|
||||
if (!metaDescription) {
|
||||
metaDescription = document.createElement('meta');
|
||||
metaDescription.setAttribute('name', 'description');
|
||||
document.head.appendChild(metaDescription);
|
||||
}
|
||||
metaDescription.setAttribute('content', options.description);
|
||||
}
|
||||
|
||||
if (rootElement) {
|
||||
const node = (
|
||||
<React.StrictMode>
|
||||
<Component />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
if (rootElement.hasChildNodes()) {
|
||||
hydrateRoot(rootElement, node);
|
||||
} else {
|
||||
createRoot(rootElement).render(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"#*": ["./*"],
|
||||
"@ermnvldmr/ssg": ["../../../packages/ssg/src"],
|
||||
"@ermnvldmr/ssg/*": ["../../../packages/ssg/src/*"],
|
||||
"@ermnvldmr/stl": ["../../../packages/stl/src"],
|
||||
"@ermnvldmr/ui": ["../../../packages/ui/src"],
|
||||
"@ermnvldmr/ui/*": ["../../../packages/ui/src/*"]
|
||||
|
||||
Reference in new issue
Block a user