@@ -139,6 +139,8 @@ export function transformFileContents(filename: string, contents: string, progre
139
139
140
140
file = ts . transform ( file , [ ( ctx : ts . TransformationContext ) : ts . Transformer < ts . SourceFile > => {
141
141
const factory = ctx . factory ;
142
+ const alreadyEmittedExports = new Set < string > ( ) ;
143
+
142
144
const visit : ts . Visitor = node => {
143
145
if ( node . parent && ts . isSourceFile ( node . parent )
144
146
&& ts . isExpressionStatement ( node )
@@ -159,8 +161,8 @@ export function transformFileContents(filename: string, contents: string, progre
159
161
const module = require ( file ) ;
160
162
const entries = Object . keys ( module ) ;
161
163
162
- return entries . map ( ( entry ) =>
163
- createModuleGetter ( factory , entry , requiredModule , ( mod ) =>
164
+ return entries . flatMap ( ( entry ) =>
165
+ createModuleGetterOnce ( alreadyEmittedExports ) ( factory , entry , requiredModule , ( mod ) =>
164
166
factory . createPropertyAccessExpression ( mod , entry ) )
165
167
) ;
166
168
}
@@ -180,7 +182,7 @@ export function transformFileContents(filename: string, contents: string, progre
180
182
181
183
const exportName = node . expression . left . name . text ;
182
184
const moduleName = node . expression . right . arguments [ 0 ] . text ;
183
- return createModuleGetter ( factory , exportName , moduleName , ( x ) => x ) ;
185
+ return createModuleGetterOnce ( alreadyEmittedExports ) ( factory , exportName , moduleName , ( x ) => x ) ;
184
186
}
185
187
186
188
return ts . visitEachChild ( node , child => visit ( child ) , ctx ) ;
@@ -212,25 +214,67 @@ function createAssignment(factory: ts.NodeFactory, name: string, expression: ts.
212
214
expression ) ) ;
213
215
}
214
216
217
+ /**
218
+ * Create an lazy getter for a particular value at the module level
219
+ *
220
+ * Since Node statically analyzes CommonJS modules to determine its exports
221
+ * (using the `cjs-module-lexer` module), we need to trick it into recognizing
222
+ * these exports as legitimate.
223
+ *
224
+ * We do that by generating one form it will recognize that doesn't do anything,
225
+ * in combination with a form that actually works, that doesn't disqualify the
226
+ * export name.
227
+ */
215
228
function createModuleGetter (
216
229
factory : ts . NodeFactory ,
217
230
exportName : string ,
218
231
moduleName : string ,
219
232
moduleFormatter : ( x : ts . Expression ) => ts . Expression ,
220
233
) {
221
- return factory . createExpressionStatement ( factory . createCallExpression (
222
- factory . createPropertyAccessExpression ( factory . createIdentifier ( 'Object' ) , factory . createIdentifier ( 'defineProperty' ) ) ,
223
- undefined ,
224
- [
225
- factory . createIdentifier ( 'exports' ) ,
226
- factory . createStringLiteral ( exportName ) ,
227
- factory . createObjectLiteralExpression ( [
228
- factory . createPropertyAssignment ( 'configurable' , factory . createTrue ( ) ) ,
229
- factory . createPropertyAssignment ( 'get' ,
230
- factory . createArrowFunction ( undefined , undefined , [ ] , undefined , undefined ,
231
- moduleFormatter (
232
- factory . createCallExpression ( factory . createIdentifier ( 'require' ) , undefined , [ factory . createStringLiteral ( moduleName ) ] ) ) ) ) ,
233
- ] ) ,
234
- ]
235
- ) ) ;
234
+ return [
235
+ // exports.<name> = void 0;
236
+ factory . createExpressionStatement ( factory . createBinaryExpression (
237
+ factory . createPropertyAccessExpression (
238
+ factory . createIdentifier ( 'exports' ) ,
239
+ factory . createIdentifier ( exportName ) ) ,
240
+ ts . SyntaxKind . EqualsToken ,
241
+ factory . createVoidZero ( ) ) ) ,
242
+ // Object.defineProperty(exports, "<n>" + "<ame>", { get: () => });
243
+ factory . createExpressionStatement ( factory . createCallExpression (
244
+ factory . createPropertyAccessExpression ( factory . createIdentifier ( 'Object' ) , factory . createIdentifier ( 'defineProperty' ) ) ,
245
+ undefined ,
246
+ [
247
+ factory . createIdentifier ( 'exports' ) ,
248
+ factory . createBinaryExpression (
249
+ factory . createStringLiteral ( exportName . substring ( 0 , 1 ) ) ,
250
+ ts . SyntaxKind . PlusToken ,
251
+ factory . createStringLiteral ( exportName . substring ( 1 ) ) ,
252
+ ) ,
253
+ factory . createObjectLiteralExpression ( [
254
+ factory . createPropertyAssignment ( 'enumerable' , factory . createTrue ( ) ) ,
255
+ factory . createPropertyAssignment ( 'configurable' , factory . createTrue ( ) ) ,
256
+ factory . createPropertyAssignment ( 'get' ,
257
+ factory . createArrowFunction ( undefined , undefined , [ ] , undefined , undefined ,
258
+ moduleFormatter (
259
+ factory . createCallExpression ( factory . createIdentifier ( 'require' ) , undefined , [ factory . createStringLiteral ( moduleName ) ] ) ) ) ) ,
260
+ ] ) ,
261
+ ]
262
+ )
263
+ ) ] ;
264
+ }
265
+
266
+ /**
267
+ * Prevent emitting an export if it has already been emitted before
268
+ *
269
+ * This assumes that the symbols have the same definition, and are only duplicated because of
270
+ * accidental multiple `export *`s.
271
+ */
272
+ function createModuleGetterOnce ( alreadyEmittedExports : Set < string > ) : typeof createModuleGetter {
273
+ return ( factory , exportName , moduleName , moduleFormatter ) => {
274
+ if ( alreadyEmittedExports . has ( exportName ) ) {
275
+ return [ ] ;
276
+ }
277
+ alreadyEmittedExports . add ( exportName ) ;
278
+ return createModuleGetter ( factory , exportName , moduleName , moduleFormatter ) ;
279
+ } ;
236
280
}
0 commit comments