完全解決 TypeScript 報錯:「沒法從新聲明塊範圍變量」的問題

背景

當使用 TypeScript + TSlint + Babel + Jest 搭建開發環境時,在開發過程當中偶爾會被 IDE 提示「沒法從新聲明塊範圍變量」,從而致使編譯出錯,報錯圖示以下:node

相關開發環境配置以下:typescript

  • typescript: ^3.5.3
  • tslint: ^5.19.0
  • babel: ^7.0.0
  • jest: ^24.9.0
  • ts-jest: ^24.0.2

解決方案

之因此 tslint 會提示這個錯誤,是由於在 Commonjs 規範裏,沒有像 ESModule 能造成閉包的「模塊」概念,全部的模塊在引用時都默認被拋至全局,所以當再次聲明某個模塊時,TypeScript 會認爲重複聲明瞭兩次相同的變量進而拋錯。json

對於這個問題,最簡單的解決方法是在報錯的文件底部添加一行代碼:export {}。這行代碼會「欺騙」tslint 使其認爲當前文件是一個 ESModule 模塊,所以不存在變量重複聲明的可能性。當使用這個方法時,記得這樣配置你的 tsconfig.json 文件:babel

{
  "include": ["src", "demo"],
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "esModuleInterop": true, // important!
    "target": "esnext",
    "strict": true,
    "outDir": "app",
    "declaration": true,
    "sourceMap": true
  }
}

其中 esMoudleInterop 這個配置容許文件中出現 export 關鍵字。閉包

問題 2

當你覺得已經萬事大吉的時候,你會發現第二個問題又浮出水面:你沒法執行編譯後的 JavaScript 代碼!是的,由於 babel 雖然可以幫你成功轉譯了 TypeScript 代碼,可是並無幫你去掉,你 hack 上的 export 關鍵字,所以 node 會因爲沒法識別該關鍵字而報錯。app

此時你有兩個選擇:測試

刪除 export 關鍵字,忍受惱人的 IDE 提示,強行讓 babel 編譯;插件

幸運的是,這樣作真的行得通,由於 tslint 只是一個 「lint」,它只負責提示你哪裏有問題,你能夠強行忽略它。可是,當你使用 Jest 配合 TypeScript 進行測試時這樣作就行不通了,由於 Jest 會把 tslint 發現的錯誤當成沒法原諒的錯誤告訴你,這意味着,你別想開開心心的測試你的代碼,固然你還能夠選擇第二種解法:rest

寫一個 babel 插件,讓 babel 轉譯時去除 export 關鍵字;code

這樣你的 node 能夠識別轉譯後的代碼,你的 Jest 也再也不會抱怨什麼,一箭雙鵰!然而,你真的想要專門爲此寫一個插件嗎?

若是你的第一反應和我同樣是腦海中一個大大的 「NO!!!」,你應該繼續往下看了,其實咱們還有第三個方案:)

終極解決方案

實際上,已經有一個 babel 插件能夠知足咱們得需求了:@babel/plugin-transform-modules-commonjs ,這就是咱們一直求之不得的東西。正如插件名所暗示的,它能夠將 ESModule 模塊轉換爲符合 Commonjs 規範的代碼,而通過個人測試,當遇到 export {} 這樣的表達式時,其轉譯的方案是:「直接忽略」!這正是咱們想要的效果!

就這樣,在你的 babel.config.js 中加入這個插件,TSlint 不會再抱怨什麼,Jest 可以乖乖測試,Node 也不會朝你大吼 "What the * export !!",整個世界都清淨了。

最後,再分享一下個人全套相關配置,但願大家再也不爲這個問題感到困擾 😉:

tsconfig.json

{
  "include": ["src", "demo"],
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "esModuleInterop": true,
    "target": "esnext",
    "strict": true,
    "outDir": "app",
    "declaration": true,
    "sourceMap": true
  }
}

jest.config.js

module.exports = {
  roots: ['<rootDir>/src'],
  transform: {
    '^.+\\.tsx?$': 'ts-jest',
  },
  testPathIgnorePatterns: ['/node_moudles/', './src/utils/test.ts'],
}

babel.config.js

module.exports = {
  presets: ['@babel/typescript'],
  plugins: [
    '@babel/plugin-transform-modules-commonjs',
    '@babel/proposal-class-properties',
    '@babel/proposal-object-rest-spread',
  ],
}
相關文章
相關標籤/搜索