diff --git a/client/.gitignore b/client/.gitignore index f5f41512776..9dfba12e68f 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -8,8 +8,8 @@ yarn-error.log .DS_Store /static/js -./static/_redirects -static/curriculum-data +/static/css +/static/curriculum-data # Generated config i18n/locales/**/trending.json diff --git a/client/src/templates/Challenges/classic/xterm.tsx b/client/src/templates/Challenges/classic/xterm.tsx index 47f758534df..8bb04f543aa 100644 --- a/client/src/templates/Challenges/classic/xterm.tsx +++ b/client/src/templates/Challenges/classic/xterm.tsx @@ -139,7 +139,7 @@ export const XtermTerminal = ({ return (
- +
); }; diff --git a/client/tools/copy-browser-scripts.ts b/client/tools/copy-browser-scripts.ts index aadf261a712..26673bdd0a7 100644 --- a/client/tools/copy-browser-scripts.ts +++ b/client/tools/copy-browser-scripts.ts @@ -8,12 +8,20 @@ const browserScriptDist = resolve( ); const destJsDir = resolve(__dirname, '../static/js'); +const srcJsDir = resolve(browserScriptDist, './js'); +const destCssDir = resolve(__dirname, '../static/css'); +const srcCssDir = resolve(browserScriptDist, './css'); // 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 }); +rmSync(destCssDir, { recursive: true, force: true }); mkdirSync(destJsDir, { recursive: true }); +mkdirSync(destCssDir, { recursive: true }); -cpSync(resolve(browserScriptDist, 'artifacts'), destJsDir, { +cpSync(srcJsDir, destJsDir, { + recursive: true +}); +cpSync(srcCssDir, destCssDir, { recursive: true }); diff --git a/curriculum/src/test/vitest-global-setup.mjs b/curriculum/src/test/vitest-global-setup.mjs index 16a68bbb27e..4a9f512ab62 100644 --- a/curriculum/src/test/vitest-global-setup.mjs +++ b/curriculum/src/test/vitest-global-setup.mjs @@ -28,7 +28,7 @@ function setupStubs() { rmSync(destArtifactsDir, { recursive: true, force: true }); mkdirSync(destArtifactsDir, { recursive: true }); - cpSync(path.resolve(browserScriptDist, 'artifacts'), destArtifactsDir, { + cpSync(path.resolve(browserScriptDist, 'js'), destArtifactsDir, { recursive: true }); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e9cf122fcca..6f3f595bad5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1090,6 +1090,12 @@ importers: '@freecodecamp/curriculum-helpers': specifier: ^7.2.0 version: 7.2.0(debug@4.3.4)(typescript@5.9.3) + pyodide: + specifier: ^0.23.3 + version: 0.23.4 + sass.js: + specifier: 0.11.1 + version: 0.11.1 xterm: specifier: ^5.2.1 version: 5.3.0 @@ -1109,30 +1115,21 @@ importers: '@freecodecamp/eslint-config': specifier: workspace:* version: link:../../../packages/eslint-config - '@types/copy-webpack-plugin': - specifier: ^8.0.1 - version: 8.0.1(webpack-cli@4.10.0) '@typescript/vfs': specifier: 1.6.1 version: 1.6.1(typescript@5.9.3) babel-loader: specifier: 8.3.0 version: 8.3.0(@babel/core@7.28.5)(webpack@5.90.3) - copy-webpack-plugin: - specifier: 9.1.0 - version: 9.1.0(webpack@5.90.3) eslint: specifier: ^9.39.1 version: 9.39.2(jiti@2.6.1) process: specifier: 0.11.10 version: 0.11.10 - pyodide: - specifier: ^0.23.3 - version: 0.23.4 - sass.js: - specifier: 0.11.1 - version: 0.11.1 + tsx: + specifier: ^4.21.0 + version: 4.21.0 typescript: specifier: 5.9.3 version: 5.9.3 @@ -5123,9 +5120,6 @@ packages: '@types/cookiejar@2.1.2': resolution: {integrity: sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==} - '@types/copy-webpack-plugin@8.0.1': - resolution: {integrity: sha512-TwEeGse0/wq+t3SFW0DEwroMS/cDkwVZT+vj7tMAYTp7llt/yz6NuW2n04X2M5P/kSfBQOORhrHAN2mqZdmybg==} - '@types/cors@2.8.18': resolution: {integrity: sha512-nX3d0sxJW41CqQvfOzVG1NCTXfFDrDWIghCZncpHeWlVFd81zxB/DLhg7avFg6eHLCRX7ckBmoIIcqa++upvJA==} @@ -7016,12 +7010,6 @@ packages: resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} engines: {node: '>=0.10.0'} - copy-webpack-plugin@9.1.0: - resolution: {integrity: sha512-rxnR7PaGigJzhqETHGmAcxKnLZSR5u1Y3/bcIv/1FnqXedcL/E2ewK7ZCNrArJKCiSv8yVXhTqetJh8inDvfsA==} - engines: {node: '>= 12.13.0'} - peerDependencies: - webpack: ^5.1.0 - core-js-compat@3.36.0: resolution: {integrity: sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==} @@ -8189,10 +8177,6 @@ packages: fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - fast-glob@3.3.1: - resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} - engines: {node: '>=8.6.0'} - fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -20383,17 +20367,6 @@ snapshots: '@types/cookiejar@2.1.2': {} - '@types/copy-webpack-plugin@8.0.1(webpack-cli@4.10.0)': - dependencies: - '@types/node': 24.10.9 - tapable: 2.2.1 - webpack: 5.90.3(webpack-cli@4.10.0) - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - - webpack-cli - '@types/cors@2.8.18': dependencies: '@types/node': 24.10.9 @@ -21133,7 +21106,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.9)(typescript@5.9.3))(terser@5.28.1)(tsx@4.21.0)(yaml@2.8.1) + vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.9)(typescript@5.9.3))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1) '@vitest/utils@3.2.4': dependencies: @@ -22687,16 +22660,6 @@ snapshots: copy-descriptor@0.1.1: {} - copy-webpack-plugin@9.1.0(webpack@5.90.3): - dependencies: - fast-glob: 3.3.1 - glob-parent: 6.0.2 - globby: 11.1.0 - normalize-path: 3.0.0 - schema-utils: 3.3.0 - serialize-javascript: 6.0.1 - webpack: 5.90.3(webpack-cli@4.10.0) - core-js-compat@3.36.0: dependencies: browserslist: 4.28.1 @@ -23735,7 +23698,7 @@ snapshots: confusing-browser-globals: 1.0.11 eslint: 7.32.0 eslint-plugin-flowtype: 5.10.0(eslint@7.32.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@4.33.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react: 7.37.4(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react-hooks: 4.6.0(eslint@9.39.2(jiti@2.6.1)) @@ -23765,7 +23728,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@4.33.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: @@ -23819,7 +23782,7 @@ snapshots: - typescript - utf-8-validate - eslint-plugin-import@2.31.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@4.33.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -23830,7 +23793,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@4.33.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -24322,14 +24285,6 @@ snapshots: fast-fifo@1.3.2: {} - fast-glob@3.3.1: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -25068,7 +25023,7 @@ snapshots: eslint-config-react-app: 6.0.0(@typescript-eslint/eslint-plugin@4.33.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.9.3))(eslint@7.32.0)(typescript@5.9.3))(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.9.3))(babel-eslint@10.1.0(eslint@9.39.2(jiti@2.6.1)))(eslint-plugin-flowtype@5.10.0(eslint@7.32.0))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.9.3))(eslint@7.32.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.0(eslint@7.32.0))(eslint-plugin-react@7.37.4(eslint@7.32.0))(eslint@7.32.0)(typescript@5.9.3) eslint-plugin-flowtype: 5.10.0(eslint@7.32.0) eslint-plugin-graphql: 4.0.0(@types/node@24.10.9)(graphql@15.8.0)(typescript@5.9.3) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@4.33.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react: 7.37.4(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react-hooks: 4.6.0(eslint@9.39.2(jiti@2.6.1)) diff --git a/tools/client-plugins/browser-scripts/copy-scripts.ts b/tools/client-plugins/browser-scripts/copy-scripts.ts new file mode 100644 index 00000000000..1e58e827394 --- /dev/null +++ b/tools/client-plugins/browser-scripts/copy-scripts.ts @@ -0,0 +1,33 @@ +import { cpSync, mkdirSync, rmSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import { version as workerVersion } from '@freecodecamp/browser-scripts/package.json'; +import { version as helperVersion } from '@freecodecamp/curriculum-helpers/package.json'; + +const __dirname = import.meta.dirname; + +const distDir = resolve(__dirname, 'dist'); + +const destJsDir = resolve(distDir, './js'); +const destCssDir = resolve(distDir, './css'); + +rmSync(distDir, { recursive: true, force: true }); +mkdirSync(destJsDir, { recursive: true }); +mkdirSync(destCssDir, { recursive: true }); + +cpSync( + resolve(__dirname, './node_modules/sass.js/dist/sass.sync.js'), + resolve(destJsDir, 'workers', workerVersion, 'sass.sync.js') +); +cpSync( + resolve(__dirname, './node_modules/xterm/css/xterm.css'), + resolve(destCssDir, 'xterm.css') +); +cpSync( + resolve( + __dirname, + './node_modules/@freecodecamp/curriculum-helpers/dist/test-runner' + ), + resolve(destJsDir, `test-runner/${helperVersion}/`), + { recursive: true } +); diff --git a/tools/client-plugins/browser-scripts/package.json b/tools/client-plugins/browser-scripts/package.json index 6a679e0faca..3a2b1c7023e 100644 --- a/tools/client-plugins/browser-scripts/package.json +++ b/tools/client-plugins/browser-scripts/package.json @@ -29,7 +29,9 @@ "main": "index.js", "scripts": { "lint": "eslint --max-warnings 0", - "build": "NODE_OPTIONS=\"--max-old-space-size=7168\" webpack -c webpack.config.cjs --env production" + "build": "pnpm copy-scripts && pnpm bundle", + "bundle": "NODE_OPTIONS=\"--max-old-space-size=7168\" webpack -c webpack.config.cjs --env production", + "copy-scripts": "tsx ./copy-scripts.ts" }, "type": "module", "keywords": [], @@ -39,14 +41,11 @@ "@babel/preset-env": "7.23.7", "@babel/preset-typescript": "7.23.3", "@freecodecamp/eslint-config": "workspace:*", - "@types/copy-webpack-plugin": "^8.0.1", "@typescript/vfs": "1.6.1", "babel-loader": "8.3.0", - "copy-webpack-plugin": "9.1.0", "eslint": "^9.39.1", "process": "0.11.10", - "pyodide": "^0.23.3", - "sass.js": "0.11.1", + "tsx": "^4.21.0", "typescript": "5.9.3", "util": "0.12.5", "webpack": "5.90.3", @@ -54,6 +53,8 @@ }, "dependencies": { "@freecodecamp/curriculum-helpers": "^7.2.0", + "pyodide": "^0.23.3", + "sass.js": "0.11.1", "xterm": "^5.2.1" } } diff --git a/tools/client-plugins/browser-scripts/webpack.config.cjs b/tools/client-plugins/browser-scripts/webpack.config.cjs index 09217df9606..efe47304e38 100644 --- a/tools/client-plugins/browser-scripts/webpack.config.cjs +++ b/tools/client-plugins/browser-scripts/webpack.config.cjs @@ -1,9 +1,6 @@ const path = require('path'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); const webpack = require('webpack'); -const { - version: helperVersion -} = require('@freecodecamp/curriculum-helpers/package.json'); + const { version } = require('./package.json'); module.exports = (env = {}) => { @@ -20,8 +17,8 @@ module.exports = (env = {}) => { devtool: __DEV__ ? 'inline-source-map' : 'source-map', output: { chunkFilename: '[name]-[contenthash].js', - path: path.resolve(__dirname, `dist/artifacts/workers/${version}`), - clean: true + path: path.resolve(__dirname, `dist/js/workers/${version}`), + clean: false // We handle cleaning in copy-scripts.ts }, stats: { // Display bailout reasons @@ -53,17 +50,6 @@ module.exports = (env = {}) => { ] }, plugins: [ - new CopyWebpackPlugin({ - patterns: [ - './node_modules/sass.js/dist/sass.sync.js', - // TODO: copy this into the css folder, not the js folder - './node_modules/xterm/css/xterm.css', - { - from: './node_modules/@freecodecamp/curriculum-helpers/dist/test-runner', - to: `../../test-runner/${helperVersion}/` - } - ] - }), new webpack.ProvidePlugin({ process: 'process/browser' }),