Skip to content

Commit ea66233

Browse files
author
Suwei Chen
committed
Dynamic Module Import
This PR adds support for import() dynamic module import sematic. Per https://github.com/tc39/proposal-dynamic-import: "A call to import(specifier) returns a promise for the module namespace object of the requested module, which is created after fetching, instantiating, and evaluating all of the module's dependencies, as well as the module itself. "Here specifier will be interpreted the same way as in an import declaration (i.e., the same strings will work in both places). However, while specifier is a string it is not necessarily a string literal; thus code like import(`./language-packs/${navigator.language}.js`) will work—something impossible to accomplish with the usual import declarations. "import() is proposed to work in both scripts and modules. This gives script code an easy asynchronous entry point into the module world, allowing it to start running module code." This PR includes following changes: - Update parser and bytecode generator to support import() sematic in module and in script - Add new bytecode 'ImportCall' - Add runtime function for import() that: ○ Uses caller from stack to look up module record or source context that are associated with the module or script from which 'import()' is called ○ Requests host to load target module source file (gets module record in return) ○ Creates promise unless the module record has one ○ Resolves/rejects promise if appropriates ○ Returns promise - Add new host callback ('FetchImportedModuleFromScript') for fetching imported module from script (accepts source context) - Add 'promiseCapability' field to module record class - Update SourceTextModuleRecord's methods to accept callback from host and to handle dynamically imported module and its promise capability - Update exception checks and assertions to cover new usage scenario of importing and evaluating module code with active script Add unit tests for dynamic import functionality
1 parent 3553dd7 commit ea66233

37 files changed

+810
-76
lines changed

bin/NativeTests/JsRTApiTest.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1739,6 +1739,7 @@ namespace JsRTApiTest
17391739
REQUIRE(JsInitializeModuleRecord(nullptr, specifier, &requestModule) == JsNoError);
17401740
successTest.mainModule = requestModule;
17411741
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleCallback, Success_FIMC) == JsNoError);
1742+
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, Success_FIMC) == JsNoError);
17421743
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_NotifyModuleReadyCallback, Succes_NMRC) == JsNoError);
17431744

17441745
JsValueRef errorObject = JS_INVALID_REFERENCE;
@@ -1834,6 +1835,7 @@ namespace JsRTApiTest
18341835
REQUIRE(JsInitializeModuleRecord(nullptr, specifier, &requestModule) == JsNoError);
18351836
reentrantParseData.mainModule = requestModule;
18361837
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleCallback, ReentrantParse_FIMC) == JsNoError);
1838+
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, ReentrantParse_FIMC) == JsNoError);
18371839
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_NotifyModuleReadyCallback, ReentrantParse_NMRC) == JsNoError);
18381840

18391841
JsValueRef errorObject = JS_INVALID_REFERENCE;
@@ -1913,6 +1915,7 @@ namespace JsRTApiTest
19131915
REQUIRE(JsInitializeModuleRecord(nullptr, specifier, &requestModule) == JsNoError);
19141916
reentrantNoErrorParseData.mainModule = requestModule;
19151917
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleCallback, reentrantNoErrorParse_FIMC) == JsNoError);
1918+
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, reentrantNoErrorParse_FIMC) == JsNoError);
19161919
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_NotifyModuleReadyCallback, reentrantNoErrorParse_NMRC) == JsNoError);
19171920

19181921
JsValueRef errorObject = JS_INVALID_REFERENCE;

