Skip to content

Commit 083a2cc

Browse files
sync specific addition/removal of typings and do not retranspile on adding/removing packages, do it on first codechange
1 parent 69907bf commit 083a2cc

File tree

5 files changed

+161
-80
lines changed

5 files changed

+161
-80
lines changed

packages/app/src/app/overmind/effects/vscode/SandboxFsSync/index.ts

Lines changed: 139 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ import {
1010
Sandbox,
1111
SandboxFs,
1212
} from '@codesandbox/common/lib/types';
13-
import { getAbsoluteDependencies } from '@codesandbox/common/lib/utils/dependencies';
13+
import {
14+
fetchPackageJSON,
15+
isAbsoluteVersion,
16+
} from '@codesandbox/common/lib/utils/dependencies';
1417
import { getGlobal } from '@codesandbox/common/lib/utils/global';
1518
import { protocolAndHost } from '@codesandbox/common/lib/utils/url-generator';
1619
import { json } from 'overmind';
17-
import { mkdir, rename, rmdir, unlink, writeFile } from './utils';
20+
1821
import { WAIT_INITIAL_TYPINGS_MS } from '../constants';
22+
import { mkdir, rename, rmdir, unlink, writeFile } from './utils';
1923

2024
const global = getGlobal() as Window & { BrowserFS: any };
2125

