在线商店国际化与服务器端渲染实战
1. 国际化功能实现
在开发多语言支持的应用时,我们需要实现动态加载不同语言的翻译内容。以下是创建国际化对象的函数
createI18n
的实现:
export async function createI18n (locale) {
const { default: localeMessages } = await
import(`../../i18n/locales/${locale}`)
const messages = {
[locale]: localeMessages,
}
const i18n = new VueI18n({
locale,
messages,
})
return i18n
}
上述代码使用了
async/await
语法,当然也可以使用
Promise
来实现:
export function createI18n (locale) {
return import(`../../i18n/locales/${locale}`)
.then(module => {
const localeMessages = module.default
// ...
})
}
为了自动加载用户的语言设置,我们可以使用
navigator.language
或
userLanguage
(兼容 Internet Explorer)来获取语言代码。以下是
getAutoLang
函数的实现:
export function getAutoLang () {
let result = window.navigator.userLanguage ||
window.navigator.language
if (result) {
result = result.substr(0, 2)
}
if (langs.indexOf(result) === -1) {
return 'en'
} else {
return result
}
}
在
src/main.js
文件中,我们需要引入这两个新的工具函数,并修改
main
函数:
import { createI18n, getAutoLang } from './utils/i18n'
async function main () {
const locale = getAutoLang()
const i18n = await createI18n(locale)
await store.dispatch('init')
// eslint-disable-next-line no-new
new Vue({
el: '#app',
router,
store,
i18n, // Inject i18n into the app
...App,
})
}
2. 语言切换页面的添加
为了让用户能够切换语言,我们需要添加一个语言选择页面。具体步骤如下:
1. 在
src/router.js
文件中,导入
PageLocale
组件:
import PageLocale from './components/PageLocale.vue'
-
在
routes数组中添加语言切换路由:
{ path: '/locale', name: 'locale', component: PageLocale },
-
在
AppFooter.vue组件的模板中添加路由链接:
<div v-if="$route.name !== 'locale'">
<router-link :to="{ name: 'locale' }">{{ $t('change-lang') }}
</router-link>
</div>
3. 服务器端渲染(SSR)
服务器端渲染(SSR)是指在服务器上运行并渲染应用,然后将 HTML 发送到浏览器。它有两个主要优点:
- 更好的搜索引擎优化(SEO),因为应用的初始内容会在页面 HTML 中渲染。
- 对于较慢的网络或设备,渲染后的 HTML 不需要 JavaScript 就能显示给用户,从而更快地显示内容。
然而,使用 SSR 也有一些权衡:
- 代码需要能够在服务器上运行(除非是在客户端专用的钩子中,如
mounted
),并且一些库可能在浏览器上表现不佳,需要特殊处理。
- 服务器负载会增加,因为它需要做更多的工作。
- 开发设置会更复杂。
4. 通用应用结构
为了编写一个既能在客户端又能在服务器上运行的通用应用,我们需要改变源代码的架构。具体操作如下:
1. 在
src/router.js
文件中,将路由器创建封装到一个新的导出函数
createRouter
中:
export function createRouter () {
const router = new VueRouter({
routes,
mode: 'history',
scrollBehavior (to, from, savedPosition) {
// ...
},
})
return router
}
-
在
src/store/index.js文件中,将 Vuex 存储创建封装到一个新的导出函数createStore中:
export function createStore () {
const store = new Vuex.Store({
strict: process.env.NODE_ENV !== 'production',
// ...
modules: {
cart,
item,
items,
ui,
},
})
return store
}
-
将
src/main.js文件重命名为src/app.js,并将main函数改为导出的createApp函数:
export async function createApp (context) {
const router = createRouter()
const store = createStore()
sync(store, router)
const i18n = await createI18n(context.locale)
await store.dispatch('init')
const app = new Vue({
router,
store,
i18n,
...App,
})
return {
app,
router,
store,
}
}
5. 客户端入口和服务器入口
-
客户端入口
:创建一个新的
src/entry-client.js文件,作为客户端包的入口点:
import { createApp } from './app'
import { getAutoLang } from './utils/i18n'
const locale = getAutoLang()
createApp({
locale,
}).then(({ app }) => {
app.$mount('#app')
})
同时,在
webpack.config.js
文件中更改入口路径:
entry: './src/entry-client.js',
-
服务器入口
:创建一个新的
src/entry-server.js文件,作为服务器包的入口点:
import { createApp } from './app'
export default context => {
return new Promise(async (resolve, reject) => {
const { app, router, store } = await createApp(context)
// Set the current route
router.push(context.url)
// TODO get matched components to preload data
// TODO resolve(app)
})
}
6. 状态管理
在处理请求时,我们需要在渲染应用之前获取相关组件的数据。为此,我们为组件添加
asyncData
自定义选项。
- 在
PageHome.vue
组件中添加
asyncData
函数:
asyncData ({ store }) {
return store.dispatch('items/fetchItems')
},
-
在
PageStoreItem.vue组件中添加asyncData函数:
asyncData ({ store, route }) {
return store.dispatch('item/fetchStoreItemDetails', {
id: route.params.id,
})
},
在
entry-server.js
文件中,我们可以使用
router.getMatchedComponents()
方法获取与当前路由匹配的组件列表,并调用它们的
asyncData
选项:
export default context => {
return new Promise(async (resolve, reject) => {
const { app, router, store } = await createApp(context)
router.push(context.url)
// Wait for the component resolution
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
Promise.all(matchedComponents.map(Component => {
if (Component.asyncData) {
return Component.asyncData({
store,
route: router.currentRoute,
})
}
})).then(() => {
// Send back the store state
context.state = store.state
// Send the app to the renderer
resolve(app)
}).catch(reject)
}, reject)
})
}
7. 恢复客户端的 Vuex 状态
服务器会将存储状态序列化为
__INITIAL_STATE__
变量存储在 HTML 页面中。我们可以在应用挂载之前使用该变量来设置状态:
createApp({
locale,
}).then(({ app, store }) => {
if (window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__)
}
app.$mount('#app')
})
8. Webpack 配置
为了适应客户端和服务器的不同需求,我们需要重构 Webpack 配置。具体步骤如下:
1. 安装
webpack-merge
和
webpack-node-externals
包:
npm i -D webpack-merge webpack-node-externals
-
在项目根目录下创建一个新的
webpack文件夹,并将webpack.config.js文件移动并重命名为webpack/common.js。 -
移除配置中的
entry选项,并更新output选项:
output: {
path: path.resolve(__dirname, '../dist'),
publicPath: '/dist/',
filename: '[name].[chunkhash].js',
},
-
创建客户端配置文件
webpack/client.js:
const webpack = require('webpack')
const merge = require('webpack-merge')
const common = require('./common')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
module.exports = merge(common, {
entry: './src/entry-client',
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity,
}),
// Generates the client manifest file
new VueSSRClientPlugin(),
],
})
-
创建服务器配置文件
webpack/server.js:
const merge = require('webpack-merge')
const common = require('./common')
const nodeExternals = require('webpack-node-externals')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
module.exports = merge(common, {
entry: './src/entry-server',
target: 'node',
devtool: 'source-map',
output: {
libraryTarget: 'commonjs2',
},
// Skip webpack processing on node_modules
externals: nodeExternals({
// Force css files imported from no_modules
// to be processed by webpack
whitelist: /\.css$/,
}),
plugins: [
// Generates the server bundle file
new VueSSRServerPlugin(),
],
})
9. 服务器端设置
在开发环境中,我们不能直接使用
webpack-dev-server
进行 SSR。我们需要使用 Express 服务器和 Webpack 来设置开发环境。具体操作如下:
1. 下载
server.dev.js
文件,并将其放在项目根目录下。
2. 安装开发依赖包:
npm i -D memory-fs chokidar webpack-dev-middleware webpack-hot-middleware
-
创建页面模板文件
index.template.html,并将body内容替换为<!--vue-ssr-outlet-->。 -
安装
express和reify包:
npm i -S express reify
-
下载
server.js文件,并将其放在项目根目录下。 -
在
server.js文件中替换// TODO development注释:
const setupDevServer = require('./server.dev')
readyPromise = setupDevServer({
server,
templatePath,
onUpdate: (bundle, options) => {
// Re-create the bundle renderer
renderer = createBundleRenderer(bundle, {
runInNewContext: false,
...options,
})
},
})
-
在
server.js文件中替换// TODO render注释:
const context = {
url: req.url,
// Languages sent by the browser
locale: req.acceptsLanguages(langs) || 'en',
}
renderer.renderToString(context, (err, html) => {
if (err) {
// Render Error Page or Redirect
res.status(500).send('500 | Internal Server Error')
console.error(`error during render : ${req.url}`)
console.error(err.stack)
}
res.send(html)
})
-
更改
dev脚本以运行 Express 服务器:
"dev": "node server",
总结
通过以上步骤,我们实现了一个支持多语言切换的在线商店应用,并引入了服务器端渲染(SSR)来提升应用的性能和 SEO。在开发过程中,我们需要注意代码的可移植性和服务器负载问题,同时合理配置 Webpack 以优化构建过程。
流程图
graph LR
A[开始] --> B[自动加载用户语言]
B --> C[创建国际化对象]
C --> D[添加语言切换页面]
D --> E[实现服务器端渲染]
E --> F[重构 Webpack 配置]
F --> G[设置服务器端环境]
G --> H[运行应用]
H --> I[结束]
表格
| 功能 | 实现步骤 |
|---|---|
| 自动加载用户语言 |
1. 使用
navigator.language
获取语言代码
2. 截取前两个字符 3. 检查是否在支持的语言列表中,否则使用默认语言 |
| 创建国际化对象 |
1. 异步导入语言文件
2. 创建
VueI18n
实例
|
| 添加语言切换页面 |
1. 导入
PageLocale
组件
2. 添加路由 3. 在页脚添加路由链接 |
| 实现服务器端渲染 |
1. 封装路由器和存储创建函数
2. 创建客户端和服务器入口文件 3. 处理状态管理 4. 恢复客户端状态 |
| 重构 Webpack 配置 |
1. 安装相关包
2. 创建通用配置文件 3. 创建客户端和服务器配置文件 |
| 设置服务器端环境 |
1. 下载相关文件
2. 安装依赖包 3. 创建页面模板 4. 配置服务器文件 |
| 运行应用 |
更改
dev
脚本以运行 Express 服务器
|
在线商店国际化与服务器端渲染实战
10. 详细操作步骤解析
为了更清晰地理解上述步骤,下面对关键操作进行详细解析。
10.1 国际化功能
- 自动加载用户语言 :
-
首先,通过
window.navigator.userLanguage || window.navigator.language获取用户浏览器设置的语言代码。 -
然后,截取该代码的前两个字符,因为有些浏览器返回的代码格式是
en-US,我们只需要en这种格式。 -
最后,检查该代码是否在支持的语言列表
langs中,如果不在则使用默认语言en。
export function getAutoLang () {
let result = window.navigator.userLanguage || window.navigator.language
if (result) {
result = result.substr(0, 2)
}
if (langs.indexOf(result) === -1) {
return 'en'
} else {
return result
}
}
- 创建国际化对象 :
-
使用
async/await异步导入对应语言的翻译文件。 -
将导入的翻译文件作为
messages对象的属性,键为语言代码。 -
创建
VueI18n实例并返回。
export async function createI18n (locale) {
const { default: localeMessages } = await import(`../../i18n/locales/${locale}`)
const messages = {
[locale]: localeMessages,
}
const i18n = new VueI18n({
locale,
messages,
})
return i18n
}
10.2 语言切换页面
- 路由配置 :
-
在
src/router.js中导入PageLocale组件。 -
在
routes数组中添加/locale路由,指向PageLocale组件。
import PageLocale from './components/PageLocale.vue'
const routes = [
// 其他路由
{ path: '/locale', name: 'locale', component: PageLocale },
// 其他路由
]
- 页脚添加链接 :
-
在
AppFooter.vue组件的模板中,使用router-link添加语言切换链接。 -
使用
$t方法显示翻译后的文本。
<div v-if="$route.name !== 'locale'">
<router-link :to="{ name: 'locale' }">{{ $t('change-lang') }}</router-link>
</div>
10.3 服务器端渲染
- 通用应用结构调整 :
-
路由器
:在
src/router.js中创建createRouter函数,每次请求时创建新的路由器实例。
export function createRouter () {
const router = new VueRouter({
routes,
mode: 'history',
scrollBehavior (to, from, savedPosition) {
// ...
},
})
return router
}
-
存储
:在
src/store/index.js中创建createStore函数,每次请求时创建新的 Vuex 存储实例。
export function createStore () {
const store = new Vuex.Store({
strict: process.env.NODE_ENV !== 'production',
// ...
modules: {
cart,
item,
items,
ui,
},
})
return store
}
-
根实例
:将
src/main.js重命名为src/app.js,创建createApp函数,返回新的根实例、路由器和存储。
export async function createApp (context) {
const router = createRouter()
const store = createStore()
sync(store, router)
const i18n = await createI18n(context.locale)
await store.dispatch('init')
const app = new Vue({
router,
store,
i18n,
...App,
})
return {
app,
router,
store,
}
}
- 客户端和服务器入口 :
-
客户端入口
:在
src/entry-client.js中获取用户语言,调用createApp函数并挂载应用。
import { createApp } from './app'
import { getAutoLang } from './utils/i18n'
const locale = getAutoLang()
createApp({
locale,
}).then(({ app }) => {
app.$mount('#app')
})
-
服务器入口
:在
src/entry-server.js中接收上下文对象,创建应用实例,设置当前路由,处理组件数据预加载。
import { createApp } from './app'
export default context => {
return new Promise(async (resolve, reject) => {
const { app, router, store } = await createApp(context)
router.push(context.url)
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
Promise.all(matchedComponents.map(Component => {
if (Component.asyncData) {
return Component.asyncData({
store,
route: router.currentRoute,
})
}
})).then(() => {
context.state = store.state
resolve(app)
}).catch(reject)
}, reject)
})
}
10.4 Webpack 配置
-
通用配置
:将
webpack.config.js重命名为webpack/common.js,移除entry选项,更新output选项。
output: {
path: path.resolve(__dirname, '../dist'),
publicPath: '/dist/',
filename: '[name].[chunkhash].js',
},
-
客户端配置
:在
webpack/client.js中合并通用配置,设置入口和插件。
const webpack = require('webpack')
const merge = require('webpack-merge')
const common = require('./common')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
module.exports = merge(common, {
entry: './src/entry-client',
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity,
}),
new VueSSRClientPlugin(),
],
})
-
服务器配置
:在
webpack/server.js中合并通用配置,设置入口、目标、输出和插件。
const merge = require('webpack-merge')
const common = require('./common')
const nodeExternals = require('webpack-node-externals')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
module.exports = merge(common, {
entry: './src/entry-server',
target: 'node',
devtool: 'source-map',
output: {
libraryTarget: 'commonjs2',
},
externals: nodeExternals({
whitelist: /\.css$/,
}),
plugins: [
new VueSSRServerPlugin(),
],
})
10.5 服务器端设置
- 开发环境设置 :
-
下载
server.dev.js文件,安装开发依赖包。 -
创建页面模板文件
index.template.html,替换body内容。 -
安装
express和reify包,下载server.js文件。 -
在
server.js中配置开发环境和渲染逻辑。
const setupDevServer = require('./server.dev')
readyPromise = setupDevServer({
server,
templatePath,
onUpdate: (bundle, options) => {
renderer = createBundleRenderer(bundle, {
runInNewContext: false,
...options,
})
},
})
const context = {
url: req.url,
locale: req.acceptsLanguages(langs) || 'en',
}
renderer.renderToString(context, (err, html) => {
if (err) {
res.status(500).send('500 | Internal Server Error')
console.error(`error during render : ${req.url}`)
console.error(err.stack)
}
res.send(html)
})
11. 注意事项和常见问题
-
代码可移植性
:在编写代码时,要确保代码能够在服务器和客户端都能正常运行。避免在服务器端使用客户端特有的 API,如
window和document。 - 服务器负载 :SSR 会增加服务器的负载,因为服务器需要处理更多的请求和渲染工作。可以通过优化代码、缓存数据等方式来减轻服务器负载。
-
Webpack 配置
:Webpack 配置可能会比较复杂,要确保每个配置文件的设置正确。特别是服务器配置中的
externals选项,要正确处理node_modules中的依赖。 - 错误处理 :在服务器端渲染过程中,要做好错误处理。当渲染出错时,要返回合适的错误页面或错误信息给客户端。
12. 进一步优化建议
- 缓存机制 :可以在服务器端实现缓存机制,缓存已经渲染好的页面,减少重复渲染的时间和服务器负载。
- 代码分割 :使用 Webpack 的代码分割功能,将应用代码分割成更小的块,按需加载,提高应用的加载速度。
- 性能监控 :使用性能监控工具,如 Google Analytics、New Relic 等,监控应用的性能,找出性能瓶颈并进行优化。
总结
通过实现多语言切换和服务器端渲染,我们提升了在线商店应用的用户体验和搜索引擎优化效果。在开发过程中,我们需要注意代码的可移植性、服务器负载和 Webpack 配置等问题。同时,可以通过进一步优化,如缓存机制、代码分割和性能监控,来提高应用的性能和稳定性。
流程图
graph LR
A[详细解析开始] --> B[国际化功能解析]
B --> C[语言切换页面解析]
C --> D[服务器端渲染解析]
D --> E[Webpack 配置解析]
E --> F[服务器端设置解析]
F --> G[注意事项和优化建议]
G --> H[总结]
H --> I[结束]
表格
| 操作步骤 | 详细说明 |
|---|---|
| 国际化功能 |
自动加载用户语言,创建国际化对象,使用
async/await
异步导入语言文件
|
| 语言切换页面 |
配置路由,在页脚添加链接,使用
$t
方法显示翻译文本
|
| 服务器端渲染 | 调整通用应用结构,创建客户端和服务器入口,处理组件数据预加载 |
| Webpack 配置 |
创建通用配置、客户端配置和服务器配置,使用
webpack-merge
合并配置
|
| 服务器端设置 | 下载文件,安装依赖,创建页面模板,配置服务器文件 |
| 注意事项 | 代码可移植性、服务器负载、Webpack 配置、错误处理 |
| 优化建议 | 缓存机制、代码分割、性能监控 |
在线商店国际化与SSR实战
超级会员免费看
912

被折叠的 条评论
为什么被折叠?



