為何在打包工具中導(dǎo)入 Cesium 的 css 失敗了?
1 問題起因
我使用 vite2 + vanillajs 模板創(chuàng)建 CesiumJS 項目,其中,main.js 是這樣的:
import { Viewer } from 'cesium'
import './style.css'
import 'cesium/Source/Widgets/widgets.css'
let viewer
const main = () => {
const dom = document.getElementById('app')
viewer = new Viewer(dom)
}
document.addEventListener('DOMContentLoaded', () => {
main()
})
看起來邏輯完美,思路清晰,沒什么特別的疑問點。于是我就吭哧吭哧地運行起 npm script:
pnpm dev
可是,Vite 在控制臺給我報了個錯:
[vite] Internal server error: Missing "./Source/Widgets/widgets.css" export in "cesium" package
這個問題貌似在各前端框架的模板中是不會出現(xiàn)的,我不確定。也有人在 Webpack 中遇到了這個類似的情況,究其原因,我認為還是 cesium 包的導(dǎo)出有些不完備,見下面第二節(jié)的分析。
簡單點說,就是 Vite 的內(nèi)置預(yù)構(gòu)建工具 esbuild 在搜索依賴樹時,沒有找到 "cesium" 包導(dǎo)出的一個路徑為 "./Source/Widgets/widgets.css" 文件。
2 尋找解決方案
可是,當我打開 node_modules/cesium/Source/Widgets/ 目錄,widgets.css 文件的確就在那里放著。
于是我打開了谷歌,果不其然找到了類似的 issue:github.com/CesiumGS/cesium issue#9212,我在 2021 年 8 月也跟帖回復(fù)了我的情況。
我當時并沒有找到解決方案,就暫時跳過了。
后來,有外國朋友跟帖回復(fù),大致原因找到了:
cesium 包的 package.json 沒有導(dǎo)出樣式文件,主要是 package.json 中的 exports 屬性。
于是,我打開官方源碼的 package.json,找到對應(yīng)的部分:
{
"exports": {
"./package.json": "./package.json",
".": {
"require": "./index.cjs",
"import": "./Source/Cesium.js"
}
}
}
2.1. 歷史原因
眾所周知,NodeJS 最先使用的模塊化機制是 CommonJS,后來才支持的 ESModule,現(xiàn)在 NodeJS 仍然默認新建的包是 CommonJS 模塊的。
外國佬在 NodeJS 的包中允許雙模塊化,這就容易存在兼容性問題。我們看看最開始是怎么實現(xiàn)雙模塊化的,這里以默認 ESModule 為模塊化方式:
- 設(shè)置
package.json的"type": "module",這樣所有的.js文件都是 ESM 了 - 設(shè)置
package.json的"module": "./dist/esm/index.js",這個意思是使用 import 語法導(dǎo)入時,ESM 模塊將從哪里尋找主文件 - 設(shè)置
package.json的"main": "./index.cjs",這個意思是使用 require 函數(shù)導(dǎo)入模塊時,CommonJS 的主文件是哪個
后來,隨著 ESModule 成為主流標準,NodeJS 改進了上面的配置方式,你仍可以設(shè)置 "type": "module" 令當前包的模塊化是 ESM,但是對包的多模塊化機制的配置則改用了 "exports" 字段,正如上面 cesium 的配置。
我檢查了我的 NodeJS 版本:
> node -v
> v16.14.0
顯然比較新,那么它應(yīng)該就是從 "exports" 中讀取的導(dǎo)出信息。
2.2. 增加導(dǎo)出
于是,我增加了 "exports" 的導(dǎo)出字段,讓打包工具在識別 cesium 包的導(dǎo)出時,可以正確識別 widgets.css 文件。
{
"exports": {
"./package.json": "./package.json",
".": {
"require": "./index.cjs",
"import": "./Source/Cesium.js"
},
+ "./Source/Widgets/widgets.css": "./Source/Widgets/widgets.css"
}
}
這樣,下面兩個導(dǎo)入語句:
import { Viewer } from 'cesium'
import 'cesium/Source/Widgets/widgets.css'
實際上就是:
import { Viewer } from 'cesium/Source/Cesium.js'
import 'cesium/Source/Widgets/widgets.css'
2.3. 耍個花招
我覺得這樣導(dǎo)入 css 文件還是太長,不妨在 "exports" 中給它改個名:
{
"exports": {
"./package.json": "./package.json",
".": {
"require": "./index.cjs",
"import": "./Source/Cesium.js"
},
+ "./index.css": "./Source/Widgets/widgets.css"
}
}
然后就可以愉快地使用短路徑導(dǎo)入了:
import { Viewer } from 'cesium'
import 'cesium/index.css'
事實上,package.json 中的這個 exports 屬性,就起到類似導(dǎo)出別名的作用,其中 "." 就相當于包的根路徑。
3 類型提示是哪來的
考慮這樣導(dǎo)入 cesium 各個 API:
import {
Viewer,
Cartesian3,
Camera
} from 'cesium'
當你使用這些類的時候,會得到不錯的類型提示。回顧前面的內(nèi)容,其實從 "cesium" 導(dǎo)入子模塊,實際上是從 "cesium/Source/Cesium.js" 文件導(dǎo)入的,而這個文件的旁邊就有一個 "Cesium.d.ts" 文件,它就起類型提示的作用。
這個類型聲明文件是 Cesium 使用 gulp 打包時輸出的。
說了這么多,根本原因還是 JavaScript 的歷史包袱導(dǎo)致的各種問題,而且官方也暫時沒有修改 package.json 中 exports 的計劃,如果你有報這個錯誤,那么你僅僅需要按我上面的方式稍作修改即可。

浙公網(wǎng)安備 33010602011771號