Deno入门与Node对比
1 安装
由于国内GFW问题,各种方式安装deno会遇到问题。所以推荐使用国内镜像安装。
curl -fsSL https://x.deno.js.cn/install.sh | sudo DENO_INSTALL=/usr/local sh
echo "export PATH=$HOME/.deno/bin/:$PATH" >> ~/.bash_profile
在命令行中输入deno --version
➜ ~ deno --version
deno 1.0.4
v8 8.4.300
typescript 3.9.2
2 demo 与 node 有什么区别
2.1 API引入方式
Node
// 引入文件系统
const fs = require('fs');
fs.stat('/tmp/world', (err, stats) => {
if (err) throw err;
console.log(`stats: ${JSON.stringify(stats)}`);
});
Deno
const fileInfo = await Deno.stat("hello.txt");
assert(fileInfo);
Node 在使用每个api之前,都需要引入对应的包。而Deno则是全局公共调用,仅需要引入Deno即可。
2.2 es module标准支持
Node
在node中使用标准的es module有很多种方式:
1. 使用babel转译
2. 使用node实验性支持
在这个演示案例中,我们使用node实验性支持。
首先 新建两个文件 esModule.mjs,testEsModule.mjs。然后填充代码如下:
esModule.mjs
import testEsModule from "./testEsModule.mjs";
console.log("文件测试引入es module");
const t = new testEsModule();
t.print();
testEsModule.mjs
export default class test {
print() {
console.log("我是被引入的testEsModule文件");
}
}
之后使用命令执行他们,node --experimental-modules esModule.mjs
得到如下结果
(node:26622) ExperimentalWarning: The ESM module loader is experimental.
文件测试引入es module
我是被引入的testEsModule文件
Deno
原生支持es module。没有任何操作,我们复制之前的两个文件,将 m
去掉。直接执行
➜ start deno run esModule.js
文件测试引入es module
我是被引入的testEsModule文件
2.3 安全权限定义
Node
???Node没有。
Deno
deno对于安全十分敏感,如果你没有开放某个权限,在代码里执行这个操作是会被拒绝的。
我们来写一个文件读取案例
const file = await Deno.open("readTestFile.txt", { read: true });
const myFileContent = await Deno.readAll(file);
console.log(new TextDecoder().decode(myFileContent));
Deno.close(file.rid);
这个文件,我如果直接运行deno run denoReadFile.js
是会失败的
➜ start deno run denoReadFile.js
error: Uncaught PermissionDenied: read access to "/Users/zhaoyf/deno/start/readTestFile.txt", run again with the --allow-read flag
at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
at Object.sendAsync ($deno$/ops/dispatch_json.ts:98:10)
at async Object.open ($deno$/files.ts:37:15)
at async file:///Users/zhaoyf/deno/start/denoReadFile.js:1:14
异常信息明确告诉我们,如果需要读取文件,需要打开--allow-read
标记。
➜ start deno run --allow-read denoReadFile.js
这个文件用于读取测试
测试
更多的安全策略:
--allow-all:打开所有权限
--allow-read:文件读取权限
--allow-write:文件写入权限
--allow-net: 网络权限
--allow-hrtime:允许高精度时间测量
--allow-run: 允许程序可执行 命令行
--allow-env: 是否可读取环境变量
以上标记了一些常用的,更多请参考api文档。
2.4 Typescript 支持
Node
!!!也没有,需要外部支持,比如 babel转译,或者直接使用ts-node.
Deno
原生支持,可以直接执行ts代码。
~ deno --version
deno 1.0.2
v8 8.4.300
typescript 3.9.2
可以看到,1.0.2版本中带的是3.9.2版本ts
创建一个文件testTS.ts
console.log("ts 文件测试");
执行命令进行测试。
deno run testTS.ts
Compile file:///Users/zhaoyf/deno/start/testTS.ts
ts 文件测试
2.5 异步操作
Node
我们使用1.1的案例,可以看到,node采用的是回调方式执行异步代码的。虽然后期通过各种封装使得node支持了同步的操作。但是这样的设计略显繁琐。
// 引入文件系统
const fs = require('fs');
fs.stat('/tmp/world', (err, stats) => {
if (err) throw err;
console.log(`stats: ${JSON.stringify(stats)}`);
});
Deno
deno原生就支持Promise,我们可以直接通过then,或者async await进行异步转同步操作。
Deno.open("readTestFile.txt", { read: true }).then(async (file) => {
const myFileContent = await Deno.readAll(file);
console.log(new TextDecoder().decode(myFileContent));
Deno.close(file.rid);
});
2.6 包分发与引入
Node
node可以通过两种方式引入新包:
1. 通过npmjs.com安装新包
2. 建立自己的私服,通过私服安装新包
node项目会使用package.json文件来管理当前项目需要引入的模块。
Deno
Deno参考了go语言的包管理机制,去中心化管理,所有的文件都有很多分发入口,允许远程加载模块。我们来举个例子
import { hello } from "https://www.yodfz.com/test.ts";
console.log("远程加载ts文件,使用 --reload tag可以重载整个远程包");
console.log(hello());
Deno在获取到远程包之后,会进行编译缓存。第二次如果没有增加--reload标记,将直接使用缓存文件。
2.7 打包,测试
Node
调试
node debugger 可以让你的node程序进入调试模式。配合vscode功能会更强大。
测试
node没有原生测试工具,需要使用第三方包支持。
Deno
调试
deno集合了debugger、inpsect功能。在运行的时候 加入 --inspect-brk
标签即可接入调试模式。
运行之后,打开Chrome浏览器的 inspect,inspect
进入程序即可进行调试。
测试
deno内部自带了测试框架
import * as i from "./index.js";
import { assertEquals } from "https://deno.land/std@v0.50.0/testing/asserts.ts";
Deno.test("example", function () {
const result = i.add(1, 2);
assertEquals(result, 3);
});
运行deno test test.js
查看测试结果
deno test test.js
running 1 tests
test example ... ok (7ms)
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (7ms)
3 deno 开发运行react程序
新建一个 helloworld.tsx
// @deno-types="https://deno.land/x/types/react/v16.13.1/react.d.ts"
import React, { useState } from "https://dev.jspm.io/react";
export const App = () => {
return (
<>
<div>Hello test deno react and event!</div>
<p>无热刷</p>
</>
);
};
新建入口文件
import { serve } from "https://deno.land/std@0.55.0/http/server.ts";
import React from "https://dev.jspm.io/react";
import ReactDOMServer from "https://dev.jspm.io/react-dom/server";
import { App } from "./src/helloworld.tsx";
export const str = ReactDOMServer.renderToString(<App />);
const body = new TextEncoder().encode(str);
const s = serve({ port: 8022 });
console.log("http://localhost:8022/");
for await (const req of s) {
const headers = new Headers();
headers.append("content-type", "text/html; charset=utf-8");
req.respond({
body,
headers,
});
}
接着运行 deno run --allow-net start.tsx
。即可。
4 deno源码解析之如何执行js文件
入口:cli/main.rs
第line:601行开始,是整个deno进入的入口。
可以看到line:621行是对deno的argv分析。从而知道你需要执行什么命令。
let fut = match flags.clone().subcommand {
...
}
我们只看deno执行js的地方。可以看line:652
DenoSubcommand::Run { script } => run_command(flags, script).boxed_local(),
在这个地方,解析的是run
命令。
来看看run_command
里面是什么。
async fn run_command(flags: Flags, script: String) -> Result<(), ErrBox> {
// let flags = flags::flags_from_vec(args);
// 这个地方是,根据命令行上的标签装载所需要的行为,比如典型的如 --reload
// 具体代码可以看 cli/global_state.rs line:54
let global_state = GlobalState::new(flags.clone())?;
// 根据脚本地址,载入
// 从名字上来看,他支持url载入与本地文件载入
let main_module = ModuleSpecifier::resolve_url_or_path(&script).unwrap();
// 初始化v8引擎
let mut worker =
MainWorker::create(global_state.clone(), main_module.clone())?;
debug!("main_module {}", main_module);
// 执行这个js/ts文件
worker.execute_module(&main_module).await?;
write_lockfile(global_state)?;
// 执行文件中的生命周期
worker.execute("window.dispatchEvent(new Event('load'))")?;
(&mut *worker).await?;
worker.execute("window.dispatchEvent(new Event('unload'))")?;
Ok(())
}
execute_module ->. preload_module -> load_module -> let (_load_id, prepare_result) = load.prepare().await; ->prepare_load ->
// cli/global_state.rs
// line:145-163
// Check if we need to compile files.
let should_compile = needs_compilation(
self.ts_compiler.compile_js,
out.media_type,
module_graph.values().collect::<Vec<_>>(),
);
// todo 判断是否需要进行ts编译
if should_compile {
self
.ts_compiler
.compile_module_graph(
self.clone(),
&out,
target_lib,
permissions,
module_graph,
)
.await?;
}
在通过以上编译之后,会将文件送入 v8引擎。
pub fn mod_evaluate(&mut self, id: ModuleId) -> Result<(), ErrBox> {
let state_rc = Self::state(self);
let state = state_rc.borrow();
let core_state_rc = CoreIsolate::state(self);
let mut hs = v8::HandleScope::new(&mut self.0);
let scope = hs.enter();
let context = {
let core_state = core_state_rc.borrow();
core_state.global_context.get(scope).unwrap()
};
let mut cs = v8::ContextScope::new(scope, context);
let scope = cs.enter();
let info = state.modules.get_info(id).expect("ModuleInfo not found");
let module = info.handle.get(scope).expect("Empty module handle");
let mut status = module.get_status();
drop(state);
if status == v8::ModuleStatus::Instantiated {
let maybe_value = module.evaluate(scope, context);
// Update status after evaluating.
status = module.get_status();
if let Some(value) = maybe_value {
assert!(
status == v8::ModuleStatus::Evaluated
|| status == v8::ModuleStatus::Errored
);
let promise = v8::Local::<v8::Promise>::try_from(value)
.expect("Expected to get promise as module evaluation result");
let promise_id = promise.get_identity_hash();
let mut core_state = core_state_rc.borrow_mut();
if let Some(mut handle) =
core_state.pending_promise_exceptions.remove(&promise_id)
{
handle.reset(scope);
}
} else {
assert!(status == v8::ModuleStatus::Errored);
}
}
v8 与 deno通讯,类似于app中的hybrid通讯,可以打开core.js
文件查阅通讯文件,绑定文件在core/bindings.rs
中
https://github.com/asos-craigmorten/deno-react-base-server/blob/master/mod.tsx