feat(transformers): Disable JS in preview on error
This commit is contained in:
@ -4,7 +4,7 @@ export default function errorSaga(actions) {
|
|||||||
return actions.filter(({ error }) => !!error)
|
return actions.filter(({ error }) => !!error)
|
||||||
.map(({ error }) => error)
|
.map(({ error }) => error)
|
||||||
.doOnNext(error => console.error(error))
|
.doOnNext(error => console.error(error))
|
||||||
.map(() => makeToast({
|
.map(error => makeToast({
|
||||||
message: 'Something went wrong, please try again later'
|
message: error.message || 'Something went wrong, please try again later'
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React, { PropTypes, PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import ns from './ns.json';
|
import ns from './ns.json';
|
||||||
|
@ -50,22 +50,28 @@ const wrapInStyle = partial(transformContents, (content) => (
|
|||||||
const setExtToHTML = partial(setExt, 'html');
|
const setExtToHTML = partial(setExt, 'html');
|
||||||
const padContentWithJsCatch = partial(compileHeadTail, jsCatch);
|
const padContentWithJsCatch = partial(compileHeadTail, jsCatch);
|
||||||
const padContentWithHTMLCatch = partial(compileHeadTail, htmlCatch);
|
const padContentWithHTMLCatch = partial(compileHeadTail, htmlCatch);
|
||||||
|
function transformErrorProtect(fn) {
|
||||||
|
return cond([
|
||||||
|
[ matchesProperty('transformError', false), fn ],
|
||||||
|
[ stubTrue, identity ]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
export const jsToHtml = cond([
|
export const jsToHtml = transformErrorProtect(cond([
|
||||||
[
|
[
|
||||||
matchesProperty('ext', 'js'),
|
matchesProperty('ext', 'js'),
|
||||||
flow(padContentWithJsCatch, wrapInScript, setExtToHTML)
|
flow(padContentWithJsCatch, wrapInScript, setExtToHTML)
|
||||||
],
|
],
|
||||||
[ stubTrue, identity ]
|
[ stubTrue, identity ]
|
||||||
]);
|
]));
|
||||||
|
|
||||||
export const cssToHtml = cond([
|
export const cssToHtml = transformErrorProtect(cond([
|
||||||
[
|
[
|
||||||
matchesProperty('ext', 'css'),
|
matchesProperty('ext', 'css'),
|
||||||
flow(padContentWithHTMLCatch, wrapInStyle, setExtToHTML)
|
flow(padContentWithHTMLCatch, wrapInStyle, setExtToHTML)
|
||||||
],
|
],
|
||||||
[ stubTrue, identity ]
|
[ stubTrue, identity ]
|
||||||
]);
|
]));
|
||||||
|
|
||||||
// FileStream::concactHtml(
|
// FileStream::concactHtml(
|
||||||
// required: [ ...Object ],
|
// required: [ ...Object ],
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
|
attempt,
|
||||||
cond,
|
cond,
|
||||||
flow,
|
flow,
|
||||||
identity,
|
identity,
|
||||||
|
isError,
|
||||||
matchesProperty,
|
matchesProperty,
|
||||||
overEvery,
|
overEvery,
|
||||||
overSome,
|
overSome,
|
||||||
@ -105,15 +107,42 @@ export const replaceNBSP = cond([
|
|||||||
[ stubTrue, identity ]
|
[ stubTrue, identity ]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const transformErrorWarn = '/* __fcc--TransformError__ */';
|
||||||
|
|
||||||
|
function tryJSTransform(wrap = identity, defaultAssignment = 'code') {
|
||||||
|
return function transformWrappedPoly(source) {
|
||||||
|
const result = attempt(wrap, source);
|
||||||
|
if (isError(result)) {
|
||||||
|
const friendlyError = `${result}`
|
||||||
|
.match(/[\w\W]+?\n/)[0]
|
||||||
|
.replace(' unknown:', '');
|
||||||
|
console.error(friendlyError);
|
||||||
|
return `var ${defaultAssignment} = null; ${transformErrorWarn}`;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkForTransformError(file) {
|
||||||
|
const potentialError = file.contents.includes(transformErrorWarn);
|
||||||
|
if (potentialError) {
|
||||||
|
return {
|
||||||
|
...vinyl.setTransformError(true, file)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
export const babelTransformer = cond([
|
export const babelTransformer = cond([
|
||||||
[
|
[
|
||||||
testJS$JSX,
|
testJS$JSX,
|
||||||
flow(
|
flow(
|
||||||
partial(
|
partial(
|
||||||
vinyl.transformHeadTailAndContents,
|
vinyl.transformHeadTailAndContents,
|
||||||
babelTransformCode
|
tryJSTransform(babelTransformCode, 'JSX')
|
||||||
),
|
),
|
||||||
partial(vinyl.setExt, 'js')
|
partial(vinyl.setExt, 'js'),
|
||||||
|
checkForTransformError
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
[ stubTrue, identity ]
|
[ stubTrue, identity ]
|
||||||
|
@ -12,7 +12,8 @@ import {
|
|||||||
|
|
||||||
codeLockedSelector,
|
codeLockedSelector,
|
||||||
showPreviewSelector,
|
showPreviewSelector,
|
||||||
testsSelector
|
testsSelector,
|
||||||
|
disableJSOnError
|
||||||
} from './';
|
} from './';
|
||||||
import {
|
import {
|
||||||
buildFromFiles,
|
buildFromFiles,
|
||||||
@ -26,7 +27,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
createErrorObservable,
|
createErrorObservable,
|
||||||
|
|
||||||
challengeSelector
|
challengeSelector,
|
||||||
|
doActionOnError
|
||||||
} from '../../../redux';
|
} from '../../../redux';
|
||||||
|
|
||||||
|
|
||||||
@ -40,7 +42,6 @@ export function updateMainEpic(actions, { getState }, { document }) {
|
|||||||
const proxyLogger = new Subject();
|
const proxyLogger = new Subject();
|
||||||
const frameMain = createMainFramer(document, getState, proxyLogger);
|
const frameMain = createMainFramer(document, getState, proxyLogger);
|
||||||
const buildAndFrameMain = actions::ofType(
|
const buildAndFrameMain = actions::ofType(
|
||||||
types.unlockUntrustedCode,
|
|
||||||
types.modernEditorUpdated,
|
types.modernEditorUpdated,
|
||||||
types.classicEditorUpdated,
|
types.classicEditorUpdated,
|
||||||
types.executeChallenge,
|
types.executeChallenge,
|
||||||
@ -55,7 +56,7 @@ export function updateMainEpic(actions, { getState }, { document }) {
|
|||||||
.flatMapLatest(() => buildFromFiles(getState(), true)
|
.flatMapLatest(() => buildFromFiles(getState(), true)
|
||||||
.map(frameMain)
|
.map(frameMain)
|
||||||
.ignoreElements()
|
.ignoreElements()
|
||||||
.catch(createErrorObservable)
|
.catch(doActionOnError(() => disableJSOnError()))
|
||||||
);
|
);
|
||||||
return Observable.merge(buildAndFrameMain, proxyLogger.map(updateOutput));
|
return Observable.merge(buildAndFrameMain, proxyLogger.map(updateOutput));
|
||||||
});
|
});
|
||||||
|
@ -77,6 +77,7 @@ export const types = createTypes([
|
|||||||
'checkChallenge',
|
'checkChallenge',
|
||||||
createAsyncTypes('submitChallenge'),
|
createAsyncTypes('submitChallenge'),
|
||||||
'moveToNextChallenge',
|
'moveToNextChallenge',
|
||||||
|
'disableJSOnError',
|
||||||
|
|
||||||
// help
|
// help
|
||||||
'openHelpModal',
|
'openHelpModal',
|
||||||
@ -151,6 +152,8 @@ export const submitChallengeComplete = createAction(
|
|||||||
|
|
||||||
export const moveToNextChallenge = createAction(types.moveToNextChallenge);
|
export const moveToNextChallenge = createAction(types.moveToNextChallenge);
|
||||||
|
|
||||||
|
export const disableJSOnError = createAction(types.disableJSOnError);
|
||||||
|
|
||||||
// help
|
// help
|
||||||
export const openHelpModal = createAction(types.openHelpModal);
|
export const openHelpModal = createAction(types.openHelpModal);
|
||||||
export const closeHelpModal = createAction(types.closeHelpModal);
|
export const closeHelpModal = createAction(types.closeHelpModal);
|
||||||
@ -306,6 +309,7 @@ export default combineReducers(
|
|||||||
[
|
[
|
||||||
combineActions(
|
combineActions(
|
||||||
types.classicEditorUpdated,
|
types.classicEditorUpdated,
|
||||||
|
types.disableJSOnError,
|
||||||
types.modernEditorUpdated
|
types.modernEditorUpdated
|
||||||
)
|
)
|
||||||
]: state => ({
|
]: state => ({
|
||||||
|
@ -42,6 +42,15 @@ const globalRequires = [
|
|||||||
jQuery
|
jQuery
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function postTransformCheck(file) {
|
||||||
|
if (file.transformError) {
|
||||||
|
// this will enable us to tap into the dipatch pipeline
|
||||||
|
// and disable JS in the preview
|
||||||
|
throw new Error('There was an error whilst transforming your code');
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
export function buildFromFiles(state, shouldProxyConsole) {
|
export function buildFromFiles(state, shouldProxyConsole) {
|
||||||
const files = filesSelector(state);
|
const files = filesSelector(state);
|
||||||
const required = challengeRequiredSelector(state);
|
const required = challengeRequiredSelector(state);
|
||||||
@ -49,6 +58,7 @@ export function buildFromFiles(state, shouldProxyConsole) {
|
|||||||
return createFileStream(files)
|
return createFileStream(files)
|
||||||
::pipe(throwers)
|
::pipe(throwers)
|
||||||
::pipe(applyTransformers)
|
::pipe(applyTransformers)
|
||||||
|
::pipe(postTransformCheck)
|
||||||
::pipe(shouldProxyConsole ? proxyLoggerTransformer : identity)
|
::pipe(shouldProxyConsole ? proxyLoggerTransformer : identity)
|
||||||
::pipe(jsToHtml)
|
::pipe(jsToHtml)
|
||||||
::pipe(cssToHtml)
|
::pipe(cssToHtml)
|
||||||
|
@ -77,7 +77,8 @@ export function createPoly({
|
|||||||
path: name + '.' + ext,
|
path: name + '.' + ext,
|
||||||
key: name + ext,
|
key: name + ext,
|
||||||
contents,
|
contents,
|
||||||
error: null
|
error: null,
|
||||||
|
transformError: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +117,7 @@ export function setContent(contents, poly) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// setExt(contents: String, poly: PolyVinyl) => PolyVinyl
|
// setExt(ext: String, poly: PolyVinyl) => PolyVinyl
|
||||||
export function setExt(ext, poly) {
|
export function setExt(ext, poly) {
|
||||||
checkPoly(poly);
|
checkPoly(poly);
|
||||||
const newPoly = {
|
const newPoly = {
|
||||||
@ -129,7 +130,7 @@ export function setExt(ext, poly) {
|
|||||||
return newPoly;
|
return newPoly;
|
||||||
}
|
}
|
||||||
|
|
||||||
// setName(contents: String, poly: PolyVinyl) => PolyVinyl
|
// setName(name: String, poly: PolyVinyl) => PolyVinyl
|
||||||
export function setName(name, poly) {
|
export function setName(name, poly) {
|
||||||
checkPoly(poly);
|
checkPoly(poly);
|
||||||
const newPoly = {
|
const newPoly = {
|
||||||
@ -142,7 +143,7 @@ export function setName(name, poly) {
|
|||||||
return newPoly;
|
return newPoly;
|
||||||
}
|
}
|
||||||
|
|
||||||
// setError(contents: String, poly: PolyVinyl) => PolyVinyl
|
// setError(error: Object, poly: PolyVinyl) => PolyVinyl
|
||||||
export function setError(error, poly) {
|
export function setError(error, poly) {
|
||||||
invariant(
|
invariant(
|
||||||
typeof error === 'object',
|
typeof error === 'object',
|
||||||
@ -155,6 +156,19 @@ export function setError(error, poly) {
|
|||||||
error
|
error
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// setTransformError(transformError: Boolean, poly: PolyVinyl) => PolyVinyl
|
||||||
|
export function setTransformError(transformError, poly) {
|
||||||
|
invariant(
|
||||||
|
typeof transformError === 'boolean',
|
||||||
|
'transformError must be a boolean, but got %',
|
||||||
|
transformError
|
||||||
|
);
|
||||||
|
checkPoly(poly);
|
||||||
|
return {
|
||||||
|
...poly,
|
||||||
|
transformError
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// clearHeadTail(poly: PolyVinyl) => PolyVinyl
|
// clearHeadTail(poly: PolyVinyl) => PolyVinyl
|
||||||
export function clearHeadTail(poly) {
|
export function clearHeadTail(poly) {
|
||||||
@ -166,6 +180,7 @@ export function clearHeadTail(poly) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// appendToTail (tail: String, poly: PolyVinyl) => PolyVinyl
|
||||||
export function appendToTail(tail, poly) {
|
export function appendToTail(tail, poly) {
|
||||||
checkPoly(poly);
|
checkPoly(poly);
|
||||||
return {
|
return {
|
||||||
@ -174,7 +189,7 @@ export function appendToTail(tail, poly) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// compileHeadTail(contents: String, poly: PolyVinyl) => PolyVinyl
|
// compileHeadTail(padding: String, poly: PolyVinyl) => PolyVinyl
|
||||||
export function compileHeadTail(padding = '', poly) {
|
export function compileHeadTail(padding = '', poly) {
|
||||||
return clearHeadTail(transformContents(
|
return clearHeadTail(transformContents(
|
||||||
() => [ poly.head, poly.contents, poly.tail ].join(padding),
|
() => [ poly.head, poly.contents, poly.tail ].join(padding),
|
||||||
|
Reference in New Issue
Block a user