深入 Deno 远程引入 d.ts 实现原理(上)

949 阅读4分钟

1.前言

本文将深入讨论 @deno-types 远程引用,Deno 远程引用这部分十分吸引人,尤其是远程引入 d.ts ,目前微前端架构很需要这部分补充,可能后续会写一个 Webpack插件用来远程引入 d.ts (挖大坑)。 Deno 使用 Rust 和 Typescript 编写,如果是前端工程师,可能接触 Rust 比较少,但是大部分 Rust 语法和 Typescript 是相通的,出现 Rust 特有的语法将会同时粗略讲解。

2.下载 Deno 源码

git clone https://github.com/denoland/deno 

3.顺藤摸瓜找到 @deno-types 实现相关源码

3.1找到 parse_deno_types 单元测试用例

3.1.1 rust 前置知识

  • assert_eq! 是 Rust 提供的一个宏,用来比较两个参数是否相等。 例子:
#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}
  • Some 关键字是 Rust 特有的,功能是用来自动推导参数的数据类型。 例子:
//Option<T>:Rust标准库提供Option,其中'T'是通用数据类型。 //它提供了一种类型的泛型。
enum Option<T>  
{  
    Some(T),  
    None,  
}
//在上面的例子中,enum是自定义类型,其中<T>是通用数据类型。 //可以用任何数据类型替换T。下面来看看这几个示例 :
let x : Option<i32> = Some(10);  // 'T' is of type i32.  
let x : Option<bool> = Some(true);  // 'T' is of type bool.  
let x : Option<f64> = Some(10.5); // 'T' is of type f64.  
let x : Option<char> = Some('b'); // 'T' is of type char.
//在上面的例子中,观察到'T'可以是任何类型,即i32,bool,f64或char。 //但是,如果左侧的类型与右侧的值不匹配,则会发生错误。 如下示例:
let x : Option<i32> = Some(10.8);
//在上述情况下,左侧的类型是i32,右侧的值是f64类型。 //因此,错误发生“类型不匹配”。

3.1.2 从单元测试可以看出 parse_deno_types 的目的是从

@deno-types = https://dneo.land/x/some/package/a.d.ts

中解析出 d.ts文件的路径,直白点是分割出 @deno-types = 后的内容。

3.2 继续搜索找到 parse_deno_types 源码:

fn parse_deno_types(comment: &str) -> Option<String> {
  if comment.starts_with("@deno-types") {
    let split: Vec<String> =
      comment.split('=').map(|s| s.to_string()).collect();
    assert_eq!(split.len(), 2);
    let specifier_in_quotes = split.get(1).unwrap().to_string();
    let specifier = specifier_in_quotes
      .trim()
      .trim_start_matches('\"')
      .trim_start_matches('\'')
      .trim_end_matches('\"')
      .trim_end_matches('\'')
      .to_string();
    return Some(specifier);
  }

  None
}

3.2.1 Rust前置知识

  • Vec 是动态数组声明。和我们之前接触到的Array不同,Vec具有动态的添加和删除元素的能力,并且能够以O(1)的效率进行随机访问。同时,对其尾部进行push或者pop操作的效率也是平摊O(1)的。 同时,有一个非常重要的特性(虽然我们编程的时候大部分都不会考量它)就是,Vec的所有内容项都是生成在堆空间上的,也就是说,你可以轻易的将Vec move出一个栈而不用担心内存拷贝影响执行效率——毕竟只是拷贝的栈上的指针。 另外的就是,Vec中的泛型T必须是Sized的,也就是说必须在编译的时候就知道存一个内容项需要多少内存。对于那些在编译时候未知大小的项(函数类型等),我们可以用Box将其包裹,当成一个指针。

  • Vec<String>声明一个动态字符串数组

  • trim_end_matches 返回具有与模式重复删除的所有后缀的字符串切片。

3.2.2 parse_deno_types 功能是分割出 @deno-types = 后的内容

3.3 get_deno_types 调用了 parse_deno_types

// 获取 deno_types
fn get_deno_types(parser: &AstParser, span: Span) -> Option<String> {
  let comments = parser.get_span_comments(span);

  if comments.is_empty() {
    return None;
  }

  // @deno-types must directly prepend import statement - hence
  // checking last comment for span
  let last = comments.last().unwrap();
  let comment = last.text.trim_start();
  parse_deno_types(&comment)
}

可以很直接看出, get_deno_types 通过 ASTParser 对象通过指定的 Span 解析出 comment, 将 comment 传到 parse_deno_types 解析出 deno_types 的路径。

3.4 pre_process_file 调用了 get_deno_types,pre_process_file 是将远程模块和本地模块(包括类型d.ts)引入全过程,待续...

pub fn pre_process_file(
  file_name: &str,
  media_type: MediaType,
  source_code: &str,
  analyze_dynamic_imports: bool,
) -> Result<(Vec<ImportDesc>, Vec<TsReferenceDesc>), SwcDiagnosticBuffer> {
  let parser = AstParser::new();
  parser.parse_module(file_name, media_type, source_code, |parse_result| {
    let module = parse_result?;
    let mut collector = DependencyVisitor {
      dependencies: vec![],
    };
    let module_span = module.span;
    collector.visit_module(&module, &module);

    let dependency_descriptors = collector.dependencies;

    // for each import check if there's relevant @deno-types directive
    let imports = dependency_descriptors
      .iter()
      .filter(|desc| {
        if analyze_dynamic_imports {
          return true;
        }

        desc.kind != DependencyKind::DynamicImport
      })
      .map(|desc| {
        let location = parser.get_span_location(desc.span);
        let deno_types = get_deno_types(&parser, desc.span);
        ImportDesc {
          specifier: desc.specifier.to_string(),
          deno_types,
          location: location.into(),
        }
      })
      .collect();

    // analyze comment from beginning of the file and find TS directives
    let comments = parser
      .comments
      .take_leading_comments(module_span.lo())
      .unwrap_or_else(Vec::new);

    let mut references = vec![];
    for comment in comments {
      if comment.kind != CommentKind::Line {
        continue;
      }

      let text = comment.text.to_string();
      if let Some((kind, specifier)) = parse_ts_reference(text.trim()) {
        let location = parser.get_span_location(comment.span);
        references.push(TsReferenceDesc {
          kind,
          specifier,
          location: location.into(),
        });
      }
    }
    Ok((imports, references))
  })
}