Webpack 配置多页面国际化应用
- 作者:Bougie
- 创建于:2019-11-20
- 更新于:2023-03-09
# 多入口配置
通过读取文件结构来动态生成入口。务必使用对象写法,键名表示 chunkName
module.exports = {
entry: collectEntries()
}
function collectEntries() {
const pagesDir = path.resolve(__dirname, '../src/pages')
return fs
.readdirSync(pagesDir)
.map((pageName) => ({
chunkName: pageName,
entryFile: path.join(pagesDir, pageName, 'index.jsx')
}))
.reduce(
(entries, item) => ({
...entries,
[item.chunkName]: item.entryFile
}),
{}
)
}
# 多 html 文件输出
plugins
添加多个 HtmlWebpackPlugin
实例,添加 chunks 配置将只在页面注入当前页面的代码块,不设置 chunks 会注入所有页面代码块
module.exports = {
plugins: [
...collectHtmls()
// ...
]
}
function collectHtmls() {
const pagesDir = path.resolve(__dirname, '../src/pages')
const templatesDir = path.resolve(__dirname, '../public/templates')
const templateOutputsDir = path.resolve(__dirname, '../dist')
return fs.readdirSync(pagesDir).map((pageName) => {
return new HtmlWebpackPlugin({
inject: true,
chunks: [pageName],
filename: path.join(templateOutputsDir, `${pageName}.html`),
template: path.join(templatesDir, `${pageName}.html`)
})
})
}
# devServer 配置
多国家网站地址一般为 https://www.xxx.com/locale, 参考苹果官网 (opens new window) 和 微软官网 (opens new window)。所以我们需要处理一下重定向,也就是 devServer 的 historyApiFallback 项,需提前配置支持的国家。
module.exports = {
devServer: {
historyApiFallback: {
rewrites: collectRewrites()
}
}
}
const avalibleLocales = '(cn|us|ru|id)'
function collectRewrites() {
return fs
.readdirSync(path.resolve(__dirname, '../src/pages'))
.map((page) => ({
from: new RegExp(`^/${avalibleLocales}/${page}/?$`, 'i'),
to: `/${page}.html`
}))
}
使用 react-router
时 basename
需要配置为当前国家
const App = () => {
return <Router basename={`/${locale}`}>{/*...*/}</Router>
}
# 多语言配置
使用 React-Intl
配置多国语言包。为防止 bundle
体积过大,语言包用 code spliting
分割。注意语言包未加载时不要显示页面内容,否则页面会有闪烁。
const Intl = ({ children }) => {
const [messages, setMessages] = useState()
const loadLocaleData = useCallback(() => {
import(`@/languages/${locale}.json`).then(setMessages)
}, [language])
useEffect(loadLocaleData, [])
return messages ? (
<IntlProvider messages={messages} locale={locale}>
{children}
</IntlProvider>
) : null
}
# 如何 SEO ?
使用多页面后由服务端渲染每个页面的 title
, description
, keywords
。如果想要更进一步的 SEO 的话,页面内容也需要服务端渲染。
# 方法一
在页面配置结构化数据,结构化数据交给服务端渲染,示例 https://search.google.com/structured-data/ (opens new window)
此种方法仅适用于谷歌,百度可能有不同的解析规则。
# 方法二
页面内容交给服务端渲染,但是渲染后的结果隐藏。在 react-app 中用 dangerouslySetInnerHTML=\{\{ __html: document.getElementById('ssr-home-first-screen') \}\}
设置页面内容。不过可能会被搜索引擎识别为恶意 SEO 而遭到搜索引擎屏蔽。因此这种方法有风险。
<!DOCTYPE html>
<html lang="<%= lang %>">
<head>
<meta name="keywords" content="<%= keywords %>" />
<meta name="description" content="<%= description %>" />
<title><%= title %></title>
<style>
[id^='ssr-'] {
position: absolute;
width: 1px; /* Setting this to 0 make it invisible for VoiceOver */
height: 1px; /* Setting this to 0 make it invisible for VoiceOver */
padding: 0;
margin: -1px;
border: 0;
clip: rect(0 0 0 0);
overflow: hidden;
}
</style>
</head>
<body>
<div id="root"></div>
<div id="ssr-home-first-screen"><%= homeFirstScreen %></div>
</body>
</html>
# 方法三
由服务端渲染页面主体内容,其它部分由客户端渲染。将页面拆分成多个区域,客户端需执行多个 ReactDOM.render
Function. 这样不存在被搜索引擎识别为恶意 SEO 的风险,缺点是比第一种方法麻烦。
<!DOCTYPE html>
<html lang="<%= lang %>">
<head>
<meta name="keywords" content="<%= keywords %>" />
<meta name="description" content="<%= description %>" />
<title><%= title %></title>
</head>
<body>
<div id="root">
<header></header>
<aside></aside>
<div id="ssr-home-first-screen"><%= homeFirstScreen %></div>
<footer></footer>
</div>
</body>
</html>
# 结语
如果网站比较简单,例如新闻、博客类,那么值得用上述方法一试。如果网站比较复杂,例如电商类,那还是老实用 SSR 比较好。