这个文件夹为国际化文件夹。如果不做任何处理,在后续我们需要使用到国际化的文件,每个文件、每个组件都需要import进来,为了简化不需要每个组件都写下面的代码
import locales from 'locales/index'
我们使用 vue-router 和 vuex 源码挂载 router 和 store 的思想 (组件树深度优先遍历规则,所以我们可以在 组件内部使用 this.$store
和 this.$router
this.$route
),在初始化Vue的时候,传入 i18n
import i18n from '@/locales';
import mixinsGlobal from 'utils/mixinsGlobal'
new Vue({
router,
store,
i18n,
render: h => h(App)
}).$mount('#app');
mixinsGlobal()
import Vue from 'vue';
const mixinsGlobal = () => {
Vue.mixin({
beforeCreate() {
if (this.$options && this.$options.i18n) { //根组件
this.cusI18n = this.$options && this.$options.i18n;
} else {
// 深度先续遍历
this.cusI18n = this.$parent && this.$parent.cusI18n;
}
}
});
};
export default mixinsGlobal;
<el-select
v-model="form.status"
:placeholder="cusI18n.t('chooseStatus')"
style="width: 100%"
size="small"
>
<el-option
:label="statusValObj[statusEnmu.statusYes]"
:value="statusEnmu.statusYes"
></el-option>
</el-select>
通过上述操作代码,我们已经成功在每个 vue文件都注入了i18n,在每个组件都可以通过 this.cusI18n 直接访问到了 i18n,相当于挂载到每个vue组件的 this 上面了 (每个 vue组件对应 源码其实都是一个 vue 实列,只不过挂载通过 $mount 手动挂载,挂载给父组件)
此时。我们已经可以在每个组件通过 this.cusI18n访问到 i18n,并且在 vue 的template 里面可以直接 通过 cusI18n.t 直接使用 {{cusI18n.t('inputappname')}} (默认 vue 的tempalte 编译出来是一个render 函数,挂载在 options 上, 并且 此 render函数通过 with{} 绑定 tempalte 上面写的变量,其实就是通过 new Function 加上 with 创建一个沙箱环境,并且绑定了作用域的 this)
此时看上去已经ok了,但是存在一个问题。我们如果在非 vue文件需要使用 i18n, 还是需要在每个文件里面使用
import locales from 'locales/index'
因为上面的 demo 展示的是 在每个 vue文件成功绑定了 i18n。 此时我们想到了 webpack的 ProvidePlugin
config.plugin('provider').use(webpack.ProvidePlugin, [{
providerI18n: [ path.resolve(path.join(__dirname, 'src/locales/index.js')), 'default']
}]);
通过上面的配置已经成功在每个webpack 的 module 绑定了 providerI18n(相当于此项目的每个文件都可以之间通过providerI18n 访问到 i18n)。
此处埋个坑,后面写一写webpack 原理文章,讲一讲 module chunk bundle 之间的关系,现在先理解 module 就是项目里面的每个文件
此时,内心窃喜(我们做到了 don't repeat youself),但是这时候存在一个问题。因为vue tempalte 的问题 看下面的代码
<el-select
v-model="form.status"
:placeholder="providerI18n.t('chooseStatus')"
style="width: 100%"
size="small"
>
<el-option
:label="statusValObj[statusEnmu.statusYes]"
:value="statusEnmu.statusYes"
></el-option>
</el-select>
如果此时我们这样写,在vue 的tempalte 里面之间用 providerI18n.t , 此时被 vue-loader 编译出来之后我们发现 placeholder 这里变成了 _vm.providerI18n.t('chooseStatus'), 默认被绑定了 _vm, 这是因为上面提到的 vue template 被编译为 render 函数的愿意(new Function + with 构建了一个沙箱环境,默认用 _vm 在template上面绑定了使用到的变量),此时运行肯定报错,因为 _vm上面没有 providerI18n, 这个 providerI18n是一个__webpack__require内部的一个全局变量,可以直接使用。
此时运行肯定报错,_vm.providerI18n.t is not a function. 这时我们想到使用 babel 来帮我们处理一下 这个vue-loader 处理的结果
babel插件 parseTemplatei18
module.exports = function ({ types }) {
return {
visitor: {
CallExpression(path, state) {
let options = state.opts;
let calleeObjectCode = path.get('callee').toString()
if (types.isMemberExpression(path.node.callee) && calleeObjectCode === options.calleeSourceCode ) {
const newNode = types.identifier(options.calleeTargetCode)
path.get("callee").get('object').replaceWith(newNode)
}
}
}
}
}
babel.config.js 配置
const parseTemplatei18 = require('./parseTemplatei18')
module.exports = {
presets: [
],
plugins: [
[parseTemplatei18, { calleeSourceCode: '_vm.providerI18n.t', calleeTargetCode: 'providerI18n' }]
]
};
此时在编译的时候就会把 vue tempalte 编译的结果 _vm.providerI18n.t('chooseStatus') 替换为 providerI18n.t('chooseStatus')。因为使用了 types.identifier(options.calleeTargetCode) 替换了 老的 path.node.callee.
至此,我们完成了功能 (不需要在所有模块导入locales/index, 规避了don't repeat youself,借助了 webpack的ProvidePlugin + babel 插件实现了功能)。
因为这边文章涉及部分原理知识,后续会断断续续补充完整 (vue 编译原理, vue 组件渲染原理,webpack module chunk bundle tapable 原理, babel 插件原理,后续还有一个增强的 remove-console babel插件)。