chore: experimental vinext build (#3486)

This commit is contained in:
Huang Xin
2026-03-07 01:04:28 +08:00
committed by GitHub
parent 54bc1514df
commit dac4f2e8ec
18 changed files with 971 additions and 525 deletions
+7 -12
View File
@@ -70,11 +70,6 @@ jobs:
run: |
pnpm format:check || (pnpm format && git diff && exit 1)
- name: run tests
working-directory: apps/readest-app
run: |
pnpm test -- --watch=false
- name: install playwright browsers
if: matrix.config.platform == 'web'
working-directory: apps/readest-app
@@ -91,6 +86,11 @@ jobs:
with:
toolchain: stable
- if: matrix.config.platform == 'tauri'
uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.config.platform }}-cargo
- name: install system dependencies (tauri)
if: matrix.config.platform == 'tauri'
run: |
@@ -103,18 +103,13 @@ jobs:
run: xvfb-run pnpm test:pr:tauri
- name: run lint
if: matrix.config.platform == 'web'
working-directory: apps/readest-app
run: |
pnpm lint
- name: build the web App
- name: build the web app
if: matrix.config.platform == 'web'
working-directory: apps/readest-app
run: |
pnpm build-web && pnpm check:all
- name: build the Tauri App
if: matrix.config.platform == 'tauri'
working-directory: apps/readest-app
run: |
pnpm build && pnpm check:all
+1
View File
@@ -60,3 +60,4 @@ src-tauri/gen
/public/fallback-*.js
/public/swe-worker-*.js
/dist/
+5
View File
@@ -11,6 +11,7 @@ const eslintConfig = defineConfig([
{
rules: {
...jsxA11y.configs.recommended.rules,
'@next/next/no-img-element': 'off',
'@typescript-eslint/no-unused-vars': [
'warn',
{
@@ -25,10 +26,14 @@ const eslintConfig = defineConfig([
'node_modules/**',
'.next/**',
'.open-next/**',
'.wrangler/**',
'dist/**',
'out/**',
'build/**',
'public/**',
'src-tauri/**',
'next-env.d.ts',
'i18next-scanner.config.cjs',
]),
]);
@@ -0,0 +1,63 @@
const options = {
debug: false,
sort: false,
func: {
list: ['_'],
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
lngs: [
'de',
'ja',
'es',
'fa',
'fr',
'it',
'el',
'ko',
'uk',
'nl',
'sl',
'sv',
'pl',
'pt',
'ru',
'tr',
'hi',
'id',
'vi',
'ms',
'he',
'ar',
'th',
'bo',
'bn',
'ta',
'si',
'zh-CN',
'zh-TW',
],
ns: ['translation'],
defaultNs: 'translation',
defaultValue: '__STRING_NOT_TRANSLATED__',
resource: {
loadPath: './public/locales/{{lng}}/{{ns}}.json',
savePath: './public/locales/{{lng}}/{{ns}}.json',
jsonIndent: 2,
lineEnding: '\n',
},
keySeparator: false,
nsSeparator: false,
interpolation: {
prefix: '{{',
suffix: '}}',
},
metadata: {},
allowDynamicKeys: true,
removeUnusedKeys: true,
};
module.exports = {
input: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.test.{js,jsx,ts,tsx}'],
output: '.',
options,
};
@@ -1,61 +0,0 @@
module.exports = {
input: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.test.{js,jsx,ts,tsx}'],
output: '.',
options: {
debug: false,
sort: false,
func: {
list: ['_'],
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
lngs: [
'de',
'ja',
'es',
'fa',
'fr',
'it',
'el',
'ko',
'uk',
'nl',
'sl',
'sv',
'pl',
'pt',
'ru',
'tr',
'hi',
'id',
'vi',
'ms',
'he',
'ar',
'th',
'bo',
'bn',
'ta',
'si',
'zh-CN',
'zh-TW',
],
ns: ['translation'],
defaultNs: 'translation',
defaultValue: '__STRING_NOT_TRANSLATED__',
resource: {
loadPath: './public/locales/{{lng}}/{{ns}}.json',
savePath: './public/locales/{{lng}}/{{ns}}.json',
jsonIndent: 2,
lineEnding: '\n',
},
keySeparator: false,
nsSeparator: false,
interpolation: {
prefix: '{{',
suffix: '}}',
},
metadata: {},
allowDynamicKeys: true,
removeUnusedKeys: true,
},
};
+8
View File
@@ -59,6 +59,14 @@ const nextConfig = {
'marked',
]),
],
async rewrites() {
return [
{
source: '/reader/:ids',
destination: '/reader?ids=:ids',
},
];
},
async headers() {
return [
{
+11 -3
View File
@@ -2,6 +2,7 @@
"name": "@readest/readest-app",
"version": "0.9.101",
"private": true,
"type": "module",
"scripts": {
"dev": "dotenv -e .env.tauri -- next dev",
"build": "dotenv -e .env.tauri -- next build",
@@ -9,8 +10,11 @@
"dev-web": "dotenv -e .env.web -- next dev",
"build-web": "dotenv -e .env.web -- next build",
"start-web": "dotenv -e .env.web -- next start",
"dev-web:vinext": "dotenv -e .env.web -- vinext dev --port 3000",
"build-web:vinext": "dotenv -e .env.web -- vinext build",
"start-web:vinext": "dotenv -e .env.web -- vinext start",
"build-tauri": "dotenv -e .env.tauri -- next build",
"i18n:extract": "i18next-scanner",
"i18n:extract": "i18next-scanner --config i18next-scanner.config.cjs",
"lint": "eslint .",
"test": "dotenv -e .env -e .env.test.local vitest",
"test:browser": "vitest --config vitest.browser.config.mts --watch=false",
@@ -146,9 +150,9 @@
"overlayscrollbars": "^2.11.4",
"overlayscrollbars-react": "^0.5.6",
"posthog-js": "^1.246.0",
"react": "19.2.0",
"react": "19.2.4",
"react-color": "^2.19.3",
"react-dom": "19.2.0",
"react-dom": "19.2.4",
"react-i18next": "^15.2.0",
"react-icons": "^5.4.0",
"react-responsive": "^10.0.0",
@@ -191,6 +195,7 @@
"@typescript-eslint/eslint-plugin": "^8.48.0",
"@typescript-eslint/parser": "^8.48.0",
"@vitejs/plugin-react": "^5.1.1",
"@vitejs/plugin-rsc": "^0.5.21",
"@vitest/browser-playwright": "^4.0.18",
"@vitest/browser-webdriverio": "^4.0.18",
"@wdio/cli": "^9.24.0",
@@ -216,9 +221,12 @@
"postcss-cli": "^11.0.0",
"postcss-nested": "^7.0.2",
"raw-loader": "^4.0.2",
"react-server-dom-webpack": "^19.2.4",
"serwist": "^9.3.0",
"tailwindcss": "^3.4.18",
"typescript": "^5.7.2",
"vinext": "^0.0.21",
"vite": "^7.3.1",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^4.0.18",
"wrangler": "^4.60.0"
@@ -1,6 +1,5 @@
import clsx from 'clsx';
import React, { useState, useRef, useEffect } from 'react';
import Image from 'next/image';
import { IoChevronBack, IoChevronForward } from 'react-icons/io5';
import { useTranslation } from '@/hooks/useTranslation';
import { Insets } from '@/types/misc';
@@ -439,7 +438,8 @@ const ImageViewer: React.FC<ImageViewerProps> = ({
className={clsx('relative flex h-full w-full items-center justify-center overflow-hidden')}
onClick={handleContainerClick}
>
<Image
<img
role='none'
src={decodeURIComponent(src)}
ref={imageRef}
alt={_('Zoomed')}
@@ -1,6 +1,5 @@
import clsx from 'clsx';
import React from 'react';
import Image from 'next/image';
import { MdCheck } from 'react-icons/md';
import { useRouter } from 'next/navigation';
@@ -144,7 +143,7 @@ const BookMenu: React.FC<BookMenuProps> = ({ menuClassName, setIsDropdownOpen })
<MenuItem
key={book.hash}
Icon={
<Image
<img
src={book.coverImageUrl!}
alt={book.title}
width={56}
@@ -20,7 +20,7 @@ const useBooksManager = () => {
useEffect(() => {
if (shouldUpdateSearchParams) {
const ids = bookKeys.map((key) => key.split('-')[0]!);
if (ids) {
if (ids.length > 0) {
navigateToReader(router, ids, searchParams?.toString() || '', { scroll: false });
}
setShouldUpdateSearchParams(false);
@@ -1,5 +1,4 @@
import clsx from 'clsx';
import Image from 'next/image';
import { memo, useEffect, useRef, useState } from 'react';
import { Book } from '@/types/book';
import { LibraryCoverFitType, LibraryViewModeType } from '@/types/settings';
@@ -70,10 +69,9 @@ const BookCover: React.FC<BookCoverProps> = memo<BookCoverProps>(
>
{coverFit === 'crop' ? (
<>
<Image
<img
src={book.metadata?.coverImageUrl || book.coverImageUrl!}
alt={book.title}
fill={true}
className={clsx('cover-image crop-cover-img object-cover', imageClassName)}
onLoad={handleImageLoad}
onError={handleImageError}
@@ -90,7 +88,7 @@ const BookCover: React.FC<BookCoverProps> = memo<BookCoverProps>(
mode === 'grid' ? 'items-end' : 'items-center',
)}
>
<Image
<img
src={book.metadata?.coverImageUrl || book.coverImageUrl!}
alt={book.title}
width={0}
@@ -1,6 +1,5 @@
'use client';
import Image from 'next/image';
import { useState, useEffect, memo } from 'react';
interface CachedImageProps {
@@ -120,11 +119,11 @@ const CachedImageComponent = ({
}
if (fill) {
return <Image src={cachedUrl} alt={alt} fill className={className} sizes={sizes} />;
return <img src={cachedUrl} alt={alt} className={className} sizes={sizes} />;
}
return (
<Image
<img
src={cachedUrl}
alt={alt}
width={width}
@@ -1,5 +1,4 @@
import clsx from 'clsx';
import Image from 'next/image';
import { useEffect, useState } from 'react';
import { IconType } from 'react-icons';
@@ -68,7 +67,7 @@ const UserAvatar: React.FC<UserAvatarProps> = ({
>
{url ? (
<div>
<Image
<img
src={cachedImageUrl || url}
alt='User Avatar'
className={clsx('rounded-full', className, borderClassName)}
+36 -4
View File
@@ -1,7 +1,39 @@
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
import { options } from '../../i18next-scanner.config';
// Keep in sync with i18next-scanner.config.cjs
const SUPPORTED_LNGS = [
'en',
'de',
'ja',
'es',
'fa',
'fr',
'it',
'el',
'ko',
'uk',
'nl',
'sl',
'sv',
'pl',
'pt',
'ru',
'tr',
'hi',
'id',
'vi',
'ms',
'he',
'ar',
'th',
'bo',
'bn',
'ta',
'si',
'zh-CN',
'zh-TW',
];
const isBrowser = typeof window !== 'undefined';
@@ -15,7 +47,7 @@ const initI18n = async () => {
.use(LanguageDetector)
.use(initReactI18next)
.init({
supportedLngs: ['en', ...options.lngs],
supportedLngs: SUPPORTED_LNGS,
fallbackLng: {
'zh-HK': ['zh-TW', 'en'],
kk: ['ru', 'en'],
@@ -26,8 +58,8 @@ const initI18n = async () => {
tt: ['ru', 'en'],
default: ['en'],
},
ns: options.ns,
defaultNS: options.defaultNs,
ns: ['translation'],
defaultNS: 'translation',
...(isBrowser && {
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
+10 -4
View File
@@ -1,6 +1,5 @@
import { md5 } from 'js-md5';
import WebSocket from 'isomorphic-ws';
import { createHash } from 'crypto';
import { randomMd5 } from '@/utils/misc';
import { LRUCache } from '@/utils/lru';
import { genSSML } from '@/utils/ssml';
@@ -205,7 +204,7 @@ const EDGE_TTS_VOICES = {
*/
const WIN_EPOCH_OFFSET = 11644473600; // Windows epoch offset in seconds (1601 to 1970)
const S_TO_NS = 1000000000; // Seconds to nanoseconds conversion
const generateSecMsGec = () => {
const generateSecMsGec = async () => {
let ticks = Math.floor(Date.now() / 1000);
// Switch to Windows file time epoch (1601-01-01 00:00:00 UTC)
ticks += WIN_EPOCH_OFFSET;
@@ -216,7 +215,14 @@ const generateSecMsGec = () => {
// Create the string to hash by concatenating the ticks and the trusted client token
const strToHash = `${ticks.toFixed(0)}${EDGE_API_TOKEN}`;
// Compute the SHA256 hash and return the uppercased hex digest
return createHash('sha256').update(strToHash, 'ascii').digest('hex').toUpperCase();
const encoder = new TextEncoder();
const data = encoder.encode(strToHash);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray
.map((b) => b.toString(16).padStart(2, '0'))
.join('')
.toUpperCase();
};
const generateMuid = () => {
@@ -298,7 +304,7 @@ export class EdgeSpeechTTS {
const params = new URLSearchParams({
ConnectionId: connectId,
TrustedClientToken: EDGE_API_TOKEN,
'Sec-MS-GEC': generateSecMsGec(),
'Sec-MS-GEC': await generateSecMsGec(),
'Sec-MS-GEC-Version': `1-${CHROMIUM_FULL_VERSION}`,
});
const url = `${EDGE_SPEECH_URL}?${params.toString()}`;
+24
View File
@@ -0,0 +1,24 @@
import path from 'node:path';
import vinext from 'vinext';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [vinext()],
resolve: {
alias: {
'@pdfjs': path.resolve('public/vendor/pdfjs'),
'@simplecc': path.resolve('public/vendor/simplecc'),
},
},
build: {
rollupOptions: {
onwarn(warning, defaultHandler) {
if (warning.message?.includes("Can't resolve original location of error")) return;
defaultHandler(warning);
},
},
},
ssr: {
noExternal: ['tinycolor2'],
},
});
+796 -426
View File
File diff suppressed because it is too large Load Diff