bin/ch/WScriptJsrt.cpp

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -304,14 +304,22 @@ JsErrorCode WScriptJsrt::InitializeModuleInfo(JsValueRef specifier, JsModuleReco
304304
{
305305
JsErrorCode errorCode = JsNoError;
306306
errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_FetchImportedModuleCallback, (void*)WScriptJsrt::FetchImportedModule);
307+
307308
if (errorCode == JsNoError)
308309
{
309-
errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_NotifyModuleReadyCallback, (void*)WScriptJsrt::NotifyModuleReadyCallback);
310-
}
311-
if (errorCode == JsNoError)
312-
{
313-
errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_HostDefined, specifier);
310+
errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, (void*)WScriptJsrt::FetchImportedModuleFromScript);
311+
312+
if (errorCode == JsNoError)
313+
{
314+
errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_NotifyModuleReadyCallback, (void*)WScriptJsrt::NotifyModuleReadyCallback);
315+
316+
if (errorCode == JsNoError)
317+
{
318+
errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_HostDefined, specifier);
319+
}
320+
}
314321
}
322+
315323
IfJsrtErrorFailLogAndRetErrorCode(errorCode);
316324
return JsNoError;
317325
}
@@ -875,10 +883,24 @@ bool WScriptJsrt::Initialize()
875883
IfJsrtErrorFail(CreatePropertyIdFromString("console", &consoleName), false);
876884
IfJsrtErrorFail(ChakraRTInterface::JsSetProperty(global, consoleName, console, true), false);
877885

886+
IfJsrtErrorFail(InitializeModuleCallbacks(), false);
887+
878888
Error:
879889
return hr == S_OK;
880890
}
881891

