Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 58d2dad

Browse filesBrowse files
joyeecheungaduh95
authored andcommitted
module: integrate TypeScript into compile cache
This integrates TypeScript into the compile cache by caching the transpilation (either type-stripping or transforming) output in addition to the V8 code cache that's generated from the transpilation output. Locally this speeds up loading with type stripping of `benchmark/fixtures/strip-types-benchmark.ts` by ~65% and loading with type transforms of `fixtures/transform-types-benchmark.ts` by ~128%. When comparing loading .ts and loading pre-transpiled .js on-disk with the compile cache enabled, previously .ts loaded 46% slower with type-stripping and 66% slower with transforms compared to loading .js files directly. After this patch, .ts loads 12% slower with type-stripping and 22% slower with transforms compared to .js. (Note that the numbers are based on microbenchmark fixtures and do not necessarily represent real-world workloads, though with bigger real-world files, the speed up should be more significant). PR-URL: #56629 Fixes: #54741 Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent a62345e commit 58d2dad
Copy full SHA for 58d2dad
Expand file treeCollapse file tree

9 files changed

+846
-16
lines changed
Open diff view settings
Collapse file

‎lib/internal/modules/typescript.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/typescript.js
+59-4Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ const {
2222
const { getOptionValue } = require('internal/options');
2323
const assert = require('internal/assert');
2424
const { Buffer } = require('buffer');
25+
const {
26+
getCompileCacheEntry,
27+
saveCompileCacheEntry,
28+
cachedCodeTypes: { kStrippedTypeScript, kTransformedTypeScript, kTransformedTypeScriptWithSourceMaps },
29+
} = internalBinding('modules');
2530

2631
/**
2732
* The TypeScript parsing mode, either 'strip-only' or 'transform'.
@@ -105,11 +110,19 @@ function stripTypeScriptTypes(code, options = kEmptyObject) {
105110
});
106111
}
107112

113+
/**
114+
* @typedef {'strip-only' | 'transform'} TypeScriptMode
115+
* @typedef {object} TypeScriptOptions
116+
* @property {TypeScriptMode} mode Mode.
117+
* @property {boolean} sourceMap Whether to generate source maps.
118+
* @property {string|undefined} filename Filename.
119+
*/
120+
108121
/**
109122
* Processes TypeScript code by stripping types or transforming.
110123
* Handles source maps if needed.
111124
* @param {string} code TypeScript code to process.
112-
* @param {object} options The configuration object.
125+
* @param {TypeScriptOptions} options The configuration object.
113126
* @returns {string} The processed code.
114127
*/
115128
function processTypeScriptCode(code, options) {
@@ -126,6 +139,20 @@ function processTypeScriptCode(code, options) {
126139
return transformedCode;
127140
}
128141

142+
/**
143+
* Get the type enum used for compile cache.
144+
* @param {TypeScriptMode} mode Mode of transpilation.
145+
* @param {boolean} sourceMap Whether source maps are enabled.
146+
* @returns {number}
147+
*/
148+
function getCachedCodeType(mode, sourceMap) {
149+
if (mode === 'transform') {
150+
if (sourceMap) { return kTransformedTypeScriptWithSourceMaps; }
151+
return kTransformedTypeScript;
152+
}
153+
return kStrippedTypeScript;
154+
}
155+
129156
/**
130157
* Performs type-stripping to TypeScript source code internally.
131158
* It is used by internal loaders.
@@ -142,12 +169,40 @@ function stripTypeScriptModuleTypes(source, filename, emitWarning = true) {
142169
if (isUnderNodeModules(filename)) {
143170
throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename);
144171
}
172+
const sourceMap = getOptionValue('--enable-source-maps');
173+
174+
const mode = getTypeScriptParsingMode();
175+
176+
// Instead of caching the compile cache status, just go into C++ to fetch it,
177+
// as checking process.env equally involves calling into C++ anyway, and
178+
// the compile cache can be enabled dynamically.
179+
const type = getCachedCodeType(mode, sourceMap);
180+
// Get a compile cache entry into the native compile cache store,
181+
// keyed by the filename. If the cache can already be loaded on disk,
182+
// cached.transpiled contains the cached string. Otherwise we should do
183+
// the transpilation and save it in the native store later using
184+
// saveCompileCacheEntry().
185+
const cached = (filename ? getCompileCacheEntry(source, filename, type) : undefined);
186+
if (cached?.transpiled) { // TODO(joyeecheung): return Buffer here.
187+
return cached.transpiled;
188+
}
189+
145190
const options = {
146-
mode: getTypeScriptParsingMode(),
147-
sourceMap: getOptionValue('--enable-source-maps'),
191+
mode,
192+
sourceMap,
148193
filename,
149194
};
150-
return processTypeScriptCode(source, options);
195+
196+
const transpiled = processTypeScriptCode(source, options);
197+
if (cached) {
198+
// cached.external contains a pointer to the native cache entry.
199+
// The cached object would be unreachable once it's out of scope,
200+
// but the pointer inside cached.external would stay around for reuse until
201+
// environment shutdown or when the cache is manually flushed
202+
// to disk. Unwrap it in JS before passing into C++ since it's faster.
203+
saveCompileCacheEntry(cached.external, transpiled);
204+
}
205+
return transpiled;
151206
}
152207

153208
/**
Collapse file

‎src/compile_cache.cc‎

Copy file name to clipboardExpand all lines: src/compile_cache.cc
+56-9Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,27 @@ v8::ScriptCompiler::CachedData* CompileCacheEntry::CopyCache() const {
7777
// See comments in CompileCacheHandler::Persist().
7878
constexpr uint32_t kCacheMagicNumber = 0x8adfdbb2;
7979

80+
const char* CompileCacheEntry::type_name() const {
81+
switch (type) {
82+
case CachedCodeType::kCommonJS:
83+
return "CommonJS";
84+
case CachedCodeType::kESM:
85+
return "ESM";
86+
case CachedCodeType::kStrippedTypeScript:
87+
return "StrippedTypeScript";
88+
case CachedCodeType::kTransformedTypeScript:
89+
return "TransformedTypeScript";
90+
case CachedCodeType::kTransformedTypeScriptWithSourceMaps:
91+
return "TransformedTypeScriptWithSourceMaps";
92+
default:
93+
UNREACHABLE();
94+
}
95+
}
96+
8097
void CompileCacheHandler::ReadCacheFile(CompileCacheEntry* entry) {
8198
Debug("[compile cache] reading cache from %s for %s %s...",
8299
entry->cache_filename,
83-
entry->type == CachedCodeType::kCommonJS ? "CommonJS" : "ESM",
100+
entry->type_name(),
84101
entry->source_filename);
85102

86103
uv_fs_t req;
@@ -256,7 +273,8 @@ void CompileCacheHandler::MaybeSaveImpl(CompileCacheEntry* entry,
256273
v8::Local<T> func_or_mod,
257274
bool rejected) {
258275
DCHECK_NOT_NULL(entry);
259-
Debug("[compile cache] cache for %s was %s, ",
276+
Debug("[compile cache] V8 code cache for %s %s was %s, ",
277+
entry->type_name(),
260278
entry->source_filename,
261279
rejected ? "rejected"
262280
: (entry->cache == nullptr) ? "not initialized"
@@ -287,6 +305,25 @@ void CompileCacheHandler::MaybeSave(CompileCacheEntry* entry,
287305
MaybeSaveImpl(entry, func, rejected);
288306
}
289307

308+
void CompileCacheHandler::MaybeSave(CompileCacheEntry* entry,
309+
std::string_view transpiled) {
310+
CHECK(entry->type == CachedCodeType::kStrippedTypeScript ||
311+
entry->type == CachedCodeType::kTransformedTypeScript ||
312+
entry->type == CachedCodeType::kTransformedTypeScriptWithSourceMaps);
313+
Debug("[compile cache] saving transpilation cache for %s %s\n",
314+
entry->type_name(),
315+
entry->source_filename);
316+
317+
// TODO(joyeecheung): it's weird to copy it again here. Convert the v8::String
318+
// directly into buffer held by v8::ScriptCompiler::CachedData here.
319+
int cache_size = static_cast<int>(transpiled.size());
320+
uint8_t* data = new uint8_t[cache_size];
321+
memcpy(data, transpiled.data(), cache_size);
322+
entry->cache.reset(new v8::ScriptCompiler::CachedData(
323+
data, cache_size, v8::ScriptCompiler::CachedData::BufferOwned));
324+
entry->refreshed = true;
325+
}
326+
290327
/**
291328
* Persist the compile cache accumulated in memory to disk.
292329
*
@@ -316,18 +353,25 @@ void CompileCacheHandler::Persist() {
316353
// incur a negligible overhead from thread synchronization.
317354
for (auto& pair : compiler_cache_store_) {
318355
auto* entry = pair.second.get();
356+
const char* type_name = entry->type_name();
319357
if (entry->cache == nullptr) {
320-
Debug("[compile cache] skip %s because the cache was not initialized\n",
358+
Debug("[compile cache] skip persisting %s %s because the cache was not "
359+
"initialized\n",
360+
type_name,
321361
entry->source_filename);
322362
continue;
323363
}
324364
if (entry->refreshed == false) {
325-
Debug("[compile cache] skip %s because cache was the same\n",
326-
entry->source_filename);
365+
Debug(
366+
"[compile cache] skip persisting %s %s because cache was the same\n",
367+
type_name,
368+
entry->source_filename);
327369
continue;
328370
}
329371
if (entry->persisted == true) {
330-
Debug("[compile cache] skip %s because cache was already persisted\n",
372+
Debug("[compile cache] skip persisting %s %s because cache was already "
373+
"persisted\n",
374+
type_name,
331375
entry->source_filename);
332376
continue;
333377
}
@@ -363,17 +407,20 @@ void CompileCacheHandler::Persist() {
363407
auto cleanup_mkstemp =
364408
OnScopeLeave([&mkstemp_req]() { uv_fs_req_cleanup(&mkstemp_req); });
365409
std::string cache_filename_tmp = entry->cache_filename + ".XXXXXX";
366-
Debug("[compile cache] Creating temporary file for cache of %s...",
367-
entry->source_filename);
410+
Debug("[compile cache] Creating temporary file for cache of %s (%s)...",
411+
entry->source_filename,
412+
type_name);
368413
int err = uv_fs_mkstemp(
369414
nullptr, &mkstemp_req, cache_filename_tmp.c_str(), nullptr);
370415
if (err < 0) {
371416
Debug("failed. %s\n", uv_strerror(err));
372417
continue;
373418
}
374419
Debug(" -> %s\n", mkstemp_req.path);
375-
Debug("[compile cache] writing cache for %s to temporary file %s [%d %d %d "
420+
Debug("[compile cache] writing cache for %s %s to temporary file %s [%d "
421+
"%d %d "
376422
"%d %d]...",
423+
type_name,
377424
entry->source_filename,
378425
mkstemp_req.path,
379426
headers[kMagicNumberOffset],
Collapse file

‎src/compile_cache.h‎

Copy file name to clipboardExpand all lines: src/compile_cache.h
+12-3Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,17 @@
1313
namespace node {
1414
class Environment;
1515

16-
// TODO(joyeecheung): move it into a CacheHandler class.
16+
#define CACHED_CODE_TYPES(V) \
17+
V(kCommonJS, 0) \
18+
V(kESM, 1) \
19+
V(kStrippedTypeScript, 2) \
20+
V(kTransformedTypeScript, 3) \
21+
V(kTransformedTypeScriptWithSourceMaps, 4)
22+
1723
enum class CachedCodeType : uint8_t {
18-
kCommonJS = 0,
19-
kESM,
24+
#define V(type, value) type = value,
25+
CACHED_CODE_TYPES(V)
26+
#undef V
2027
};
2128

2229
struct CompileCacheEntry {
@@ -34,6 +41,7 @@ struct CompileCacheEntry {
3441
// Copy the cache into a new store for V8 to consume. Caller takes
3542
// ownership.
3643
v8::ScriptCompiler::CachedData* CopyCache() const;
44+
const char* type_name() const;
3745
};
3846

3947
#define COMPILE_CACHE_STATUS(V) \
@@ -70,6 +78,7 @@ class CompileCacheHandler {
7078
void MaybeSave(CompileCacheEntry* entry,
7179
v8::Local<v8::Module> mod,
7280
bool rejected);
81+
void MaybeSave(CompileCacheEntry* entry, std::string_view transpiled);
7382
std::string_view cache_dir() { return compile_cache_dir_; }
7483

7584
private:

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.