用vue-cli3从0开始搭建一个多入口,国际化的前端项目

1,479 阅读22分钟

前言

大家好,我是Frank。最近公司准备开发一个功能,由于功能复杂,且需要多端协同开发,所以,单拎出来新建了一个git项目。项目中用到的技术也是大家耳熟能详的,比如,vue-cli3、vie-router、vue-i18n、element-ui、dayjs、eslint、stylelint等等。下面带大家一起看一下整个过程。

项目简介

该项目是一个大项目单拎出来的一个大的模块功能,因为涉及多端协作同步开发。

项目目录

项目目录

遇到的问题

多入口

因为项目需要多端协同开发,所以设置了多入口,这样大家就可以在一个项目上共同开发了。根据 vue-cli3 官方文档进行配置,项目最外层添加 vue.config.js 文件,并作如下配置

// 配置文件信息
var pageData = {
  publicPath'/client', // 项目文件部署的目录,例 https:www.baidu.com/saas
  assetsDir`assets`, // 项目中静态资源目录,若不需要可以不配置
  outputDir`./dist`, // 项目文件打包后输出的问卷目录
  buildVersionnew Date().valueOf(),
  entry`./src/pages/main.js`, // 项目入口文件
  template`./src/pages/index.html`, // 项目默认html模板
  filename'index.html',
  title`...`,
}
// 根据不同入口配置
switch (pageName){
  case 'pc':
  case 'mb':
    pageData.publicPath = `/client/team/${pageName}`;
    pageData.outputDir = `./dist/client/team/${pageName}`;
    pageData.entry = `./src/main_${pageName}.js`;
    pageData.template = `./src/index_${pageName}.html`;
    break;
  case 'www':
    pageData.publicPath = `/saas/team/${pageName}`;
    pageData.outputDir = `./dist/client/team/${pageName}`;
    pageData.entry = `./src/main_${pageName}.js`;
    pageData.template = `./src/index_${pageName}.html`;
    break;
  default:
    pageData.publicPath = `/saas/team/${pageName}`;
    pageData.outputDir = `./dist/client/team/${pageName}`;
    pageData.entry = `./src/main_${pageName}.js`;
    pageData.template = `./src/index_${pageName}.html`;
    break;
}
module.exports = {
  publicPath: pageData.publicPath,
  assetsDir: pageData.assetsDir,
  outputDir: pageData.outputDir,
  productionSourceMapfalse, // 是否开启生产模式下的sourceMap压缩
  lintOnSavetrue, // 是否开始eslint校验的控制台打印错误信息
  css: {
    loaderOptions: {
      css: {},
      postcss: {
        plugins(loader)=>[
          // Add the plugin
          new IconfontWebpackPlugin(loader), // 一个插件,用于将icon转为svg,兼容低版本浏览器,可以忽略
        ]
      }
    }
  },
  chainWebpack(config)=>{
    config.resolve.alias
      .set('@'resolve('src'))
      .set('_assets'resolve('src/assets')); // 路径别名
    fs.remove(pageData.outputDir + '/' + pageData.assetsDir); // 打包文件路径,每次打包之前删除旧的文件
  },
  // devServer的相关配置可以查看vue-cli官网
  devServer: {
    disableHostChecktrue,
    host: hosts.local,
    openPage: pageData.filename,
    progressfalse,
    port: port,
    proxy: {
      '^/assets': {
        target'http://localhost:8501',
        changeOrigintrue,
        pathRwrite: {
          '/config/''/server/' + hosts.base + '/config/',
          '/''/'
        }
      }
    },
  },
  // 真正启动的项目对应的入口文件
  pages: {[pageName]: {
    buildVersion: pageData.buildVersion,
    entry: pageData.entry,
    template: pageData.template,
    filename: pageData.filename,
    title: pageData.title,
  }}
}

以上是主要内容,作用在注释中说明。仔细看过后就会发现其实就是多个入口的参数按照规则匹配到正确的入口,最终还是输出的单个入口,最后在添加好对应的入口文件即可。详细内容建议查看vue-cli官方文档

国际化

因为项目需要做海外版。同样使用官方推荐的插件 vue-i18n。由于有些插件还需要单独配置国际化,所以下面单独来说每一部分的具体配置:

  • 项目本身多语言抽取

