網站搜尋

javascript - 在瀏覽器中使用 TypeScript 轉譯的代碼,該代碼利用 es6 風格的類和 import,而不需要額外的轉譯步驟?


如果我使用 target:"es5" 來轉譯使用 es6 風格類別和 React 的 TypeScript 代碼,我的預期入口點(這裡是一個名為 App.ts 的文件)轉譯後的代碼看起來像這樣:

Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = require("react");
var react_router_1 = require("react-router");

當這段程式碼中的第一行被執行時,我會得到一個 Uncaught ReferenceError: exports is not defined 錯誤。在 TypeScript 的 GitHub 問題頁面上也描述了類似的問題。

簡單的回答是,在支援類別加載的環境中,exports 將會存在。

commonjs 意味著有一個可用的 exports 變數。你不能直接在瀏覽器中運行 commonjs 代碼。

這是我的 HTML 外層容器:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World</title>
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script src="./components/App.js"></script>
  </head>
  <body>
    <div id="root"></div>

    <script>
      ReactDOM.render(
        React.createElement('App', null),
        document.getElementById('root')
      );
    </script>
  </body>
</html>

看起來(1, 2, 3)通常的做法是將額外的轉譯步驟放在你的 TypeScript 和它釋放到瀏覽器客戶端之間。等等,什麼?我需要對已轉譯的程式碼再轉譯一次?這有點像那些“我以為洗碗機會洗碗”的廣告。

另一個建議是使用像 RequireJS 或 SystemJS 這樣的模組加載庫 (1, 2)。這是合理的,但我還沒看到將模組加載庫添加到 TypeScript 編譯後送往瀏覽器的範例。而且,再次說明,TypeScript 似乎本可以智能地(意思是「以最少的重複代碼」)在轉譯時包含這樣的庫。

(看起來 RequireJS 至少在慣例上是使用不同的語法與 require,例如 require(['lib1', 'lib2', 'lib3'], function (lib1, lib2, lib3) {...}),而不是 TypeScript 所產生的較簡單 var lib1=require('lib1'),不過也許我有些地方沒理解到?)

所以這裡至少有兩個相關問題:

  1. 預期用例是什麼,針對 target="es5" 的 TypeScript 轉譯程式碼?
  2. 如何在不超出 TypeScript 本身能做的轉譯步驟之外,將 es6 TypeScript 代碼交付給瀏覽器?

    • 也就是說,有沒有我忽略的 platform:"browser" 風格設定?
    • 如果不是這樣,RequireJS 應該能與 TS 產生的程式碼一起使用嗎?那應該如何「啟動」/開始?

更新:(將我對 @felixmosh 答案的評論移到問題中)

我已經嘗試在 tsconfig.json 中添加 "module":"es2015",但是代碼開始在像這樣的行上出現錯誤:import Home from './pages/Home';,錯誤訊息為 error TS2307: Cannot find module './pages/Home'。

我的印象是,module 選項定義了 TypeScript 中模組的 定義 方式,而不是它們在生成的 JavaScript 代碼中將如何被 轉譯。也就是說,如果這個選項只影響輸出,為什麼一旦添加或更改模組設置,我未轉譯的代碼就會拋出錯誤,而不僅僅改變生成的 JavaScript 文件的語法呢?

@felixmosh 也[熱心地]建議使用類似 RollUp.js 的東西,但這個方法有點繞過模組加載的方式,嗯,它聲稱會編譯,但實際上只是合併「小段程式碼成為更大且更複雜的東西」。把所有模組放進同一個檔案有點像是作弊。(不過為什麼 TS 至少不做這種合併呢?)

所以 +1 給 Felix,感謝幫忙,但我仍然不明白為什麼 TS 會在它停止的地方停止——它產生的代碼的使用情境是什麼?TypeScript 真的是 需要 在 TS 之外的一個步驟才能在交付給瀏覽器的代碼中使用 es6 風格的類嗎?

我為什麼在意? 我之前使用過基於 RequireJS 的代碼庫,將文件拆分開來確實比較方便調試,相比單一的大型代碼庫更容易。但我也不明白為什麼在這個使用情境下,TS 似乎不會產出可部署的代碼。

答案#1

如果你想保留 ES6 的 import/export,你應該定義 module:"es2015",預設情況下,它會將這些轉換為 Node 中使用的 CJS 風格。

現代瀏覽器不支援 Node 的 cjs 風格,而支援 es2015 風格,所以你需要指定那個模組類型,並且在你的 script 標籤上指定一個屬性 type="module",類似這樣:

<script type="module" src="entry-point.js"></script>

目前只有 Chrome 原生支持 模組,因此如今的最佳做法是使用 匯入 創建的依賴樹來生成一個 包。

常見的打包工具有 Webpack 和 RollUp,你可以閱讀它們的配置以生成可在瀏覽器中運行的打包文件。

TypeScript 與 Webpack 配置的小參考:https://webpack.js.org/guides/typescript/

答案#2

我看到你正在開始建立 TypeScript 網頁應用程式。如果是這樣,這是我的建議:

將 TypeScript 轉譯為 JS 是無法避免的,

不要使用 RequireJS 或 SystemJS,而應該使用像 parcel、webpack、rollup、browserify 這類的打包工具。特別是我推薦 parcel,因為它非常簡單,且與 TypeScript 相容良好

  • 使用打包工具,擺脫所有那些 defines()、requires()、SystemJs.calls(),僅使用標準的 JavaScript import 聲明(import {foo} from 'bar')。我也強烈推薦這樣做

  • 順便提一下,避免使用 require.js、AMD、UMD、SystemJS 或類似的工具。剛開始使用這些並不是一個好建議。只有在真的非常必要的情況下才使用它們。

祝你好運