fix: insert user html more consistently (#42195)
* fix: use an iframe to preserve head and body * fix: remove unnecessary parsing of html The contents gets inserted into the DOM during transformHtml, which is always part of the build pipeline * fix: pipe contents through iframe * refactor: use the same code for both transforms * fix: try to handle test errors better Co-authored-by: moT01 <20648924+moT01@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
76529a17ba
commit
b1fb6adc39
@@ -14,13 +14,6 @@ import {
|
||||
transformContents
|
||||
} from '../../../../../utils/polyvinyl';
|
||||
|
||||
const defaultTemplate = ({ source }) => {
|
||||
return `
|
||||
<body id='display-body'>
|
||||
${source}
|
||||
</body>`;
|
||||
};
|
||||
|
||||
const wrapInScript = partial(
|
||||
transformContents,
|
||||
content => `<script>${content}</script>`
|
||||
@@ -67,7 +60,7 @@ export function concatHtml({
|
||||
template,
|
||||
challengeFiles = []
|
||||
} = {}) {
|
||||
const createBody = template ? _template(template) : defaultTemplate;
|
||||
const embedSource = template ? _template(template) : ({ source }) => source;
|
||||
const head = required
|
||||
.map(({ link, src }) => {
|
||||
if (link && src) {
|
||||
@@ -99,5 +92,5 @@ A required file can not have both a src and a link: src = ${src}, link = ${link}
|
||||
}
|
||||
}, '');
|
||||
|
||||
return `<head>${head}</head>${createBody({ source })}`;
|
||||
return `<head>${head}</head>${embedSource({ source })}`;
|
||||
}
|
||||
|
@@ -180,10 +180,12 @@ function getBabelOptions({ preview = false, protect = true }) {
|
||||
}
|
||||
|
||||
const sassWorker = createWorker(sassCompile);
|
||||
async function transformSASS(element) {
|
||||
async function transformSASS(contentDocument) {
|
||||
// we only teach scss syntax, not sass. Also the compiler does not seem to be
|
||||
// able to deal with sass.
|
||||
const styleTags = element.querySelectorAll('style[type~="text/scss"]');
|
||||
const styleTags = contentDocument.querySelectorAll(
|
||||
'style[type~="text/scss"]'
|
||||
);
|
||||
await Promise.all(
|
||||
[].map.call(styleTags, async style => {
|
||||
style.type = 'text/css';
|
||||
@@ -192,10 +194,10 @@ async function transformSASS(element) {
|
||||
);
|
||||
}
|
||||
|
||||
async function transformScript(element) {
|
||||
async function transformScript(contentDocument) {
|
||||
await loadBabel();
|
||||
await loadPresetEnv();
|
||||
const scriptTags = element.querySelectorAll('script');
|
||||
const scriptTags = contentDocument.querySelectorAll('script');
|
||||
scriptTags.forEach(script => {
|
||||
script.innerHTML = tryTransform(babelTransformCode(babelOptionsJS))(
|
||||
script.innerHTML
|
||||
@@ -203,59 +205,86 @@ async function transformScript(element) {
|
||||
});
|
||||
}
|
||||
|
||||
const transformHtml = async function (file) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = file.contents;
|
||||
await Promise.all([transformSASS(div), transformScript(div)]);
|
||||
return transformContents(() => div.innerHTML, file);
|
||||
};
|
||||
|
||||
// Find if the base html refers to the css or js files and record if they do. If
|
||||
// the link or script exists we remove those elements since those files don't
|
||||
// exist on the site, only in the editor
|
||||
const transformIncludes = async function (fileP) {
|
||||
const addImportedFiles = async function (fileP) {
|
||||
const file = await fileP;
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = file.contents;
|
||||
const link =
|
||||
div.querySelector('link[href="styles.css"]') ??
|
||||
div.querySelector('link[href="./styles.css"]');
|
||||
const script =
|
||||
div.querySelector('script[src="script.js"]') ??
|
||||
div.querySelector('script[src="./script.js"]');
|
||||
const importedFiles = [];
|
||||
if (link) {
|
||||
importedFiles.push('styles.css');
|
||||
link.remove();
|
||||
}
|
||||
if (script) {
|
||||
importedFiles.push('script.js');
|
||||
script.remove();
|
||||
}
|
||||
const transform = frame => {
|
||||
const documentElement = frame.contentDocument.documentElement;
|
||||
const link =
|
||||
documentElement.querySelector('link[href="styles.css"]') ??
|
||||
documentElement.querySelector('link[href="./styles.css"]');
|
||||
const script =
|
||||
documentElement.querySelector('script[src="script.js"]') ??
|
||||
documentElement.querySelector('script[src="./script.js"]');
|
||||
const importedFiles = [];
|
||||
if (link) {
|
||||
importedFiles.push('styles.css');
|
||||
link.remove();
|
||||
}
|
||||
if (script) {
|
||||
importedFiles.push('script.js');
|
||||
script.remove();
|
||||
}
|
||||
return {
|
||||
contents: documentElement.innerHTML,
|
||||
importedFiles
|
||||
};
|
||||
};
|
||||
|
||||
const { importedFiles, contents } = await transformWithFrame(
|
||||
transform,
|
||||
file.contents
|
||||
);
|
||||
|
||||
return flow(
|
||||
partial(setImportedFiles, importedFiles),
|
||||
partial(transformContents, () => div.innerHTML)
|
||||
partial(transformContents, () => contents)
|
||||
)(file);
|
||||
};
|
||||
|
||||
export const composeHTML = cond([
|
||||
[
|
||||
testHTML,
|
||||
flow(
|
||||
partial(transformHeadTailAndContents, source => {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = source;
|
||||
return div.innerHTML;
|
||||
}),
|
||||
partial(compileHeadTail, '')
|
||||
)
|
||||
],
|
||||
const transformWithFrame = async function (transform, contents) {
|
||||
// we use iframe here since file.contents is destined to be be inserted into
|
||||
// the root of an iframe.
|
||||
const frame = document.createElement('iframe');
|
||||
frame.style = 'display: none';
|
||||
let out = { contents };
|
||||
try {
|
||||
// the frame needs to be inserted into the document to create the html
|
||||
// element
|
||||
document.body.appendChild(frame);
|
||||
// replace the root element with user code
|
||||
frame.contentDocument.documentElement.innerHTML = contents;
|
||||
// grab the contents now, in case the transformation fails
|
||||
out = { contents: frame.contentDocument.documentElement.innerHTML };
|
||||
out = await transform(frame);
|
||||
} finally {
|
||||
document.body.removeChild(frame);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const transformHtml = async function (file) {
|
||||
const transform = async frame => {
|
||||
await Promise.all([
|
||||
transformSASS(frame.contentDocument),
|
||||
transformScript(frame.contentDocument)
|
||||
]);
|
||||
return { contents: frame.contentDocument.documentElement.innerHTML };
|
||||
};
|
||||
|
||||
const { contents } = await transformWithFrame(transform, file.contents);
|
||||
return transformContents(() => contents, file);
|
||||
};
|
||||
|
||||
const composeHTML = cond([
|
||||
[testHTML, partial(compileHeadTail, '')],
|
||||
[stubTrue, identity]
|
||||
]);
|
||||
|
||||
export const htmlTransformer = cond([
|
||||
[testHTML, flow(transformHtml, transformIncludes)],
|
||||
const htmlTransformer = cond([
|
||||
[testHTML, flow(transformHtml, addImportedFiles)],
|
||||
[stubTrue, identity]
|
||||
]);
|
||||
|
||||
|
Reference in New Issue
Block a user