feat(tools): modularize browser-scripts (#65399)

This commit is contained in:
Oliver Eyton-Williams
2026-01-26 13:21:20 +01:00
committed by GitHub
parent 3152bff893
commit e5cae6909c
15 changed files with 74 additions and 38 deletions
-1
View File
@@ -12,7 +12,6 @@ yarn-error.log
static/curriculum-data static/curriculum-data
# Generated config # Generated config
config/browser-scripts/*.json
i18n/locales/**/trending.json i18n/locales/**/trending.json
i18n/locales/**/search-bar.json i18n/locales/**/search-bar.json
+2 -1
View File
@@ -21,7 +21,7 @@
"scripts": { "scripts": {
"prebuild": "pnpm run common-setup && pnpm run build:scripts --env production", "prebuild": "pnpm run common-setup && pnpm run build:scripts --env production",
"build": "NODE_OPTIONS=\"--max-old-space-size=7168 --no-deprecation\" gatsby build --prefix-paths", "build": "NODE_OPTIONS=\"--max-old-space-size=7168 --no-deprecation\" gatsby build --prefix-paths",
"build:scripts": "pnpm run -F=browser-scripts compile", "build:scripts": "pnpm run -F=browser-scripts compile && tsx ./tools/copy-browser-scripts.ts",
"build:external-curriculum": "tsx ./tools/external-curriculum/build", "build:external-curriculum": "tsx ./tools/external-curriculum/build",
"clean": "gatsby clean", "clean": "gatsby clean",
"common-setup": "pnpm -w turbo compile && pnpm run create:env && pnpm run create:trending && pnpm run create:search-placeholder", "common-setup": "pnpm -w turbo compile && pnpm run create:env && pnpm run create:trending && pnpm run create:search-placeholder",
@@ -144,6 +144,7 @@
"@freecodecamp/eslint-config": "workspace:*", "@freecodecamp/eslint-config": "workspace:*",
"@freecodecamp/shared": "workspace:*", "@freecodecamp/shared": "workspace:*",
"@freecodecamp/curriculum": "workspace:*", "@freecodecamp/curriculum": "workspace:*",
"@freecodecamp/browser-scripts": "workspace:*",
"@testing-library/jest-dom": "^6.8.0", "@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "12.1.5", "@testing-library/react": "12.1.5",
"@testing-library/react-hooks": "^8.0.1", "@testing-library/react-hooks": "^8.0.1",
@@ -9,21 +9,20 @@ import {
stubTrue stubTrue
} from 'lodash-es'; } from 'lodash-es';
import sassData from '../../../../config/browser-scripts/sass-compile.json';
import { import {
transformContents, transformContents,
transformHeadTailAndContents, transformHeadTailAndContents,
compileHeadTail, compileHeadTail,
createSource createSource
} from '@freecodecamp/shared/utils/polyvinyl'; } from '@freecodecamp/shared/utils/polyvinyl';
import { version } from '@freecodecamp/browser-scripts/package.json';
import { WorkerExecutor } from '../utils/worker-executor'; import { WorkerExecutor } from '../utils/worker-executor';
import { import {
compileTypeScriptCode, compileTypeScriptCode,
checkTSServiceIsReady checkTSServiceIsReady
} from '../utils/typescript-worker-handler'; } from '../utils/typescript-worker-handler';
const { filename: sassCompile } = sassData;
const protectTimeout = 100; const protectTimeout = 100;
const testProtectTimeout = 1500; const testProtectTimeout = 1500;
const loopsPerTimeoutCheck = 100; const loopsPerTimeoutCheck = 100;
@@ -209,7 +208,9 @@ function getBabelOptions(
return presets; return presets;
} }
const sassWorkerExecutor = new WorkerExecutor(sassCompile); const sassWorkerExecutor = new WorkerExecutor(
`workers/${version}/sass-compile`
);
async function transformSASS(documentElement) { async function transformSASS(documentElement) {
// we only teach scss syntax, not sass. Also the compiler does not seem to be // we only teach scss syntax, not sass. Also the compiler does not seem to be
// able to deal with sass. // able to deal with sass.
@@ -1,7 +1,7 @@
// TODO: This might be cleaner as a class. import { version } from '@freecodecamp/browser-scripts/package.json';
import pythonWorkerData from '../../../../config/browser-scripts/python-worker.json';
const pythonWorkerSrc = `/js/${pythonWorkerData.filename}.js`; // TODO: This might be cleaner as a class.
const pythonWorkerSrc = `/js/workers/${version}/python-worker.js`;
let worker: Worker | null = null; let worker: Worker | null = null;
let listener: ((event: MessageEvent) => void) | null = null; let listener: ((event: MessageEvent) => void) | null = null;
@@ -1,7 +1,8 @@
import typeScriptWorkerData from '../../../../config/browser-scripts/typescript-worker.json'; import { version } from '@freecodecamp/browser-scripts/package.json';
import { awaitResponse } from './awaitable-messenger'; import { awaitResponse } from './awaitable-messenger';
const typeScriptWorkerSrc = `/js/${typeScriptWorkerData.filename}.js`; const typeScriptWorkerSrc = `/js/workers/${version}/typescript-worker.js`;
let worker: Worker | null = null; let worker: Worker | null = null;
+19
View File
@@ -0,0 +1,19 @@
import { cpSync, mkdirSync, rmSync } from 'node:fs';
import { resolve } from 'node:path';
const browserScriptDist = resolve(
__dirname,
'../../tools/client-plugins/browser-scripts/dist'
);
const destJsDir = resolve(__dirname, '../static/js');
// Everything is done synchronously to keep the script simple. There's no
// performance benefit to doing this asynchronously since it's already so fast.
rmSync(destJsDir, { recursive: true, force: true });
mkdirSync(destJsDir, { recursive: true });
cpSync(resolve(browserScriptDist, 'artifacts'), destJsDir, {
recursive: true
});
+1
View File
@@ -1 +1,2 @@
generated generated
src/test/stubs/js
+1
View File
@@ -50,6 +50,7 @@
"@babel/register": "7.23.7", "@babel/register": "7.23.7",
"@freecodecamp/eslint-config": "workspace:*", "@freecodecamp/eslint-config": "workspace:*",
"@freecodecamp/shared": "workspace:*", "@freecodecamp/shared": "workspace:*",
"@freecodecamp/browser-scripts": "workspace:*",
"@total-typescript/ts-reset": "^0.6.1", "@total-typescript/ts-reset": "^0.6.1",
"@types/debug": "^4.1.12", "@types/debug": "^4.1.12",
"@types/js-yaml": "4.0.5", "@types/js-yaml": "4.0.5",
+19 -7
View File
@@ -3,10 +3,8 @@ import path from 'node:path';
import sirv from 'sirv'; import sirv from 'sirv';
import polka from 'polka'; import polka from 'polka';
import puppeteer from 'puppeteer'; import puppeteer from 'puppeteer';
import { cpSync, mkdirSync, rmSync } from 'node:fs';
import { helperVersion } from '../../../client/src/templates/Challenges/utils/frame'; import { version } from '@freecodecamp/browser-scripts/test-runner';
const clientPath = path.resolve(__dirname, '../../../client');
async function createBrowser() { async function createBrowser() {
return puppeteer.launch({ return puppeteer.launch({
@@ -21,6 +19,20 @@ async function createBrowser() {
let browser, server; let browser, server;
function setupStubs() {
const browserScriptDist = path.resolve(
__dirname,
'../../../tools/client-plugins/browser-scripts/dist'
);
const destArtifactsDir = path.resolve(__dirname, 'stubs/js');
rmSync(destArtifactsDir, { recursive: true, force: true });
mkdirSync(destArtifactsDir, { recursive: true });
cpSync(path.resolve(browserScriptDist, 'artifacts'), destArtifactsDir, {
recursive: true
});
}
async function startServer() { async function startServer() {
const host = '127.0.0.1'; const host = '127.0.0.1';
const port = 8080; const port = 8080;
@@ -29,16 +41,16 @@ async function startServer() {
// Mount static files used by the tests // Mount static files used by the tests
app.use( app.use(
'/dist', '/dist', // the runner is mounted at dist so we don't need to specify the asset path when initializing
sirv(path.join(clientPath, `static/js/test-runner/${helperVersion}`)) sirv(path.resolve(__dirname, `stubs/js/test-runner/${version}`))
); );
app.use('/js', sirv(path.join(clientPath, 'static/js')));
app.use('/', sirv(path.resolve(__dirname, 'stubs'))); app.use('/', sirv(path.resolve(__dirname, 'stubs')));
app.listen(port, host); app.listen(port, host);
return app.server; return app.server;
} }
export async function setup() { export async function setup() {
setupStubs();
server = await startServer(); server = await startServer();
browser = await createBrowser(); browser = await createBrowser();
// Sharing the Websocket endpoint so that setup files can connect. This allows // Sharing the Websocket endpoint so that setup files can connect. This allows
+6
View File
@@ -550,6 +550,9 @@ importers:
'@babel/plugin-syntax-dynamic-import': '@babel/plugin-syntax-dynamic-import':
specifier: 7.8.3 specifier: 7.8.3
version: 7.8.3(@babel/core@7.28.5) version: 7.8.3(@babel/core@7.28.5)
'@freecodecamp/browser-scripts':
specifier: workspace:*
version: link:../tools/client-plugins/browser-scripts
'@freecodecamp/curriculum': '@freecodecamp/curriculum':
specifier: workspace:* specifier: workspace:*
version: link:../curriculum version: link:../curriculum
@@ -710,6 +713,9 @@ importers:
'@babel/register': '@babel/register':
specifier: 7.23.7 specifier: 7.23.7
version: 7.23.7(@babel/core@7.23.7) version: 7.23.7(@babel/core@7.23.7)
'@freecodecamp/browser-scripts':
specifier: workspace:*
version: link:../tools/client-plugins/browser-scripts
'@freecodecamp/eslint-config': '@freecodecamp/eslint-config':
specifier: workspace:* specifier: workspace:*
version: link:../packages/eslint-config version: link:../packages/eslint-config
@@ -0,0 +1 @@
dist
@@ -8,6 +8,13 @@
"node": ">=24", "node": ">=24",
"pnpm": ">=10" "pnpm": ">=10"
}, },
"files": [
"dist"
],
"exports": {
"./test-runner": "./test-runner.ts",
"./package.json": "./package.json"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git" "url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git"
@@ -1,3 +1,4 @@
import { version } from '@freecodecamp/browser-scripts/package.json';
// work around for SASS error in Edge // work around for SASS error in Edge
// https://github.com/medialize/sass.js/issues/96#issuecomment-424386171 // https://github.com/medialize/sass.js/issues/96#issuecomment-424386171
interface WorkerWithSass extends Worker { interface WorkerWithSass extends Worker {
@@ -20,7 +21,7 @@ if (!ctx.crypto) {
}; };
} }
ctx.importScripts('/js/sass.sync.js'); ctx.importScripts(`/js/workers/${version}/sass.sync.js`);
ctx.onmessage = e => { ctx.onmessage = e => {
const data: unknown = e.data; const data: unknown = e.data;
@@ -1,18 +1,14 @@
const { writeFileSync } = require('fs');
const path = require('path'); const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require('webpack'); const webpack = require('webpack');
const { const {
version: helperVersion version: helperVersion
} = require('@freecodecamp/curriculum-helpers/package.json'); } = require('@freecodecamp/curriculum-helpers/package.json');
const { version } = require('./package.json');
module.exports = (env = {}) => { module.exports = (env = {}) => {
const __DEV__ = env.production !== true; const __DEV__ = env.production !== true;
const staticPath = path.join(__dirname, '../../../client/static/js');
const configPath = path.join(
__dirname,
'../../../client/config/browser-scripts/'
);
return { return {
cache: __DEV__ ? { type: 'filesystem' } : false, cache: __DEV__ ? { type: 'filesystem' } : false,
mode: __DEV__ ? 'development' : 'production', mode: __DEV__ ? 'development' : 'production',
@@ -23,19 +19,9 @@ module.exports = (env = {}) => {
}, },
devtool: __DEV__ ? 'inline-source-map' : 'source-map', devtool: __DEV__ ? 'inline-source-map' : 'source-map',
output: { output: {
publicPath: '/js/',
filename: chunkData => {
// construct and output the filename here, so the client can use the
// json to find the file.
const filename = `${chunkData.chunk.name}-${chunkData.chunk.contentHash.javascript}`;
writeFileSync(
path.join(configPath, `${chunkData.chunk.name}.json`),
`{"filename": "${filename}"}`
);
return filename + '.js';
},
chunkFilename: '[name]-[contenthash].js', chunkFilename: '[name]-[contenthash].js',
path: staticPath path: path.resolve(__dirname, `dist/artifacts/workers/${version}`),
clean: true
}, },
stats: { stats: {
// Display bailout reasons // Display bailout reasons
@@ -74,7 +60,7 @@ module.exports = (env = {}) => {
'./node_modules/xterm/css/xterm.css', './node_modules/xterm/css/xterm.css',
{ {
from: './node_modules/@freecodecamp/curriculum-helpers/dist/test-runner', from: './node_modules/@freecodecamp/curriculum-helpers/dist/test-runner',
to: `test-runner/${helperVersion}/` to: `../../test-runner/${helperVersion}/`
} }
] ]
}), }),