簡體   English   中英

在 Typescript 編譯期間在相對導入語句上附加.js 擴展(ES6 模塊)

[英]Appending .js extension on relative import statements during Typescript compilation (ES6 modules)

這似乎是一個微不足道的問題,但解決此問題需要使用哪些設置/配置並不是很明顯。

下面是 Hello World 程序目錄結構和源代碼:

目錄結構:

| -- HelloWorldProgram
     | -- HelloWorld.ts
     | -- index.ts
     | -- package.json
     | -- tsconfig.json

索引.ts:

import {HelloWorld} from "./HelloWorld";

let world = new HelloWorld();

HelloWorld.ts:

export class HelloWorld {
    constructor(){
        console.log("Hello World!");
    }
}

package.json:

{
  "type": "module",
  "scripts": {
    "start": "tsc && node index.js"
  }
}

現在,執行命令tsc && node index.js會導致以下錯誤:

internal/modules/run_main.js:54
    internalBinding('errors').triggerUncaughtException(
                              ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'HelloWorld' imported from HelloWorld\index.js
Did you mean to import ../HelloWorld.js?
    at finalizeResolution (internal/modules/esm/resolve.js:284:11)
    at moduleResolve (internal/modules/esm/resolve.js:662:10)
    at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:752:11)
    at Loader.resolve (internal/modules/esm/loader.js:97:40)
    at Loader.getModuleJob (internal/modules/esm/loader.js:242:28)
    at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:50:40)
    at link (internal/modules/esm/module_job.js:49:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}

很明顯,問題似乎源於index.ts Typescript 文件中 import 語句中沒有.js擴展名( import {HelloWorld} from "./HelloWorld"; )。 Typescript 在編譯過程中沒有拋出任何錯誤。 但是,在運行時 Node (v14.4.0) 需要.js擴展名。

希望上下文清楚。

現在,如何更改編譯器 output 設置(tsconfig.json 或任何標志)以便本地相對路徑導入,例如import {HelloWorld} from./Helloworld; 將被替換為import {HelloWorld} from./Helloworld.js; index.js文件中 Typescript 到 Javascript 編譯期間?

注意: It is possible to directly use the.js extension while importing inside typescript file. However, it doesn't help much while working with hundreds of old typescript modules, because then we have to go back and manually add.js extension. Rather than that for us better solution is to batch rename and remove all the.js extension from all the generated.js filenames at last. It is possible to directly use the.js extension while importing inside typescript file. However, it doesn't help much while working with hundreds of old typescript modules, because then we have to go back and manually add.js extension. Rather than that for us better solution is to batch rename and remove all the.js extension from all the generated.js filenames at last.

對於正在尋找此問題的解決方案的開發人員,我們遇到的可能解決方法如下:

  1. 對於新文件,可以在編輯時在 Typescript 文件的導入語句中簡單地添加".js"擴展名。 示例: import {HelloWorld} from "./HelloWorld.js";

  2. 如果使用舊項目,而不是遍歷每個文件並更新導入語句,我們發現通過簡單的自動化腳本從生成的 Javascript 中簡單地批量重命名和刪除".js"擴展名更容易。 但是請注意,這可能需要對服務器端代碼進行細微更改,以便為客戶端提供這些具有正確 MIME 類型的無擴展名".js"文件。 如果您想避免這種情況,您可能希望使用正則表達式以遞歸方式批量查找和替換導入語句以添加.js擴展名。

邊注:

關於 TS 團隊在解決這個問題上的失敗,似乎有一種趨勢是試圖斷章取義地炸毀這個問題,而不是把它附加到一些設計原則上來捍衛。

然而,實際上這只不過是編譯器如何不對稱地處理擴展名的問題。 Typescript 編譯器允許不帶擴展名的導入語句。 然后,它繼續在文件被翻譯時將“.js”擴展名添加到相應的 output 文件名,但對於引用該文件的相應導入語句,它忽略了它在翻譯期間添加了“.js”擴展名的事實。 脫離上下文的 URI 重寫原則如何保護這種不對稱性?

Typescript文件與編譯時生成的Javascript output文件之間存在固定的一一對應關系。 如果引用的導入不存在,編譯器會拋出錯誤。 這些文件甚至不會編譯,所以。 提及其他沖突 URI 的可能性的斷章取義或不可編譯的示例使此類聲明無效。

如果編譯器只生成無擴展名的 output 文件,它也可以解決問題 但是,這也會以某種方式違反有關 URI 重寫的設計原則嗎? 當然,在那種情況下,可能存在其他設計原則來保護 position? 但這樣的固執,豈不是更加印證了TS團隊在這個問題上的執着或無知?

如果您在使用 TypeScript 和 ESM 時遇到問題,那么這個微型庫實際上可以完美運行。

npm install @digitak/tsc-esm --save-dev

在腳本中用tsc-esm替換tsc調用:

{
   "scripts": {
      "build": "tsc-esm"
   }
}

最后你可以運行:

npm run build

