网站建设企业如何为公司建设做试试彩网站

当前位置: 首页 > news >正文

网站建设企业如何为公司建设,做试试彩网站,在虚拟主机上建设多个网站,软件开发公司架构先来看最后的效果 问题 用过国际化i18n的朋友都知道#xff0c;天下苦国际化久矣#xff0c;尤其是中文为母语的开发者#xff0c;在面对代码中一堆的\(t(abc.def)这种一点也不直观毫无可读性的代码#xff0c;根本不知道自己写了啥 #xff08;如上图#xff0c;你看得出…先来看最后的效果 问题 用过国际化i18n的朋友都知道天下苦国际化久矣尤其是中文为母语的开发者在面对代码中一堆的\)t(abc.def)这种一点也不直观毫无可读性的代码根本不知道自己写了啥 如上图你看得出来这是些啥吗 第二个问题就是i18n各种语言版本的语言包难以维护随着项目变大这个语言包会越来越难以维护能不能自动去维护呢
解决思路 所以我们前端组的小伙伴就想了个办法能不能直接\(t(中文)呢就像下图 这样是不是就方便看了但是问题依然有使用中文做key可能会在打包时乱码或者在浏览器查看下乱码总归就是直接使用中文不安全。 因此我们想出了一个万全之策 针对以前做了国际化的项目写node扫描一遍src目录找出所有\)t(xxxx)替换成\(t(对应中文)由于需要改的项目是vue2编写所以写webpack插件做以下事 在打包开始前扫描src目录找到 \)t(对应中文)使用crc32将中文转为加密后的key然后将key: 对应中文自动追加到语言包文件中,对应的语言包会长这样 在打包结束后扫描打包后的文件将\(t(对应中文)修改为\)t(key)打包后的\(t会长这样 这样就不担心乱码问题了而且可以自动维护语言包 将源码中的英文键替换成中文键 这一步之前没有写国际化的项目不用执行。 这一步只需要执行一次即可因此不写进webpack插件中去直接写nodejs脚本具体步骤如下 扫描src下所有文件夹这个步骤需要用到递归如果是文件夹就继续往下扫描用正则表达式找出 \)t(xxxx) 和 i18n.t(xxxx)这样的字符串从之前的中文语言包中找出对应的中文并替换进源码 // replaceLang.js const path require(path) const fs require(fs);let zhLang require(./src/utils/languages/zh.js); // 扫描文件的根路径 let gFilePath resolve(/src) // 需要扫描的文件 let gExtension [.js,.vue,.ts,.tsx,.jsx]function resolve(dir){return path.join(dirname,dir)//path.join(dirname)设置绝对路径 }// 提取多级嵌套结构中的中文 function getValueByAttrs(attrs){let str ,langObj zhLang;attrs.forEach(item{str langObj[item]if(typeof str object){langObj langObj[item]}})return str }/*** 替换文件夹下的英文键* * folderPath: 需要扫描替换的文件夹* extension 需要替换的文件后缀集合/ function replaceLangs(folderPath, extension){// 读取文件夹下文件const files fs.readdirSync(folderPath,utf8)files.forEach((fileName) {const filePath path.join(folderPath, fileName)const stats fs.statSync(filePath)if(stats.isDirectory()) {// 如果该目录是文件夹继续往下扫描this.replaceLangs(filePath,extension)}else if(stats.isFile()) {// 如果该目录是文件进一步判断文件类型if(extension.includes(path.extname(fileName).toLowerCase())) {//读取文件内容const fileContent fs.readFileSync(filePath, utf8)// 用正则表达式找出 \(t(xxxx) 和 i18n.t(xxxx)这样的字符串let results fileContent.match(/\\)t((.?))/g)||[]let results2 fileContent.match(/i18n.t((.?))/g)||[]results.concat(results2).forEach(info{let regex /(?()(.?)(?))/g; let attr info.match(regex)[0]try{let attrs eval(attr).split(.)||[];// 从之前的语言包中获取对应的中文let str getValueByAttrs(attrs)if(str){if(info.includes(i18n.t)){fileContent fileContent.replace(info,i18n.t(str))}else{fileContent fileContent.replace(info,$t(str))}}}catch(e){console.log(e)}})// 更新文件fs.writeFileSync(filePath, fileContent)}}}) }replaceLangs(gFilePath, gExtension)webpack插件开发基础知识 可以参考插件开发文档 插件向第三方开发者提供了 webpack 引擎中完整的能力。使用阶段式的构建回调开发者可以在 webpack 构建流程中引入自定义的行为。 插件可以做些什么 可以在关键时间点执行一些逻辑要改变输出取决于我们可以获取到什么以及对它做些什么修改操作比如我们可以去除注释去除空格合并代码压缩文件提取公共代码改变配置修改改变输出等。 webpack插件组成 webpack插件由一下组成 一个JavaScript命名函数或JavaScript类。在插件函数的prototype上定义一个 apply 方法。指定一个绑定到webpack自身的事件钩子。处理webpack内部实例的特定数据。功能完成后调用webpack提供的回调。 插件基本架构 插件是由「具有 apply 方法的 prototype 对象」所实例化出来的。这个 apply 方法在安装插件时会被 webpack compiler 调用一次。apply 方法可以接收一个 webpack compiler 对象的引用从而可以在回调函数中访问到 compiler 对象。一个插件结构如下 class HelloWorldPlugin {apply(compiler) {compiler.hooks.done.tap(Hello World Plugin,(stats / 绑定 done 钩子后stats 会作为参数传入。 /) {console.log(Hello World!);});} }module.exports HelloWorldPlugin;然后要安装这个插件只需要在你的 webpack 配置的 plugin 数组中添加一个实例 // webpack.config.js var HelloWorldPlugin require(hello-world);module.exports {// … 这里是其他配置 …plugins: [new HelloWorldPlugin({ options: true })], };Compiler Compiler 负责编译贯穿webpack的整个生命周期Compiler 对象包含了当前运行Webpack的配置包括entry、output、loaders等配置这个对象在启动Webpack时被实例化而且是全局唯一的。Plugin可以通过该对象获取到Webpack的配置信息进行处理。 常用钩子 beforeRun 在开始执行一次构建之前调用compiler.run 方法开始执行后立刻进行调用。watchRun 在监听模式下一个新的 compilation 触发之后但在 compilation 实际开始之前执行。compilation compilation 创建之后执行。emit 输出 asset 到 output 目录之前执行。done 在 compilation 完成时执行。这个钩子 不会 被复制到子编译器。 Compilation Compilation对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时每当检测到一个文件变化就会创建一个新的 compilation从而生成一组新的编译资源。一个 Compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息简单来讲就是把本次打包编译的内容存到内存里。Compilation 对象也提供了插件需要自定义功能的回调以供插件做自定义处理时选择使用拓展。 简单来说,Compilation的职责就是构建模块和Chunk并利用插件优化构建过程。 Compiler 和 Compilation 的区别 Compiler 代表了整个 Webpack 从启动到关闭的生命周期而 Compilation 只是代表了一次新的编译只要文件有改动compilation就会被重新创建。 注意 有些插件钩子是异步的。我们可以像同步方式一样用 tap 方法来绑定也可以用 tapAsync 或 tapPromise 这两个异步方法来绑定。 当我们用 tapAsync 方法来绑定插件时必须 调用函数的最后一个参数 callback 指定的回调函数。当我们用 tapPromise 方法来绑定插件时必须 返回一个 pormise 异步任务完成后 resolve 。 Language插件开发 流程 在编译开始前扫描src目录下的所有文件将 \(t(中文) 字符串找到将其通过crc32加密得到key,并追加到语言包中检测到文件变化时重新执行步骤1更新语言包编译完成后输出到dist目录前将打包好的文件中的 \)t(中文) 换成 \(t(key)再输出到目标目录 源码 //languagePlugin.js const path require(path) const { crc32 } require(crc) const fs require(fs);// 扫描文件的根路径 let gFilePath resolve(/src) // 需要扫描的文件 let gExtension [.js,.vue,.ts,.tsx,.jsx]function resolve(dir){return path.join(__dirname,dir)//path.join(__dirname)设置绝对路径 }class LanguagePlugin {constructor(config) {this.config {// 指定中文语言包zh: resolve(config.zh),// 需要生成的语言包注意需要包含中文语言包langs: config.langs.map(path resolve(path))}// 中文语言包内容this.zh {}// 所有语言包内容this.keyFileList []// key引用计数引用为0的key会被删除this.keyUseNumber {}}apply(compiler) {// 编译开始前执行的钩子compiler.hooks.run.tap(LanguagePluginRun,() {this.saveZhToCrc32JSON()})// 文件发生改变时执行的钩子compiler.hooks.watchRun.tap(LanguagePluginWatch,() {this.saveZhToCrc32JSON()})compiler.hooks.emit.tapAsync(LanguagePlugin, (compilation, callback) {const now Date.now()const zh this.zh// 检索每个构建输出的chunkcompilation.chunks.forEach(chunk {// 检索由 chunk 生成的每个资源(asset)文件名chunk.files.forEach(filename {// 文件类型是js才做检测和替换var fileType filename.split(.).pop()if(fileTypejs compilation.assets[filename] compilation.assets[filename].source) {// 获取到源码var source compilation.assets[filename].source();var newVal sourcevar reg /((i18n\.t)|(\\)t))((\)(|)(.?)(\)(|))/g// 替换源码newVal newVal.replace(reg, function(val) {let str val.replace(/((i18n.t)|($t))((\)(|)/g,).replace(/(\)(|))/g,).replace(/\/g,).replace(/\/g,)let hashKey crc32(str).toString(16)if(zh[hashKey]) {let ret val.replace(str, hashKey)return ret}else{return val}})// 覆盖文件compilation.assets[filename] {source: function () {return newVal},size: function () {return newVal.length}}}});});// 计时console.log((Date.now() - now) / 1000)callback();});}saveZhToCrc32JSON() {this.keyFileList []this.keyUseNumber {}// 判断几个XXkey.js文件存不存在如果不存在就创建一个this.config.langs.forEach(filePath {const { dir, base } path.parse(filePath);try {fs.accessSync(dir, fs.constants.F_OK)try {fs.accessSync(filePath, fs.constants.F_OK)} catch(err) {console.log(filePath 不存在,将为您自动创建)fs.writeFileSync(filePath,const lang {\n}\nexport default lang)}} catch (err) {console.log(dir 不存在,将为您自动创建)fs.mkdirSync(dir)fs.writeFileSync(filePath,const lang {\n}\nexport default lang)}})// 提取出langs文件夹下文件的内容this.config.langs.forEach(filePath {let langFileContent fs.readFileSync(filePath,utf8)// 提取{}之间的有效内容langFileContent langFileContent.match(/{[\S\s]}/g)[0]const obj JSON.parse(langFileContent)const origin JSON.parse(langFileContent)for(let key in obj) {// 去掉首尾空格obj[key] obj[key].replace(/^\s/g,).replace(/\s*\(/g,)}this.keyFileList.push({path: filePath,val: obj,origin: JSON.stringify(origin)})for(let key in obj) {this.keyUseNumber[key] 0}})// 更新langs文件this.updateLangsByFiles(gFilePath, gExtension)this.keyFileList.forEach((keyFileItem) {// 删除没有使用到的keyfor(let key in this.keyUseNumber) {if(this.keyUseNumber[key] 0) {console.log([keyFileItem.val[key]]没有被使用将为您自动删除)delete keyFileItem.val[key]}}// 语言包有改动才更新if(JSON.stringify(keyFileItem.val) ! keyFileItem.origin) {console.log(更改了文件keyFileItem.path)let content const lang {for(let key in keyFileItem.val) {content \n key : keyFileItem.val[key] ,}// 去掉最后一个逗号content content.substring(0, content.length - 1)content \n}\nexport default langfs.writeFileSync(keyFileItem.path, content)}if(this.config.zh keyFileItem.path) {this.zh keyFileItem.val}})}/*扫描文件更新语言包*/updateLangsByFiles(folderPath, extension) {// 读取文件夹下的内容const files fs.readdirSync(folderPath,utf8)files.forEach((fileName) {const filePath path.join(folderPath, fileName)const stats fs.statSync(filePath)// 判断是文件夹还是文件if(stats.isDirectory()) {this.updateLangsByFiles(filePath,extension)}else if(stats.isFile()) {if(extension.includes(path.extname(fileName).toLowerCase())) {const fileContent fs.readFileSync(filePath, utf8)let results fileContent.match(/((i18n\.t)|(\\)t))((|)(.?)(|))/g)if(results) {results.forEach(result {// 获取中文并且获取crclet str result.replace(/((i18n.t)|($t))((|)/g,).replace(/(|))/g,).replace(/\/g,\).replace(/\/g,\)let hashKey crc32(str).toString(16)// 更新语言包数据以及计数数据this.keyFileList.forEach((keyFileItem) {const obj keyFileItem.valif(!obj[hashKey]) {obj[hashKey] strthis.keyUseNumber[hashKey] 0}this.keyUseNumber[hashKey]})})}}}})} }module.exports LanguagePlugin;插件使用 项目的根目录下添加languagePlugin.jspackage.json 的 devDependencies 加上 “crc”:“^4.3.2”运行npm install安装crc依赖vue.config.js中使用该插件 configureWebpack(config) {return {plugins: [new LanguagePlugin({zh: /src/langs/zhKey.js,langs: [// 这里有中文版中文繁体版英文版语音包/src/langs/zhKey.js,/src/langs/zhTWKey.js,/src/langs/enKey.js]})]} }修改国际化i18n插件引入语言包的路径运行npm run dev能看到语言包自动更新页面效果正常。运行npm run build正常打包。 后记 这个插件其实不难就是我的正则表达式水平不太行后面我可能会专门花时间去学习正则表达式。 webpack的各种钩子我理解的不是很深刻目前这个代码里的钩子都是我一个一个试出来的。 关于这个插件我其实还有一些想法想实现比如可以调用AI翻译的API自动翻译出来对应的语言包国际化从此以后完全傻瓜式啦。 还可以写个vite版本的插件不过这是以后的事情啦。。。公司的国际化改造告一段落撒花~