本篇章要為各位讀者帶來的是: Deno 的編譯 API 。筆者會先從 Deno namespace 開始介紹,再進入 Compiler APIs 的介紹!
所有非 Web 標準
的 API 都會被歸類到命名空間 Deno
當中。
其中包含了像是: 檔案讀取、 TCP Socket
、運行子程序...等功能。
詳細的內容都被紀錄到了 Deno doc 上面,若讀者對於這些 API 有興趣都可以在上面找到答案。
此外,官方文件也有提供該命名空間的 TypeScript 定義文件 (.d.ts) ,請參考該連結。
相信如果各位接觸過 C 語言
的開發者對命名空間的概念肯定不陌生。如果沒有接觸過也沒關係,就跟著筆者繼續往下看吧!
命名空間(英語:Namespace),也稱名字空間、名稱空間等,它表示著一個識別碼(identifier)的可見範圍。 一個識別碼可在多個命名空間中定義,它在不同命名空間中的含義是互不相干的。
用白話文來說,假設我們有兩個來自不同外部模組且用途不同的變數或是函式。碰巧的是,他們的名稱一模一樣,這時如果直接執行便會造成衝突。
使用 namespace 就能很好的處理這個問題,開發者可以利用不同的命名空間讓名稱相同但功能不同的函式、變數區隔開來。
Deno 原生支持了在 TypeScript 編譯器運行時訪問的功能。Deno 命名空間中提供了三種訪問方法。
該功能與 deno.cache
相似,它能夠載入並將程式碼暫存下來、編譯程式碼,但無法執行它。
該 API 包含 3 個參數:
rootName
編譯後目標程式的名稱。
sources
Sources 是一個 Hash table ,它的 Key
為模組名稱、 Value
則為模組的內容。
筆者不太確定為何在官方文件中會以
Hash
形容它。實際上,在填入選項時就只是輸入一個鍵值對而已,如果我有找到答案會在第一時間更新到這邊。
options
options 是一组 Deno.CompilerOptions
類型的選項,它包含了 Deno 支持的 TypeScript 編譯器選項。
介紹完三個可選參數後,我們來進一步看看範例:
import { assert } from "https://deno.land/std@0.73.0/testing/asserts.ts";
const [diagnostics, emitMap] = await Deno.compile("/foo.ts", {
"/foo.ts": `import * as bar from "./bar.ts";\nconsole.log(bar);\n`,
"/bar.ts": `export const bar = "bar";\n`,
},{
outDir: "dist"
}
);
assert(diagnostics == null); // ensuring no diagnostics are returned
console.log(emitMap);
輸出:
{
"dist/bar.js.map": '{"version":3,"file":"bar.js","sourceRoot":"","sources":["file:///bar.ts"],"names":[],"mappings":"AAA...',
"dist/bar.js": 'export const bar = "bar";\n//# sourceMappingURL=bar.js.map',
"dist/foo.js.map": '{"version":3,"file":"foo.js","sourceRoot":"","sources":["file:///foo.ts"],"names":[],"mappings":"AAA...',
"dist/foo.js": 'import * as bar from "./bar.ts";\nconsole.log(bar);\n//# sourceMappingURL=foo.js.map'
}
注意!本章介紹的三種 API 都是不穩定的功能,所以在執行時都需要加入
--unstable
唷!像是: deno run --unstable compile.ts
此外,關於什麼是 map ,筆者也有將相關文章放到本日的延伸閱讀唷!
該 API 類似於 Deno.compiler()
。不同的是,它會直接回傳打包後的結果。
至於可選參數都與 Deno.compiler()
相同。
範例:
import { assert } from "https://deno.land/std@0.73.0/testing/asserts.ts";
const [diagnostics, emitMap] = await Deno.bundle("/foo.ts", {
"/foo.ts": `import * as bar from "./bar.ts";\nconsole.log(bar);\n`,
"/bar.ts": `export const bar = "bar";\n`,
},{
outDir: "dist"
}
);
assert(diagnostics == null); // ensuring no diagnostics are returned
console.log(emitMap);
輸出:
/ Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
// This is a specialised implementation of a System module loader.
"use strict";
// @ts-nocheck
/* eslint-disable */
let System, __instantiate;
(() => {
const r = new Map();
System = {
register(id, d, f) {
r.set(id, { d, f, exp: {} });
},
};
async function dI(mid, src) {
let id = mid.replace(/\.\w+$/i, "");
if (id.includes("./")) {
const [o, ...ia] = id.split("/").reverse(),
[, ...sa] = src.split("/").reverse(),
oa = [o];
let s = 0,
i;
while ((i = ia.shift())) {
if (i === "..") s++;
else if (i === ".") break;
else oa.push(i);
}
if (s < sa.length) oa.push(...sa.slice(s));
id = oa.reverse().join("/");
}
return r.has(id) ? gExpA(id) : import(mid);
}
function gC(id, main) {
return {
id,
import: (m) => dI(m, id),
meta: { url: id, main },
};
}
function gE(exp) {
return (id, v) => {
const e = typeof id === "string" ? { [id]: v } : id;
for (const [id, value] of Object.entries(e)) {
Object.defineProperty(exp, id, {
value,
writable: true,
enumerable: true,
});
}
return v;
};
}
function rF(main) {
for (const [id, m] of r.entries()) {
const { f, exp } = m;
const { execute: e, setters: s } = f(gE(exp), gC(id, id === main));
delete m.f;
m.e = e;
m.s = s;
}
}
async function gExpA(id) {
if (!r.has(id)) return;
const m = r.get(id);
if (m.s) {
const { d, e, s } = m;
delete m.s;
delete m.e;
for (let i = 0; i < s.length; i++) s[i](await gExpA(d[i]));
const r = e();
if (r) await r;
}
return m.exp;
}
function gExp(id) {
if (!r.has(id)) return;
const m = r.get(id);
if (m.s) {
const { d, e, s } = m;
delete m.s;
delete m.e;
for (let i = 0; i < s.length; i++) s[i](gExp(d[i]));
e();
}
return m.exp;
}
__instantiate = (m, a) => {
System = __instantiate = undefined;
rF(m);
return a ? gExpA(m) : gExp(m);
};
})();
System.register("bar", [], function (exports_1, context_1) {
"use strict";
var bar;
var __moduleName = context_1 && context_1.id;
return {
setters: [],
execute: function () {
exports_1("bar", bar = "bar");
}
};
});
System.register("foo", ["bar"], function (exports_2, context_2) {
"use strict";
var bar;
var __moduleName = context_2 && context_2.id;
return {
setters: [
function (bar_1) {
bar = bar_1;
}
],
execute: function () {
console.log(bar);
}
};
});
__instantiate("foo", false);
該 API 是基於 TypeScript 的 transpileModule()
,該功能會將任何型別去除並輸出成 JavaScript 程式。同時,它也不具有型別檢查和依賴模組解析的功能,相較於前兩個 API ,它僅接受兩個參數: options 和 sources 。
接著,讓我們看看範例:
const result = await Deno.transpileOnly({
"/foo.ts": `enum Foo { Foo, Bar, Baz };\n`,
});
console.log(result["/foo.ts"].source);
console.log(result["/foo.ts"].map);
執行後,我們會在命令列看到 enum 被輸出成了一個 IIFE 以及 map 檔案的內容:
var Foo;
(function (Foo) {
Foo[Foo["Foo"] = 0] = "Foo";
Foo[Foo["Bar"] = 1] = "Bar";
Foo[Foo["Baz"] = 2] = "Baz";
})(Foo || (Foo = {}));
;
//# sourceMappingURL=foo.js.map
{"version":3,"file":"foo.js","sourceRoot":"","sources":["foo.ts"],"names":[],"mappings":"AAAA,IAAK,GAAqB;AAA1B,WAAK,GAAG;IAAG,2BAAG,CAAA;IAAE,2BAAG,CAAA;IAAE,2BAAG,CAAA;AAAC,CAAC,EAArB,GAAG,KAAH,GAAG,QAAkB;AAAA,CAAC"}
當使用者有其他特殊需求,可能會需要將 Deno 默認的 Library 覆蓋掉、重新載入,這時我們可以在 options
加入 lib
選項:
let greeter: HTMLElement | null = document.getElementById("greeter")!; // Please forgive the Non-Null Assertion Operator
greeter.innerText = "Hello world!";
const [diagnostics, emitMap] = await Deno.compile("test-dom.ts", undefined, {
lib: ["dom", "esnext"], // include "deno.ns" for deno namespace
outDir: "dist",
});
注意:就像
tsc
一樣,當你提供一個lib
選項時,它會將默認的選項覆蓋掉。這時,基本的 JavaScript Library 也會消失,使用者需要加上自己使用的 es version ,像是:es5
,es2015
,es2016
,es2017
,es2018
... 等。
可以參考
lib
compiler option 更進一步暸解lib
選項。
除了 TypeScript 提供的 Library 之外, Deno 也提供了 4 個 Library 供開發者使用:
deno.ns
- Deno
命名空間。deno.shared_globals
- 提供 Deno 運作時支持的全域介面和變數。deno.window
- 公開全域變數和 Deno 主線程中可用的 Deno 命名空間,是編譯器的默認 API 。deno.worker
- 公開在 Deno 下的工作線程中可用的全域變數。除了使用 lib
選項外,我們也可以利用該方法將需要使用的 Library 導入:
/// <reference lib="dom" />
document.getElementById("foo");
要準備 Compiler APIs 的文章還蠻不容易的,因為相關文章實在不多或是與官方文件雷同。
官方文件中有很多地方都讓筆者覺得有看沒有懂 Q_Q
但因為最近在準備交大資工的備審資料,實在沒有時間好好研究 Deno ,要在這邊跟各位讀者道歉。
放榜後筆者會針對不完全的內容逐一填補,甘溫!
同樣的事情在不同人眼中可能會有不同的見解、看法。
在讀完本篇以後,筆者也強烈建議大家去看看以下文章,或許會對型別、變數宣告...等觀念有更深層的看法唷!