為了修復此錯誤,您可以更改tsconfig.json文件,如下所示:

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "resolveJsonModule": true
  }
}

這是NestJS使用的配置。

我通常只在 typescript 文件的導入語句中使用 .js 擴展名,它也可以工作。

在導入路徑中不使用文件擴展名是 nodejs 唯一的事情。 由於您沒有使用 commonjs 而是模塊,因此您沒有使用 nodejs。 因此,您必須在導入路徑中使用 .is 擴展名。

TypeScript不可能知道您將使用什么 URI 來提供文件,因此它必須相信您提供的模塊路徑是正確的。 在這種情況下,你給它一個不存在的 URI 的路徑,但 TypeScript 不知道,所以它無能為力。

如果您使用以.js結尾的 URI 為模塊提供服務,那么您的模塊路徑需要以.js結尾。 如果您的模塊路徑不以.js結尾,那么您需要在不以.js結尾的 URI 上提供它。

請注意, W3C強烈建議不要在 URIs 中使用文件擴展名,因為它會使您的系統更難發展,並主張轉而依賴內容協商。

重寫路徑會破壞 TypeScript 的幾個基本設計原則。 一個設計原則是 TypeScript 是 ECMAScript 的適當超集,每個有效的 ECMAScript 程序和模塊都是語義上等效的 TypeScript 程序和模塊。 重寫路徑會破壞這個原則,因為一段 ECMAScript 的行為會根據它是作為 ECMAScript 還是 TypeScript 執行而有所不同。 想象一下,您有以下代碼:

。/你好

export default "ECMAScript";

./hello.js

export default "TypeScript";

。/主要的

import Hello from "./hello";

console.log(Hello);

If TypeScript did what you suggest, this would print two different things depending on whether you execute it as ECMAScript or as TypeScript, but the TypeScript design principles say that TypeScript does never change the meaning of ECMAScript . 當我以 TypeScript 執行一段 ECMAScript 時,它的行為應該與我以 ECMAScript 執行時完全相同

正如許多人指出的那樣。 The reason why Typescript doesn'y and will never add file extension to import statements is their premise that transpiling pure javascript code should output the same javascript code.

我認為有一個標志讓 typescript 在導入語句中強制執行文件擴展名將是他們能做的最好的事情。 然后像 eslint 這樣的linter可能會根據該規則提供自動修復程序

編輯添加了指向 eslint_plugin-import 的鏈接

您還可以添加 nodejs CLI 標志以啟用node module resolution

  • 用於導入 json --experimental-json-modules
  • 用於不帶擴展名的導入--experimental-specifier-resolution=node

我知道--experimental-specifier-resolution=node有一個錯誤(或沒有),那么你不能運行沒有擴展的 bin 腳本(例如在 package.json bin "tsc" 不起作用,但 "tsc":"tsc. js”將起作用)。 對於許多軟件包來說,bin 腳本沒有任何擴展,因此添加NODE_OPTIONS="--experimental-specifier-resolution=node"變量會有些麻煩

如果您知道所有導入語句確實應該具有.js擴展名,並且所有導入都沒有擴展名或已經具有.js擴展名,那么您可以使用正則表達式查找/替換來“規范化”所有內容。 我建議您在提交更改之前檢查您的 git(或其他 VCS)日志。 以下是我在 VSCode 中使用的正則表達式:

  • 查找: (import.* from ".*(?.\.js)(.){3})"
  • 替換: $1.js

find 表達式將匹配沒有.js擴展名的導入。 第一組將捕獲引號內的部分。 然后,替換表達式采用第一組匹配項,該組始終沒有.js擴展名,然后附加擴展名。

如果設置 linter 失敗,您可以定期運行它並檢查 git 日志,以確保在沒有擴展名的情況下沒有導入代碼庫。

在大型 monorepo 上遇到了同樣的問題,無法手動編輯每個文件,所以我編寫了一個腳本來修復我的項目中的所有 esm 導入和 append .js/index.js以安全的方式:

修復 esm 導入路徑

在您的項目中使用之前進行測試。

如果您使用的是 VS 代碼,則可以使用正則表達式替換路徑。

查找: (\bfrom\s+["']\..*)(["'])

替換: $1.js$2

此解決方案的靈感來自先前的解決方案,但由於以下原因,此解決方案效果更好。 請注意,此解決方案並不完美,因為它使用正則表達式而不是語法分析文件導入。

忽略 npm 導入。 例子:

import fs from 'fs'

支持多行導入。 例子:

import {
  foo,
  bar
} from './file'

支持as進口。 例子:

import * as foo from './file'

支持單引號和雙引號。 例子:

import foo from "./file"

支持導出。 請參閱導出文檔。 例子:

export { foo } from './file'

您可以使用與我相同的解決方案

文件:tsconfig.json

"compilerOptions": {
    "module": "commonjs",   ==> not required extension when import   
    "target": "ES6",
},

因為使用commonjs,所以必須去掉package.json中的“type”:“module”

完成:D

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM