需要理解的是,TypeScript 作為語言,他只處理代碼模塊。其他類型的文件這種非代碼模塊的導入,講道理是通過另外的打包工具來完成的,比如 Web 應用中的圖片,樣式,JSON 還有 HTML 模板文件。只是我們需要在 TypeScript 層面解決模塊解析報錯的問題。
通配符模塊聲明
直接導入非代碼模塊,TypeScript 會報錯。
/** 🚨 Cannot find module './index.html'. */
import * as html from "./index.html";
TypeScript 文檔中關於這部分的描述是 Wildcard module declarations,即通過定義通配符模塊。與 SystemJS 和 AMD 一致,在導入時路徑上加上定義好的文件類型前后綴,這些路徑上的額外信息在編譯后可通過運行時實際的加載器中相應的插件來解析處理。
typings.d.ts
declare module "*!text" {
const content: string;
export default content;
}
// Some do it the other way around.
declare module "json!*" {
const value: any;
export default value;
}
main.ts
import fileContent from "./xyz.txt!text";
import data from "json!http://example.com/data.json";
console.log(data, fileContent);
使用定義類型前后綴的方式是可以解決代碼中 TypeScript 報錯的問題,但編譯后因為模塊地址中的類型前綴或后綴(!text,json!)沒有去掉,這就需要對 require 定義相應的插件來處理。
$ node main.js
internal/modules/cjs/loader.js:670
throw err;
^
Error: Cannot find module './xyz.txt!text'
如果你看過 VSCode 的源碼,會發現里面大量使用了有類似這樣的方式導入樣式文件:
import 'vs/css!./selections';
這里 vs/css! 便是上面提到的方式,但 VSCode 是使用了自定義的一個模塊加載器 Microsoft/vscode-loader 來處理。
還可以像下面這樣來進行文件類型的聲明:
typings.d.ts
declare module "*.html" {
const value: string;
export default value;
}
然后寫正常的路徑來導入即可,編譯后的產出中路徑沒有改變。
/** ✅ ojbk */
import * as html from "./index.html";
對於其他類型的后綴同理。
typings.d.ts
declare module "*.png" {
const value: string;
export default value;
}
declare module '*.scss' {
const content: any;
export default content;
}
需要注意的是,這只是解決了 TypeScript 的模塊解析報錯的問題,實際文件的導入並不是在 TypeScript 中做的,而需要額外的打包工具。Webpack 中則是相應的樣式 loader 和 圖片 loader 來負責這些文件的解析加載。
JSON 文件的導入
因為 JSON 格式太過常見,TypeScript 確實在自己的編譯器中提供了對其加載的支持,通過相應的編譯參數 --resolveJsonModul 來開啟。
創建 tsconfig.json 開啟對 JSON 文件導入的支持。
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"resolveJsonModule": true
}
}
然后代碼中導入 JSON 文件時指定正確的路徑,像其他正常的 TypeScript 模塊一樣。無須前綴,也無須編寫額外的聲明文件。
main.ts
import * as data from "./bar.json";
console.log(data);
當嘗試編譯上面代碼時,你會得到如下的報錯:
$ tsc
error TS5055: Cannot write file '/Users/wayou/Documents/dev/github/ts-wildcard-module/bar.json' because it would overwrite input file.
因為編譯后 JSON 文件也會有一份同名的產出,在沒有指定編譯輸出目錄的情況下, tsc 默認輸出到當前目錄,所以會有上面 JSON 文件會被覆蓋的提示。所以解決辦法就是添加輸出目錄的配置。
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"resolveJsonModule": true,
+ "outDir": "dist"
}
}
再次編譯后可正常運行。
$ tsc
$ node dist/main.js
{ data: 'blah' }
配合着 esModuleInterop 選項可以讓導入更加簡潔。
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
+ "esModuleInterop": true,
"resolveJsonModule": true,
"outDir": "dist"
}
}
main.ts
- import * as data from "./bar.json";
+ import data from "./bar.json";
console.log(data);
總結
一般項目中都使用專門的打包工具比如 Webpack,Parcel 等,資源的解析加載都會被很好地處理。只是我們需要解決 TypeScipt 模塊解析報錯的問題,此時可通過對相應文件編寫聲明文件的方式。