892+
JsErrorCode WScriptJsrt::InitializeModuleCallbacks()
893+
{
894+
JsModuleRecord moduleRecord = JS_INVALID_REFERENCE;
895+
JsErrorCode errorCode = ChakraRTInterface::JsInitializeModuleRecord(nullptr, nullptr, &moduleRecord);
896+
if (errorCode == JsNoError)
897+
{
898+
errorCode = InitializeModuleInfo(nullptr, moduleRecord);
899+
}
900+
901+
return errorCode;
902+
}
903+
882904
bool WScriptJsrt::Uninitialize()
883905
{
884906
// moduleRecordMap is a global std::map, its destructor may access overrided
@@ -1267,6 +1289,45 @@ JsErrorCode WScriptJsrt::FetchImportedModule(_In_ JsModuleRecord referencingModu
12671289
return errorCode;
12681290
}
12691291

1292+
// Callback from chakracore to fetch module dynamically during runtime. In the test harness,
1293+
// we are not doing any translation, just treat the specifier as fileName.
1294+
// While this call will come back directly from runtime script or module code, the additional
1295+
// task can be scheduled asynchronously that executed later.
1296+
JsErrorCode WScriptJsrt::FetchImportedModuleFromScript(_In_ JsSourceContext dwReferencingSourceContext,
1297+
_In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord)
1298+
{
1299+
JsModuleRecord moduleRecord = JS_INVALID_REFERENCE;
1300+
AutoString specifierStr;
1301+
*dependentModuleRecord = nullptr;
1302+
1303+
if (specifierStr.Initialize(specifier) != JsNoError)
1304+
{
1305+
return specifierStr.GetError();
1306+
}
1307+
auto moduleEntry = moduleRecordMap.find(std::string(*specifierStr));
1308+
if (moduleEntry != moduleRecordMap.end())
1309+
{
1310+
*dependentModuleRecord = moduleEntry->second;
1311+
return JsNoError;
1312+
}
1313+
1314+
JsErrorCode errorCode = ChakraRTInterface::JsInitializeModuleRecord(nullptr, specifier, &moduleRecord);
1315+
if (errorCode == JsNoError)
1316+
{
1317+
InitializeModuleInfo(specifier, moduleRecord);
1318+
moduleRecordMap[std::string(*specifierStr)] = moduleRecord;
1319+
ModuleMessage* moduleMessage =
1320+
WScriptJsrt::ModuleMessage::Create(nullptr, specifier);
1321+
if (moduleMessage == nullptr)
1322+
{
1323+
return JsErrorOutOfMemory;
1324+
}
1325+
WScriptJsrt::PushMessage(moduleMessage);
1326+
*dependentModuleRecord = moduleRecord;
1327+
}
1328+
return errorCode;
1329+
}
1330+
12701331
// Callback from chakraCore when the module resolution is finished, either successfuly or unsuccessfully.
12711332
JsErrorCode WScriptJsrt::NotifyModuleReadyCallback(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar)
12721333
{

bin/ch/WScriptJsrt.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ class WScriptJsrt
5353
static void PushMessage(MessageBase *message) { messageQueue->InsertSorted(message); }
5454

5555
static JsErrorCode FetchImportedModule(_In_ JsModuleRecord referencingModule, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord);
56+
static JsErrorCode FetchImportedModuleFromScript(_In_ DWORD_PTR dwReferencingSourceContext, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord);
5657
static JsErrorCode NotifyModuleReadyCallback(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar);
58+
static JsErrorCode InitializeModuleCallbacks();
5759
static void CALLBACK PromiseContinuationCallback(JsValueRef task, void *callbackState);
5860

5961
static LPCWSTR ConvertErrorCodeToMessage(JsErrorCode errorCode)

lib/Backend/JnHelperMethodList.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,8 @@ HELPERCALL(SetHomeObj, Js::JavascriptOperators::OP_SetHomeObj,
507507
HELPERCALL(LdHomeObjProto, Js::JavascriptOperators::OP_LdHomeObjProto, 0)
508508
HELPERCALL(LdFuncObjProto, Js::JavascriptOperators::OP_LdFuncObjProto, 0)
509509

510+
HELPERCALL(ImportCall, Js::JavascriptOperators::OP_ImportCall, 0)
511+
510512
HELPERCALL(ResumeYield, Js::JavascriptOperators::OP_ResumeYield, AttrCanThrow)
511513

512514
#include "ExternalHelperMethodList.h"

lib/Backend/Lower.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2844,6 +2844,17 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa
28442844
break;
28452845
}
28462846

2847+
case Js::OpCode::ImportCall:
2848+
{
2849+
IR::Opnd *src1Opnd = instr->UnlinkSrc1();
2850+
2851+
LoadScriptContext(instr);
2852+
m_lowererMD.LoadHelperArgument(instr, src1Opnd);
2853+
m_lowererMD.ChangeToHelperCall(instr, IR::HelperImportCall);
2854+
2855+
break;
2856+
}
2857+
28472858
case Js::OpCode::SetComputedNameVar:
28482859
{
28492860
IR::Opnd *src2Opnd = instr->UnlinkSrc2();

lib/Common/ConfigFlagsList.h

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ PHASE(All)
531531
// If ES6Module needs to be disabled by compile flag, DEFAULT_CONFIG_ES6Module should be false
532532
#define DEFAULT_CONFIG_ES6Module (false)
533533
#else
534-
#define DEFAULT_CONFIG_ES6Module (false)
534+
#define DEFAULT_CONFIG_ES6Module (true)
535535
#endif
536536
#define DEFAULT_CONFIG_ES6Object (true)
537537
#define DEFAULT_CONFIG_ES6Number (true)
@@ -997,10 +997,7 @@ FLAGPR (Boolean, ES6, ES7TrailingComma , "Enable ES7 trailing co
997997
FLAGPR (Boolean, ES6, ES6IsConcatSpreadable , "Enable ES6 isConcatSpreadable Symbol" , DEFAULT_CONFIG_ES6IsConcatSpreadable)
998998
FLAGPR (Boolean, ES6, ES6Math , "Enable ES6 Math extensions" , DEFAULT_CONFIG_ES6Math)
999999

1000-
#ifndef COMPILE_DISABLE_ES6Module
1001-
#define COMPILE_DISABLE_ES6Module 0
1002-
#endif
1003-
FLAGPR_REGOVR_EXP(Boolean, ES6, ES6Module , "Enable ES6 Modules" , DEFAULT_CONFIG_ES6Module)
1000+
FLAGPR (Boolean, ES6, ES6Module , "Enable ES6 Modules" , DEFAULT_CONFIG_ES6Module)
10041001
FLAGPR (Boolean, ES6, ES6Object , "Enable ES6 Object extensions" , DEFAULT_CONFIG_ES6Object)
10051002
FLAGPR (Boolean, ES6, ES6Number , "Enable ES6 Number extensions" , DEFAULT_CONFIG_ES6Number)
10061003
FLAGPR (Boolean, ES6, ES6ObjectLiterals , "Enable ES6 Object literal extensions" , DEFAULT_CONFIG_ES6ObjectLiterals)

lib/Common/Exceptions/ExceptionCheck.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ void ExceptionCheck::SetHandledExceptionType(ExceptionType e)
5858
#if DBG
5959
if(!(e == ExceptionType_None ||
6060
e == ExceptionType_DisableCheck ||
61-
!JsUtil::ExternalApi::IsScriptActiveOnCurrentThreadContext() ||
61+
(e & ExceptionType_OutOfMemory) == ExceptionType_OutOfMemory || // InitializeModuleRecord handles OOM during dynamic import
62+
!JsUtil::ExternalApi::IsScriptActiveOnCurrentThreadContext() ||
6263
(e & ExceptionType_JavascriptException) == ExceptionType_JavascriptException ||
6364
e == ExceptionType_HasStackProbe))
6465
{
@@ -84,6 +85,7 @@ AutoHandledExceptionType::AutoHandledExceptionType(ExceptionType e)
8485
AutoHandledExceptionType::~AutoHandledExceptionType()
8586
{
8687
Assert(ExceptionCheck::GetData().handledExceptionType == ExceptionType_DisableCheck ||
88+
(ExceptionCheck::GetData().handledExceptionType & ExceptionType_OutOfMemory) == ExceptionType_OutOfMemory ||
8789
!JsUtil::ExternalApi::IsScriptActiveOnCurrentThreadContext() ||
8890
ExceptionCheck::GetData().handledExceptionType == ExceptionType_HasStackProbe ||
8991
(ExceptionCheck::GetData().handledExceptionType & ExceptionType_JavascriptException) == ExceptionType_JavascriptException);
@@ -98,6 +100,7 @@ AutoNestedHandledExceptionType::AutoNestedHandledExceptionType(ExceptionType e)
98100
AutoNestedHandledExceptionType::~AutoNestedHandledExceptionType()
99101
{
100102
Assert(ExceptionCheck::GetData().handledExceptionType == ExceptionType_DisableCheck ||
103+
(ExceptionCheck::GetData().handledExceptionType & ExceptionType_OutOfMemory) == ExceptionType_OutOfMemory ||
101104
!JsUtil::ExternalApi::IsScriptActiveOnCurrentThreadContext() ||
102105
ExceptionCheck::GetData().handledExceptionType == ExceptionType_HasStackProbe ||
103106
(ExceptionCheck::GetData().handledExceptionType & ExceptionType_JavascriptException) == ExceptionType_JavascriptException);

lib/Jsrt/ChakraCore.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ typedef enum JsModuleHostInfoKind
3838
JsModuleHostInfo_Exception = 0x01,
3939
JsModuleHostInfo_HostDefined = 0x02,
4040
JsModuleHostInfo_NotifyModuleReadyCallback = 0x3,
41-
JsModuleHostInfo_FetchImportedModuleCallback = 0x4
41+
JsModuleHostInfo_FetchImportedModuleCallback = 0x4,
42+
JsModuleHostInfo_FetchImportedModuleFromScriptCallback = 0x5
4243
} JsModuleHostInfoKind;
4344

4445
/// <summary>
@@ -70,6 +71,21 @@ typedef JsErrorCode(CHAKRA_CALLBACK * FetchImportedModuleCallBack)(_In_ JsModule
7071
/// <returns>
7172
/// true if the operation succeeded, false otherwise.
7273
/// </returns>
74+
typedef JsErrorCode(CHAKRA_CALLBACK * FetchImportedModuleFromScriptCallBack)(_In_ JsSourceContext dwReferencingSourceContext, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord);
75+
76+
/// <summary>
77+
/// User implemented callback to get notification when the module is ready.
78+
/// </summary>
79+
/// <remarks>
80+
/// Notify the host after ModuleDeclarationInstantiation step (15.2.1.1.6.4) is finished. If there was error in the process, exceptionVar
81+
/// holds the exception. Otherwise the referencingModule is ready and the host should schedule execution afterwards.
82+
/// </remarks>
83+
/// <param name="dwReferencingSourceContext">The referencing script that calls import()</param>
84+
/// <param name="exceptionVar">If nullptr, the module is successfully initialized and host should queue the execution job
85+
/// otherwise it's the exception object.</param>
86+
/// <returns>
87+
/// true if the operation succeeded, false otherwise.
88+
/// </returns>
7389
typedef JsErrorCode(CHAKRA_CALLBACK * NotifyModuleReadyCallback)(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar);
7490

7591
/// <summary>

lib/Jsrt/Core/JsrtContextCore.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,28 @@ HRESULT ChakraCoreHostScriptContext::FetchImportedModule(Js::ModuleRecordBase* r
120120
return E_INVALIDARG;
121121
}
122122

123+
HRESULT ChakraCoreHostScriptContext::FetchImportedModuleFromScript(JsSourceContext dwReferencingSourceContext, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord)
124+
{
125+
if (fetchImportedModuleFromScriptCallback == nullptr)
126+
{
127+
return E_INVALIDARG;
128+
}
129+
130+
Js::JavascriptString* specifierVar = Js::JavascriptString::NewCopySz(specifier, GetScriptContext());
131+
JsModuleRecord dependentRecord = JS_INVALID_REFERENCE;
132+
{
133+
AUTO_NO_EXCEPTION_REGION;
134+
JsErrorCode errorCode = fetchImportedModuleFromScriptCallback(dwReferencingSourceContext, specifierVar, &dependentRecord);
135+
if (errorCode == JsNoError)
136+
{
137+
*dependentModuleRecord = static_cast<Js::ModuleRecordBase*>(dependentRecord);
138+
return NOERROR;
139+
}
140+
}
141+
142+
return E_INVALIDARG;
143+
}
144+
123145
HRESULT ChakraCoreHostScriptContext::NotifyHostAboutModuleReady(Js::ModuleRecordBase* referencingModule, Js::Var exceptionVar)
124146
{
125147
if (notifyModuleReadyCallback == nullptr)

lib/Jsrt/Core/JsrtContextCore.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ class ChakraCoreHostScriptContext sealed : public HostScriptContext
168168
}
169169

170170
HRESULT FetchImportedModule(Js::ModuleRecordBase* referencingModule, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) override;
171+
HRESULT FetchImportedModuleFromScript(JsSourceContext dwReferencingSourceContext, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) override;
171172

172173
HRESULT NotifyHostAboutModuleReady(Js::ModuleRecordBase* referencingModule, Js::Var exceptionVar) override;
173174

@@ -177,6 +178,9 @@ class ChakraCoreHostScriptContext sealed : public HostScriptContext
177178
void SetFetchImportedModuleCallback(FetchImportedModuleCallBack fetchCallback) { this->fetchImportedModuleCallback = fetchCallback ; }
178179
FetchImportedModuleCallBack GetFetchImportedModuleCallback() const { return this->fetchImportedModuleCallback; }
179180

181+
void SetFetchImportedModuleFromScriptCallback(FetchImportedModuleFromScriptCallBack fetchCallback) { this->fetchImportedModuleFromScriptCallback = fetchCallback; }
182+
FetchImportedModuleFromScriptCallBack GetFetchImportedModuleFromScriptCallback() const { return this->fetchImportedModuleFromScriptCallback; }
183+
180184
#if DBG_DUMP || defined(PROFILE_EXEC) || defined(PROFILE_MEM)
181185
void EnsureParentInfo(Js::ScriptContext* scriptContext = NULL) override
182186
{
@@ -187,5 +191,6 @@ class ChakraCoreHostScriptContext sealed : public HostScriptContext
187191

188192
private:
189193
FetchImportedModuleCallBack fetchImportedModuleCallback;
194+
FetchImportedModuleFromScriptCallBack fetchImportedModuleFromScriptCallback;
190195
NotifyModuleReadyCallback notifyModuleReadyCallback;
191196
};

0 commit comments

Comments
 (0)