Skip to content

Commit 2826f77

Browse files
authored
SSE Sync (codesandbox#1275)
* SSE Sync * Fix tests * Put env back in for integration tests * Move back to .io * Don't do all transforms for babel dev
1 parent 5496843 commit 2826f77

File tree

10 files changed

+148
-14
lines changed

10 files changed

+148
-14
lines changed

packages/app/config/babel.dev.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module.exports = {
77
presets: [
88
// Latest stable ECMAScript features
99
require.resolve('@babel/preset-flow'),
10-
process.env.NODE_ENV === 'test' && [
10+
[
1111
require.resolve('@babel/preset-env'),
1212
{
1313
targets: {
@@ -18,7 +18,7 @@ module.exports = {
1818
// Disable polyfill transforms
1919
useBuiltIns: false,
2020
modules: false,
21-
forceAllTransforms: true,
21+
forceAllTransforms: !process.env.LOCAL_DEV,
2222
},
2323
],
2424
// JSX, Flow

packages/app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"uglify-es": "npm:terser"
88
},
99
"scripts": {
10-
"start": "cross-env LOCAL_SERVER=1 node scripts/start.js",
10+
"start": "cross-env LOCAL_SERVER=1 LOCAL_DEV=1 node scripts/start.js",
1111
"start:sandbox": "cross-env SANDBOX_ONLY=true node scripts/start.js",
1212
"start:dev_api": "node scripts/start.js",
1313
"start:test": "cross-env LOCAL_SERVER=1 SANDBOX_ONLY=true node scripts/start.js",

packages/app/src/app/components/Preview/index.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type Props = {
3939
alignDirection?: 'right' | 'bottom',
4040
delay?: number,
4141
setServerStatus?: (status: string) => void,
42+
syncSandbox?: (updates: any) => void,
4243
};
4344

4445
type State = {
@@ -148,6 +149,7 @@ class BasePreview extends React.Component<Props, State> {
148149
modules: {
149150
[path: string]: any,
150151
},
152+
ignoreNextUpdate: boolean,
151153
};
152154
// TODO: Find typedefs for this
153155
$socket: ?any;
@@ -179,10 +181,7 @@ class BasePreview extends React.Component<Props, State> {
179181
// when the user navigates the iframe app, which shows the loading screen
180182
this.initialPath = props.initialPath;
181183

182-
this.lastSent = {
183-
sandboxId: props.sandbox.id,
184-
modules: this.getModulesToSend(),
185-
};
184+
this.initializeLastSent();
186185

187186
if (this.serverPreview) {
188187
this.connectTimeout = null;
@@ -198,6 +197,14 @@ class BasePreview extends React.Component<Props, State> {
198197
window.openNewWindow = this.openNewWindow;
199198
}
200199

200+
initializeLastSent = () => {
201+
this.lastSent = {
202+
sandboxId: this.props.sandbox.id,
203+
modules: this.getModulesToSend(),
204+
ignoreNextUpdate: false,
205+
};
206+
};
207+
201208
componentWillUpdate(nextProps: Props, nextState: State) {
202209
if (
203210
nextState.frameInitialized !== this.state.frameInitialized &&
@@ -295,6 +302,14 @@ class BasePreview extends React.Component<Props, State> {
295302
});
296303
});
297304

305+
socket.on('sandbox:update', message => {
306+
this.lastSent.ignoreNextUpdate = true;
307+
308+
if (this.props.syncSandbox) {
309+
this.props.syncSandbox({ updates: message.updates });
310+
}
311+
});
312+
298313
socket.on('sandbox:start', () => {
299314
sseTerminalMessage(`sandbox ${this.props.sandbox.id} started.`);
300315

@@ -405,6 +420,7 @@ class BasePreview extends React.Component<Props, State> {
405420
: frameUrl(newId, this.props.initialPath || '');
406421

407422
if (this.serverPreview) {
423+
this.initializeLastSent();
408424
this.setupSSESockets();
409425
}
410426
this.setState(
@@ -553,8 +569,10 @@ class BasePreview extends React.Component<Props, State> {
553569

554570
this.lastSent.modules = modulesToSend;
555571

556-
if (Object.keys(diff).length > 0 && this.$socket) {
572+
const ignoreUpdate = this.lastSent.ignoreNextUpdate;
573+
if (!ignoreUpdate && Object.keys(diff).length > 0 && this.$socket) {
557574
this.$socket.emit('sandbox:update', diff);
575+
this.lastSent.ignoreNextUpdate = false;
558576
}
559577
} else {
560578
dispatch({

packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ class Preview extends React.Component<Props, State> {
330330
setServerStatus={(status: string) => {
331331
signals.server.statusChanged({ status });
332332
}}
333+
syncSandbox={signals.files.syncSandbox}
333334
/>
334335
) : (
335336
<RunOnClick

packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Server/Tasks.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export default class Tasks extends React.PureComponent<Props> {
7171
};
7272

7373
render() {
74-
if (!this.props.package) {
74+
if (!this.props.package || !this.props.package.scripts) {
7575
return null;
7676
}
7777

packages/app/src/app/store/modules/files/actions.js

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,58 @@ import { MAX_FILE_SIZE } from 'codesandbox-import-utils/lib/is-text';
66
import denormalize from 'codesandbox-import-utils/lib/create-sandbox/denormalize';
77
import track from 'common/utils/analytics';
88

9-
import { resolveModuleWrapped } from '../../utils/resolve-module-wrapped';
9+
import {
10+
resolveModuleWrapped,
11+
resolveDirectoryWrapped,
12+
} from '../../utils/resolve-module-wrapped';
13+
14+
export function processSSEUpdates({ state, props, controller }) {
15+
const newSandbox = props.sandbox;
16+
const oldSandbox = state.get('editor.currentSandbox');
17+
18+
props.updates.forEach(update => {
19+
const { op, path, type } = update;
20+
if (type === 'file') {
21+
const resolveModuleOld = resolveModuleWrapped(oldSandbox);
22+
const resolveModuleNew = resolveModuleWrapped(newSandbox);
23+
const oldModule = resolveModuleOld(path);
24+
if (op === 'update') {
25+
const newModule = resolveModuleNew(path);
26+
27+
if (oldModule) {
28+
const modulePos = oldSandbox.modules.indexOf(oldModule);
29+
state.merge(
30+
`editor.sandboxes.${oldSandbox.id}.modules.${modulePos}`,
31+
newModule
32+
);
33+
} else {
34+
state.push(`editor.sandboxes.${oldSandbox.id}.modules`, newModule);
35+
}
36+
} else if (op === 'delete') {
37+
controller.getSignal('files.removeModule')({
38+
moduleShortid: oldModule.shortid,
39+
});
40+
}
41+
} else {
42+
const resolveDirectoryOld = resolveDirectoryWrapped(oldSandbox);
43+
const resolveDirectoryNew = resolveDirectoryWrapped(newSandbox);
44+
45+
if (op === 'update') {
46+
// Create
47+
const newDirectory = resolveDirectoryNew(path);
48+
state.push(
49+
`editor.sandboxes.${oldSandbox.id}.directories`,
50+
newDirectory
51+
);
52+
} else {
53+
const oldDirectory = resolveDirectoryOld(path);
54+
controller.getSignal('files.removeDirectory')({
55+
directoryShortid: oldDirectory.shortid,
56+
});
57+
}
58+
}
59+
});
60+
}
1061

1162
export function whenModuleIsSelected({ state, props, path }) {
1263
const currentModule = state.get('editor.currentModule');

packages/app/src/app/store/modules/files/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,8 @@ export default Module({
1818
directoryDeleted: sequences.deleteDirectory,
1919
moduleDeleted: sequences.deleteModule,
2020
createModulesByPath: sequences.createModulesByPath,
21+
syncSandbox: sequences.syncSandbox,
22+
removeModule: sequences.removeModule,
23+
removeDirectory: sequences.removeDirectory,
2124
},
2225
});

packages/app/src/app/store/modules/files/sequences.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
setModal,
88
callVSCodeCallback,
99
callVSCodeCallbackError,
10+
getSandbox,
1011
} from '../../actions';
1112
import {
1213
sendModuleCreated,
@@ -35,6 +36,21 @@ export const getUploadedFiles = [
3536
},
3637
];
3738

39+
export const syncSandbox = [
40+
set(props`id`, state`editor.currentId`),
41+
getSandbox,
42+
{
43+
success: [actions.processSSEUpdates],
44+
error: [
45+
addNotification(
46+
"We weren't able to retrieve the latest files of the sandbox, please refresh",
47+
'error'
48+
),
49+
],
50+
notFound: [],
51+
},
52+
];
53+
3854
export const removeModule = [
3955
ensureOwnedEditable,
4056
actions.whenModuleIsSelected,
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { resolveModule } from 'common/sandbox/modules';
1+
import { resolveModule, resolveDirectory } from 'common/sandbox/modules';
22

33
export const resolveModuleWrapped = sandbox => (path: string) => {
44
try {
@@ -7,3 +7,11 @@ export const resolveModuleWrapped = sandbox => (path: string) => {
77
return undefined;
88
}
99
};
10+
11+
export const resolveDirectoryWrapped = sandbox => (path: string) => {
12+
try {
13+
return resolveDirectory(path, sandbox.modules, sandbox.directories);
14+
} catch (e) {
15+
return undefined;
16+
}
17+
};

packages/common/sandbox/modules.js

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const throwError = (path: string) => {
1616
throw new Error(`Cannot find module in ${path}`);
1717
};
1818

19-
export function getModulesInDirectory(
19+
export function resolveDirectory(
2020
_path: ?string,
2121
modules: Array<Module>,
2222
directories: Array<Directory>,
@@ -40,8 +40,8 @@ export function getModulesInDirectory(
4040

4141
const foundDirectoryShortid = splitPath.reduce(
4242
(dirId: ?string, pathPart: string, i: number) => {
43-
// Meaning this is the last argument, so the file
44-
if (i === splitPath.length - 1) return dirId;
43+
// Meaning this is the last argument, so the directory
44+
if (i === splitPath.length) return dirId;
4545

4646
if (pathPart === '..') {
4747
// Find the parent
@@ -67,6 +67,43 @@ export function getModulesInDirectory(
6767
startdirectoryShortid
6868
);
6969

70+
return directories.find(d => d.shortid === foundDirectoryShortid);
71+
}
72+
73+
export function getModulesInDirectory(
74+
_path: ?string,
75+
modules: Array<Module>,
76+
directories: Array<Directory>,
77+
_startdirectoryShortid: ?string = undefined
78+
) {
79+
if (!_path) return throwError('');
80+
81+
let path = _path;
82+
// If paths start with {{sandboxRoot}} we see them as root paths
83+
if (path.startsWith('{{sandboxRoot}}')) {
84+
path = _path.replace('{{sandboxRoot}}/', './');
85+
}
86+
87+
// Split path
88+
const splitPath = path
89+
.replace(/^.\//, '')
90+
.split('/')
91+
.filter(Boolean);
92+
93+
const dirPath = path
94+
.replace(/^.\//, '')
95+
.split('/')
96+
.filter(Boolean);
97+
dirPath.pop();
98+
99+
const dir = resolveDirectory(
100+
dirPath.join('/') || '/',
101+
modules,
102+
directories,
103+
_startdirectoryShortid
104+
);
105+
const foundDirectoryShortid = dir ? dir.shortid : null;
106+
70107
const lastPath = splitPath[splitPath.length - 1];
71108
const modulesInFoundDirectory = modules.filter(
72109
// eslint-disable-next-line eqeqeq

0 commit comments

Comments
 (0)