Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deno 实战 #25

Open
cheungseol opened this issue Feb 12, 2020 · 0 comments
Open

Deno 实战 #25

cheungseol opened this issue Feb 12, 2020 · 0 comments

Comments

@cheungseol
Copy link
Owner

Deno 是一个安全的 js/ts runtime 。

下面从安装、使用以及和 Node.js 对比进行 Deno 实践

安装

  • 可以使用 homebrew 安装
  • 或者使用类似 nvm 的版本管理工具 asdf 安装
  • ...

以下代码运行环境:

环境 版本
deno 0.32.0
v8 1.1.08
typescript 3.7.2

First Blood

hello world

在控制台执行以下脚本,运行一个 hello word 的例子试下:

deno https://deno.land/std/examples/welcome.ts

执行结果如下,可以看到 deno 可以直接运行第三方脚本,首先从远程下载 welcome.ts 文件,然后再经过编译运行输出运行结果:
image

typescript runtime

Deno 自带 tsc,可以看到 deno 将 welcome.ts 文件安装到了 deps 目录下,gen 目录下是 ts 编译后生成的 js 文件:
image

安全沙盒环境

Deno 是一个类似沙盒的安全环境,限制了文件、网络等操作。下面编写一个测试脚本:

// ES6 Module Import
import { serve } from "https://deno.land/[email protected]/http/server.ts";
const s = serve({ port: 8000 });
console.log("http://localhost:8000/");
// Top Level Await
for await (const req of s) {  req.respond({ body: "Hello World\n" });}

可以看到 deno 中支持直接使用 ES6 Module Import 以及 Top Level Await
由于 deno 限制了网络访问能力,所以直接运行上面的脚本会报错:
image
加上 allow-net 条件参数再次运行 deno --allow-net test-0.ts 即可。

Promise 和 Top level await

和 nodejs 的区别是,nodejs 设计之初都是基于 callback 形式,而 deno 中所有异步操作都会返回一个 Promise,并且支持 Top Level Await。

Promise

以创建子进程和系统信号为例:

  • 创建子进程:
// create subprocess
const p = Deno.run({
    args: ["echo", "hello"]
  });
  
// await its completion
await p.status();
  • Signals
const sig = Deno.signal(Deno.Signal.SIGINT);
setTimeout(() => { 
    sig.dispose(); 
}, 5000);
for await (const _ of sig) {  
    console.log("interrupted");
}

Top level Await

Top level await 目前已经进入 TC39 stage-3,再 ES8 之前只能再 async 函数中使用 await,通过 top level await 可以直接在模块顶层使用 await 语句。
Deno 中可以直接使用 top level await 语法:(目前 Node.js 可以在 ES Modules 中使用)

const strings = await import(`/i18n/${navigator.language}`);
console.log("X1");
await new Promise(r => setTimeout(r, 1000));
console.log("X2");

模块加载和打包

Deno 的设计避免 Node.js 的模块加载机制,支持 ES modules 而不使用 require,通过 ULR 或者文件路径引用依赖包。并且 deno 自带 bundle 功能,打包出的文件可以直接在浏览器中使用:

deno bundle main.ts test.bundle.js

生成的 bundle 文件基本类似于一个简单版本的 webpack 打包后的内容:

(function() {  
    const modules = new Map();  
    const require = (deps, resolve, reject) => {    
        try {      
            // ...      
            resolve(getExports(deps[0]));    
        } catch (e) {      
            // ...    
        }  
    };  
    // 加载的模块存入 modules  
    define = (id, dependencies, factory) => {    
        // ...    
        modules.set(id, {      
            dependencies,      
            factory,      
            exports: {}    
        });  
    };  
    // 执行模块内部代码逻辑  function getExports(id) {      
    // ...      
    const { factory, exports } = module;      
    delete module.factory;        
    const dependencies = module.dependencies.map(id => {          
        if (id === "require") {            
            return require;          
        } else if (id === "exports") {            
            return exports;          
        }          
            return getExports(id);        
        });        
        factory(...dependencies);      
        return exports;  
    }  
    // 加载模块  
    instantiate = dep => {    
        const result = getExports(dep);    
        return result;  
    };
})();
// ...
// 将 main 模块和依赖的 imported 模块放入 modules Map
define("main", ["require", "exports", "./imported.ts"], function (require, exports) {    
    "use strict";    
    Object.defineProperty(exports, "__esModule", { value: true });    
    // ...
});
// 加载 main 模块
instantiate("main");

包管理

Deno 摒弃类似 npm 这样的三方包管理模式,三方模块都是以类似浏览器中 import 的形式直接通过链接的形式引用:

import * as _path from "https://deno.land/[email protected]/path/mod.ts";

Deno 也没有 node.js 项目中 package.json 和 node_modules ,如果在项目中进行依赖模块维护的话,可以有两种方式:

  • 以类似下面的形式在项目中创建 dep.ts 文件,这个文件负责引入各个版本的依赖模块,项目中其它文件都统一从 dep.ts 中引入导出的模块:
