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))
})
}