@@ -36,8 +40,16 @@ type SandboxFsSyncOptions = {
3640

3741
class SandboxFsSync {
3842
private options: SandboxFsSyncOptions;
39-
private types: any = {};
40-
private deps: { [path: string]: string } = {};
43+
private types: {
44+
[packageName: string]: {
45+
[typeName: string]: string;
46+
};
47+
} = {};
48+
49+
private deps: { [path: string]: string } = {
50+
'@types/jest': 'latest',
51+
};
52+
4153
private isDisposed = false;
4254
private currentSyncId = 0;
4355
private typesInfo: Promise<any>;
@@ -47,7 +59,10 @@ class SandboxFsSync {
4759
}
4860

4961
public getTypes() {
50-
return this.types;
62+
return Object.keys(this.types).reduce(
63+
(aggr, key) => Object.assign(aggr, this.types[key]),
64+
{}
65+
);
5166
}
5267

5368
public dispose() {
@@ -180,23 +195,73 @@ class SandboxFsSync {
180195
try {
181196
this.currentSyncId = syncId;
182197

198+
this.typesInfo = await this.getTypesInfo();
183199
const syncDetails = await this.getDependencyTypingsSyncDetails();
184200

185201
if (syncDetails) {
186-
this.fetchDependencyTypings(
187-
syncId,
188-
syncDetails.packageJsonContent,
189-
syncDetails.autoInstall
190-
);
202+
const newDeps = syncDetails.dependencies;
203+
const { added, removed } = this.getDepsChanges(newDeps);
204+
205+
this.deps = newDeps;
206+
207+
added.forEach(dep => {
208+
this.fetchDependencyTyping(syncId, dep, syncDetails.autoInstall);
209+
});
210+
211+
if (removed.length) {
212+
const removedTypings = {};
213+
214+
// We go through removed deps to figure out what typings packages
215+
// has been removed, then delete the from our types as well
216+
removed.forEach(removedDep => {
217+
const typings = this.types[removedDep.name] || {};
218+
219+
Object.assign(removedTypings, typings);
220+
221+
delete this.types[removedDep.name];
222+
});
223+
224+
this.send('types-remove', removedTypings);
225+
}
191226
}
192227
} catch (error) {
193228
// eslint-disable-next-line
194229
console.error(error);
195230
}
196231
}
197232

233+
private getDepsChanges(newDeps) {
234+
const added: Array<{ name: string; version: string }> = [];
235+
const removed: Array<{ name: string; version: string }> = [];
236+
const newDepsKeys = Object.keys(newDeps);
237+
const currentDepsKeys = Object.keys(this.deps);
238+
239+
newDepsKeys.forEach(newDepKey => {
240+
if (
241+
!this.deps[newDepKey] ||
242+
this.deps[newDepKey] !== newDeps[newDepKey]
243+
) {
244+
added.push({
245+
name: newDepKey,
246+
version: newDeps[newDepKey],
247+
});
248+
}
249+
});
250+
251+
currentDepsKeys.forEach(currentDepKey => {
252+
if (currentDepKey !== '@types/jest' && !newDeps[currentDepKey]) {
253+
removed.push({
254+
name: currentDepKey,
255+
version: this.deps[currentDepKey],
256+
});
257+
}
258+
});
259+
260+
return { added, removed };
261+
}
262+
198263
private async getDependencyTypingsSyncDetails(): Promise<{
199-
packageJsonContent: string;
264+
dependencies: { [name: string]: string };
200265
autoInstall: boolean;
201266
}> {
202267
return new Promise((resolve, reject) => {
@@ -221,11 +286,21 @@ class SandboxFsSync {
221286
browserFs.stat(
222287
'/sandbox/tsconfig.json',
223288
(tsConfigError, result) => {
224-
// If tsconfig exists we want to sync the types
225-
resolve({
226-
packageJsonContent: rv.toString(),
227-
autoInstall: Boolean(tsConfigError) || !result,
228-
});
289+
// If tsconfig exists we want to sync the typesp
290+
try {
291+
const packageJson = JSON.parse(rv.toString());
292+
resolve({
293+
dependencies: {
294+
...packageJson.dependencies,
295+
...packageJson.devDependencies,
296+
},
297+
autoInstall: Boolean(tsConfigError) || !result,
298+
});
299+
} catch (error) {
300+
reject(
301+
new Error('TYPINGS: Could not parse package.json')
302+
);
303+
}
229304
}
230305
);
231306
}
@@ -242,11 +317,11 @@ class SandboxFsSync {
242317
}
243318

244319
private sendTypes() {
245-
this.send('types-sync', this.types);
320+
this.send('types-sync', this.getTypes());
246321
}
247322

248-
private sendType(type: { [path: string]: any }) {
249-
this.send('type-sync', type);
323+
private sendPackageTypes(types: { [path: string]: any }) {
324+
this.send('package-types-sync', types);
250325
}
251326

252327
/**
@@ -264,63 +339,62 @@ class SandboxFsSync {
264339
return this.typesInfo;
265340
}
266341

267-
private async fetchDependencyTypings(
342+
private setAndSendPackageTypes(
343+
name: string,
344+
types: { [name: string]: string },
345+
syncId: number
346+
) {
347+
if (!this.isDisposed && this.currentSyncId === syncId) {
348+
if (!this.types[name]) {
349+
this.types[name] = {};
350+
}
351+
352+
Object.assign(this.types[name], types);
353+
this.sendPackageTypes(types);
354+
}
355+
}
356+
357+
private async fetchDependencyTyping(
268358
syncId: number,
269-
packageJSON: string,
359+
dep: { name: string; version: string },
270360
autoInstallTypes: boolean
271361
) {
272362
try {
273-
const { dependencies = {}, devDependencies = {} } = JSON.parse(
274-
packageJSON
275-
);
276-
277-
const totalDependencies = {
278-
'@types/jest': 'latest',
279-
...dependencies,
280-
...devDependencies,
281-
};
282-
283-
if (autoInstallTypes) {
284-
const typeInfo = await this.getTypesInfo();
285-
Object.keys(totalDependencies).forEach(async dep => {
286-
if (
287-
!dep.startsWith('@types/') &&
288-
!totalDependencies[`@types/${dep}`] &&
289-
typeInfo[dep]
290-
) {
291-
totalDependencies[`@types/${dep}`] = typeInfo[dep].latest;
292-
}
363+
if (
364+
autoInstallTypes &&
365+
this.typesInfo[dep.name] &&
366+
!dep.name.startsWith('@types/') &&
367+
isAbsoluteVersion(dep.version)
368+
) {
369+
const name = `@types/${dep.name}`;
370+
fetchPackageJSON(name, dep.version).then(({ version }) => {
371+
this.setAndSendPackageTypes(
372+
dep.name,
373+
{
374+
[name]: version,
375+
},
376+
syncId
377+
);
293378
});
294379
}
295380

296-
const absoluteDependencies = await getAbsoluteDependencies(
297-
totalDependencies
298-
);
299-
300-
Object.keys(absoluteDependencies).forEach(async depName => {
301-
const depVersion = absoluteDependencies[depName];
302-
303-
try {
304-
const fetchRequest = await fetch(
305-
`${SERVICE_URL}/${depName}@${depVersion}.json`
306-
);
381+
try {
382+
const fetchRequest = await fetch(
383+
`${SERVICE_URL}/${dep.name}@${dep.version}.json`
384+
);
307385

308-
if (!fetchRequest.ok) {
309-
throw new Error('Fetch error');
310-
}
386+
if (!fetchRequest.ok) {
387+
throw new Error('Fetch error');
388+
}
311389

312-
const { files } = await fetchRequest.json();
390+
const { files } = await fetchRequest.json();
313391

314-
if (!this.isDisposed && this.currentSyncId === syncId) {
315-
Object.assign(this.types, files);
316-
this.sendType(files);
317-
}
318-
} catch (e) {
319-
if (process.env.NODE_ENV === 'development') {
320-
console.warn('Trouble fetching types for ' + depName);
321-
}
392+
this.setAndSendPackageTypes(dep.name, files, syncId);
393+
} catch (e) {
394+
if (process.env.NODE_ENV === 'development') {
395+
console.warn('Trouble fetching types for ' + dep.name);
322396
}
323-
});
397+
}
324398
} catch (e) {
325399
/* ignore */
326400
}

packages/app/src/app/overmind/effects/vscode/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ export const EXTENSIONS_LOCATION = process.env.VSCODE
44

55
export const VIM_EXTENSION_ID = 'vscodevim.vim';
66

7-
export const WAIT_INITIAL_TYPINGS_MS = 2000;
7+
export const WAIT_INITIAL_TYPINGS_MS = 1000;

packages/app/src/app/overmind/effects/vscode/extensionHostWorker/common/fs.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ export async function initializeBrowserFS({
101101
function touchFileSystem() {
102102
// This forces the file watchers to emit, which causes typescript to reload
103103
global.BrowserFS.BFSRequire('fs').rename(
104-
'/sandbox/node_modules/@types',
105-
'/sandbox/node_modules/@types',
104+
'/sandbox/node_modules',
105+
'/sandbox/node_modules',
106106
() => {}
107107
);
108108
}
@@ -125,11 +125,20 @@ export async function initializeBrowserFS({
125125
}
126126
break;
127127
}
128-
case 'type-sync': {
128+
case 'package-types-sync': {
129129
Object.assign(types, evt.data.$data);
130130
touchFileSystem();
131131
break;
132132
}
133+
case 'types-remove': {
134+
const deps = evt.data.$data;
135+
136+
Object.keys(deps).forEach(depKey => {
137+
delete types[depKey];
138+
});
139+
touchFileSystem();
140+
break;
141+
}
133142
case 'sandbox-fs': {
134143
currentSandboxFs = evt.data.$data;
135144
if (isInitialSync) {

packages/app/src/app/overmind/namespaces/editor/actions.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ export const addNpmDependency: AsyncAction<{
4545
version: newVersion,
4646
isDev: Boolean(isDev),
4747
});
48-
49-
actions.editor.internal.updatePreviewCode();
5048
}
5149
);
5250

@@ -65,8 +63,6 @@ export const npmDependencyRemoved: AsyncAction<{
6563
moduleShortid: state.editor.currentPackageJSON.shortid,
6664
cbID: null,
6765
});
68-
69-
actions.editor.internal.updatePreviewCode();
7066
});
7167

7268
export const sandboxChanged: AsyncAction<{ id: string }> = withLoadApp<{

packages/common/src/utils/dependencies.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ async function fetchWithRetries(url: string) {
1818
throw err;
1919
}
2020

21-
async function fetchPackageJSON(dep: string, version: string) {
21+
export async function fetchPackageJSON(dep: string, version: string) {
2222
try {
2323
return await fetchWithRetries(
2424
`https://unpkg.com/${dep}@${encodeURIComponent(version)}/package.json`
@@ -32,14 +32,16 @@ async function fetchPackageJSON(dep: string, version: string) {
3232
}
3333
}
3434

35-
export async function getAbsoluteDependencies(dependencies: Object) {
36-
const nonAbsoluteDependencies = Object.keys(dependencies).filter(dep => {
37-
const version = dependencies[dep];
35+
export function isAbsoluteVersion(version: string) {
36+
const isAbsolute = /^\d+\.\d+\.\d+$/.test(version);
3837

39-
const isAbsolute = /^\d+\.\d+\.\d+$/.test(version);
38+
return isAbsolute || /\//.test(version);
39+
}
4040

41-
return !isAbsolute && !/\//.test(version);
42-
});
41+
export async function getAbsoluteDependencies(dependencies: Object) {
42+
const nonAbsoluteDependencies = Object.keys(dependencies).filter(
43+
dep => !isAbsoluteVersion(dependencies[dep])
44+
);
4345

4446
const newDependencies = { ...dependencies };
4547

0 commit comments

Comments
 (0)