fix: fix ssr teleports and i18n

This commit is contained in:
YatomigaAkashi 2023-11-07 14:47:20 +08:00 committed by sunguoshu
parent c187239881
commit b574f9d263
10 changed files with 177 additions and 125 deletions

View file

@ -17,6 +17,7 @@
</head>
<body>
<div id="app"><!--ssr-outlet--></div>
<div id="teleported"><!--teleports--></div>
<script type="module" src="/src/entry-client.ts"></script>
<script>

View file

@ -13,6 +13,26 @@ const APP_PORT = 1007
const __dirname = path.dirname(fileURLToPath(import.meta.url))
// Inject teleports in template
const injectTeleports = (html: string, teleports: {
'#teleported': string
}) => {
// if (teleports) {
// for (const [target, content] of Object.entries(teleports)) {
// if (['head', 'body', 'html'].includes(target)) {
// const replacement = `</${target}>`
// html = html.replace(replacement, content + replacement)
// } else {
// const replacement = ` id="${target.replace('#', '')}">`
// html = html.replace(replacement, replacement + content)
// }
// }
// }
//
// return html
return html.replace('<!--teleports-->', teleports['#teleported'])
}
;(async () => {
const app = new Koa()
@ -21,9 +41,18 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
appType: 'custom',
})
// 解析accept-language
function parseAcceptLanguage(acceptLanguage: string) {
const languages = acceptLanguage.split(',')
const language = languages[0]
const country = language.split('-')[1]
return { language, country }
}
app.use(koaConnect(vite.middlewares))
app.use(async (ctx) => {
const { language, country } = parseAcceptLanguage(ctx.request.headers['accept-language'] as string)
try {
let template = fs.readFileSync(
path.resolve(__dirname, 'index.html'),
@ -34,9 +63,13 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
const { render } = await vite.ssrLoadModule('/src/entry-server.ts')
const [renderedHtml, renderedPinia, renderedLinks] = await render(ctx, {})
const [renderedHtml, renderedPinia, renderedLinks, renderedTeleports] =
await render(ctx, {}, {
language,
country,
})
const html = template
const html = injectTeleports(template, renderedTeleports)
.replace('<!--preload-links-->', renderedLinks)
.replace('<!--ssr-outlet-->', renderedHtml)
.replace('__pinia', renderedPinia)

View file

@ -18,21 +18,23 @@ const handleConfirm = () => {
</script>
<template>
<Teleport to="body" :disabled="showAlert">
<Teleport to="#teleported" :disabled="showAlert">
<Transition name="alert">
<div v-if="showAlert" class="mask">
<div class="container">
<div class="header">
<h3>{{ $t(`${alertMsg}`) }}</h3>
</div>
<div>
<div v-if="showAlert" class="mask">
<div class="container">
<div class="header">
<h3>{{ $t(`${alertMsg}`) }}</h3>
</div>
<div class="footer">
<button v-if="isShowCancel" class="button" @click="handleClose">
{{ $tm('ComponentAlert.cancel') }}
</button>
<button class="button" @click="handleConfirm">
{{ $tm('ComponentAlert.confirm') }}
</button>
<div class="footer">
<button v-if="isShowCancel" class="button" @click="handleClose">
{{ $tm('ComponentAlert.cancel') }}
</button>
<button class="button" @click="handleConfirm">
{{ $tm('ComponentAlert.confirm') }}
</button>
</div>
</div>
</div>
</div>

View file

@ -15,35 +15,37 @@ const handleClose = () => {
</script>
<template>
<Teleport to="body" :disabled="showInfo">
<Teleport to="#teleported" :disabled="showInfo">
<Transition
enter-active-class="animate__animated animate__fadeInUp animate__faster"
leave-active-class="animate__animated animate__fadeOutDown animate__faster"
>
<div class="container" v-if="showInfo">
<Transition
enter-active-class="animate__animated animate__swing"
appear
>
<div class="lass">
<span>{{ name }}</span>
<div>
<div class="container" v-if="showInfo">
<Transition
enter-active-class="animate__animated animate__swing"
appear
>
<div class="lass">
<span>{{ name }}</span>
</div>
</Transition>
<div class="avatar">
<img :src="loli" />
</div>
</Transition>
<div class="avatar">
<img :src="loli" />
</div>
<Transition
enter-active-class="animate__animated animate__bounceInRight animate__faster"
appear
>
<!-- A ha ha ha! You probably didn't expect that this was inspired by しゅがてん-Sugarfull tempering- -->
<div class="info">{{ `${$t(`${infoMsg}`)}` }}</div>
</Transition>
<Transition
enter-active-class="animate__animated animate__bounceInRight animate__faster"
appear
>
<!-- A ha ha ha! You probably didn't expect that this was inspired by しゅがてん-Sugarfull tempering- -->
<div class="info">{{ `${$t(`${infoMsg}`)}` }}</div>
</Transition>
<div class="close" @click="handleClose">
<Icon icon="line-md:close" />
<div class="close" @click="handleClose">
<Icon icon="line-md:close" />
</div>
</div>
</div>
</Transition>

View file

@ -119,65 +119,67 @@ const handleCloseCapture = () => {
</script>
<template>
<Teleport to="body" :disabled="isShowCapture">
<Teleport to="#teleported" :disabled="isShowCapture">
<Transition name="capture">
<!-- Mask -->
<div
class="mask"
@keydown="checkKeyPress($event)"
tabindex="0"
v-if="isShowCapture"
>
<div class="validate">
<!-- Title -->
<div class="title">
<!-- <span>{{ `` }}</span> -->
<h2>{{ $tm('AlertInfo.capture.title') }}</h2>
<!-- <span>{{ `` }}</span> -->
</div>
<p class="question">{{ currentQuestion.text }}</p>
<!-- Options -->
<div class="select">
<label
v-for="(option, index) in currentQuestion.options"
:key="index"
>
<input type="radio" v-model="userAnswers" :value="option" />
{{ option }}
</label>
</div>
<!-- Submit buttons -->
<div class="btn">
<button @click="submitAnswer">
{{ $tm('AlertInfo.capture.submit') }}
</button>
<button @click="handleCloseCapture">
{{ $tm('AlertInfo.capture.close') }}
</button>
</div>
<!-- Hints -->
<!-- tabindex allows this element to be focused on the page -->
<div class="hint-container">
<div v-if="isShowHint" class="hint">
<div>{{ $tm('AlertInfo.capture.hint1') }}</div>
<div>
{{ $tm('AlertInfo.capture.hint2') }}
<span>kun</span>
{{ $tm('AlertInfo.capture.hint3') }}
</div>
<div>
<!-- Mask -->
<div
class="mask"
@keydown="checkKeyPress($event)"
tabindex="0"
v-if="isShowCapture"
>
<div class="validate">
<!-- Title -->
<div class="title">
<!-- <span>{{ `` }}</span> -->
<h2>{{ $tm('AlertInfo.capture.title') }}</h2>
<!-- <span>{{ `` }}</span> -->
</div>
<div v-if="isShowAnswer" class="answer">
<div>{{ $tm('AlertInfo.capture.hint4') }}</div>
<a
href="https://github.com/KUN1007/kun-galgame-vue/tree/remove-server/src/components/capture"
target="_blank"
rel="noopener noreferrer"
<p class="question">{{ currentQuestion.text }}</p>
<!-- Options -->
<div class="select">
<label
v-for="(option, index) in currentQuestion.options"
:key="index"
>
{{ $tm('AlertInfo.capture.answer') }}
</a>
<input type="radio" v-model="userAnswers" :value="option" />
{{ option }}
</label>
</div>
<!-- Submit buttons -->
<div class="btn">
<button @click="submitAnswer">
{{ $tm('AlertInfo.capture.submit') }}
</button>
<button @click="handleCloseCapture">
{{ $tm('AlertInfo.capture.close') }}
</button>
</div>
<!-- Hints -->
<!-- tabindex allows this element to be focused on the page -->
<div class="hint-container">
<div v-if="isShowHint" class="hint">
<div>{{ $tm('AlertInfo.capture.hint1') }}</div>
<div>
{{ $tm('AlertInfo.capture.hint2') }}
<span>kun</span>
{{ $tm('AlertInfo.capture.hint3') }}
</div>
</div>
<div v-if="isShowAnswer" class="answer">
<div>{{ $tm('AlertInfo.capture.hint4') }}</div>
<a
href="https://github.com/KUN1007/kun-galgame-vue/tree/remove-server/src/components/capture"
target="_blank"
rel="noopener noreferrer"
>
{{ $tm('AlertInfo.capture.answer') }}
</a>
</div>
</div>
</div>
</div>

View file

@ -1,7 +1,7 @@
import { createApp } from './main'
import { createKUNGalgameRouter } from './router'
import { setupPinia } from './store'
import i18n from '@/language/i18n'
import createI18n from '@/language/i18n'
import '@/styles/index.scss'
const router = createKUNGalgameRouter()
@ -9,6 +9,8 @@ const pinia = setupPinia()
const { app } = createApp()
export const i18n = createI18n()
app.use(router).use(pinia).use(i18n)
if (window.__pinia) {

View file

@ -2,7 +2,7 @@ import { createApp } from './main'
import { createKUNGalgameRouter } from './router'
import { setupPinia } from './store'
import i18n from '@/language/i18n'
import createI18n from '@/language/i18n'
import { renderToString } from '@vue/server-renderer'
@ -46,9 +46,11 @@ const renderPreloadLinks = (
export const render = async (
ctx: ParameterizedContext,
manifest: Record<string, string[]>
): Promise<[string, string, string]> => {
manifest: Record<string, string[]>,
options: { language: string; country: string }
) => {
const { app } = createApp()
const { language, country } = options
// router
const router = createKUNGalgameRouter()
@ -63,7 +65,9 @@ export const render = async (
const renderedPinia = JSON.stringify(pinia.state.value)
// i18n
app.use(i18n)
app.use(createI18n(
language.includes('zh') ? 'zh' : 'en',
))
const renderCtx: { modules?: string[] } = {}
@ -71,5 +75,9 @@ export const render = async (
const renderedLinks = renderPreloadLinks(renderCtx.modules, manifest)
return [renderedHtml, renderedPinia, renderedLinks]
const renderedTeleports = renderCtx.teleports as {
'#teleported': string
}
return [renderedHtml, renderedPinia, renderedLinks, renderedTeleports]
}

View file

@ -1,4 +1,4 @@
import { createI18n } from 'vue-i18n'
import { createI18n as _createI18n } from 'vue-i18n'
// 读取本地存储中的语言配置
import { KUNGalgameLanguage } from '@/utils/getDefaultEnv'
@ -6,8 +6,8 @@ import { KUNGalgameLanguage } from '@/utils/getDefaultEnv'
import zh from './zh'
import en from './en'
const i18n = createI18n({
locale: KUNGalgameLanguage,
const createI18n = (language?: string) => _createI18n({
locale: language || KUNGalgameLanguage,
legacy: false,
messages: {
zh,
@ -15,4 +15,4 @@ const i18n = createI18n({
},
})
export default i18n
export default createI18n

View file

@ -1,6 +1,6 @@
import { Router } from 'vue-router'
import { createPermission } from './permission'
import i18n from '@/language/i18n'
import { i18n } from '@/entry-client'
const createPageTitle = (router: Router) => {
router.beforeEach((to) => {

View file

@ -5,26 +5,28 @@ import vue from '@vitejs/plugin-vue'
import { visualizer } from 'rollup-plugin-visualizer'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), visualizer() as PluginOption],
/* Set the 'src' alias to '@' */
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
export default defineConfig(({ mode, ssrBuild, command }) => {
return {
plugins: [vue(), visualizer() as PluginOption],
/* Set the 'src' alias to '@' */
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
},
esbuild: {
drop: ['console', 'debugger'],
},
build: {
// Dist dir name
assetsDir: 'kun',
},
server: { host: '127.0.0.1', port: 1007 },
// Suppress i18n warnings
define: {
__VUE_I18N_FULL_INSTALL__: true,
__VUE_I18N_LEGACY_API__: false,
__INTLIFY_PROD_DEVTOOLS__: false,
},
esbuild: command === 'serve' ? {} : {
drop: ['console', 'debugger']
},
build: {
// Dist dir name
assetsDir: 'kun',
},
server: { host: '127.0.0.1', port: 1007 },
// Suppress i18n warnings
define: {
__VUE_I18N_FULL_INSTALL__: true,
__VUE_I18N_LEGACY_API__: false,
__INTLIFY_PROD_DEVTOOLS__: false,
},
}
})