1/*@internal*/ 2namespace ts { 3 export function transformECMAScriptModule(context: TransformationContext) { 4 const { 5 factory, 6 getEmitHelperFactory: emitHelpers, 7 } = context; 8 const host = context.getEmitHost(); 9 const resolver = context.getEmitResolver(); 10 const compilerOptions = context.getCompilerOptions(); 11 const languageVersion = getEmitScriptTarget(compilerOptions); 12 const previousOnEmitNode = context.onEmitNode; 13 const previousOnSubstituteNode = context.onSubstituteNode; 14 context.onEmitNode = onEmitNode; 15 context.onSubstituteNode = onSubstituteNode; 16 context.enableEmitNotification(SyntaxKind.SourceFile); 17 context.enableSubstitution(SyntaxKind.Identifier); 18 19 let helperNameSubstitutions: ESMap<string, Identifier> | undefined; 20 let currentSourceFile: SourceFile | undefined; 21 let importRequireStatements: [ImportDeclaration, VariableStatement] | undefined; 22 return chainBundle(context, transformSourceFile); 23 24 function transformSourceFile(node: SourceFile) { 25 if (node.isDeclarationFile) { 26 return node; 27 } 28 29 if (isExternalModule(node) || compilerOptions.isolatedModules) { 30 currentSourceFile = node; 31 importRequireStatements = undefined; 32 let result = updateExternalModule(node); 33 currentSourceFile = undefined; 34 if (importRequireStatements) { 35 result = factory.updateSourceFile( 36 result, 37 setTextRange(factory.createNodeArray(insertStatementsAfterCustomPrologue(result.statements.slice(), importRequireStatements)), result.statements), 38 ); 39 } 40 if (!isExternalModule(node) || some(result.statements, isExternalModuleIndicator)) { 41 return result; 42 } 43 return factory.updateSourceFile( 44 result, 45 setTextRange(factory.createNodeArray([...result.statements, createEmptyExports(factory)]), result.statements), 46 ); 47 } 48 49 return node; 50 } 51 52 function updateExternalModule(node: SourceFile) { 53 const externalHelpersImportDeclaration = createExternalHelpersImportDeclarationIfNeeded(factory, emitHelpers(), node, compilerOptions); 54 if (externalHelpersImportDeclaration) { 55 const statements: Statement[] = []; 56 const statementOffset = factory.copyPrologue(node.statements, statements); 57 append(statements, externalHelpersImportDeclaration); 58 59 addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); 60 return factory.updateSourceFile( 61 node, 62 setTextRange(factory.createNodeArray(statements), node.statements)); 63 } 64 else { 65 return visitEachChild(node, visitor, context); 66 } 67 } 68 69 function visitor(node: Node): VisitResult<Node> { 70 switch (node.kind) { 71 case SyntaxKind.ImportEqualsDeclaration: 72 // Though an error in es2020 modules, in node-flavor es2020 modules, we can helpfully transform this to a synthetic `require` call 73 // To give easy access to a synchronous `require` in node-flavor esm. We do the transform even in scenarios where we error, but `import.meta.url` 74 // is available, just because the output is reasonable for a node-like runtime. 75 return getEmitModuleKind(compilerOptions) >= ModuleKind.Node16 ? visitImportEqualsDeclaration(node as ImportEqualsDeclaration) : undefined; 76 case SyntaxKind.ExportAssignment: 77 return visitExportAssignment(node as ExportAssignment); 78 case SyntaxKind.ExportDeclaration: 79 const exportDecl = (node as ExportDeclaration); 80 return visitExportDeclaration(exportDecl); 81 } 82 83 return node; 84 } 85 86 /** 87 * Creates a `require()` call to import an external module. 88 * 89 * @param importNode The declaration to import. 90 */ 91 function createRequireCall(importNode: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration) { 92 const moduleName = getExternalModuleNameLiteral(factory, importNode, Debug.checkDefined(currentSourceFile), host, resolver, compilerOptions); 93 const args: Expression[] = []; 94 if (moduleName) { 95 args.push(moduleName); 96 } 97 98 if (!importRequireStatements) { 99 const createRequireName = factory.createUniqueName("_createRequire", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel); 100 const importStatement = factory.createImportDeclaration( 101 /*modifiers*/ undefined, 102 factory.createImportClause( 103 /*isTypeOnly*/ false, 104 /*name*/ undefined, 105 factory.createNamedImports([ 106 factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("createRequire"), createRequireName) 107 ]) 108 ), 109 factory.createStringLiteral("module") 110 ); 111 const requireHelperName = factory.createUniqueName("__require", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel); 112 const requireStatement = factory.createVariableStatement( 113 /*modifiers*/ undefined, 114 factory.createVariableDeclarationList( 115 [ 116 factory.createVariableDeclaration( 117 requireHelperName, 118 /*exclamationToken*/ undefined, 119 /*type*/ undefined, 120 factory.createCallExpression(factory.cloneNode(createRequireName), /*typeArguments*/ undefined, [ 121 factory.createPropertyAccessExpression(factory.createMetaProperty(SyntaxKind.ImportKeyword, factory.createIdentifier("meta")), factory.createIdentifier("url")) 122 ]) 123 ) 124 ], 125 /*flags*/ languageVersion >= ScriptTarget.ES2015 ? NodeFlags.Const : NodeFlags.None 126 ) 127 ); 128 importRequireStatements = [importStatement, requireStatement]; 129 130 } 131 132 const name = importRequireStatements[1].declarationList.declarations[0].name; 133 Debug.assertNode(name, isIdentifier); 134 return factory.createCallExpression(factory.cloneNode(name), /*typeArguments*/ undefined, args); 135 } 136 137 /** 138 * Visits an ImportEqualsDeclaration node. 139 * 140 * @param node The node to visit. 141 */ 142 function visitImportEqualsDeclaration(node: ImportEqualsDeclaration): VisitResult<Statement> { 143 Debug.assert(isExternalModuleImportEqualsDeclaration(node), "import= for internal module references should be handled in an earlier transformer."); 144 145 let statements: Statement[] | undefined; 146 statements = append(statements, 147 setOriginalNode( 148 setTextRange( 149 factory.createVariableStatement( 150 /*modifiers*/ undefined, 151 factory.createVariableDeclarationList( 152 [ 153 factory.createVariableDeclaration( 154 factory.cloneNode(node.name), 155 /*exclamationToken*/ undefined, 156 /*type*/ undefined, 157 createRequireCall(node) 158 ) 159 ], 160 /*flags*/ languageVersion >= ScriptTarget.ES2015 ? NodeFlags.Const : NodeFlags.None 161 ) 162 ), 163 node), 164 node 165 ) 166 ); 167 168 statements = appendExportsOfImportEqualsDeclaration(statements, node); 169 170 return singleOrMany(statements); 171 } 172 173 function appendExportsOfImportEqualsDeclaration(statements: Statement[] | undefined, node: ImportEqualsDeclaration) { 174 if (hasSyntacticModifier(node, ModifierFlags.Export)) { 175 statements = append(statements, factory.createExportDeclaration( 176 /*modifiers*/ undefined, 177 node.isTypeOnly, 178 factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, idText(node.name))]) 179 )); 180 } 181 return statements; 182 } 183 184 function visitExportAssignment(node: ExportAssignment): VisitResult<ExportAssignment> { 185 // Elide `export=` as it is not legal with --module ES6 186 return node.isExportEquals ? undefined : node; 187 } 188 189 function visitExportDeclaration(node: ExportDeclaration) { 190 // `export * as ns` only needs to be transformed in ES2015 191 if (compilerOptions.module !== undefined && compilerOptions.module > ModuleKind.ES2015) { 192 return node; 193 } 194 195 // Either ill-formed or don't need to be tranformed. 196 if (!node.exportClause || !isNamespaceExport(node.exportClause) || !node.moduleSpecifier) { 197 return node; 198 } 199 200 const oldIdentifier = node.exportClause.name; 201 const synthName = factory.getGeneratedNameForNode(oldIdentifier); 202 const importDecl = factory.createImportDeclaration( 203 /*modifiers*/ undefined, 204 factory.createImportClause( 205 /*isTypeOnly*/ false, 206 /*name*/ undefined, 207 factory.createNamespaceImport( 208 synthName 209 ) 210 ), 211 node.moduleSpecifier, 212 node.assertClause 213 ); 214 setOriginalNode(importDecl, node.exportClause); 215 216 const exportDecl = isExportNamespaceAsDefaultDeclaration(node) ? factory.createExportDefault(synthName) : factory.createExportDeclaration( 217 /*modifiers*/ undefined, 218 /*isTypeOnly*/ false, 219 factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, synthName, oldIdentifier)]), 220 ); 221 setOriginalNode(exportDecl, node); 222 223 return [importDecl, exportDecl]; 224 } 225 226 // 227 // Emit Notification 228 // 229 230 /** 231 * Hook for node emit. 232 * 233 * @param hint A hint as to the intended usage of the node. 234 * @param node The node to emit. 235 * @param emit A callback used to emit the node in the printer. 236 */ 237 function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void { 238 if (isSourceFile(node)) { 239 if ((isExternalModule(node) || compilerOptions.isolatedModules) && compilerOptions.importHelpers) { 240 helperNameSubstitutions = new Map<string, Identifier>(); 241 } 242 previousOnEmitNode(hint, node, emitCallback); 243 helperNameSubstitutions = undefined; 244 } 245 else { 246 previousOnEmitNode(hint, node, emitCallback); 247 } 248 } 249 250 // 251 // Substitutions 252 // 253 254 /** 255 * Hooks node substitutions. 256 * 257 * @param hint A hint as to the intended usage of the node. 258 * @param node The node to substitute. 259 */ 260 function onSubstituteNode(hint: EmitHint, node: Node) { 261 node = previousOnSubstituteNode(hint, node); 262 if (helperNameSubstitutions && isIdentifier(node) && getEmitFlags(node) & EmitFlags.HelperName) { 263 return substituteHelperName(node); 264 } 265 266 return node; 267 } 268 269 function substituteHelperName(node: Identifier): Expression { 270 const name = idText(node); 271 let substitution = helperNameSubstitutions!.get(name); 272 if (!substitution) { 273 helperNameSubstitutions!.set(name, substitution = factory.createUniqueName(name, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel)); 274 } 275 return substitution; 276 } 277 } 278} 279