mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-06-13 21:02:08 +08:00
feat(client): import user's react file into html (#57184)
Co-authored-by: moT01 <20648924+moT01@users.noreply.github.com>
This commit is contained in:
parent
69eac7d671
commit
3b23d32396
@ -106,10 +106,10 @@ const MultifileEditor = (props: MultifileEditorProps) => {
|
||||
|
||||
const editorKeys = [];
|
||||
|
||||
if (indexjsx) editorKeys.push('indexjsx');
|
||||
if (indexhtml) editorKeys.push('indexhtml');
|
||||
if (stylescss) editorKeys.push('stylescss');
|
||||
if (scriptjs) editorKeys.push('scriptjs');
|
||||
if (indexjsx) editorKeys.push('indexjsx');
|
||||
if (mainpy) editorKeys.push('mainpy');
|
||||
if (indexts) editorKeys.push('indexts');
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@ const protectTimeout = 100;
|
||||
const testProtectTimeout = 1500;
|
||||
const loopsPerTimeoutCheck = 100;
|
||||
const testLoopsPerTimeoutCheck = 2000;
|
||||
const MODULE_TRANSFORM_PLUGIN = 'transform-modules-umd';
|
||||
|
||||
function loopProtectCB(line) {
|
||||
console.log(
|
||||
@ -132,6 +133,18 @@ const getJSXTranspiler = loopProtectOptions => async challengeFile => {
|
||||
);
|
||||
};
|
||||
|
||||
const getJSXModuleTranspiler = loopProtectOptions => async challengeFile => {
|
||||
await loadBabel();
|
||||
await loadPresetReact();
|
||||
const baseOptions = getBabelOptions(presetsJSX, loopProtectOptions);
|
||||
const babelOptions = {
|
||||
...baseOptions,
|
||||
plugins: [...baseOptions.plugins, MODULE_TRANSFORM_PLUGIN],
|
||||
moduleId: 'index' // TODO: this should be dynamic
|
||||
};
|
||||
return transformContents(babelTransformCode(babelOptions), challengeFile);
|
||||
};
|
||||
|
||||
const getTSTranspiler = loopProtectOptions => async challengeFile => {
|
||||
await loadBabel();
|
||||
await checkTSServiceIsReady();
|
||||
@ -147,7 +160,15 @@ const createTranspiler = loopProtectOptions => {
|
||||
[testJS, getJSTranspiler(loopProtectOptions)],
|
||||
[testJSX, getJSXTranspiler(loopProtectOptions)],
|
||||
[testTypeScript, getTSTranspiler(loopProtectOptions)],
|
||||
[testHTML, transformHtml],
|
||||
[testHTML, getHtmlTranspiler({ useModules: false })],
|
||||
[stubTrue, identity]
|
||||
]);
|
||||
};
|
||||
|
||||
const createModuleTransformer = loopProtectOptions => {
|
||||
return cond([
|
||||
[testJSX, getJSXModuleTranspiler(loopProtectOptions)],
|
||||
[testHTML, getHtmlTranspiler({ useModules: true })],
|
||||
[stubTrue, identity]
|
||||
]);
|
||||
};
|
||||
@ -186,7 +207,7 @@ async function transformSASS(documentElement) {
|
||||
);
|
||||
}
|
||||
|
||||
async function transformScript(documentElement) {
|
||||
async function transformScript(documentElement, { useModules }) {
|
||||
await loadBabel();
|
||||
await loadPresetEnv();
|
||||
await loadPresetReact();
|
||||
@ -196,12 +217,19 @@ async function transformScript(documentElement) {
|
||||
// TODO: make the use of JSX conditional on more than just the script type.
|
||||
// It should only be used for React challenges since it would be confusing
|
||||
// for learners to see the results of a transformation they didn't ask for.
|
||||
const options = isBabel ? presetsJSX : presetsJS;
|
||||
const baseOptions = isBabel ? presetsJSX : presetsJS;
|
||||
|
||||
if (isBabel) script.removeAttribute('type'); // otherwise the browser will ignore the script
|
||||
script.innerHTML = babelTransformCode(getBabelOptions(options))(
|
||||
script.innerHTML
|
||||
);
|
||||
const options = {
|
||||
...baseOptions,
|
||||
...(useModules && { plugins: [MODULE_TRANSFORM_PLUGIN] })
|
||||
};
|
||||
|
||||
// The type has to be removed, otherwise the browser will ignore the script.
|
||||
// However, if we're importing modules, the type will be removed when the
|
||||
// scripts are embedded in the HTML.
|
||||
if (isBabel && !useModules) script.removeAttribute('type');
|
||||
|
||||
script.innerHTML = babelTransformCode(options)(script.innerHTML);
|
||||
});
|
||||
}
|
||||
|
||||
@ -222,6 +250,15 @@ export const embedFilesInHtml = async function (challengeFiles) {
|
||||
const tsScript =
|
||||
documentElement.querySelector('script[src="index.ts"]') ??
|
||||
documentElement.querySelector('script[src="./index.ts"]');
|
||||
|
||||
const jsxScript =
|
||||
documentElement.querySelector(
|
||||
`script[data-plugins="${MODULE_TRANSFORM_PLUGIN}"][type="text/babel"][src="index.jsx"]`
|
||||
) ??
|
||||
documentElement.querySelector(
|
||||
`script[data-plugins="${MODULE_TRANSFORM_PLUGIN}"][type="text/babel"][src="./index.jsx"]`
|
||||
);
|
||||
|
||||
if (link) {
|
||||
const style = contentDocument.createElement('style');
|
||||
style.classList.add('fcc-injected-styles');
|
||||
@ -242,6 +279,13 @@ export const embedFilesInHtml = async function (challengeFiles) {
|
||||
tsScript.removeAttribute('src');
|
||||
tsScript.setAttribute('data-src', 'index.ts');
|
||||
}
|
||||
if (jsxScript) {
|
||||
jsxScript.innerHTML = indexJsx?.contents;
|
||||
jsxScript.removeAttribute('src');
|
||||
jsxScript.removeAttribute('type');
|
||||
jsxScript.setAttribute('data-src', 'index.jsx');
|
||||
jsxScript.setAttribute('data-type', 'text/babel');
|
||||
}
|
||||
return documentElement.innerHTML;
|
||||
};
|
||||
|
||||
@ -278,18 +322,19 @@ const parseAndTransform = async function (transform, contents) {
|
||||
return await transform(newDoc.documentElement, newDoc);
|
||||
};
|
||||
|
||||
const transformHtml = async function (file) {
|
||||
const transform = async documentElement => {
|
||||
await Promise.all([
|
||||
transformSASS(documentElement),
|
||||
transformScript(documentElement)
|
||||
]);
|
||||
return documentElement.innerHTML;
|
||||
};
|
||||
const getHtmlTranspiler = scriptOptions =>
|
||||
async function (file) {
|
||||
const transform = async documentElement => {
|
||||
await Promise.all([
|
||||
transformSASS(documentElement),
|
||||
transformScript(documentElement, scriptOptions)
|
||||
]);
|
||||
return documentElement.innerHTML;
|
||||
};
|
||||
|
||||
const contents = await parseAndTransform(transform, file.contents);
|
||||
return transformContents(() => contents, file);
|
||||
};
|
||||
const contents = await parseAndTransform(transform, file.contents);
|
||||
return transformContents(() => contents, file);
|
||||
};
|
||||
|
||||
export const getTransformers = loopProtectOptions => [
|
||||
createSource,
|
||||
@ -298,6 +343,12 @@ export const getTransformers = loopProtectOptions => [
|
||||
partial(compileHeadTail, '')
|
||||
];
|
||||
|
||||
export const getMultifileJSXTransformers = loopProtectOptions => [
|
||||
createSource,
|
||||
replaceNBSP,
|
||||
createModuleTransformer(loopProtectOptions)
|
||||
];
|
||||
|
||||
export const getPythonTransformers = () => [
|
||||
createSource,
|
||||
replaceNBSP,
|
||||
|
||||
@ -8,7 +8,8 @@ import { concatHtml } from '../rechallenge/builders';
|
||||
import {
|
||||
getTransformers,
|
||||
embedFilesInHtml,
|
||||
getPythonTransformers
|
||||
getPythonTransformers,
|
||||
getMultifileJSXTransformers
|
||||
} from '../rechallenge/transformers';
|
||||
import {
|
||||
createTestFramer,
|
||||
@ -227,11 +228,18 @@ export async function buildDOMChallenge(
|
||||
): Promise<BuildResult> {
|
||||
// TODO: make this required in the schema.
|
||||
if (!challengeFiles) throw Error('No challenge files provided');
|
||||
const loadEnzyme = challengeFiles.some(
|
||||
const hasJsx = challengeFiles.some(
|
||||
challengeFile => challengeFile.ext === 'jsx'
|
||||
);
|
||||
const isMultifile = challengeFiles.length > 1;
|
||||
|
||||
const pipeLine = composeFunctions(...getTransformers(options));
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const transformers =
|
||||
isMultifile && hasJsx
|
||||
? getMultifileJSXTransformers(options)
|
||||
: getTransformers(options);
|
||||
|
||||
const pipeLine = composeFunctions(...transformers);
|
||||
const usesTestRunner = options?.usesTestRunner ?? false;
|
||||
const finalFiles = await Promise.all(challengeFiles.map(pipeLine));
|
||||
const error = finalFiles.find(({ error }) => error)?.error;
|
||||
@ -257,7 +265,7 @@ export async function buildDOMChallenge(
|
||||
challengeType: challengeTypes.html,
|
||||
build: concatHtml(toBuild),
|
||||
sources: buildSourceMap(embeddedFiles),
|
||||
loadEnzyme,
|
||||
loadEnzyme: hasJsx,
|
||||
error
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user