我们在 lang 文件目录下抽取项目中的语言文件包,然后抛出来,在 index.js 中配置,最后在 main.js 中引入即可。先来看下 lang 文件目录

lang文件目录

下面看一下 index.js 文件的配置信息

import Vue from 'vue';
import VueI18from 'vue-i18n';
import messages from '@/lang/en';
import axios from 'axios';
Vue.use(VueI18n);
// 初始化i18n实例
export const i18n = new VueI18n({
  locale'en'// 设置语言环境
  fallbackLocale'en',
  messages: {en: messages} // 设置语言环境信息,注意这里的书写方法
})
const loadedLanguages = ['en'// 我们的预装默认语言
// 设置浏览器的语言
function setI18nLanguage(lang){
  i18n.locale = lang;
  axios.defaults.headers.common['Accept-Language'] = lang;
  document.querySelector('html').setAttribute('lang', lang);
  return lang;
}
// 按需加载语言包
export function loadLanguageAsync(lang){
  if (i18n.locale !== lang){
    if (!loadedLanguages.includes(lang)){
      return import(/* webpackChunkName: "lang-[request]" */ '@/lang/' + lang).then(msgs=>{
        i18n.setLocaleMessage(lang, msgs.default);
        loadedLanguages.push(lang);
        return setI18nLanguage(lang);
      })
    }
    return Promise.resolve(setI18nLanguage(lang));
  }
  return Promise.resolve(lang);
}

以上是主要内容,依然是在注释中解释代码作用,希望大家仔细阅读。需要注意的是默认加载的语言包最终引入的就是你语言包里抛出的内容,如果语言包没有配置语言类型,需要自己加上语言类型 en。这也是官网推荐的示例。下面来看下 main.js 配置

import Vue from 'vue';
import App from './app_saas.vue';
import {i18n, loadLanguageAsync} from '@/lang/index.js';
import VueCookie from 'vue-cookie';

Vue.config.productionTip = false;
// 判断需要加载的语言包
let routeQueryLang = router.resolve(window.location.hash.substr(1)).route.query.lang;
if (routeQueryLang){
  VueCookie.set('locationArgumentLang', routeQueryLang === 'zh-CN' ? 'zh-CN' : 'en');
}               
let siteLanguage = VueCookie.get('locationArgumentLang') || navigator.language || 'zh-CN';
loadLanguageAsync(siteLanguage);
// 生成Vue实例,并将初始化好的 i18n 实例挂载到Vue上即可
new Vue({
  router,
  i18n,
  renderh=>h(App),
}).$mount('#app')

因为项目中判断给用户加载的语言的逻辑是这样的:如果用户选择了某种语言,那么会存到 cookie 中,下次会默认加载该语言;若没有设置,那就会取浏览器的语言;若还没有,会默认加载英文。

  • element-ui 多语言配置

在上面基础上再添加 element-ui 的语言包,然后 main.js 中添加如下代码

Vue.use(ElementUI, {
  i18n(key, value)=>i18n.t(key, value)
})

同样是官网推荐配置

  • dayjs 多语言配置

index.js 中添加如下代码

// 按需加载dayjs语言包
function loadLanguageDayjs(lang){
  lang = lang.toLowerCase();
  return import('dayjs/locale/' + lang).then(()=>{
    return dayjs.locale(lang);
  })
}

需要注意的是 dayjs 中参数都是小写的,比如中文 element-uizh-CNdayjszh-cn。详细内容见官网推荐配置

eslint 配置

因为要规范代码风格,所以引入了 eslint 来校验。

首先,项目中需要引入以下插件

yarn add --dev @vue/cli-plugin-babel @vue/cli-plugin-babel babel-eslint eslint eslint-plugin-vue

其次,项目最外层添加文件 .eslintrc.js ,下面是我项目中的配置

module.exports = {
  roottrue// 检测到最近一级.eslintrc*
  env: {
    es6true// 支持并启用es6语法
    nodetrue// 支持node语法及变量
    browsertrue// 支持浏览器全局变量
  },
  extends: ['plugin:vue/recommended''eslint:recommended'], // 第三方插件
  plugins: ['vue'],
  parserOptions: {
    parser'babel-eslint'// 解析器,默认使用Espree
    ecmaVersion2018,
    sourceType'module'// 指定文件来源类型,"script" (默认) 或 "module"(如果你的代码是 ECMAScript 模块)
  },
  // 全局变量不做校验
  globals: {
    moment'writable',
    Vue'writable',
    VueRouter'writable',
    VueI18n'writable',
    ELEMENT'writable',
    TWEEN'writable',
    _'writable',
  },
  rules: {
    indent: ['error'2, {SwitchCase1}],
    'no-debugger'0,
    'no-console'1,
    'no-unused-vars'1,
    eqeqeq2,
    quotes: ['error''single', {'allowTemplateLiterals'true}],
    'no-irregular-whitespace'2// 禁止不规则的空格
    'no-multi-spaces''error'// 禁止多个空格
    'space-infix-ops'2// 运算符前后禁止多个空格
    'array-bracket-spacing': ['error''never'], // 数组统一空格
    'block-spacing': ['error''always'],
    'comma-spacing': ['error'],
    'comma-style': ['error''last'],
    'computed-property-spacing': ['error''never'],
    'func-call-spacing': ['error''never'],
    'key-spacing': ['error', {'beforeColon'false'afterColon'true}], // 冒号空格
    'keyword-spacing': ['error', {'before'true}], //if () {}空格else
    'no-whitespace-before-property''error',
    'semi-spacing''error'//分号空格
    'space-before-blocks': ['error''never'], //function name()空格{}
    'space-before-function-paren': ['error''never'], //function name空格(){}
    'space-in-parens': ['error''never'],
    'arrow-spacing': ['error', {'before'false'after'false}], //()空格=>空格{}
    'rest-spread-spacing': ['error''never'], // 空格...空格{}
    'no-unreachable'1,
    'no-trailing-spaces'2,
    'object-curly-spacing': ['error''never'],
    // 'vetur.validation.template': false,
    // 'vue/valid-template-root': 'off',
    'vue/no-v-html'0,
    'vue/component-tags-order': ['error', {'order': ['style''template''script']}],
    'vue/html-quotes': ['error''double'],
    'vue/require-v-for-key'1// v-for要有key
    'vue/html-self-closing''off'// 标签闭合
    'vue/html-closing-bracket-newline': ['error', {
      'singleline''never',
      'multiline''never'
    }],
    'vue/html-closing-bracket-spacing''error'// 标签后是否允许空格
    'vue/html-end-tags''error'// 标签自我关闭
    'vue/mustache-interpolation-spacing''error'// 标签插值中前后固定一个空格
    'vue/no-reserved-component-names''error'// 禁止保留名称
    'vue/no-unsupported-features'1// 未支持的语法
    'vue/no-unused-components'1// 未注册的组件
    'vue/padding-line-between-blocks'1//
    'vue/no-use-v-if-with-v-for'1,
    // "vue/static-class-names-order": 1, // name属性
    'vue/max-attributes-per-line': ['error', { // 单行和多行属性数量
      'singleline'10,
      'multiline': {
        'max'1,
        'allowFirstLine'false
      }
    }],
  },
};

大家可以按照自己需要的规范来配置,具体配置规则见eslint官网eslint-plugin-vue官网

最后,我们需要忽略一部分文件的校验。那么我们需要在 .eslintrc.js 同级添加 .eslintignore ,以下是我们项目忽略内容,大家可以根据实际情况配置

node_modules
dist

stylelint 配置

可能大多数开发人员不会去规范css书写,因为我们leader要求,所以添加了。

首先,需要加载以下插件

yarn add --dev stylelint stylelint-config-standard stylelint-order

其次,同eslint,添加 .stylelintrc.js 文件,并作如下配置

module.exports = {
  extends: ['stylelint-config-standard'],
  plugins: ["stylelint-order"],
  rules:{
    'no-descending-specificity':null,
    'function-url-quotes''always',
    'string-quotes''double''indentation'2,
    'color-hex-case''upper',
    'color-hex-length''long',
    'rule-empty-line-before''never',
    'font-family-no-missing-generic-family-keyword'null,
    'block-opening-brace-space-before':'always',
    'property-no-unknown':null,
    'no-empty-source':null,
  } 
}

这里直接使用了插件规则 stylelint-config-standardstylelint-orderstylelint官网

最后,同样忽略的文件需要在 stylelintignore 中配置

  dist
  node_modules

后记

好了,这次的分享到这里,希望对大家有所帮助,如果可以的话,给点个赞哦。嘻嘻~~