问题场景升级npm包之前,只支持esm。npm包升级后,同时支持esm和cjs。为什么升级后还是报错?如何理解ts编译配置esModuleInterop?问题场景总结我遇到了一个很有意思的场景,cjs需要引入原本用esm方式打包的模块。也就是我想通过require()引入一个导出模块。my-npm-package包的暴露方式为:importfoofrom"./foo";从“./bar”导入栏;出口{富,酒吧};支持的方法是import{foo,bar}from'my-npm-package';如果你想使用esm包const{foo}=require("my-npm-package");在cjs中,会报错SyntaxError:Cannotuseimportstatementoutsideamodule,那么如何让它只支持esm方法呢?如何更改包以同时支持esm和cjs?封装方式commonjs。这个只支持cjs,esm怎么支持呢?esm的支持是通过转换引入包的项目的babel来支持的。npm包改造前,只有esmtsconfig.json{"compilerOptions":{"target":"ES2015","module":"esnext",}}打包结果:importfoofrom"./foo";importbarfrom'./bar';export{foo,bar};//#sourceMappingURL=index.js.mapnpm包同时支持esm和cjstsconfig.json{"compilerOptions":{"target":"ES2015","module":"commonjs"}}打包结果:"usestrict";Object.defineProperty(exports,"__esModule",{value:true});exports.bar=exports.foo=void0;constfoo_1=require("./foo");exports.foo=foo_1.default;constbar_1=require("./bar");exports.bar=bar_1.default;//#sourceMappingURL=index.js.mapcjs:exports.xxxesm:Object.defineProperty(exports,“__esModule”,{值:真});“csjimportstheoriginalesmpackage”是什么原因?原来exports.xxx的esm包还能正常使用是什么原因呢?Object.defineProperty(exports,"__esModule",{value:true});那是“__esModule”,webpack会根据__esModule将模块识别为esm,最后通过babel转换成cjs模块引入回我们的场景:改造esm模块,同时支持cjs和esm。这是什么原因?第一步:target由esm改为commonjs支持cjs第二步:这一步其实可以不用,主工程的babel已经配置好,通过esm导入所有esm和cjs包即可。为什么转换后还是报错?先说结论:因为tsccjs的打包方式,importafrom'a',a.method()的打包会转化为consta_1=require('a'),a_1.default.method()默认情况下。并且一些npm包没有exports.default。解决方法:开启esModuleInterop。TypeError:Cannotreadpropertiesofundefined(reading'stringify')这是因为,在我们的npm包中,使用了查询字符串依赖项。importqueryStringfrom'query-string';constquery_string_1=require("query-string");query_string_1.default.stringify(body)//这里出错,被tsc打包后会转换为query_string_1.default。但是query-string@7.1.1的index.js没有暴露默认值。转换后constquery_string_1=exports;//query-string@7.1.1exports.parseUrlexports.stringifyUrlexports.pickexports.excludeexports.stringifyexports.extractexports.parse那么如何解决这个问题呢?将tsconfig.json中的esModuleInterop设置为true。因此默认返回导出。{"compilerOptions":{"target":"ES2015","module":"commonjs","esModuleInterop":true}}打包结果://index.js"usestrict";var__importDefault=(this&&this.__importDefault)||函数(mod){返回(mod&&mod.__esModule)?mod:{"default":mod};};Object.defineProperty(exports,"__esModule",{value:true});exports.bar=exports.foo=void0;constfoo_1=__importDefault(require("./foo"));exports.foo=foo_1.default;constbar_1=__importDefault(require("./bar"));exports.bar=bar_1.default;//#sourceMappingURL=index.js.map不仅是索引。js会注入__importDefault,但是tsc编译的所有ts文件都会注入__importDefault。//foo.jsvar__importDefault=(this&&this.__importDefault)||函数(mod){返回(mod&&mod.__esModule)?模组:{“默认”:模组};};constquery_string_1=__importDefault(require("查询字符串"));经过__importDefault转换后,变成constquery_string_1=__importDefault(exports);转换后constquery_string_1={default:exports};query_string_1.default.stringify(body)//这里没有问题。如何理解ts编译配置esModuleInterop?除了默认导入的default缺失之外,还需要配置esModuleInterop来兼容导入的命名空间。我们先看看官方的ts文档:https://www.typescriptlang.or...默认情况下,esModuleInterop是关闭的,ts的处理方式和CommonJS/AMD/UMD模块一样处理成es6模块.有两种情况不能这样处理:?import*asmomentfrom"moment"asconstmoment=require("moment")?importmomentfrom"moment"asconstmoment=require("moment").default可以避免这两个问题:import*asfsfrom"fs";import_from"lodash";fs.readFileSync("file.txt","utf8");_.chunk(["a","b","c","d"],2);禁用时(直接要求):"usestrict";Object.defineProperty(exports,"__esModule",{value:true});constfs=require("fs");constlodash_1=require("lodash");fs.readFileSync("file.txt","utf8");lodash_1.default.chunk(["a","b","c","d"],2);启用时(辅助导入函数__importStar、__importDefault):“usestrict”;var__createBinding=(this&&this.__createBinding)||(Object.create?(function(o,m,k,k2){if(k2===undefined)k2=k;vardesc=Object.getOwnPropertyDescriptor(m,k);如果(!描述||(desc中的“get”?!m.__esModule:desc.writable||desc.configurable)){desc={enumerable:true,get:function(){returnm[k];}};}Object.defineProperty(o,k2,desc);}):(function(o,m,k,k2){if(k2===undefined)k2=k;o[k2]=m[k];}));var__setModuleDefault=(this&&this.__setModuleDefault)||(Object.create?(function(o,v){Object.defineProperty(o,"default",{enumerable:true,value:v});}):function(o,v){o["default"]=v;});var__importStar=(this&&this.__importStar)||函数(mod){如果(mod&&mod.__esModule)返回mod;变量结果={};if(mod!=null)for(varkinmod)if(k!=="default"&&Object.prototype.hasOwnProperty.call(mod,k))__createBinding(result,mod,k);__setModuleDefault(结果,模组);返回结果;};var__importDefault=(this&&this.__importDefault)||功能(米od){返回(mod&&mod.__esModule)?mod:{"default":mod};};Object.defineProperty(exports,"__esModule",{value:true});constfs=__importStar(require("fs"));constlodash_1=__importDefault(require("lodash"));fs.readFileSync("file.txt","utf8");lodash_1.default.chunk(["a","b","c","d"],2);看看知乎前端同学的文章:https://zhuanlan.zhihu.com/p/...esm引入cjs互操作(互操作)的核心思想是:esmhasdefault,但cjs没有。cjs模块添加default引用了作者的一段话,非常简洁:目前很多常用的包都是基于cjs/UMD开发的,写前端代码一般都是用esm写的,所以常见的场景是esm导入cjs库。但是由于esm和cjs在概念上的差异,最大的区别就是esm有default的概念而cjs没有,所以default会有问题。TSbabelwebpack有自己的一套处理机制来处理这个兼容性问题。核心思想基本上是添加和读取默认属性。小结1.如何将esm模块打包成cjs?模块更改为commonjs。2、为什么esm可以通过import引用到cjs包?Babel会将import转换为require。3、如何理解esModuleInterop?兼容只有umd和cjs方法,不暴露default属性的包,添加default属性,这样importafrom"a"或者import*asafrom"a"就不会报没有default属性了。例如,像query-string@7.1.1这样的包。为了安全起见,建议启用此配置。4、为什么module在esnext的时候没有报错?因为当module为esnext时,代码直接是esModule模式,即import,default模式,不会转成后缀为default的cjs。可以说不管怎么写,都会原样打包。从“./webcVCS”导入webcVCS;从“./generateAssets”导入生成资产;导出{webcVCS,generateAssets,};从“查询字符串”导入查询字符串;5、以后打包时如何配置模块?esnext:packageonlyusedonlyinesmenvironmentcommonjs:purecjsorpackageusedinbothcjsandesmenvironment(esmenvironment一般由安装包项目支持,结合webpack,babel等打包工具)umd:同commonjs,and需要同时支持cjs、amd、cmd
