您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
前端工程化在 WMS 6.0 中的实践
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
前端工程化在 WMS 6.0 中的实践
自猿其说Tech
2022-05-11
IP归属:未知
19440浏览
前端
### 1 背景 仓储中台的愿景是,以用户为根本,通过发现、定义、设计、交付可被多BP复用的WMS能力,建设以仓储中台为主导的前中台协同研发内部共生态,帮助BP低成本地快速满足WMS相关业务诉求。wms6.0 依据此愿景进行建设,旨在提供轻量部署、灵活配置、高度产品化的仓储管理系统。 为了更好的支撑业务发展,提升用户体验,降低用户接入成本,wms6.0 各个子系统于年初开始相继进行国际化改造。web端基于vue开发,于是决定使用与之配套的『Vue I18n』作为解决方案。 ### 2 遇到的困难 在对前端工程国际化改造进行工时评估时,耗时较长的原因是改造面临以下问题: ##### 1)工作量大 - 6.0前端工程包含9个子工程,其中8个工程确认必须国际化,单个子工程文件量大 - 由于前期业务的快速迭代,未考虑国际化,国际化需要从零开始,代码改造量大 ##### 2)干扰项多 - 代码中中文注释的存在,会对有效中文的检索定位造成干扰 - 有些文件包含中文但是不需要国际化,也会对中文检索造成干扰 ##### 3)容易遗漏 - 在改造完成后,传统的方案是人工检查,这很容易遗漏一些场景,导致校验不够充分 - 接手其他人的工作,代码逻辑不够熟悉 因此,通过工具来提高生产效率和校验的准确度变得尤为重要! ### 3 解决方案 基于以上问题,在搜索相关资料并对比多个工具的实现方法后,我们决定使用『node cli』作为工具的实现方案,通过AST来精准识别有效中文和i18n方法调用。 使用『node cli』作为工具的实现方案,有以下几个原因: - 『node cli』使用 javascript 进行开发 - 对于vue和js文件的解析有很好的第三方库支持 - 支持 windows 和 macos 跨平台使用 - IDE(WebStorm、VS Code)无关 整体的实现思路分为以下几步: - 通过设定好的搜索规则,找到符合要求的vue和js文件,支持忽略指定目录或文件 - 读取文件的内容,将js文件转换为JS-AST,vue文件转换为Template-AST - 通过相应的方法对AST进行遍历,在找到符合条件的代码片段后,对改造结果进行校验,并记录校验结果 - 通过文件路径合并校验结果并输出到文件中。 ### 4 具体实现 #### 4.1 依赖库介绍 ##### 4.1.1 glob node的glob模块使用 *等符号, 来写一个glob规则,像在shell里一样,获取匹配对应规则的文件,本次需要使用glob的sync方法进行同步搜索 glob.sync(pattern, [options]) - pattern {String} 待匹配的模式 - options {Object} ##### 4.1.2 fs fs包含node提供的一系列文档操作api,本次用到的是fs同步文件读取方法 readFileSync ##### 4.1.3 babel提供的工具库 @babel/parser是 babel 的核心工具之一,提供两种解析代码的方法: - babelParser.parse(code, [options]):解析生成的代码含有完整的 AST 节点,包含File和Program层级。 - babelParser.parseExpression(code, [options]):解析单个 js 语句,该方法生成的 AST 不完整,所以使用@babel/traverse必须提供scope属性,限定 AST 节点遍历的范围。 @babel/traverse 提供遍历JS-AST节点的方法 @babel/types 用于判断节点类型 目前主流 JS 编译器例如 @babel/parser 定义的 AST 节点都是根据 estree/estree: The ESTree Spec (github.com) 规范来的,可以在 AST explorer 在线演示。 ##### 4.1.4 @vue/compiler-sfc vue单文件组件(SFC)内部模板语法得到的 AST 和 JS 的AST区别很大,我们需要使用 @vue/compiler-sfc 来解析单文件组件,compiler-sfc 解析后的内容我们只需要关注 template 和 script 里的内容跟即可。 ##### 4.1.5 esbuild esbuild 一个 JavaScript Bundler 打包和压缩工具,它可以将 JavaScript 和 TypeScript 代码打包分发在网页上运行,我们的 「/build/index.js」使用该工具构建。 #### 4.2 初始化项目 ##### 4.2.1 创建项目 ```bash mkdir wms-i18n-check cd wms-i18n-check npm init -y ``` ##### 4.2.2 创建可执行文件 在 『wms-i18n-check』 根目录下新建一个文件『bin/index.js』 ```javascript #!/usr/bin/env node 'use strict'; require('../build/index.js'); ``` 在 『package.json』 中添加配置项,然后在『/build/index.js』 实现 cli 能力 ```json { "bin": { "wms-i18n-check": "./bin/index.js" } } ``` #### 4.3 核心实现 ##### 4.3.1 整体流程 ![](//img1.jcloudcs.com/developer.jdcloud.com/f516cb64-2c79-4a13-971a-c49304d7d75220220511150030.png) js文件的解析包含在了vue文件的解析逻辑中,所以这里我们以vue文件的处理过程为例。 主要的流程如上图所示: 1)使用 @vue/complier-sfc 将vue SFC 转换为Template-AST 2)分别对解析结果中的 template 和 script 进行处理: - template 是解析template 标签部分得到的AST,其内部节点主要分为两种类型 props 和 children。 - - children内部我们需要处理两种类型的子节点,type为5代表节点使用了插值语法(INTERPOLATION),我们拿到内部代码后,按照标准js代码处理即可;type为1代表节点为元素(ELEMENT),需要继续作为 Template-AST进行递归处理 - - 遍历props,找到 type 为7(DIRECTIVE)的节点后,按照标准js代码处理即可 - script是解析script标签内部 JS 得到的标准js代码,我们需要使用 @babel/parser 将其转换为JS-AST,然后使用 @bable/traverse 进行节点遍历 3)将单个文件的校验结果合并后写入到 checkResult.json 文件中 ##### 4.3.2 核心代码 ###### 1)识别 vue 和 js 文件进行不同的逻辑处理 ```javascript // parse.ts import { parse as vueParser } from "@vue/compiler-sfc"; import { parse as babelParser } from "@babel/parser"; export function parseVue(code: string) { return vueParser(code).descriptor; } export function parseJS(code: string) { return babelParser(code, { sourceType: "module", plugins: ["jsx"], }); } ``` ```javascript valid() { if (!Object.values(FileType).includes(this.fileType)) { logError(`Unsupported file type: ${this.filename}`); return; } if (this.hasI18NCall(this.sourceCode)) { if (this.fileType === FileType.JS) { // js文件 this.collectRecordFromJs(this.sourceCode) } else if (this.fileType === FileType.VUE) { // vue文件 const descriptor = parseVue(this.sourceCode); if ( // <template>部分ast descriptor?.template?.content && this.hasI18NCall(descriptor?.template?.content) ) { this.collectRecordFromTemplate(descriptor?.template.ast) } if ( // <script>部分ast descriptor?.script?.content && this.hasI18NCall(descriptor?.script?.content) ) { this.collectRecordFromJs(descriptor.script.content) } } } } ``` ###### 2)遍历JS-AST 通过遍历 CallExpression 类型的节点就能覆盖所有的 i18n 方法调用,对于类似 i18n.t(status === 1 ? 'a', 'b') 这种条件表达式的国际化方法调用,我们需要拿到前后两个 i18n key:consequent 和 alternate ```javascript collectRecordFromJs(code: string) { const ast = parseJS(code); const visitor: Visitor = { CallExpression: (path) => { const source = path.toString() if(this.onlyHasI18NCall(source)){ const node = path.node const args = node.arguments const i18nNode = args[0] if(i18nNode.type === 'ConditionalExpression') { const consequentKey = ((i18nNode as ConditionalExpression).consequent as StringLiteral).value const alternateKey = ((i18nNode as ConditionalExpression).alternate as StringLiteral).value try { const consequentLang = getLang(consequentKey) const alternateLang = getLang(alternateKey) this.records.push({ keys: { consequentKey, alternateKey }, source, result: { consequentLang, alternateLang }, valid: consequentLang !== consequentKey && alternateLang !== alternateKey }) } catch (e) { this.records.push({ keys: { consequentKey, alternateKey }, source, valid: false, errorMsg: (e as PropertyResolverError).message }) } } else { const i18nKey = (i18nNode as StringLiteral).value try { const lang = getLang(i18nKey) this.records.push({ i18nKey, source, result: lang, valid: lang !== i18nKey }) } catch (e) { this.records.push({ i18nKey, source, valid: false, errorMsg: (e as PropertyResolverError).message }) } } } } }; babelTraverse(ast, visitor); } ``` ###### 3)遍历Template-AST 使用 @vue/complier-sfc 将 vue 组件文件转换为 Template-AST,然后分别解析 ```javascript collectRecordFromTemplate = (ast: ElementNode) => { /** * v-pre 的元素的属性及其子元素的属性和插值语法都不需要解析, * @vue/compiler-sfc 解析后的props中不会包含 v-pre 的属性名,所以这里暂时使用正则表达式匹配v-pre */ if ( ast.type === 1 && /^<+?[^>]+\s+(v-pre)[^>]*>+?[\s\S]*<+?\/[\s\S]*>+?$/gm.test( ast.loc.source ) ) { return } if (ast.props.length) { ast.props.forEach((prop) => { // vue指令 if ( prop.type === 7 && this.hasI18NCall((prop.exp as SimpleExpressionNode)?.content) ) { this.collectRecordFromJs((prop.exp as SimpleExpressionNode)?.content) } }); } if (ast.children.length) { ast.children.forEach((child) => { // 插值语法,如果匹配到 getLang()字符,则进行JS表达式解析并替换 if ( child.type === 5 && this.hasI18NCall((child.content as SimpleExpressionNode)?.content) ) { this.collectRecordFromJs((child.content as SimpleExpressionNode)?.content ) } // 元素 if (child.type === 1) { this.collectRecordFromTemplate(child); } }); } }; ``` ###### 4)遍历js、vue文件进行解析 ```javascript glob.sync(options.pattern!, { ignore: options.ignore }) .forEach((filename) => { const filePath = path.resolve(process.cwd(), filename); logInfo(`detecting file: ${filePath}`); const sourceCode = fs.readFileSync(filePath, "utf8"); try { const { records } = new Validator({code: sourceCode, filename, getLangCheck: options.getLangCheck}); if(options.onlyCollectError) { const errorRecords = records.filter(item => !item.valid) if(errorRecords.length > 0) { locales[filePath] =errorRecords } } else { locales[filePath] = records } } catch (err) { console.log(err); } }); ``` ### 5 成果 通过以上步骤就可以实现一个国际化校验工具了。在使用工具时,通过简单的配置即可检索指定项目指定路径下所有的 vue 和 js 文件,并且支持按文件路径来记录校验的结果并输出到 json 文件中。使用此工具可以有效降低校验的时间成本,同时工具提供的能力还能帮助使用人员快速定位问题代码,快速修复问题。 得益于工具提供的能力,wms6.0前端工程化耗时预计降低35%左右,在后续开发的过程中,可以使用该工具持续降低开发时间成本,提升校验的准确率,还能有效覆盖到历史代码,防止改动对现有逻辑造成影响。现在该工具已推广到wms其他前端工程中进行使用,反响还不错。 工具开发之初,为了快速投入到生产中,目前只支持vue和js文件的解析,暂时未对ts、tsx和jsx文件的解析进行支持,后续会根据需要提供相应的能力。『wms-i18n-check 』和『wms-chinese-check 』已发布在 jnpm 上,具体使用方法请点击链接进行查看。安装后可查看源码,在您的使用过程中,有任何疑问或更好的想法,欢迎留言沟通交流。 ------------ ###### 自猿其说Tech-JDL京东物流技术与数据智能部 ###### 作者:李瑞东
原创文章,需联系作者,授权转载
上一篇:BPO清铢产品设计思考
下一篇:mybatis-plus特性学习
相关文章
前端十年回顾 | 漫画前端的前世今生
Taro小程序跨端开发入门实战
【技术干货】企业级扫描平台EOS关于JS扫描落地与实践!
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号