Skip to content

Commit 52a87ab

Browse files
authored
Add LIFO task queue for compiles (codesandbox#187)
* Fix Current Module View for non-required modules * Queue all compile tasks instead of running parallel * Fix compile arguments * Fix task picking * Fix child module errors and warnings
1 parent 2605068 commit 52a87ab

File tree

10 files changed

+256
-278
lines changed

10 files changed

+256
-278
lines changed

src/sandbox/boilerplates/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @flow
2-
import type { Module, Directory } from 'common/types';
2+
import type { Module } from 'common/types';
33

4-
import { getCurrentManager } from '../';
4+
import { getCurrentManager } from '../compile';
55

66
let cachedBoilerplates = [];
77

src/sandbox/compile.js

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import { dispatch } from 'codesandbox-api';
2+
3+
import { getModulePath } from 'app/store/entities/sandboxes/modules/selectors';
4+
import type { Module, Directory } from 'common/types';
5+
6+
import getPreset from './eval';
7+
import Manager from './eval/manager';
8+
import resolveDependency from './eval/loaders/dependency-resolver';
9+
10+
import { resetScreen } from './status-screen';
11+
12+
import { inject, uninject } from './react-error-overlay/overlay';
13+
import handleExternalResources from './external-resources';
14+
15+
import defaultBoilerplates from './boilerplates/default-boilerplates';
16+
import resizeEventListener from './resize-event-listener';
17+
import {
18+
getBoilerplates,
19+
evalBoilerplates,
20+
findBoilerplate,
21+
} from './boilerplates';
22+
23+
import loadDependencies from './npm';
24+
25+
let initializedResizeListener = false;
26+
let manager: ?Manager = null;
27+
let actionsEnabled = false;
28+
29+
export function areActionsEnabled() {
30+
return actionsEnabled;
31+
}
32+
33+
export function getCurrentManager(): ?Manager {
34+
return manager;
35+
}
36+
37+
function getIndexHtml(modules) {
38+
const module = modules.find(
39+
m => m.title === 'index.html' && m.directoryShortid == null
40+
);
41+
if (module) {
42+
return module.code;
43+
}
44+
return '<div id="root"></div>';
45+
}
46+
47+
function updateManager(sandboxId, template, module, modules, directories) {
48+
if (!manager || manager.id !== sandboxId) {
49+
manager = new Manager(sandboxId, modules, directories, getPreset(template));
50+
return manager.transpileModules(module).catch(e => ({ error: e }));
51+
}
52+
53+
return manager
54+
.updateData(modules, directories)
55+
.then(() => manager.transpileModules(module)) // We need to transpile the module if it was never an entry
56+
.catch(e => ({ error: e }));
57+
}
58+
59+
function initializeResizeListener() {
60+
const listener = resizeEventListener();
61+
listener.addResizeListener(document.body, () => {
62+
if (document.body) {
63+
dispatch({
64+
type: 'resize',
65+
height: document.body.getBoundingClientRect().height,
66+
});
67+
}
68+
});
69+
initializedResizeListener = true;
70+
}
71+
72+
async function compile({
73+
sandboxId,
74+
modules,
75+
directories,
76+
module,
77+
changedModule,
78+
externalResources,
79+
dependencies,
80+
hasActions,
81+
isModuleView = false,
82+
template,
83+
}) {
84+
try {
85+
uninject();
86+
inject();
87+
} catch (e) {
88+
console.error(e);
89+
}
90+
91+
actionsEnabled = hasActions;
92+
handleExternalResources(externalResources);
93+
94+
try {
95+
const [{ manifest }, { error: managerError }] = await Promise.all([
96+
loadDependencies(dependencies),
97+
updateManager(sandboxId, template, module, modules, directories),
98+
]);
99+
100+
const { externals } = manifest;
101+
manager.setExternals(externals);
102+
103+
if (managerError) {
104+
throw managerError;
105+
}
106+
107+
resetScreen();
108+
109+
try {
110+
const children = document.body.children;
111+
// Do unmounting for react
112+
if (externals['react-dom']) {
113+
const reactDOM = resolveDependency('react-dom', externals);
114+
reactDOM.unmountComponentAtNode(document.body);
115+
for (const child in children) {
116+
if (
117+
children.hasOwnProperty(child) &&
118+
children[child].tagName === 'DIV'
119+
) {
120+
reactDOM.unmountComponentAtNode(children[child]);
121+
}
122+
}
123+
}
124+
} catch (e) {
125+
/* don't do anything with this error */
126+
}
127+
128+
const html = getIndexHtml(modules);
129+
document.body.innerHTML = html;
130+
131+
const evalled = manager.evaluateModule(module);
132+
133+
const domChanged = document.body.innerHTML !== html;
134+
135+
if (isModuleView && !domChanged && !module.title.endsWith('.html')) {
136+
const isReact = module.code && module.code.includes('React');
137+
138+
if (isReact) {
139+
// initiate boilerplates
140+
if (getBoilerplates().length === 0 && externals != null) {
141+
try {
142+
await evalBoilerplates(defaultBoilerplates);
143+
} catch (e) {
144+
console.log("Couldn't load all boilerplates");
145+
}
146+
}
147+
148+
const boilerplate = findBoilerplate(module);
149+
if (boilerplate) {
150+
try {
151+
boilerplate.module.default(evalled);
152+
} catch (e) {
153+
console.error(e);
154+
}
155+
}
156+
}
157+
}
158+
159+
if (!initializedResizeListener) {
160+
initializeResizeListener();
161+
}
162+
163+
dispatch({
164+
type: 'success',
165+
});
166+
} catch (e) {
167+
if (manager) {
168+
manager.clearCompiledCache();
169+
}
170+
console.log('Error in sandbox:');
171+
console.error(e);
172+
173+
e.module = e.module || changedModule;
174+
e.fileName = e.fileName || getModulePath(modules, directories, e.module);
175+
176+
const event = new Event('error');
177+
event.error = e;
178+
179+
window.dispatchEvent(event);
180+
}
181+
}
182+
183+
type Arguments = {
184+
sandboxId: string,
185+
modules: Array<Module>,
186+
directories: Array<Directory>,
187+
module: Module,
188+
changedModule: Module,
189+
externalResources: Array<string>,
190+
dependencies: Object,
191+
hasActions: boolean,
192+
isModuleView: boolean,
193+
template: string,
194+
};
195+
196+
const tasks: Array<Arguments> = [];
197+
let runningTask = false;
198+
199+
async function executeTaskIfAvailable() {
200+
if (tasks.length) {
201+
const task = tasks.pop();
202+
203+
runningTask = true;
204+
await compile(task);
205+
runningTask = false;
206+
207+
executeTaskIfAvailable();
208+
}
209+
}
210+
211+
/**
212+
* We want to ensure that no tasks (commands from the editor) are run in parallel,
213+
* this could result in state inconsistency. That's why we execute tasks after eachother,
214+
* and if there are 3 tasks we will remove the second task, this one is unnecessary as it is not the
215+
* latest version.
216+
*/
217+
export default function queueTask(data: Arguments) {
218+
tasks[0] = data;
219+
220+
if (!runningTask) {
221+
executeTaskIfAvailable();
222+
}
223+
}

src/sandbox/errors/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { dispatch, actions } from 'codesandbox-api';
33

44
import type { ErrorRecord } from '../react-error-overlay/utils/errorRegister';
5-
import { getCurrentManager } from '../';
5+
import { getCurrentManager } from '../compile';
66

77
function buildErrorMessage(e) {
88
const title = e.name;

src/sandbox/eval/manager.js

Lines changed: 9 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,6 @@ export default class Manager {
2222
modules: Array<Module>;
2323
directories: Array<Directory>;
2424

25-
// Mark if the manager is transpiling, because there is a chance that the
26-
// manager gets concurrent render requests. When the first finishes with
27-
// transpiling it will try to evaluate normally, but that's not possible
28-
// since another transpilation is happening. That's why we check for this value
29-
// first.
30-
transpiling: boolean;
31-
3225
constructor(
3326
id: string,
3427
modules: Array<Module>,
@@ -40,7 +33,6 @@ export default class Manager {
4033
this.directories = directories;
4134
this.preset = preset;
4235
this.transpiledModules = {};
43-
this.transpiling = false;
4436

4537
console.log(this);
4638
}
@@ -50,18 +42,14 @@ export default class Manager {
5042
}
5143

5244
evaluateModule(module: Module) {
53-
if (!this.transpiling) {
54-
const transpiledModule = this.getTranspiledModule(module);
55-
56-
// Run post evaluate first
57-
const exports = this.evaluateTranspiledModule(transpiledModule, []);
45+
const transpiledModule = this.getTranspiledModule(module);
5846

59-
this.getTranspiledModules().forEach(t => t.postEvaluate(this));
47+
// Run post evaluate first
48+
const exports = this.evaluateTranspiledModule(transpiledModule, []);
6049

61-
return exports;
62-
}
50+
this.getTranspiledModules().forEach(t => t.postEvaluate(this));
6351

64-
return null;
52+
return exports;
6553
}
6654

6755
evaluateTranspiledModule(
@@ -209,7 +197,6 @@ export default class Manager {
209197
* delete caches accordingly
210198
*/
211199
updateData(modules: Array<Module>, directories: Array<Directory>) {
212-
this.transpiling = true;
213200
// Create an object with mapping from modules
214201
const moduleObject = this.modules.reduce(
215202
(prev, next) => ({
@@ -270,23 +257,11 @@ export default class Manager {
270257
])
271258
);
272259

260+
this.modules = modules;
261+
this.directories = directories;
262+
273263
return Promise.all(
274264
transpiledModulesToUpdate.map(tModule => tModule.transpile(this))
275-
)
276-
.then(x => {
277-
this.modules = modules;
278-
this.directories = directories;
279-
this.transpiling = false;
280-
281-
return x;
282-
})
283-
.catch(e => {
284-
// Also set new module info for a catch
285-
this.modules = modules;
286-
this.directories = directories;
287-
this.transpiling = false;
288-
289-
throw e;
290-
});
265+
);
291266
}
292267
}

src/sandbox/eval/transpiled-module.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,6 @@ export default class TranspiledModule {
124124

125125
reset() {
126126
this.childModules = [];
127-
this.errors = [];
128-
this.warnings = [];
129127
this.emittedAssets = [];
130128
this.setIsEntry(false);
131129
this.resetCompilation();
@@ -139,6 +137,8 @@ export default class TranspiledModule {
139137
dep.resetTranspilation();
140138
});
141139
this.source = null;
140+
this.errors = [];
141+
this.warnings = [];
142142
}
143143

144144
resetCompilation() {

0 commit comments

Comments
 (0)