Skip to content

Commit 7141800

Browse files
authored
Editor + Preview + Packager rewrite + Offline Support (codesandbox#107)
* Basic monaco editor * Syntax highlighting & Prettier * Offline support * Add offline notice * Proper resizing * Move syntax highlighting to a worker * Add preferences to monaco * File changes, error highlighting * Linting and error handling * Emmet support for Monaco * Live preferences and resize probe * So much is happening * Auto fetch typings * Optimize build config * Remove codemirror from vendor build * Improve production builds * Progress * Allow opening in external windows * Move logic to sandbox * Use new dependency fetcher * New fetching * Fix typings * Intermediate * New Loading screen * Error handling * Intermediate * Fix tests * Regenerate yarn.lock * Add linebreak for react-overlay-message * Update snapshots * Handle compile errors * Disable suggestions when actions are unavailable * Check clearErrors availability * Standalone sandbox support and offline tweaks * Intermediate * Show url in address bar * Revert back to old uglify * Adjustments * Remove old fetcher * Replace all domain names * Update service worker settings * Fix circular dependency
1 parent ea0cbde commit 7141800

File tree

127 files changed

+9008
-386161
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

127 files changed

+9008
-386161
lines changed

config/webpack.config.js

Lines changed: 195 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ const paths = require('./paths');
44
const HtmlWebpackPlugin = require('html-webpack-plugin');
55
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
66
const childProcess = require('child_process');
7+
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
8+
const CopyWebpackPlugin = require('copy-webpack-plugin');
9+
const HappyPack = require('happypack');
710
const WatchMissingNodeModulesPlugin = require('../scripts/utils/WatchMissingNodeModulesPlugin');
811
const env = require('./env');
912

@@ -24,21 +27,25 @@ const COMMIT_HASH = childProcess
2427
.toString();
2528
const VERSION = `${COMMIT_COUNT}-${COMMIT_HASH}`;
2629

30+
const publicPath = __PROD__
31+
? 'https://codesandbox.io/'
32+
: 'https://codesandbox.dev/';
33+
2734
const getOutput = () =>
2835
__DEV__
2936
? {
3037
path: paths.appBuild,
3138
pathinfo: true,
3239
filename: 'static/js/[name].js',
33-
publicPath: '/',
40+
publicPath,
3441
}
3542
: {
3643
path: paths.appBuild,
3744
pathinfo: true,
3845
filename: 'static/js/[name].[chunkhash].js',
3946
chunkFilename: 'static/js/[name].[chunkhash].chunk.js',
4047
sourceMapFilename: '[file].map', // Default
41-
publicPath: 'https://codesandbox.io/',
48+
publicPath,
4249
};
4350

4451
const config = {
@@ -55,7 +62,7 @@ const config = {
5562
require.resolve('./polyfills'),
5663
path.join(paths.embedSrc, 'index.js'),
5764
],
58-
vendor: ['codemirror', 'react', 'react-dom', 'styled-components'],
65+
vendor: ['react', 'react-dom', 'styled-components', 'babel-standalone'],
5966
},
6067

6168
target: 'web',
@@ -73,24 +80,8 @@ const config = {
7380
{
7481
test: /\.js$/,
7582
include: paths.src,
76-
exclude: [/eslint\.js$/],
77-
loader: 'babel-loader',
78-
options: babelConfig,
79-
},
80-
// Used to remove strict mode from eval:
81-
{
82-
test: /eval\/js\.js$/,
83-
include: paths.src,
84-
loader: 'babel-loader?cacheDirectory',
85-
options: (() => {
86-
const altererdConfig = Object.assign({}, babelConfig);
87-
88-
// prettier-ignore
89-
altererdConfig.plugins.push(
90-
require.resolve('babel-plugin-transform-remove-strict-mode')
91-
);
92-
return altererdConfig;
93-
})(),
83+
exclude: [/eslint\.4\.1\.0\.min\.js$/, /typescriptServices\.js$/],
84+
loader: 'happypack/loader',
9485
},
9586
// JSON is not enabled by default in Webpack but both Node and Browserify
9687
// allow it implicitly so we also enable it.
@@ -152,6 +143,8 @@ const config = {
152143
},
153144
},
154145
],
146+
147+
noParse: [/eslint\.4\.1\.0\.min\.js$/, /typescriptServices\.js$/],
155148
},
156149

