国际版官网框架的搭建vue3+webpack5打包+vue-i18n国际化语言+element-plus组件库
1. 项目结构如下:
2. 主要用webpack5玩转多页面应用框架和单页面应用框架,多页面应用框架webpack.config.js配置如下:
const path = require("path");
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const {VueLoaderPlugin} = require('vue-loader');
const isProduction = process.env.NODE_ENV === 'production';
const { setEntry,setHtmlPlugin } = require('./webpack.util.js')
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const ESBuildPlugin = require('esbuild-webpack-plugin').default;
module.exports = () => {
return {
mode: isProduction ? "production" : "development",
entry: setEntry,
devtool: isProduction ? false : "inline-source-map",
devServer: {
open: true,
hot: true,
port: 3001,
},
output: {
path: path.resolve(__dirname, "./dist"),
filename: "js/[name].[contenthash:10].js",
},
mode: isProduction ? 'production' : 'development',
module: {
rules: [
{
//.js文件loader
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false
}
}
},
{
//.vue文件loader
test: /\.vue$/,
use: 'vue-loader'
},
{
//.css文件loader
test: /\.css$/,
use: [isProduction ? MiniCssExtractPlugin.loader : 'vue-style-loader', 'css-loader']
},
{ //图片
test: /\.(png|svg|jpg|jpeg|gif|bmp)$/,
type: 'asset/resource',
generator:{
filename: 'image/[contenthash:10].[ext]',
},
},
{
test: /\.mp4$/,
use: {
loader: 'file-loader',
options: {
name: 'video/[contenthash:10].[ext]',
},
},
},
]
},
resolve: {// 设置模块如何被解析
alias: {
vue: "vue/dist/vue.esm-bundler.js"
},
extensions: ['.js', '.vue']// 按顺序解析这些后缀名
},
optimization: {
minimizer: [new ESBuildPlugin()],
splitChunks: {
cacheGroups: {
defaultVendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
}
},
plugins: [
new VueLoaderPlugin(),
...setHtmlPlugin(),
isProduction ? new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:10].css',
}) : null,
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: false,
__VUE_PROD_DEVTOOLS__: false,
}),
new CleanWebpackPlugin(),
].filter(Boolean)
};
};
3. webpack配置多页面入口在文件webpack.util.js中,多入口根据'./src/pages/**/index.js'下面的文件对应,生成对应的${name}.html,代码如下
/*
* @Author: qianhua.xiong
*/
const glob = require('glob');
const HtmlWebpackPlugin = require('html-webpack-plugin');
function setEntry() {
const files = glob.sync('./src/pages/**/index.js')
const entry = {}
files.forEach(file => {
const ret = file.match(/^\.\/src\/pages\/(\S*)\/index\.js$/)
if (ret) {
entry[ret[1]] = {
import: file,
dependOn: 'vue_vendors',
}
}
})
// 拆分vue依赖
entry['vue_vendors'] = {
import: ['vue'],
filename: 'commom/[name].js'
}
return entry
}
function getTemplate() {
const files = glob.sync(`./src/index.html`)
return files[0]
}
function setHtmlPlugin() {
const files = glob.sync('./src/pages/**/index.js')
const options = []
files.forEach(file => {
const ret = file.match(/^\.\/src\/pages\/(\S*)\/index\.js$/)
if (ret) {
const name = ret[1]
if(name === 'home'){
options.push(new HtmlWebpackPlugin({
filename: 'index.html',
template: getTemplate(),
title: name,
minify: {
collapseWhitespace: false,
removeComments: true,
},
chunks: ['vue_vendors',name]
}))
}
options.push(new HtmlWebpackPlugin({
filename: `${name}.html`,
template: getTemplate(),
title: name,
minify: {
collapseWhitespace: false,
removeComments: true,
},
chunks: ['vue_vendors',name]
}))
}
})
return options
}
module.exports = {
setEntry,
setHtmlPlugin
}
最后编译时生成的dist目录文件对应如下:
4. App.vue文件是页面的展示代码如下
<template>
<HeadContent/>
<div class="pageContent">
<component :is="MyComponent" />
</div>
<FootContent/>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import HeadContent from './view/head/index.vue';
import FootContent from './view/foot/index.vue';
const getUrlPath = ()=>{
const url = window.location.href;
var name = 'home';
if(url && url.indexOf('.html')>0){
var pathStr = url.split('.html')[0];
const pathList = ['home','join','our','product','technology','about']
pathList.forEach(item=>{
if(pathStr.indexOf(item)>-1){
name = item
}
})
}
return name
}
var MyComponent = ref(null);
onMounted(async () => {
try {
const component = await import('./view/'+ getUrlPath() +'/index.vue')
MyComponent.value = component.default;
} catch (error) {
console.error('Failed to load component:', error);
}
});
</script>
<style>
* {
margin: 0;
}
</style>
5. index.html为每个页面共用的html文件,后面根据webpack.util.js文件配置的多页面应用可以生成不同的html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>支持多国语言国际化官网</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
6. pages中index.js代码如下:
index.js为每个页面的vue框架入口配置:
import { createApp } from 'vue';
import i18n from '../../i18n/index';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import './index.css';
import App from '../../App.vue';
const app = createApp(App);
app.use(i18n);
app.use(ElementPlus);
app.mount('#app');
7.view目录中为每个页面对应的业务代码,这里不举例了,重点说一下多国语言的配置,在i18n目录下的index.js文件配置如下:
// index.js
import { createI18n } from 'vue-i18n';
import zh from './zh.js';
import en from './en.js';
import fr from './fr.js';
const messages = {
en,
zh,
fr
}
const language = (navigator.language || 'en').toLocaleLowerCase() // 这是获取浏览器的语言
const i18n = createI18n({
legacy: false,
locale: localStorage.getItem('lang') || language.split('-')[0] || 'en', // 首先从缓存里拿,没有的话就用浏览器语言,
fallbackLocale: 'en', // 设置备用语言
messages,
})
export default i18n
不同的语言对应不同的配置,比如en.js:
export default {
menu: {
home:'HOME',
product:'PRODUCTS',
our:"ABOUT",
join:"CAREER",
about:"NEWS",
technology:"TECHNOLOGIES"
},
}
法语配置文件fr.js:
export default {
menu: {
home:'Accueil',
product:'Produits',
our:"A propos",
join:"Se joindre",
about:"Actualités",
technology:"Technologies"
},
}
中文配置文件zh.js
export default {
menu: {
home:'首页',
product:'产品',
our:"我们",
join:"加入",
about:"动态",
technology:"技术"
},
}
页面展示语言切换:
<template>
<div class="language-menu">
<div class="menu-item" @click="changeLang('fr')" :class="{'active':locale=== 'fr'}">FR</div><div class="jiange"></div>
<div class="menu-item" @click="changeLang('en')" :class="{'active':locale=== 'en'}">EN</div><div class="jiange"></div>
<div class="menu-item" @click="changeLang('zh')" :class="{'active':locale=== 'zh'}">CN</div>
</div>
</template>
<script setup>
import { useI18n } from 'vue-i18n';
const { locale } = useI18n();
const changeLang = (lang) => {
locale.value = lang
localStorage.setItem('lang', lang)
}
</script>
8. 页面导航栏的实现
在 template中展示以{{ $t("menu.home")}}的形式
<template>
<div class="headerPage">
<div class="logo-wrapper"><a href="/" class="header_logo"></a></div>
<div class="flex-grow" />
<div class="menu" id="router-menu">
<div class="menu-item"><a href="/home.html" :class="{'a-active':locationPath('home.html')}">{{ $t("menu.home")}}</a></div>
<div class="menu-item"><a href="/our.html" :class="{'a-active':locationPath('our.html')}">{{ $t("menu.our") }}</a></div>
<div class="menu-item"><a href="/technology.html" :class="{'a-active':locationPath('technology.html')}">{{ $t("menu.technology") }}</a></div>
<div class="menu-item"><a href="/product.html?product1" :class="{'a-active':locationPath('product.html')}" class="parentlink" @mouseenter="showSubMenu">{{ $t("menu.product") }}</a></div>
<div class="menu-item"><a href="/join.html" :class="{'a-active':locationPath('join.html')}">{{ $t("menu.join") }}</a></div>
<div class="menu-item"><a href="/about.html" :class="{'a-active':locationPath('about.html')}">{{ $t("menu.about") }}</a></div>
<div :class="{ 'submenu':true , visibleSubmenu:submenu }" @mouseenter="showSubMenu" @mouseleave="hideSubMenu" >
<a href="/product.html?product1" :class="{'a-active':locationPath('/product.html?product1')}">{{ $t("productMenu.0") }}</a>
<a href="/product.html?product2" :class="{'a-active':locationPath('/product.html?product2')}">{{ $t("productMenu.1") }}</a>
<a href="/product.html?product3" :class="{'a-active':locationPath('/product.html?product3')}">{{ $t("productMenu.2") }}</a>
</div>
</div>
<ChangeLang/>
</div>
</template>
<script setup>
import ChangeLang from '../../component/changeLang.vue';
import { ref } from 'vue';
const submenu = ref(false);
const hideSubMenu = ()=>{
submenu.value = false;
}
const showSubMenu = ()=>{
submenu.value = true;
}
const locationPath = (params)=>{
const url = window.location.href;
var tag = false;
if(url && url.indexOf('.html')>0){
if(url.indexOf(params)>-1){
tag = true
}
}
return tag
}
</script>
9. 动态监听vue-i18n语言切换,随之动态切换页面的视频或者图片
<script setup>
import {ref, watch} from 'vue';
import { useI18n } from 'vue-i18n';
import visionVideo from '../../video/vision.mp4'; //video文件路径为项目中中文版video文件的路径
import versionEnVideo from '../../video/visionen.mp4'; //英文版video文件的路径
const { locale } = useI18n();
const lang = localStorage.getItem('lang');
console.log(lang,'lang')
const VedioSelect = ref(lang == 'zh' ? visionVideo : versionEnVideo)
// 监听语言变化
watch(locale, (newValue, oldValue) => {
console.log('组件名----语言变化1');
console.log(`newValue: ${newValue}`);
console.log(`oldValue: ${oldValue}`);
if(newValue == 'en'){
VedioSelect.value = versionEnVideo
}else if(newValue == 'zh'){
VedioSelect.value = visionVideo
}
});
</script>
大致搞定,多页面入口,有利于seo检索,立刻给自己的官网写一个国际化的网站吧!
github代码地址
下载后可以直接跑着用,
- 有单页面应用版本在main分支,
- 有多页面应用版本在multiple-pages分支,
- 也有现成的demo例子在internationalWeb-demo分支,
- 需要的可以直接收藏下载,喜欢的给点一赞,是对我的鼓励哦!