新聞中心
寫在前面
在node中支持兩種模塊方案——CommonJS(cjs) 和 ECMAScript modules (esm)。

成都創(chuàng)新互聯(lián)公司是專業(yè)的磁縣網(wǎng)站建設(shè)公司,磁縣接單;提供成都做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)、成都外貿(mào)網(wǎng)站建設(shè),網(wǎng)頁設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行磁縣網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來合作!
隨著ESModule的廣泛使用,社區(qū)生態(tài)也在逐漸轉(zhuǎn)向ESModule,ESModule相比于require的運(yùn)行時執(zhí)行,可以用來做一些靜態(tài)代碼分析如tree shaking等來減小代碼體積,但是由于CommonJS已有龐大的用戶基礎(chǔ),對于第三方庫作者來說,不能完全一刀切只用ESModule,還要兼容CommonJS場景的使用,所以最合理的方式就是“魚和熊掌兼得”,即使用ESModule編寫庫代碼,然后通過TypeScript、Babel等工具輔助生成對應(yīng)的CommonJS格式的代碼,然后根據(jù)開發(fā)者的引用方式來動態(tài)替換為指定格式的代碼。
有了兩種版本的代碼,第三方庫作者就需要編寫相應(yīng)的入口文件,來達(dá)到“動態(tài)”引入的目的(即import引用的時候指向ESModule的代碼,require引入則指向CommonJS的代碼),同時也方便于打包工具對于無用代碼的剔除,減少代碼體積,本篇文章主要聚焦于如何正確地配置入口文件。
注:本篇文章以node規(guī)范為準(zhǔn),對于打包工具額外支持的配置方式會進(jìn)行額外標(biāo)注
本文的涉及的示例代碼可以通過 https://github.com/HomyeeKing/test-entry 進(jìn)行查看、測試
main
package.json的 main字段是最常見的指定入口文件的形式。
{
"name": "@homy/test-entry",
"version": "1.0.0",
"description": "",
"main": "index.js"
}當(dāng)開發(fā)者引入@homy/test-entry這個包的時候,可以確定@homy/test-entry 這個npm包的入口文件指向的是 index.js。
const pkg = require('@homy/test-entry')但是index.js究竟是cjs or esm?
一種方式是我們可以通過后綴名來顯示地標(biāo)注出當(dāng)前文件是cjs還是esm格式的:
- cjs ---> .cjs
- esm ---> .mjs
那么不同模塊格式的文件如何相互引用呢?解釋規(guī)則大致如下
- import了CJS格式的文件,module.exports會等同于export default, 具名導(dǎo)入會根據(jù)靜態(tài)分析來兼容,但是一般推薦在ESM中使用defaultExport格式來引入CJS文件
- 在CJS中,如果想要引入ESM文件,因?yàn)镋SM模塊異步執(zhí)行的機(jī)制,必須使用Dynamic Import即import()來引用
// index.cjs
const pkg = require('./index.mjs') // Error
const pkg = await import('./index.mjs') //
// index.mjs
import { someVar } from './index.cjs' // ? it dependens 推薦下邊方式引入
import pkg from './index.cjs' //
另一種方式是通過package.json的 type字段來標(biāo)識
type
package.json 里也提供了一個type字段 用于標(biāo)注用什么格式來執(zhí)行.js文件,
{
"name": "@homy/test-entry",
"version": "1.0.0",
"description": "",
"type": "commonjs", // or "module", 默認(rèn)是 commonjs
"main": "index.js"
}如果手動設(shè)置type: module, 則將index.js當(dāng)做esmodule處理,否則視為CommonJS
type: module ,只有Node.js >= 14 且使用import才能使用,不支持require引入
注:關(guān)于.js的詳細(xì)解析策略推薦閱讀 https://nodejs.org/api/modules.html#enabling
通過type和main字段,我們可以指定入口文件以及入口文件是什么類型,但是指定的只是一個入口文件,仍然不能夠滿足我們“動態(tài)”引入的需求,所以node又引入exports這個新的字段作為main更強(qiáng)大的替代品。
exports
相比較于main字段,exports可以指定多個入口文件,且優(yōu)先級高于main
{
"name": "@homy/test-entry",
"main": "index.js",
"exports":{
"import":"./index.mjs",
"require":"./index.cjs",
"default": "./index.mjs" // 兜底使用
},
}而且還有效限制了入口文件的范圍,即如果你引入指定入口文件范圍之外的文件,則會報錯
const pkg = require('@homy/test-entry/test.js');
// 報錯!Package subpath './test.js' is not defined by "exports"如果想指定submodule, 我們可以這樣編寫
"exports": {
"." : "./index.mjs",
"./mobile": "./mobile.mjs",
"./pc": "./pc.mjs"
},
// or 更詳細(xì)的配置
"exports": {
".":{
"import":"./index.mjs",
"require":"./index.cjs",
"default": "./index.mjs"
},
"./mobile": {
"import":"./mobile.mjs",
"require":"./mobile.cjs",
"default": "./mobile.mjs"
}
},然后通過如下方式可以訪問到子模塊文件
import pkg from 'pkg/mobile'
另外還有一個imports 字段,主要用于控制import的解析路徑,類似于Import Maps, 不過在node中指定的入口需要以#開頭,感興趣的可以閱讀subpath-imports
對于前端日常開發(fā)來說,我們的運(yùn)行環(huán)境主要還是瀏覽器和各種webview,我們會使用各種打包工具來壓縮、轉(zhuǎn)譯我們的代碼,除了上面提到的main exports字段,被主流打包工具廣泛支持的還有一個module字段
module
大部分時候 我們也能在第三方庫中看到module這個字段,用來指定esm的入口,但是這個提案沒有被node采納(使用exports)但是大多數(shù)打包工具比如webpack、rollup以及esbuild等支持了這一特性,方便進(jìn)行tree shaking等優(yōu)化策略
另外,TypeScript已經(jīng)成為前端的主流開發(fā)方式,同時TypeScript也有自己的一套入口解析方式,只不過解析的是類型的入口文件,有效輔助開發(fā)者進(jìn)行類型檢查和代碼提示,來提高我們編碼的效率和準(zhǔn)確性,下面我們繼續(xù)了解下TypeScript是怎么解析類型文件的。
Type Script的凱瑞小入口文件
TypeScript有著對Node的原生支持,所以會先檢查main字段,然后找對應(yīng)文件是否存在類型聲明文件,比如main指向的是lib/index.js, TypeScript就會查找有沒有l(wèi)ib/index.d.ts文件。
另外一種方式,開發(fā)者可以在package.json中通過types字段來指定類型文件,exports中同理。
{
"name": "my-package",
"type": "module",
"exports": {
".": {
// Entry-point for TypeScript resolution - must occur first!
"types": "./types/index.d.ts",
// Entry-point for `import "my-package"` in ESM
"import": "./esm/index.js",
// Entry-point for `require("my-package") in CJS
"require": "./commonjs/index.cjs",
},
},
// CJS fall-back for older versions of Node.js
"main": "./commonjs/index.cjs",
// Fall-back for older versions of TypeScript
"types": "./types/index.d.ts"
}? TypeScript模塊解析策略
tsconfig.json包含一個moduleResolution字段,支持classic(默認(rèn))和node兩種解析策略,主要針對相對路徑引入和非相對路徑引入兩種方式,我們可以通過示例來理解下
? classic
查找以.ts 或.d.ts結(jié)尾的文件
- relative import
// /root/src/folder/A.ts
import { b } from "./moduleB"
// process:
/root/src/folder/moduleB.ts
/root/src/folder/moduleB.d.ts
相對路徑會找當(dāng)前目錄下的.ts 或.d.ts的文件
- no-relative import
// /root/src/folder/A.ts
import { b } from "moduleB"
// process:
/root/src/folder/moduleB.ts
/root/src/folder/moduleB.d.ts
/root/src/moduleB.ts
/root/src/moduleB.d.ts
/root/moduleB.ts
/root/moduleB.d.ts
/moduleB.ts
則會向上查找,直到找到moduleB 相關(guān)的.ts或.d.ts文件
? node
以類似于node的解析策略來查找,但是相應(yīng)的查找的范圍是以.ts .tsx .d.ts為后綴的文件,而且會讀取package.json中對應(yīng)的types(或typings)字段
- relative
/root/src/moduleA
const pkg = require('./moduleB')
// process:
/root/src/moduleB.js
/root/src/package.json (查找/root/src下有無package.json 如果指定了main字段 則指向main字段對應(yīng)的文件)
/root/src/moduleB/index.js
在node環(huán)境下,會依次解析.js 當(dāng)前package.json中main字段指向的文件以及是否存在對應(yīng)的index.js文件。
TypeScript解析的時候則是把后綴名替換成ts專屬的后綴.ts .tsx .d.ts,而且ts這時候會讀取types字段 而非main
/root/src/moduleB.ts
/root/src/moduleB.tsx
/root/src/moduleB.d.ts
/root/src/moduleB/package.json (if it specifies a types property)
/root/src/moduleB/index.ts
/root/src/moduleB/index.tsx
/root/src/moduleB/index.d.ts
- no-relative
no-relative就直接查看指定node_modules下有沒有對應(yīng)文件
/root/src/moduleA
const pkg = require('moduleB')
// process:
/root/src/node_modules/moduleB.js
/root/src/node_modules/package.json
/root/src/node_modules/moduleB/index.js
/root/node_modules/moduleB.js
/root/node_modules/moduleB/package.json (if it specifies a "main" property)
/root/node_modules/moduleB/index.js
/node_modules/moduleB.js
/node_modules/moduleB/package.json (if it specifies a "main" property)
/node_modules/moduleB/index.js
類似的 TypeScript也會替換對應(yīng)后綴名,而且多了@types下類型的查找
/root/src/node_modules/moduleB.ts
/root/src/node_modules/moduleB.tsx
/root/src/node_modules/moduleB.d.ts
/root/src/node_modules/moduleB/package.json (if it specifies a types property)
/root/src/node_modules/@types/moduleB.d.ts <----- check out @types
/root/src/node_modules/moduleB/index.ts
/root/src/node_modules/moduleB/index.tsx
/root/src/node_modules/moduleB/index.d.ts
....
另外TypeScript支持版本選擇來映射不同的文件,感興趣的可以閱讀version-selection-with-typesversions(地址:https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#version-selection-with-typesversions)
總結(jié)
- node中可以通過main 和 type: module | commonjs 來指定入口文件及其模塊類型, exports 則是更強(qiáng)大的替代品,擁有更靈活的配置方式
- 主流打包工具如webpack rollup esbuild 則在此基礎(chǔ)上增加了對top-level module的支持
- TypeScript 則會先查看package.json中有沒有types字段,否則查看main字段指定的文件有沒有對應(yīng)的類型聲明文件
文章題目:如何正確地配置入口文件?
文章源于:http://fisionsoft.com.cn/article/dhejods.html


咨詢
建站咨詢
