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功能会更强大。

详细参考 https://i5ting.github.io/node-debug-tutorialc

测试

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

摄影爱好者,全栈工程师,游戏玩家,积木苦手,超穷手办收藏爱好者

发表评论