88#include < map>
99#include < string>
1010#include < string_view>
11+ #include < unordered_set>
1112#include < vector>
1213#include " builtin_info.h"
1314#include " embedded_data.h"
1415#include " executable_wrapper.h"
1516#include " simdutf.h"
17+ #include " typescript_transpiler.h"
1618#include " uv.h"
1719
1820#if defined(_WIN32)
@@ -131,13 +133,14 @@ bool SearchFiles(const std::string& dir,
131133
132134constexpr std::string_view kMjsSuffix = " .mjs" ;
133135constexpr std::string_view kJsSuffix = " .js" ;
136+ constexpr std::string_view kTsSuffix = " .ts" ;
134137constexpr std::string_view kGypiSuffix = " .gypi" ;
135138constexpr std::string_view depsPrefix = " deps/" ;
136139constexpr std::string_view libPrefix = " lib/" ;
137140
138141constexpr std::string_view HasAllowedExtensions (
139142 const std::string_view filename) {
140- for (const auto & ext : {kGypiSuffix , kJsSuffix , kMjsSuffix }) {
143+ for (const auto & ext : {kGypiSuffix , kJsSuffix , kMjsSuffix , kTsSuffix }) {
141144 if (filename.ends_with (ext)) {
142145 return ext;
143146 }
@@ -329,11 +332,13 @@ std::string GetFileId(const std::string& filename) {
329332 size_t end = filename.size ();
330333 size_t start = 0 ;
331334 std::string prefix;
332- // Strip .mjs and .js suffix
335+ // Strip .mjs, .js and .ts suffix
333336 if (filename.ends_with (kMjsSuffix )) {
334337 end -= kMjsSuffix .size ();
335338 } else if (filename.ends_with (kJsSuffix )) {
336339 end -= kJsSuffix .size ();
340+ } else if (filename.ends_with (kTsSuffix )) {
341+ end -= kTsSuffix .size ();
337342 }
338343
339344 // deps/acorn/acorn/dist/acorn.js -> internal/deps/acorn/acorn/dist/acorn
@@ -670,6 +675,7 @@ Fragment GetDefinition(const std::string& var, const std::vector<char>& code) {
670675}
671676
672677int AddModule (const std::string& filename,
678+ TypeScriptTranspiler* transpiler,
673679 Fragments* definitions,
674680 Fragments* initializers,
675681 Fragments* registrations) {
@@ -684,6 +690,21 @@ int AddModule(const std::string& filename,
684690 if (error != 0 ) {
685691 return error;
686692 }
693+
694+ if (filename.ends_with (kTsSuffix )) {
695+ std::vector<char > transpiled;
696+ if (transpiler->Strip (std::string_view (code.data (), code.size ()),
697+ filename,
698+ &transpiled) != 0 ) {
699+ fprintf (stderr,
700+ " Failed to transpile TypeScript file %s: %s\n " ,
701+ filename.c_str (),
702+ std::string (transpiler->LastError ()).c_str ());
703+ return 1 ;
704+ }
705+ code = std::move (transpiled);
706+ }
707+
687708 std::string file_id = GetFileId (filename);
688709 std::string var = GetVariableName (file_id);
689710
@@ -696,18 +717,20 @@ int AddModule(const std::string& filename,
696717 // {"internal/deps/v8/tools/tickprocessor-driver",
697718 // BuiltinSource{UnionBytes(&fs_resource),
698719 // BuiltinSourceType::kSourceTextModule}},
699- Fragment& init_buf = initializers->emplace_back (Fragment (512 , 0 ));
720+ Fragment& init_buf = initializers->emplace_back (Fragment (640 , 0 ));
700721 int init_size = snprintf (init_buf.data (),
701722 init_buf.size (),
702723 " {\" %s\" ,"
703724 " BuiltinSource{"
704725 " \" %s\" ,"
705726 " UnionBytes(&%s_resource),"
706- " BuiltinSourceType::%s} }," ,
727+ " BuiltinSourceType::%s,"
728+ " %s} }," ,
707729 file_id.c_str (),
708730 file_id.c_str (),
709731 var.c_str (),
710- source_type.c_str ());
732+ source_type.c_str (),
733+ filename.ends_with (kTsSuffix ) ? " true" : " false" );
711734 init_buf.resize (init_size);
712735
713736 // Registrations:
@@ -826,23 +849,39 @@ int AddGypi(const std::string& var,
826849
827850int JS2C (const FileList& js_files,
828851 const FileList& mjs_files,
852+ const FileList& ts_files,
829853 const std::string& config,
854+ const char * argv0,
830855 const std::string& dest) {
856+ TypeScriptTranspiler transpiler;
831857 Fragments definitions;
832- definitions.reserve (js_files.size () + mjs_files.size () + 1 );
858+ definitions.reserve (js_files.size () + mjs_files.size () + ts_files. size () + 1 );
833859 Fragments initializers;
834- initializers.reserve (js_files.size () + mjs_files.size ());
860+ initializers.reserve (js_files.size () + mjs_files.size () + ts_files. size () );
835861 Fragments registrations;
836- registrations.reserve (js_files.size () + mjs_files.size () + 1 );
862+ registrations.reserve (js_files.size () + mjs_files.size () + ts_files.size () +
863+ 1 );
837864
838- for ( const auto & filename : js_files ) {
839- int r = AddModule (filename, &definitions, &initializers, ®istrations);
840- if (r != 0 ) {
841- return r ;
842- }
865+ if (!ts_files. empty () && transpiler. Initialize (argv0) != 0 ) {
866+ fprintf (stderr,
867+ " Failed to initialize TypeScript transpiler: %s \n " ,
868+ std::string (transpiler. LastError ()). c_str ()) ;
869+ return 1 ;
843870 }
844- for (const auto & filename : mjs_files) {
845- int r = AddModule (filename, &definitions, &initializers, ®istrations);
871+
872+ auto add_modules = [&](const FileList& files) {
873+ for (const auto & filename : files) {
874+ int r = AddModule (
875+ filename, &transpiler, &definitions, &initializers, ®istrations);
876+ if (r != 0 ) {
877+ return r;
878+ }
879+ }
880+ return 0 ;
881+ };
882+
883+ for (const auto * files : {&js_files, &mjs_files, &ts_files}) {
884+ int r = add_modules (*files);
846885 if (r != 0 ) {
847886 return r;
848887 }
@@ -910,7 +949,8 @@ int Main(int argc, char* argv[]) {
910949 const std::string& file = args[i];
911950 if (IsDirectory (file, &error)) {
912951 if (!SearchFiles (file, &file_map, kJsSuffix ) ||
913- !SearchFiles (file, &file_map, kMjsSuffix )) {
952+ !SearchFiles (file, &file_map, kMjsSuffix ) ||
953+ !SearchFiles (file, &file_map, kTsSuffix )) {
914954 return 1 ;
915955 }
916956 } else if (error != 0 ) {
@@ -927,8 +967,15 @@ int Main(int argc, char* argv[]) {
927967 }
928968 }
929969
930- // Should have exactly 3 types: `.js`, `.mjs` and `.gypi`.
931- assert (file_map.size () == 3 );
970+ // Should have at most 4 extension types: `.js`, `.mjs`, `.ts` and `.gypi`.
971+ // Any other extension indicates an unexpected file slipped into the inputs.
972+ if (file_map.size () > 4 ) {
973+ fprintf (stderr,
974+ " Unexpected file types in inputs (expected .js, .mjs, .ts, "
975+ " .gypi only)\n " );
976+ return 1 ;
977+ }
978+
932979 auto gypi_it = file_map.find (" .gypi" );
933980 // Currently config.gypi is the only `.gypi` file allowed
934981 if (gypi_it == file_map.end () || gypi_it->second .size () != 1 ||
@@ -940,19 +987,55 @@ int Main(int argc, char* argv[]) {
940987 }
941988 auto js_it = file_map.find (" .js" );
942989 auto mjs_it = file_map.find (" .mjs" );
943- assert (js_it != file_map.end () && mjs_it != file_map.end ());
990+ auto ts_it = file_map.find (" .ts" );
991+ if (mjs_it != file_map.end ()) {
992+ auto it = std::find (mjs_it->second .begin (),
993+ mjs_it->second .end (),
994+ " lib/eslint.config_partial.mjs" );
995+ if (it != mjs_it->second .end ()) {
996+ mjs_it->second .erase (it);
997+ }
998+ }
944999
945- auto it = std::find (mjs_it->second .begin (),
946- mjs_it->second .end (),
947- " lib/eslint.config_partial.mjs" );
948- if (it != mjs_it->second .end ()) {
949- mjs_it->second .erase (it);
1000+ if (js_it != file_map.end ()) {
1001+ std::sort (js_it->second .begin (), js_it->second .end ());
1002+ }
1003+ if (mjs_it != file_map.end ()) {
1004+ std::sort (mjs_it->second .begin (), mjs_it->second .end ());
1005+ }
1006+ if (ts_it != file_map.end ()) {
1007+ std::sort (ts_it->second .begin (), ts_it->second .end ());
9501008 }
9511009
952- std::sort (js_it->second .begin (), js_it->second .end ());
953- std::sort (mjs_it->second .begin (), mjs_it->second .end ());
1010+ static const FileList empty_list;
1011+ const FileList& js_files =
1012+ js_it == file_map.end () ? empty_list : js_it->second ;
1013+ const FileList& mjs_files =
1014+ mjs_it == file_map.end () ? empty_list : mjs_it->second ;
1015+ const FileList& ts_files =
1016+ ts_it == file_map.end () ? empty_list : ts_it->second ;
1017+
1018+ // Detect duplicate module IDs across .js/.mjs and .ts file lists.
1019+ // GetFileId strips the extension, so lib/foo.ts and lib/foo.js would both
1020+ // resolve to the same ID "foo", causing a silent registration conflict.
1021+ if (!ts_files.empty ()) {
1022+ std::unordered_set<std::string> known_ids;
1023+ for (const auto & f : js_files) known_ids.insert (GetFileId (f));
1024+ for (const auto & f : mjs_files) known_ids.insert (GetFileId (f));
1025+ for (const auto & f : ts_files) {
1026+ std::string id = GetFileId (f);
1027+ if (known_ids.count (id) != 0 ) {
1028+ fprintf (stderr,
1029+ " Duplicate module ID '%s': a .ts file and a .js/.mjs file "
1030+ " both resolve to the same module ID\n " ,
1031+ id.c_str ());
1032+ return 1 ;
1033+ }
1034+ }
1035+ }
9541036
955- return JS2C (js_it->second , mjs_it->second , gypi_it->second [0 ], output);
1037+ return JS2C (
1038+ js_files, mjs_files, ts_files, gypi_it->second [0 ], argv[0 ], output);
9561039}
9571040} // namespace js2c
9581041} // namespace node
0 commit comments