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

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


咨詢
建站咨詢
