最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Es-Module-Lexer,ES Module 语法的词法分析利器

    正文概述 掘金(五柳)   2021-04-13   562

    前言

    说到词法分析,我想很多同学第一时间想到的可能是 Babel、Acorn 等工具。不可否认,它们都很强大 ?。

    但是,具体到今天这个话题 ES Module 语句的词法分析而言,es-module-lexer 会胜过它们很多!

    那么,今天我们将围绕以下 2 点,深入浅出一番 es-module-lexer:

    • 认识 es-module-lexer
    • 实际场景下如何应用 es-module-lexer

    1 认识 es-module-lexer

    es-module-lexer 是一个可以对 ES Module 语句进行词法分析的工具包。它压缩后之后只有 4 KiB,其底层通过内联(Inline) WebAssembly 的方式来实现对 ES Module 语句的快速词法分析

    那么,具体会有多快?根据官方给的例子,Angular1(720 KiB)使用 Acorn 解析所需要的时间为 100 ms,而 es-module-lexer 解析只需要 5 ms,也就是前者的 1/20 ?。

    并且,es-module-lexer 的使用也非常简单,它提供了 init Promise 对象和 parse 方法,下面我们来看一下它们分别做了什么?

    1.1 init(Promise 对象)

    init 必须parse() 方法前 Resolve(解析),它的实现可以分为 3 个步骤:

    • 首先,调用 WebAssembly.compile() 方法编译 WebAssembly 二进制代码到为 WebAssembly.ModulePromise 对象
    • 然后,再调用 WebAssembly.Instantiate() 方法创建一个实例
    • 最后,则可以在实例上访问 exports 属性来获取调用的模块提供的方法

    这个过程对应的代码:

    let wasm;
    const init = WebAssembly.compile(
      (binary => typeof window !== 'undefined' && typeof atob === 'function' ? Uint8Array.from(atob(binary), x => x.charCodeAt(0)) : Buffer.from(binary, 'base64'))
      ('WASM_BINARY')
    ).then(WebAssembly.instantiate)
     .then(({ exports }) => { wasm = exports; });
    

    而这里的二进制代码,则是由 C 实现的对 ES Module 语句进行词法分析的代码编译得来。 并且,可以看到实例的 exports 会被赋值给 wasm

    1.2 parse() 方法

    parse() 方法则会使用在上面得到的 WebAssembly.Module 提供的方法(即 wasm)来实现对 ES Module 语法的词法分析。

    这个过程对应的代码(伪代码):

    function parse (source, name = '@') {
      if (!wasm)
        return init.then(() => parse(source));
      // 调用 wasm 上的方法进行对应的操作
      return [imports, exports, !!wasm.f()];
    }
    

    可以看到,如果我们在调用 parse() 方法之前没有 Resolve(解析)initparse() 方法会自己先 Resolve(解析) init。然后,在 .then 中调用并返回 parse() 方法,所以在这种情况下,parse() 方法会返回一个 Promise 对象。

    当然,不管任何情况下,parse() 方法的本质是返回一个数组(长度为 3)。并且,和我们使用密切相关的主要是 importsexports

    importsexports 都是一个数组,其中每个元素(对象)代表一个导入语句的解析后的结果,具体会包含导入或导出的模块的名称、在源代码中的位置等信息。

    接下来,我们通过一个简单的例子来认识一下 es-module-lexer 的基本使用。

    1.3 基本使用

    首先,我们基于 es-module-lexer 定义一个 parseImportSyntax() 方法:

    const { init, parse } = require("es-module-lexer")
    
    async function parseImportSyntax(code = "") {
      try {
        await init
        
        const importSpecifier = parse(code)
        return importSpecifier
      } catch(e) {
        console.error(e)
      }
    }
    

    可以看到 parseImportSyntax() 方法会返回 parse 后的结果。假设,此时我们需要解析导入 ant-design-vue 的 Button 组件的语句:

    const code = `import { Button } from 'ant-design-vue'`
    parseImportSyntax(code).then(importSpecifier => {
      console.log(importSpecifier)
    })
    

    对应的输出:

    [ 
      [ 
        { 
          n: 'ant-design-vue', 
          s: 24, 
          e: 38, 
          ss: 0, 
          se: 39, 
          d: -1 
        } 
      ], 
      [], 
      true 
    ]
    

    由于,我们只声明了导入语句,所以最后解析的结果只有 imports 内有元素,该元素(对象)的每个属性对应的含义:

    • n 表示模块的名称
    • s 表示模块名称在导入语句中的开始位置
    • e 表示模块名称在导入语句中的结束位置
    • ss 表示导入语句在源代码中的开始位置
    • se 表示导入语句在源代码中的结束位置
    • d 表示导入语句是否为动态导入,如果是则为对应的开始位置,否则默认为 -1

    那么,在简单了解完 es-module-lexer 的实现原理和使用后,我想同学们可能会思考它在实际场景下中要如何运用?(请继续阅读 ?)

    2 实际场景下如何应用 es-module-lexer

    在同学们可能还没意识到哪里用到了 es-module-lexer 的时候,其实它已经走进了我们平常的开发中。

    那么,这里我们以 vite-plugin-style-import 插件为例,认识一下它又是如何使用 es-module-lexer 的?(别走开,接下来会非常有趣 ?)

    2.1 浅析 vite-plugin-style-import 原理

    在正式开讲 es-module-lexer 在 vite-plugin-style-import 中的使用之前,我们需要知道 vite-plugin-style-import 做了什么?

    它解决了我们按需引入组件时,需要手动引入对应组件样式的问题。例如,在使用 ant-design-vue 的时候,按需引入 Button 只需要声明:

    import { Button } from "ant-design-vue"
    

    然后,经过 vite-plugin-style-import 处理后对应的代码片段:

    import { Button } from 'ant-design-vue';
    import 'ant-design-vue/es/button/style/index.js';
    

    而这个过程的实现可以分为以下 3 个步骤:

    • 使用 es-module-lexer 对源代码的导入(import)语句进行词法分析

    • 根据配置文件 vite.config.js 中的 vite-plugin-style-import 的配置项来构造样式文件的导入语句

    • 根据环境(会区分 Dev 或 Prod),选择性地注入特定的代码到源代码中

    2.2 使用 es-module-lexer 的黑魔法

    1.3 基础使用小节的部分,我们讲了 es-module-lexer 解析导入语句时,只会返回导入模块相关的信息,那么这在 vite-plugin-style-import 中显然是不够的!

    因为,vite-plugin-style-import 还需要知道此时导入了该模块的什么组件,这样才能去拼接生成对应的样式文件的导入语句。

    那么,这个时候使用 es-module-lexer 的黑魔法就来了,我们可以将原来的导入语句的 import 替换为 export,然后 es-module-lexer 就会解析出导出的组件信息(想不到吧 ?)!

    例如,同样是上面导入 ant-design-vue 的 Button 的例子,替换 import 后会是这样:

    export { Button } from "ant-design-vue"
    

    这个时候,使用 es-module-lexer 解析后返回的结果:

    [
      [
        { 
          n: 'ant-design-vue', 
          s: 24, 
          e: 38, 
          ss: 0, 
          se: 39, 
          d: -1 
        }
      ],
      [ 'Button' ],
      true
    ]
    

    可以看到,Button 被放到了解析结果的(数组第二个元素) exports 中,这样一来我们就知道了使用导入模块的组件有哪些 ?。

    而这个过程,在 vite-plugin-style-import 中是由 transformImportVar() 方法完成的:

    function transformImportVar(importStr: string) {
      if (!importStr) {
        return [];
      }
    
      const exportStr = importStr.replace('import', 'export').replace(/\s+as\s+\w+,?/g, ',');
      let importVariables: readonly string[] = [];
      try {
        importVariables = parse(exportStr)[1];
        debug('importVariables:', importVariables);
      } catch (error) {
        debug('transformImportVar:', error);
      }
      return importVariables;
    }
    

    结语

    很有趣的一点是 awesome-vite 上有两个支持按需引入组件样式文件的插件。通过阅读,我想同学们应该知道用哪个了吧 ?! 最后,用一句话总结 es-module-lexer 的优点,那就是:“快到飞起”。如果,文中存在表达不当或错误的地方,欢迎同学提 Issue~

    点赞 ?

    通过阅读本篇文章,如果有收获的话,可以点个赞,这将会成为我持续分享的动力,感谢~


    起源地下载网 » Es-Module-Lexer,ES Module 语法的词法分析利器

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元