feat(ts-migrate): migrate sass-compile.js and test-evaluator.js to ts (#43145)

* feat(ts-migrate): rename sass-compile.js to ts

* feat(ts-migrate): rename test-evaluator.js to ts

* feat(ts-migrate): add webworker in client tsconfig

* fix(ts-migrate): fix errors in sass-compile.ts

* chore(ts-migrate): install chai types in client

* fix(ts-migrate): fix errors in test-evaluator.ts

* fix(ts-migrate): rename extensions in webpack-worker

* fix(ts-migrate): separate tsconfig for workers

* fix(ts-migrate): add worker tsconfig to parser options

* chore(ts-migrate): remove unnecessary comment

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

* fix(ts-migrate): use let instead of const

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

* fix(ts-migrate): fix eslint errors in sass-compile.ts

* fix(ts-migrate): fix eslint errors in test-evaluator.ts

* chore(ts-migrate): use unknown instead of generics

* chore(ts-migrate): revert worker tsconfig

* chore(ts-migrate): add libs in client tsconfig

* fix(ts-migrate): use ctx alias in test-evaluator.ts

* fix(ts-migrate): use ctx alias in sass-compile.ts

* chore: fix errors

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Meron Ogbai
2021-10-14 21:44:55 +03:00
committed by GitHub
parent 9bb1919e83
commit 06518c04a7
7 changed files with 102 additions and 53 deletions

View File

@ -5013,6 +5013,12 @@
"@types/responselike": "*" "@types/responselike": "*"
} }
}, },
"@types/chai": {
"version": "4.2.21",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.21.tgz",
"integrity": "sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg==",
"dev": true
},
"@types/common-tags": { "@types/common-tags": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.1.tgz", "resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.1.tgz",

View File

@ -131,6 +131,7 @@
"@codesee/tracker": "0.112.1", "@codesee/tracker": "0.112.1",
"@testing-library/jest-dom": "5.14.1", "@testing-library/jest-dom": "5.14.1",
"@testing-library/react": "12.1.2", "@testing-library/react": "12.1.2",
"@types/chai": "^4.2.21",
"@types/jest": "26.0.24", "@types/jest": "26.0.24",
"@types/loadable__component": "5.13.4", "@types/loadable__component": "5.13.4",
"@types/lodash-es": "4.17.5", "@types/lodash-es": "4.17.5",

View File

@ -1,27 +0,0 @@
// work around for SASS error in Edge
// https://github.com/medialize/sass.js/issues/96#issuecomment-424386171
if (!self.crypto) {
self.crypto = {
getRandomValues: function (array) {
for (var i = 0, l = array.length; i < l; i++) {
array[i] = Math.floor(Math.random() * 256);
}
return array;
}
};
}
self.importScripts('/js/sass.sync.js');
self.onmessage = e => {
const data = e.data;
self.Sass.compile(data, result => {
if (result.status === 0) {
self.postMessage(result.text);
} else {
self.postMessage({ type: 'error', data: { message: result.formatted } });
}
});
};
self.postMessage({ type: 'contentLoaded' });

View File

@ -0,0 +1,40 @@
/* eslint-disable import/unambiguous */
// work around for SASS error in Edge
// https://github.com/medialize/sass.js/issues/96#issuecomment-424386171
interface WorkerWithSass extends Worker {
Sass: {
compile(data: unknown, callback: unknown): void;
};
}
const ctx: WorkerWithSass & typeof globalThis =
self as unknown as WorkerWithSass & typeof globalThis;
if (!ctx.crypto) {
(ctx.crypto as unknown) = {
getRandomValues: function (array: number[]) {
for (let i = 0, l = array.length; i < l; i++) {
array[i] = Math.floor(Math.random() * 256);
}
return array;
}
};
}
ctx.importScripts('/js/sass.sync.js');
ctx.onmessage = e => {
const data: unknown = e.data;
ctx.Sass.compile(data, (result: Record<string, unknown>) => {
if (result.status === 0) {
ctx.postMessage(result.text);
} else {
ctx.postMessage({
type: 'error',
data: { message: result.formatted }
});
}
});
};
ctx.postMessage({ type: 'contentLoaded' });

View File

@ -6,13 +6,16 @@ import curriculumHelpers, {
} from '../../utils/curriculum-helpers'; } from '../../utils/curriculum-helpers';
import { format as __format } from '../../utils/format'; import { format as __format } from '../../utils/format';
const ctx: Worker & typeof globalThis = self as unknown as Worker &
typeof globalThis;
const __utils = (() => { const __utils = (() => {
const MAX_LOGS_SIZE = 64 * 1024; const MAX_LOGS_SIZE = 64 * 1024;
let logs = []; let logs: string[] = [];
function flushLogs() { function flushLogs() {
if (logs.length) { if (logs.length) {
self.postMessage({ ctx.postMessage({
type: 'LOG', type: 'LOG',
data: logs.join('\n') data: logs.join('\n')
}); });
@ -20,8 +23,8 @@ const __utils = (() => {
} }
} }
const oldLog = self.console.log.bind(self.console); const oldLog = ctx.console.log.bind(ctx.console);
function proxyLog(...args) { function proxyLog(...args: string[]) {
logs.push(args.map(arg => __format(arg)).join(' ')); logs.push(args.map(arg => __format(arg)).join(' '));
if (logs.join('\n').length > MAX_LOGS_SIZE) { if (logs.join('\n').length > MAX_LOGS_SIZE) {
flushLogs(); flushLogs();
@ -30,12 +33,12 @@ const __utils = (() => {
} }
// unless data.type is truthy, this sends data out to the testRunner // unless data.type is truthy, this sends data out to the testRunner
function postResult(data) { function postResult(data: unknown) {
flushLogs(); flushLogs();
self.postMessage(data); ctx.postMessage(data);
} }
function log(...msgs) { function log(...msgs: Error[]) {
if (msgs && msgs[0] && !(msgs[0] instanceof chai.AssertionError)) { if (msgs && msgs[0] && !(msgs[0] instanceof chai.AssertionError)) {
// discards the stack trace via toString as it only useful to debug the // discards the stack trace via toString as it only useful to debug the
// site, not a specific challenge. // site, not a specific challenge.
@ -43,8 +46,8 @@ const __utils = (() => {
} }
} }
const toggleProxyLogger = on => { const toggleProxyLogger = (on: unknown) => {
self.console.log = on ? proxyLog : oldLog; ctx.console.log = on ? proxyLog : oldLog;
}; };
return { return {
@ -55,9 +58,25 @@ const __utils = (() => {
}; };
})(); })();
interface TestEvaluatorEvent extends MessageEvent {
data: {
code: {
contents: string;
editableContents: string;
};
removeComments: boolean;
firstTest: unknown;
testString: string;
build: string;
sources: {
[fileName: string]: unknown;
};
};
}
/* Run the test if there is one. If not just evaluate the user code */ /* Run the test if there is one. If not just evaluate the user code */
self.onmessage = async e => { ctx.onmessage = async (e: TestEvaluatorEvent) => {
/* eslint-disable no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
let code = (e.data?.code?.contents || '').slice(); let code = (e.data?.code?.contents || '').slice();
code = e.data?.removeComments ? removeJSComments(code) : code; code = e.data?.removeComments ? removeJSComments(code) : code;
let editableContents = (e.data?.code?.editableContents || '').slice(); let editableContents = (e.data?.code?.editableContents || '').slice();
@ -68,13 +87,16 @@ self.onmessage = async e => {
const assert = chai.assert; const assert = chai.assert;
const __helpers = curriculumHelpers; const __helpers = curriculumHelpers;
// Fake Deep Equal dependency // Fake Deep Equal dependency
const DeepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b); const DeepEqual = (a: unknown, b: unknown) =>
JSON.stringify(a) === JSON.stringify(b);
// Build errors should be reported, but only once: // Build errors should be reported, but only once:
__utils.toggleProxyLogger(e.data.firstTest); __utils.toggleProxyLogger(e.data.firstTest);
/* eslint-enable no-unused-vars */ /* eslint-enable @typescript-eslint/no-unused-vars */
try { try {
let testResult; let testResult;
// This can be reassigned by the eval inside the try block, so it should be declared as a let
// eslint-disable-next-line prefer-const
let __userCodeWasExecuted = false; let __userCodeWasExecuted = false;
/* eslint-disable no-eval */ /* eslint-disable no-eval */
try { try {
@ -86,7 +108,7 @@ self.onmessage = async e => {
__utils.flushLogs(); __utils.flushLogs();
__userCodeWasExecuted = true; __userCodeWasExecuted = true;
__utils.toggleProxyLogger(true); __utils.toggleProxyLogger(true);
${e.data.testString}`); ${e.data.testString}`) as unknown;
} catch (err) { } catch (err) {
if (__userCodeWasExecuted) { if (__userCodeWasExecuted) {
// rethrow error, since test failed. // rethrow error, since test failed.
@ -95,19 +117,21 @@ ${e.data.testString}`);
// log build errors unless they're related to import/export/require (there // log build errors unless they're related to import/export/require (there
// are challenges that use them and they should not trigger warnings) // are challenges that use them and they should not trigger warnings)
if ( if (
err.name !== 'ReferenceError' || (err as Error).name !== 'ReferenceError' ||
(err.message !== 'require is not defined' && ((err as Error).message !== 'require is not defined' &&
err.message !== 'exports is not defined') (err as Error).message !== 'exports is not defined')
) { ) {
__utils.log(err); __utils.log(err as Error);
} }
// the tests may not require working code, so they are evaluated even if // the tests may not require working code, so they are evaluated even if
// the user code does not get executed. // the user code does not get executed.
testResult = eval(e.data.testString); testResult = eval(e.data.testString) as unknown;
} }
/* eslint-enable no-eval */ /* eslint-enable no-eval */
if (typeof testResult === 'function') { if (typeof testResult === 'function') {
await testResult(fileName => __toString(e.data.sources[fileName])); await testResult((fileName: string) =>
__toString(e.data.sources[fileName])
);
} }
__utils.postResult({ __utils.postResult({
pass: true pass: true
@ -117,15 +141,15 @@ ${e.data.testString}`);
__utils.toggleProxyLogger(false); __utils.toggleProxyLogger(false);
// Report execution errors in case user code has errors that are only // Report execution errors in case user code has errors that are only
// uncovered during testing. // uncovered during testing.
__utils.log(err); __utils.log(err as Error);
// postResult flushes the logs and must be called after logging is finished. // postResult flushes the logs and must be called after logging is finished.
__utils.postResult({ __utils.postResult({
err: { err: {
message: err.message, message: (err as Error).message,
stack: err.stack stack: (err as Error).stack
} }
}); });
} }
}; };
self.postMessage({ type: 'contentLoaded' }); ctx.postMessage({ type: 'contentLoaded' });

View File

@ -1,6 +1,11 @@
{ {
"include": ["./i18n/**/*", "./plugins/**/*", "./src/**/*", "./utils/**/*"], "include": ["./i18n/**/*", "./plugins/**/*", "./src/**/*", "./utils/**/*"],
"compilerOptions": { "compilerOptions": {
"lib": [
"WebWorker",
"DOM",
"DOM.Iterable"
],
"target": "es2020", "target": "es2020",
"module": "es2020", "module": "es2020",
"moduleResolution": "node", "moduleResolution": "node",

View File

@ -12,8 +12,8 @@ module.exports = (env = {}) => {
mode: __DEV__ ? 'development' : 'production', mode: __DEV__ ? 'development' : 'production',
entry: { entry: {
'frame-runner': './src/client/frame-runner.js', 'frame-runner': './src/client/frame-runner.js',
'sass-compile': './src/client/workers/sass-compile.js', 'sass-compile': './src/client/workers/sass-compile.ts',
'test-evaluator': './src/client/workers/test-evaluator.js' 'test-evaluator': './src/client/workers/test-evaluator.ts'
}, },
devtool: __DEV__ ? 'inline-source-map' : 'source-map', devtool: __DEV__ ? 'inline-source-map' : 'source-map',
output: { output: {