Skip to content
Permalink
Browse files

feat: Isolate iframe code from parent window (#57)

fixes #45
  • Loading branch information...
markdalgleish committed Apr 8, 2019
1 parent 9ccae36 commit 03e41a3dfe715caef01b948548ece40c8dc50468
@@ -0,0 +1,3 @@
cypress/plugins/
cypress/fixtures/
dist
@@ -4,7 +4,8 @@ export const typeCode = code =>
.click() .click()
.focused() .focused()
.clear({ force: true }) .clear({ force: true })
.type(code, { force: true, delay: 100 }); .type(code, { force: true, delay: 100 })
.wait(1000);


export const assertFrameContains = async text => { export const assertFrameContains = async text => {
const iframe = await cy.get('iframe').first(); const iframe = await cy.get('iframe').first();
@@ -10,7 +10,7 @@ module.exports = {
components: 'braid-design-system', components: 'braid-design-system',
themes: 'braid-design-system/lib/themes', themes: 'braid-design-system/lib/themes',
frameComponent: './playroom/FrameComponent.js', frameComponent: './playroom/FrameComponent.js',
widths: [320, 375, 768, 1024], widths: [320, 1024],
exampleCode: ` exampleCode: `
<ChecklistCard> <ChecklistCard>
<Checkbox id="1" label="This is a checkbox" message={false} onChange={() => {}}> <Checkbox id="1" label="This is a checkbox" message={false} onChange={() => {}}>
@@ -18,18 +18,21 @@ module.exports = async (playroomConfig, options) => {


const staticTypes = await getStaticTypes(playroomConfig); const staticTypes = await getStaticTypes(playroomConfig);


const devServerEntries = options.production
? []
: [
`webpack-dev-server/client?http://localhost:${playroomConfig.port}`,
'webpack/hot/dev-server'
];

const ourConfig = { const ourConfig = {
mode: options.production ? 'production' : 'development', mode: options.production ? 'production' : 'development',
entry: [ entry: {
...(options.production index: [...devServerEntries, require.resolve('../src/index.js')],
? [] frame: [...devServerEntries, require.resolve('../src/frame.js')]
: [ },
`webpack-dev-server/client?http://localhost:${playroomConfig.port}`,
'webpack/hot/dev-server'
]),
require.resolve('../src/index.js')
],
output: { output: {
filename: '[name].[hash].js',
path: path.resolve(playroomConfig.cwd, playroomConfig.outputPath), path: path.resolve(playroomConfig.cwd, playroomConfig.outputPath),
publicPath: '' publicPath: ''
}, },
@@ -102,6 +105,14 @@ module.exports = async (playroomConfig, options) => {
} }
] ]
}, },
optimization: {
splitChunks: {
chunks: 'all'
},
runtimeChunk: {
name: 'runtime'
}
},
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
__PLAYROOM_GLOBAL__CONFIG__: JSON.stringify(playroomConfig), __PLAYROOM_GLOBAL__CONFIG__: JSON.stringify(playroomConfig),
@@ -111,7 +122,15 @@ module.exports = async (playroomConfig, options) => {
title: playroomConfig.title title: playroomConfig.title
? `Playroom | ${playroomConfig.title}` ? `Playroom | ${playroomConfig.title}`
: 'Playroom', : 'Playroom',
chunksSortMode: 'none' chunksSortMode: 'none',
chunks: ['index'],
filename: 'index.html'
}),
new HtmlWebpackPlugin({
title: 'Playroom Frame',
chunksSortMode: 'none',
chunks: ['frame'],
filename: 'frame.html'
}), }),
...(options.production ...(options.production
? [] ? []
@@ -57,7 +57,6 @@
"@babel/core": "^7.1.0", "@babel/core": "^7.1.0",
"@babel/preset-env": "^7.2.0", "@babel/preset-env": "^7.2.0",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"acorn-jsx": "^4.1.1",
"babel-loader": "^8.0.2", "babel-loader": "^8.0.2",
"babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-class-properties": "^6.24.1",
"base64-url": "^2.2.0", "base64-url": "^2.2.0",
@@ -71,7 +70,7 @@
"fast-glob": "^2.2.4", "fast-glob": "^2.2.4",
"find-up": "^3.0.0", "find-up": "^3.0.0",
"friendly-errors-webpack-plugin": "^1.7.0", "friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^4.0.0-beta.5",
"less": "^3.8.1", "less": "^3.8.1",
"less-loader": "^4.1.0", "less-loader": "^4.1.0",
"localforage": "^1.7.2", "localforage": "^1.7.2",
@@ -87,7 +86,6 @@
"react-codemirror": "^1.0.0", "react-codemirror": "^1.0.0",
"react-docgen-typescript": "^1.12.2", "react-docgen-typescript": "^1.12.2",
"react-dom": "^16.5.2", "react-dom": "^16.5.2",
"react-frame-component": "^4.0.1",
"read-pkg-up": "^5.0.0", "read-pkg-up": "^5.0.0",
"scope-eval": "^1.0.0", "scope-eval": "^1.0.0",
"style-loader": "^0.23.0", "style-loader": "^0.23.0",
@@ -4,33 +4,36 @@ import styles from './CatchErrors.less';


export default class CatchErrors extends Component { export default class CatchErrors extends Component {
static propTypes = { static propTypes = {
code: PropTypes.string.isRequired,
children: PropTypes.node.isRequired children: PropTypes.node.isRequired
}; };


state = { state = {
error: null, error: null,
invalidCode: null,
info: null info: null
}; };


componentDidCatch(error, info) { componentDidCatch(error, info) {
this.setState({ error, info }); const { code } = this.props;
this.setState({ invalidCode: code, error, info });
} }


render() { render() {
const { error, info } = this.state; const { invalidCode, error, info } = this.state;
const { children } = this.props; const { code, children } = this.props;


if (!error) { if (code !== invalidCode) {
return children; return children;
} }


// Ensure the stack only contains user-provided components // Ensure the stack only contains user-provided components
const componentStack = info.componentStack const componentStack = info.componentStack
.split('\n') .split('\n')
.filter(line => /RenderJsx/.test(line)) .filter(line => /RenderCode/.test(line))
.map(line => line.replace(/ \(created by .*/g, '')); .map(line => line.replace(/ \(created by .*/g, ''));


// Ignore the RenderJsx container component // Ignore the RenderCode container component
const lines = componentStack.slice(0, componentStack.length - 1); const lines = componentStack.slice(0, componentStack.length - 1);


return ( return (

This file was deleted.

This file was deleted.

@@ -0,0 +1,73 @@
import React, { Component } from 'react';
import queryString from 'query-string';
import CatchErrors from './CatchErrors/CatchErrors';
import RenderCode from './RenderCode/RenderCode';

const themesImport = require('./themes');
const componentsImport = require('./components');
const frameComponentImport = require('./frameComponent');

const getQueryParams = () => {
try {
const hash = window.location.hash.replace(/^#/, '');
return queryString.parse(hash);
} catch (err) {
return {};
}
};

export default class Frame extends Component {
constructor(props) {
super(props);

const { themeName, code = '' } = getQueryParams();

this.state = {
themeName,
themes: themesImport,
components: componentsImport,
FrameComponent: frameComponentImport,
code
};
}

componentDidMount() {
window.addEventListener('hashchange', () => {
const { themeName, code } = getQueryParams();

if (themeName && code) {
this.setState({ themeName, code });
}
});

if (module.hot) {
module.hot.accept('./themes', () => {
this.setState({ themes: require('./themes') });
});

module.hot.accept('./components', () => {
this.setState({ components: require('./components') });
});

module.hot.accept('./frameComponent', () => {
this.setState({ frameComponent: require('./frameComponent') });
});
}
}

render() {
const { FrameComponent, themes, themeName, components, code } = this.state;

const resolvedThemeName =
themeName === '__PLAYROOM__NO_THEME__' ? null : themeName;
const resolvedTheme = resolvedThemeName ? themes[resolvedThemeName] : null;

return (
<CatchErrors code={code}>
<FrameComponent themeName={resolvedThemeName} theme={resolvedTheme}>
<RenderCode code={code} scope={components} />
</FrameComponent>
</CatchErrors>
);
}
}

0 comments on commit 03e41a3

Please sign in to comment.
You can’t perform that action at this time.