Fix: Babel errors in the browser console for user code (#99)

These changes will print out babel errors to the console and will help to not crash the app when bad/unfinished code is in the editor.
This commit is contained in:
Stuart Taylor
2018-05-26 21:32:40 +01:00
committed by Mrugesh Mohapatra
parent d004171593
commit a62e459c2a
5 changed files with 48 additions and 43 deletions

View File

@ -82,10 +82,12 @@ function tryTransform(wrap = identity) {
return function transformWrappedPoly(source) { return function transformWrappedPoly(source) {
const result = attempt(wrap, source); const result = attempt(wrap, source);
if (isError(result)) { if (isError(result)) {
const friendlyError = `${result}` console.error(result);
.match(/[\w\W]+?\n/)[0] // note(Bouncey): Error thrown here to collapse the build pipeline
.replace(' unknown:', ''); // At the minute, it will not bubble up
throw new Error(friendlyError); // We collapse the pipeline so the app doesn't fall over trying
// parse bad code (syntax/type errors etc...)
throw new Error();
} }
return result; return result;
}; };
@ -110,12 +112,7 @@ export const sassTransformer = cond([
[stubTrue, identity] [stubTrue, identity]
]); ]);
export const _transformers = [ export const _transformers = [replaceNBSP, babelTransformer, sassTransformer];
// addLoopProtectHtmlJsJsx,
replaceNBSP,
babelTransformer,
sassTransformer
];
export function applyTransformers(file, transformers = _transformers) { export function applyTransformers(file, transformers = _transformers) {
return transformers.reduce((obs, transformer) => { return transformers.reduce((obs, transformer) => {

View File

@ -126,7 +126,7 @@ export default function completionEpic(action$, { getState }) {
const { nextChallengePath, introPath, challengeType } = meta; const { nextChallengePath, introPath, challengeType } = meta;
const next = of(push(introPath ? introPath : nextChallengePath)); const next = of(push(introPath ? introPath : nextChallengePath));
const closeChallengeModal = of(closeModal('completion')); const closeChallengeModal = of(closeModal('completion'));
let submitter = () => of({type: 'no-user-signed-in'}); let submitter = () => of({ type: 'no-user-signed-in' });
if ( if (
!(challengeType in submitTypes) || !(challengeType in submitTypes) ||
!(submitTypes[challengeType] in submitters) !(submitTypes[challengeType] in submitters)
@ -140,7 +140,6 @@ export default function completionEpic(action$, { getState }) {
submitter = submitters[submitTypes[challengeType]]; submitter = submitters[submitTypes[challengeType]];
} }
return submitter(type, state).pipe( return submitter(type, state).pipe(
concat(next), concat(next),
concat(closeChallengeModal), concat(closeChallengeModal),

View File

@ -9,7 +9,12 @@ import {
map, map,
filter, filter,
pluck, pluck,
concat concat,
tap,
catchError,
ignoreElements,
startWith,
delay
} from 'rxjs/operators'; } from 'rxjs/operators';
import { ofType, combineEpics } from 'redux-observable'; import { ofType, combineEpics } from 'redux-observable';
import { overEvery, isString } from 'lodash'; import { overEvery, isString } from 'lodash';
@ -22,7 +27,8 @@ import {
updateConsole, updateConsole,
checkChallenge, checkChallenge,
updateTests, updateTests,
disableJSOnError disableJSOnError,
isJSEnabledSelector
} from './'; } from './';
import { buildFromFiles, buildBackendChallenge } from '../utils/build'; import { buildFromFiles, buildBackendChallenge } from '../utils/build';
import { import {
@ -49,10 +55,11 @@ function updateMainEpic(actions, { getState }, { document }) {
), ),
debounceTime(executeDebounceTimeout), debounceTime(executeDebounceTimeout),
switchMap(() => switchMap(() =>
buildFromFiles(getState(), true) buildFromFiles(getState(), true).pipe(
.map(frameMain) map(frameMain),
.ignoreElements() ignoreElements(),
.catch(() => of({ type: 'NULL' })) catchError(err => of(disableJSOnError(err)))
)
) )
); );
return merge(buildAndFrameMain, proxyLogger.map(updateConsole)); return merge(buildAndFrameMain, proxyLogger.map(updateConsole));
@ -76,40 +83,41 @@ function executeChallengeEpic(action$, { getState }, { document }) {
const postTests = of( const postTests = of(
updateConsole('// tests completed'), updateConsole('// tests completed'),
checkChallenge(checkChallengePayload) checkChallenge(checkChallengePayload)
).delay(250); ).pipe(delay(250));
// run the tests within the test iframe return runTestsInTestFrame(document, tests).pipe(
return runTestsInTestFrame(document, tests) switchMap(tests => {
.flatMap(tests => {
return from(tests).pipe( return from(tests).pipe(
map(({ message }) => message), map(({ message }) => message),
filter(overEvery(isString, Boolean)), filter(overEvery(isString, Boolean)),
map(updateConsole), map(updateConsole),
concat(of(updateTests(tests))) concat(of(updateTests(tests)))
); );
}) }),
.concat(postTests); concat(postTests)
);
}) })
); );
const buildAndFrameChallenge = action$.pipe( const buildAndFrameChallenge = action$.pipe(
ofType(types.executeChallenge), ofType(types.executeChallenge),
debounceTime(executeDebounceTimeout), debounceTime(executeDebounceTimeout),
// if isCodeLocked do not run challenges filter(() => isJSEnabledSelector(getState())),
// .filter(() => !codeLockedSelector(getState()))
switchMap(() => { switchMap(() => {
const state = getState(); const state = getState();
const { challengeType } = challengeMetaSelector(state); const { challengeType } = challengeMetaSelector(state);
if (challengeType === backend) { if (challengeType === backend) {
return buildBackendChallenge(state) return buildBackendChallenge(state).pipe(
.do(frameTests) tap(frameTests),
.ignoreElements() ignoreElements(),
.startWith(initConsole('// running test')) startWith(initConsole('// running test')),
.catch(err => disableJSOnError(err)); catchError(err => of(disableJSOnError(err)))
);
} }
return buildFromFiles(state, false) return buildFromFiles(state, false).pipe(
.do(frameTests) tap(frameTests),
.ignoreElements() ignoreElements(),
.startWith(initConsole('// running test')) startWith(initConsole('// running test')),
.catch(err => disableJSOnError(err)); catchError(err => of(disableJSOnError(err)))
);
}) })
); );
return merge(buildAndFrameChallenge, challengeResults); return merge(buildAndFrameChallenge, challengeResults);

View File

@ -104,10 +104,13 @@ export const updateSuccessMessage = createAction(types.updateSuccessMessage);
export const lockCode = createAction(types.lockCode); export const lockCode = createAction(types.lockCode);
export const unlockCode = createAction(types.unlockCode); export const unlockCode = createAction(types.unlockCode);
export const disableJSOnError = createAction(types.disableJSOnError, err => { export const disableJSOnError = createAction(
console.error(err); types.disableJSOnError,
return {}; ({ payload }) => {
}); console.error(JSON.stringify(payload));
return null;
}
);
export const storedCodeFound = createAction(types.storedCodeFound); export const storedCodeFound = createAction(types.storedCodeFound);
export const noStoredCodeFound = createAction(types.noStoredCodeFound); export const noStoredCodeFound = createAction(types.noStoredCodeFound);

View File

@ -8,7 +8,6 @@ import {
challengeFilesSelector, challengeFilesSelector,
isJSEnabledSelector, isJSEnabledSelector,
challengeMetaSelector, challengeMetaSelector,
disableJSOnError,
backendFormValuesSelector backendFormValuesSelector
} from '../redux'; } from '../redux';
import { import {
@ -55,8 +54,7 @@ export function buildFromFiles(state, shouldProxyConsole) {
::pipe(shouldProxyConsole ? proxyLoggerTransformer : identity) ::pipe(shouldProxyConsole ? proxyLoggerTransformer : identity)
::pipe(jsToHtml) ::pipe(jsToHtml)
::pipe(cssToHtml) ::pipe(cssToHtml)
::concatHtml(finalRequires, template) ::concatHtml(finalRequires, template);
.catch(err => disableJSOnError(err));
} }
export function buildBackendChallenge(state) { export function buildBackendChallenge(state) {