-
Notifications
You must be signed in to change notification settings - Fork 41
Expand file tree
/
Copy pathmodules.cpp
More file actions
193 lines (163 loc) · 6.3 KB
/
Copy pathmodules.cpp
File metadata and controls
193 lines (163 loc) · 6.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#include <map>
#include <string>
#include <jsapi.h>
#include <jsfriendapi.h>
#include <js/CompilationAndEvaluation.h>
#include <js/Initialization.h>
#include <js/Modules.h>
#include <js/SourceText.h>
#include "boilerplate.h"
// This examples demonstrates how to compile ES modules in an embedding.
//
// See 'boilerplate.cpp' for the parts of this example that are reused in many
// simple embedding examples.
// Translates source code into a JSObject representing the compiled module. This
// module is not yet linked/instantiated.
static JSObject* CompileExampleModule(JSContext* cx, const char* filename,
const char* code) {
JS::CompileOptions options(cx);
options.setFileAndLine(filename, 1);
JS::SourceText<mozilla::Utf8Unit> source;
if (!source.init(cx, code, strlen(code), JS::SourceOwnership::Borrowed)) {
return nullptr;
}
// Compile the module source to bytecode.
//
// NOTE: This generates a JSObject instead of a JSScript. This contains
// additional metadata to resolve imports/exports. This object should not be
// exposed to other JS code or unexpected behaviour may occur.
return JS::CompileModule(cx, options, source);
}
// Maintain a registry of imported modules. The ResolveHook may be called
// multiple times for the same specifier and we need to return the same compiled
// module.
//
// NOTE: This example assumes only one JSContext/GlobalObject is used, but in
// general the registry needs to be distinct for each GlobalObject.
static std::map<std::u16string, JS::PersistentRootedObject> moduleRegistry;
// Callback for embedding to provide modules for import statements. This example
// hardcodes sources, but an embedding would normally load files here.
static JSObject* ExampleResolveHook(JSContext* cx,
JS::HandleValue modulePrivate,
JS::HandleObject moduleRequest) {
// Extract module specifier string.
JS::Rooted<JSString*> specifierString(
cx, JS::GetModuleRequestSpecifier(cx, moduleRequest));
if (!specifierString) {
return nullptr;
}
// Convert specifier to a std::u16char for simplicity.
JS::UniqueTwoByteChars specChars(JS_CopyStringCharsZ(cx, specifierString));
if (!specChars) {
return nullptr;
}
std::u16string filename(specChars.get());
// If we already resolved before, return same module.
auto search = moduleRegistry.find(filename);
if (search != moduleRegistry.end()) {
return search->second;
}
JS::RootedObject mod(cx);
if (filename == u"a") {
mod = CompileExampleModule(cx, "a", "export const C1 = 1;");
if (!mod) {
return nullptr;
}
}
if (filename == u"b") {
mod = CompileExampleModule(cx, "b", "export const C2 = 2;");
if (!mod) {
return nullptr;
}
}
// Register result in table.
if (mod) {
moduleRegistry.emplace(filename, JS::PersistentRootedObject(cx, mod));
return mod;
}
JS_ReportErrorASCII(cx, "Cannot resolve import specifier");
return nullptr;
}
// Callback for embedding to implement an asynchronous dynamic import. This must
// do the same thing as the module resolve hook, but also link and evaluate the
// module, and it must always call JS::FinishDynamicModuleImport when done.
static bool ExampleDynamicImportHook(JSContext* cx,
JS::Handle<JS::Value> referencingPrivate,
JS::Handle<JSObject*> moduleRequest,
JS::Handle<JSObject*> promise) {
JS::Rooted<JSObject*> mod{
cx, ExampleResolveHook(cx, referencingPrivate, moduleRequest)};
if (!mod || !JS::ModuleLink(cx, mod)) {
return JS::FinishDynamicModuleImport(cx, nullptr, referencingPrivate,
moduleRequest, promise);
}
JS::Rooted<JS::Value> rval{cx};
if (!JS::ModuleEvaluate(cx, mod, &rval)) {
return JS::FinishDynamicModuleImport(cx, nullptr, referencingPrivate,
moduleRequest, promise);
}
if (rval.isObject()) {
JS::Rooted<JSObject*> evaluationPromise{cx, &rval.toObject()};
return JS::FinishDynamicModuleImport(
cx, evaluationPromise, referencingPrivate, moduleRequest, promise);
}
return JS::FinishDynamicModuleImport(cx, nullptr, referencingPrivate,
moduleRequest, promise);
}
static bool ModuleExample(JSContext* cx) {
// In order to use dynamic imports, we need a job queue. We can use the
// default SpiderMonkey job queue for this example, but a more sophisticated
// embedding would use a custom job queue to schedule its own tasks.
if (!js::UseInternalJobQueues(cx)) return false;
// We must instantiate self-hosting *after* setting up job queue.
if (!JS::InitSelfHostedCode(cx)) return false;
JS::RootedObject global(cx, boilerplate::CreateGlobal(cx));
if (!global) {
return false;
}
JSAutoRealm ar(cx, global);
// Register a hook in order to provide modules
JSRuntime* rt = JS_GetRuntime(cx);
JS::SetModuleResolveHook(rt, ExampleResolveHook);
JS::SetModuleDynamicImportHook(rt, ExampleDynamicImportHook);
// Compile the top module.
static const char top_module_source[] = R"js(
import {C1} from 'a';
const {C2} = await import('b');
)js";
JS::Rooted<JSObject*> mod{cx,
CompileExampleModule(cx, "top", top_module_source)};
if (!mod) {
boilerplate::ReportAndClearException(cx);
return false;
}
// Resolve imports by loading and compiling additional scripts.
if (!JS::ModuleLink(cx, mod)) {
boilerplate::ReportAndClearException(cx);
return false;
}
// Result value, used for top-level await.
JS::RootedValue rval(cx);
// Execute the module bytecode.
if (!JS::ModuleEvaluate(cx, mod, &rval)) {
boilerplate::ReportAndClearException(cx);
return false;
}
js::RunJobs(cx);
if (rval.isObject()) {
JS::Rooted<JSObject*> evaluationPromise{cx, &rval.toObject()};
if (!JS::ThrowOnModuleEvaluationFailure(
cx, evaluationPromise,
JS::ModuleErrorBehaviour::ThrowModuleErrorsSync)) {
boilerplate::ReportAndClearException(cx);
return false;
}
}
return true;
}
int main(int argc, const char* argv[]) {
if (!boilerplate::RunExample(ModuleExample, /* initSelfHosting = */ false)) {
return 1;
}
return 0;
}