157150
resolve: {
@@ -166,6 +159,14 @@ const config = {
166159
},
167160

168161
plugins: [
162+
new HappyPack({
163+
loaders: [
164+
{
165+
path: 'babel-loader',
166+
query: babelConfig,
167+
},
168+
],
169+
}),
169170
// Generates an `index.html` file with the <script> injected.
170171
new HtmlWebpackPlugin({
171172
inject: true,
@@ -234,6 +235,19 @@ const config = {
234235
// makes the discovery automatic so you don't have to restart.
235236
// See https://github.com/facebookincubator/create-react-app/issues/186
236237
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
238+
// Make the monaco editor work
239+
new CopyWebpackPlugin([
240+
{
241+
from: __DEV__
242+
? 'node_modules/monaco-editor/dev/vs'
243+
: 'node_modules/monaco-editor/min/vs',
244+
to: 'public/vs',
245+
},
246+
{
247+
from: 'src/homepage/static',
248+
to: 'static',
249+
},
250+
]),
237251
// Try to dedupe duplicated modules, if any:
238252
new webpack.optimize.CommonsChunkPlugin({
239253
name: 'common',
@@ -246,7 +260,7 @@ const config = {
246260
new webpack.optimize.CommonsChunkPlugin({
247261
async: true,
248262
children: true,
249-
minChunks: 4,
263+
minChunks: 2,
250264
}),
251265
new webpack.NamedModulesPlugin(),
252266
],
@@ -290,6 +304,165 @@ if (__PROD__) {
290304
},
291305
sourceMap: true,
292306
}),
307+
// Generate a service worker script that will precache, and keep up to date,
308+
// the HTML & assets that are part of the Webpack build.
309+
new SWPrecacheWebpackPlugin({
310+
// By default, a cache-busting query parameter is appended to requests
311+
// used to populate the caches, to ensure the responses are fresh.
312+
// If a URL is already hashed by Webpack, then there is no concern
313+
// about it being stale, and the cache-busting can be skipped.
314+
dontCacheBustUrlsMatching: /\.\w{8}\./,
315+
filename: 'service-worker.js',
316+
cacheId: 'code-sandbox',
317+
logger(message) {
318+
if (message.indexOf('Total precache size is') === 0) {
319+
// This message occurs for every build and is a bit too noisy.
320+
return;
321+
}
322+
if (message.indexOf('Skipping static resource') === 0) {
323+
// This message obscures real errors so we ignore it.
324+
// https://github.com/facebookincubator/create-react-app/issues/2612
325+
return;
326+
}
327+
console.log(message);
328+
},
329+
minify: true,
330+
// For unknown URLs, fallback to the index page
331+
navigateFallback: publicPath + 'app.html',
332+
// Ignores URLs starting from /__ (useful for Firebase):
333+
// https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219
334+
navigateFallbackWhitelist: [/^(?!\/__).*/],
335+
// Don't precache sourcemaps (they're large) and build asset manifest:
336+
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
337+
maximumFileSizeToCacheInBytes: 5242880,
338+
runtimeCaching: [
339+
{
340+
urlPattern: /api\/v1\/sandboxes/,
341+
handler: 'networkFirst',
342+
options: {
343+
cache: {
344+
maxEntries: 50,
345+
name: 'sandboxes-cache',
346+
},
347+
},
348+
},
349+
{
350+
urlPattern: /^https:\/\/unpkg\.com/,
351+
handler: 'cacheFirst',
352+
options: {
353+
cache: {
354+
maxEntries: 300,
355+
name: 'unpkg-cache',
356+
},
357+
},
358+
},
359+
{
360+
urlPattern: /cloudflare\.com/,
361+
handler: 'cacheFirst',
362+
options: {
363+
cache: {
364+
maxEntries: 20,
365+
name: 'cloudflare-cache',
366+
},
367+
},
368+
},
369+
],
370+
}),
371+
// Generate a service worker script that will precache, and keep up to date,
372+
// the HTML & assets that are part of the Webpack build.
373+
new SWPrecacheWebpackPlugin({
374+
// By default, a cache-busting query parameter is appended to requests
375+
// used to populate the caches, to ensure the responses are fresh.
376+
// If a URL is already hashed by Webpack, then there is no concern
377+
// about it being stale, and the cache-busting can be skipped.
378+
dontCacheBustUrlsMatching: /\.\w{8}\./,
379+
filename: 'sandbox-service-worker.js',
380+
logger(message) {
381+
if (message.indexOf('Total precache size is') === 0) {
382+
// This message occurs for every build and is a bit too noisy.
383+
return;
384+
}
385+
if (message.indexOf('Skipping static resource') === 0) {
386+
// This message obscures real errors so we ignore it.
387+
// https://github.com/facebookincubator/create-react-app/issues/2612
388+
return;
389+
}
390+
console.log(message);
391+
},
392+
minify: true,
393+
// For unknown URLs, fallback to the index page
394+
navigateFallback: 'https://new.codesandbox.io/frame.html',
395+
staticFileGlobs: ['www/frame.html'],
396+
stripPrefix: 'www/',
397+
cacheId: 'code-sandbox-sandbox',
398+
// Ignores URLs starting from /__ (useful for Firebase):
399+
// https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219
400+
navigateFallbackWhitelist: [/^(?!\/__).*/],
401+
// Don't precache sourcemaps (they're large) and build asset manifest:
402+
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
403+
maximumFileSizeToCacheInBytes: 10485760,
404+
runtimeCaching: [
405+
{
406+
urlPattern: /api\/v1\/sandboxes/,
407+
handler: 'networkFirst',
408+
options: {
409+
cache: {
410+
maxEntries: 50,
411+
name: 'sandboxes-cache',
412+
},
413+
},
414+
},
415+
{
416+
// These should be dynamic, since it's not loaded from this domain
417+
// But from the root domain
418+
urlPattern: /codesandbox\.io\/static\/js\/(vendor|common|sandbox)/,
419+
handler: 'networkFirst',
420+
options: {
421+
cache: {
422+
name: 'static-root-cache',
423+
},
424+
},
425+
},
426+
{
427+
urlPattern: /api\/v1\/sandboxes/,
428+
handler: 'networkFirst',
429+
options: {
430+
cache: {
431+
maxEntries: 50,
432+
name: 'sandboxes-cache',
433+
},
434+
},
435+
},
436+
{
437+
urlPattern: /\.amazonaws\.com\/prod\/package/,
438+
handler: 'fastest',
439+
options: {
440+
cache: {
441+
// a week
442+
maxAgeSeconds: 60 * 60 * 24 * 7,
443+
name: 'dependency-url-generator-cache',
444+
},
445+
},
446+
},
447+
{
448+
urlPattern: /https:\/\/d3i2v4dxqvxaq9\.cloudfront\.net/,
449+
handler: 'fastest',
450+
options: {
451+
cache: {
452+
maxEntries: 200,
453+
name: 'dependency-files-cache',
454+
},
455+
},
456+
},
457+
],
458+
}),
459+
// Moment.js is an extremely popular library that bundles large locale files
460+
// by default due to how Webpack interprets its code. This is a practical
461+
// solution that requires the user to opt into importing specific locales.
462+
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
463+
// You can remove this if you don't use Moment.js:
464+
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
465+
new webpack.optimize.ModuleConcatenationPlugin(),
293466
];
294467
} else {
295468
config.plugins = [

package.json

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"case-sensitive-paths-webpack-plugin": "^2.0.0",
2525
"chalk": "1.1.3",
2626
"connect-history-api-fallback": "1.3.0",
27+
"copy-webpack-plugin": "^4.0.1",
2728
"cross-spawn": "^5.0.1",
2829
"css-loader": "^0.28.1",
2930
"detect-port": "^1.1.1",
@@ -44,6 +45,7 @@
4445
"fs-extra": "^2.1.2",
4546
"gulp-replace": "^0.5.4",
4647
"gzip-size": "3.0.0",
48+
"happypack": "^4.0.0-beta.2",
4749
"html-loader": "^0.4.4",
4850
"html-webpack-plugin": "^2.24.1",
4951
"http-proxy-middleware": "^0.17.3",
@@ -66,11 +68,13 @@
6668
"run-sequence": "^1.2.2",
6769
"strip-ansi": "3.0.1",
6870
"style-loader": "^0.18.2",
71+
"sw-precache-webpack-plugin": "^0.11.4",
6972
"url-loader": "^0.5.9",
70-
"webpack": "^3.1.0",
73+
"webpack": "^3.5.4",
7174
"webpack-dev-middleware": "^1.11.0",
7275
"webpack-dev-server": "^2.5.1",
73-
"whatwg-fetch": "^2.0.3"
76+
"whatwg-fetch": "^2.0.3",
77+
"worker-loader": "^0.8.1"
7478
},
7579
"dependencies": {
7680
"@emmetio/codemirror-plugin": "^0.3.5",
@@ -101,6 +105,7 @@
101105
"jszip": "^3.1.3",
102106
"lodash": "^4.17.2",
103107
"moment": "^2.18.1",
108+
"monaco-editor": "CompuIves/codesandbox-monaco-editor",
104109
"normalize.css": "^5.0.0",
105110
"normalizr": "^3.2.3",
106111
"qs": "^6.5.0",
@@ -111,6 +116,7 @@
111116
"react-dnd": "^2.4.0",
112117
"react-dnd-html5-backend": "^2.4.1",
113118
"react-dom": "^15.6.1",
119+
"react-error-overlay": "^1.0.10",
114120
"react-icons": "^2.2.5",
115121
"react-instantsearch": "^4.0.6",
116122
"react-loadable": "^3.3.1",
@@ -159,7 +165,8 @@
159165
"rootDir": "src",
160166
"moduleDirectories": ["node_modules", "src"],
161167
"moduleNameMapper": {
162-
"\\.css$": "<rootDir>/__mocks__/styleMock.js"
168+
"\\.css$": "<rootDir>/__mocks__/styleMock.js",
169+
"\\.html$": "<rootDir>/__mocks__/styleMock.js"
163170
}
164171
}
165172
}

public/manifest.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"short_name": "CodeSandbox",
3+
"name": "CodeSandbox",
4+
"description": "An online editor tailored for web applications",
5+
"icons": [
6+
{
7+
"src": "favicon.ico",
8+
"sizes": "192x192",
9+
"type": "image/png"
10+
}
11+
],
12+
"start_url": "/s/new?utm_source=homescreen",
13+
"display": "standalone",
14+
"theme_color": "#6CAEDD",
15+
"background_color": "#1C2022"
16+
}

0 commit comments

Comments
 (0)