// dep.ts
import * as _path from "https://deno.land/[email protected]/path/mod.ts";

export {  serve,  ServerRequest,  Response,  Server} from "https://deno.land/[email protected]/http/server.ts";
export {  Status,  STATUS_TEXT} from "https://deno.land/[email protected]/http/http_status.ts";
export { parse } from "https://denolib.com/denolib/[email protected]/mod.ts";
export const path = _path;
  • 使用 imports json 文件,这个文件类似 webpack alias 可以给引入的依赖包定义别名,项目中的其它文件可以直接使用别名
// imports.json
{    
    "imports": {       
        "http/": "https://deno.land/std/http/"    
    }
}
// index.ts
import { serve } from "http/server.ts";
const body = new TextEncoder().encode("Hello World\n");
for await (const req of serve(":8000")) {  
    req.respond({ body });
}
## 预置功能
此外 Deno 还包揽了 test 、format 的功能,通过 `deno fmt file.ts` 可以格式化代码,类似常用的 prettier 。lint 、debug 等功能还再进行中。
## 浏览器环境兼容
deno  browser 是两个 js runtime,deno 的设计兼容浏览器环境实现的,并且还兼容很多 node.js api。
- 与浏览器兼容的 API:
比如 deno 实现了 *window* 以及 *eventListener*  API 
main.ts 分别通过 addEventListener  onload  window load , unload 上绑定事件监听:
```ts
// main.ts
import "./imported.ts";
const handler = (e: Event): void => {  
    console.log(`got ${e.type} event in event handler (main)`);
};
window.addEventListener("load", handler);
window.addEventListener("unload", handler);
window.onload = (e: Event): void => {  
    console.log(`got ${e.type} event in onload function (main)`);
};
window.onunload = (e: Event): void => {  
    console.log(`got ${e.type} event in onunload function (main)`);
};

imported.ts 分别通过 addEventListener 和 onload 在 window load , unload 上绑定事件监听:

// imported.ts
const handler = (e: Event): void => { 
    console.log(`got ${e.type} event in event handler (imported)`);
};
window.addEventListener("load", handler);
window.addEventListener("unload", handler);
window.onload = (e: Event): void => { 
    console.log(`got ${e.type} event in onload function (imported)`);
};
window.onunload = (e: Event): void => { 
    console.log(`got ${e.type} event in onunload function (imported)`);
};
console.log("log from imported script");

执行 deno main.ts 脚本,可以看到 addEventListener 和 on 事件的区别,imported.ts 中的 on 事件会被 main.ts 中的覆盖,而 addEventListener 不会被覆盖:
image

  • 在浏览器中运行:
    将上面 deno bundle 打包出的脚本,直接以 script 的形式内联到 index.html 中:
<script type="module" src="test.bundle.js"></script>

打开浏览器控制台可以看到运行结果:
image

使用 node_module

因为 Deno 支持 ES modules,所以可以使用部分 ES module 格式的 node.js 包,https://jspm.io/ 上面是一些 ES module 格式的 npm 包,如果没有依赖 node.js API 的话,都是可以直接在 deno 或者 浏览器环境使用的:
比如:

const r = await import('https://dev.jspm.io/react')
console.log(r)

输出结果:
image

  • 兼容 Node.js API:
    deno std 库里面包含已经兼容的 Nods.js API
    image

语法

Deno 直接支持了许多新的 TC 39 语法,比如 nullish default 和 optional chaining ,以及上面提到的 top level await 等等:

let a = undefinedconsole.log(a ?? 1)
let b = {}console.log(b?.["test"] ?? 10)

输出结果:
image

API

在终端输入 deno ,可以进入 REPL,执行 console.log(Deno) ,返回的是 Deno 提供的各种 API:
image

v.s. Node.js

Deno Node.js
技术 chrome v8、ruby 和 tokio(类似 nodejs 中的 libuv,提供 event loop 等实现) chrome v8、ruby 和 libuv
typescript 集成 typescript compiler 需要额外安装 tsc 或者 ts-node
安全性 类似 browser 沙盒,限制使用 file 、network 等
语法 支持许多新的 TC39 语法,比如 top level await 使用新语法需要借助 babel
异步操作 所有的异步操作都返回 Promise 大多以 callback 的形式
包管理 不需要包管理工具(npm/package.json),引入依赖包直接 import network url path ,通过网址或者文件路径引用模块 使用 npm 等包管理工具、node_modules 问题
模块 支持 ES Modules ,而不是用 require 需要 .mjs 或者 babel 使用 ES Modules
浏览器兼容 可以在浏览器环境使用(没有 nodejs api 依赖的包)
Node.js API 兼容 支持部分 Node.js core API
预置功能 自带打包、格式化、测试等功能
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant