diff --git a/packages/blockly/gulpfile.mjs b/packages/blockly/gulpfile.mjs index bb5ab244b5c..586e73be89e 100644 --- a/packages/blockly/gulpfile.mjs +++ b/packages/blockly/gulpfile.mjs @@ -25,8 +25,6 @@ import { import { build, buildAdvancedCompilationTest, - langfiles, - messages, minify, } from './scripts/gulpfiles/build_tasks.mjs'; import {cleanBuildDir, cleanReleaseDir} from './scripts/lib/fs_utils.mjs'; @@ -42,7 +40,6 @@ export default build; // // prettier-ignore export { - langfiles, minify, build, prepareDemos, @@ -55,7 +52,6 @@ export { // // prettier-ignore export { - messages, // Generate msg/json/en.json et al. clean, buildAdvancedCompilationTest, } diff --git a/packages/blockly/package.json b/packages/blockly/package.json index 2c849a97432..861eaecf5e7 100644 --- a/packages/blockly/package.json +++ b/packages/blockly/package.json @@ -94,7 +94,7 @@ } }, "scripts": { - "build": "npm run tsc && gulp build", + "build": "npm run tsc && gulp build && npm run langfiles", "build-debug": "gulp build --verbose --debug", "build-debug-log": "npm run build:debug > build-debug.log 2>&1 && tail -3 build-debug.log", "build-strict": "gulp build --verbose --strict", @@ -102,10 +102,10 @@ "clean": "gulp clean", "deployDemos": "npm ci && gulp deployDemos", "deployDemos:beta": "npm ci && gulp deployDemosBeta", - "messages": "gulp messages", + "messages": "node \"scripts/messages.mjs\"", "lint": "eslint .", "lint-fix": "eslint . --fix", - "langfiles": "gulp langfiles", + "langfiles": "node \"scripts/langfiles.mjs\"", "minify": "gulp minify", "package": "node scripts/package.mjs", "prepareDemos": "gulp prepareDemos", diff --git a/packages/blockly/scripts/build_constants.mjs b/packages/blockly/scripts/build_constants.mjs index f54900626fa..1b2ae1339f0 100644 --- a/packages/blockly/scripts/build_constants.mjs +++ b/packages/blockly/scripts/build_constants.mjs @@ -28,3 +28,10 @@ export const TEST_TSC_OUTPUT_DIR = path.join(BUILD_DIR, 'tests'); // Directory in which to assemble (and from which to publish) the // blockly npm package. export const RELEASE_DIR = 'dist'; + +/** + * Path to the python runtime. + * This will normalize the command across platforms (e.g. python3 on Linux and + * Mac, python on Windows). + */ +export const PYTHON = process.platform === 'win32' ? 'python' : 'python3'; diff --git a/packages/blockly/scripts/gulpfiles/build_tasks.mjs b/packages/blockly/scripts/gulpfiles/build_tasks.mjs index 33e3c1eadc4..5bcaf4b2a0f 100644 --- a/packages/blockly/scripts/gulpfiles/build_tasks.mjs +++ b/packages/blockly/scripts/gulpfiles/build_tasks.mjs @@ -13,7 +13,6 @@ import rename from 'gulp-rename'; import replace from 'gulp-replace'; import sourcemaps from 'gulp-sourcemaps'; -import {execSync} from 'child_process'; import * as fs from 'fs'; import * as fsPromises from 'fs/promises'; import * as path from 'path'; @@ -25,7 +24,6 @@ import {hideBin} from 'yargs/helpers'; import { BUILD_DIR, - LANG_BUILD_DIR, RELEASE_DIR, TSC_OUTPUT_DIR, } from './config.mjs'; @@ -39,13 +37,6 @@ const argv = yargs(hideBin(process.argv)).parse(); // Build // //////////////////////////////////////////////////////////// -/** - * Path to the python runtime. - * This will normalize the command across platforms (e.g. python3 on Linux and - * Mac, python on Windows). - */ -const PYTHON = process.platform === 'win32' ? 'python' : 'python3'; - /** * Posix version of TSC_OUTPUT_DIR */ @@ -330,80 +321,6 @@ const JSCOMP_OFF = [ 'visibility', ]; -/** - * This task regenerates msg/json/en.js and msg/json/qqq.js from - * msg/messages.js. - */ -export function messages(done) { - // Run js_to_json.py - const jsToJsonCmd = `${PYTHON} scripts/i18n/js_to_json.py \ - --input_file ${path.join('msg', 'messages.js')} \ - --output_dir ${path.join('msg', 'json')} \ - --quiet`; - execSync(jsToJsonCmd, {stdio: 'inherit'}); - - console.log(` -Regenerated several flies in msg/json/. Now run - - git diff msg/json/*.json - -and check that operation has not overwritten any modifications made to -hints, etc. by the TranslateWiki volunteers. If it has, backport -their changes to msg/messages.js and re-run 'npm run messages'. - -Once you are satisfied that any new hints have been backported you may -go ahead and commit the changes, but note that the messages script -will have removed the translator credits - be careful not to commit -this removal! -`); - - done(); -} - -var languages = null; - -/** - * Get list of languages to build langfiles and/or shims for, based on .json - * files in msg/json/, skipping certain entries that do not correspond to an - * actual language). Results are cached as this is called from both - * buildLangfiles and buildLangfileShims. - */ -function getLanguages() { - if (!languages) { - const skip = /^(keys|synonyms|qqq|constants)\.json$/; - languages = fs - .readdirSync(path.join('msg', 'json')) - .filter((file) => file.endsWith('json') && !skip.test(file)) - .map((file) => file.replace(/\.json$/, '')); - } - return languages; -} - -/** - * This task builds Blockly's lang files. - * msg/*.js - */ -function buildLangfiles(done) { - // Create output directory. - fs.mkdirSync(LANG_BUILD_DIR, {recursive: true}); - - // Run create_messages.py. - const inputFiles = getLanguages().map((lang) => - path.join('msg', 'json', `${lang}.json`), - ); - - const createMessagesCmd = `${PYTHON} ./scripts/i18n/create_messages.py \ - --source_lang_file ${path.join('msg', 'json', 'en.json')} \ - --source_synonym_file ${path.join('msg', 'json', 'synonyms.json')} \ - --source_constants_file ${path.join('msg', 'json', 'constants.json')} \ - --key_file ${path.join('msg', 'json', 'keys.json')} \ - --output_dir ${LANG_BUILD_DIR} \ - --quiet ${inputFiles.join(' ')}`; - execSync(createMessagesCmd, {stdio: 'inherit'}); - - done(); -} - /** * Return the path to the generated chunk exporter for the given * chunk, relative to TSC_OUTPUT_DIR. @@ -786,43 +703,6 @@ ${exportedNames.map((name) => ` ${name},`).join('\n')} await fsPromises.rm(TMP_PACKAGE_JSON); } -/** - * This task builds the ESM wrappers used by the langfiles "import" - * entrypoints declared in package.json. - */ -async function buildLangfileShims() { - // Create output directory. - fs.mkdirSync(path.join(RELEASE_DIR, 'msg'), {recursive: true}); - - // Get the names of the exports from the langfile by require()ing - // msg/messages.js and letting it mutate the (global) Blockly.Msg. - // (We have to do it this way because messages.js is a script and - // not a CJS module with exports.) - globalThis.Blockly = {Msg: {}}; - await import('../../msg/messages.js'); - const exportedNames = Object.keys(globalThis.Blockly.Msg); - delete globalThis.Blockly; - - await Promise.all( - getLanguages().map(async (lang) => { - // Write an ESM wrapper that imports the CJS module and re-exports - // its named exports. - const cjsPath = `./${lang}.js`; - const wrapperPath = path.join(RELEASE_DIR, 'msg', `${lang}.mjs`); - const safeLang = lang.replace(/-/g, '_'); - - await fsPromises.writeFile( - wrapperPath, - `import ${safeLang} from '${cjsPath}'; -export const { -${exportedNames.map((name) => ` ${name},`).join('\n')} -} = ${safeLang}; -`, - ); - }), - ); -} - /** * This task uses Closure Compiler's ADVANCED_OPTIMIZATIONS mode to * compile together Blockly core, blocks and generators with a simple @@ -862,15 +742,13 @@ function compileAdvancedCompilationTest() { .pipe(gulp.dest('./tests/compile/')); } -// Main sequence targets. Each should invoke any immediate prerequisite(s). -export const langfiles = gulp.parallel(buildLangfiles, buildLangfileShims); // function tsc, above export const minify = gulp.series( buildChunkExporters, buildCompiled, buildShims, ); -export const build = gulp.parallel(minify, langfiles); +export const build = minify; // Manually-invokable targets, with prerequisites where required. // function messages, above diff --git a/packages/blockly/scripts/langfiles.mjs b/packages/blockly/scripts/langfiles.mjs new file mode 100644 index 00000000000..b2cc495eba2 --- /dev/null +++ b/packages/blockly/scripts/langfiles.mjs @@ -0,0 +1,104 @@ +/** + * @license + * Copyright 2026 Raspberry Pi Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Functions for building language files. + */ + +import {execSync} from 'child_process'; +import {mkdirSync, readdirSync} from 'fs'; +import {writeFile} from 'fs/promises'; +import * as path from 'path'; + +import {LANG_BUILD_DIR, PYTHON, RELEASE_DIR} from './build_constants.mjs'; + +let languages = null; + +/** + * Get list of languages to build langfiles and/or shims for, based on .json + * files in msg/json/, skipping certain entries that do not correspond to an + * actual language). Results are cached as this is called from both + * buildLangfiles and buildLangfileShims. + */ +function getLanguages() { + if (!languages) { + const skip = /^(keys|synonyms|qqq|constants)\.json$/; + languages = readdirSync(path.join('msg', 'json')) + .filter((file) => file.endsWith('json') && !skip.test(file)) + .map((file) => file.replace(/\.json$/, '')); + } + return languages; +} + +/** + * This task builds Blockly's lang files. + * msg/*.js + */ +function buildLangfiles() { + // Create output directory. + mkdirSync(LANG_BUILD_DIR, {recursive: true}); + + // Run create_messages.py. + const inputFiles = getLanguages().map((lang) => + path.join('msg', 'json', `${lang}.json`), + ); + + const createMessagesCmd = `${PYTHON} ./scripts/i18n/create_messages.py \ + --source_lang_file ${path.join('msg', 'json', 'en.json')} \ + --source_synonym_file ${path.join('msg', 'json', 'synonyms.json')} \ + --source_constants_file ${path.join('msg', 'json', 'constants.json')} \ + --key_file ${path.join('msg', 'json', 'keys.json')} \ + --output_dir ${LANG_BUILD_DIR} \ + --quiet ${inputFiles.join(' ')}`; + execSync(createMessagesCmd, {stdio: 'inherit'}); +} + +/** + * This task builds the ESM wrappers used by the langfiles "import" + * entrypoints declared in package.json. + */ +async function buildLangfileShims() { + // Create output directory. + mkdirSync(path.join(RELEASE_DIR, 'msg'), {recursive: true}); + + // Get the names of the exports from the langfile by require()ing + // msg/messages.js and letting it mutate the (global) Blockly.Msg. + // (We have to do it this way because messages.js is a script and + // not a CJS module with exports.) + globalThis.Blockly = {Msg: {}}; + await import('../msg/messages.js'); + const exportedNames = Object.keys(globalThis.Blockly.Msg); + delete globalThis.Blockly; + + await Promise.all( + getLanguages().map(async (lang) => { + // Write an ESM wrapper that imports the CJS module and re-exports + // its named exports. + const cjsPath = `./${lang}.js`; + const wrapperPath = path.join(RELEASE_DIR, 'msg', `${lang}.mjs`); + const safeLang = lang.replace(/-/g, '_'); + + await writeFile( + wrapperPath, + `import ${safeLang} from '${cjsPath}'; +export const { +${exportedNames.map((name) => ` ${name},`).join('\n')} +} = ${safeLang}; +`, + ); + }), + ); +} + +/** + * Run all of the functions needed to build langfiles. + */ +async function langfiles() { + await buildLangfiles(); + await buildLangfileShims(); +} + +await langfiles(); diff --git a/packages/blockly/scripts/messages.mjs b/packages/blockly/scripts/messages.mjs new file mode 100644 index 00000000000..66d333d8489 --- /dev/null +++ b/packages/blockly/scripts/messages.mjs @@ -0,0 +1,44 @@ +/** + * @license + * Copyright 2026 Raspberry Pi Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Functions for generating message files + */ + +import {execSync} from 'child_process'; +import * as path from 'path'; + +import {PYTHON} from './build_constants.mjs'; + +/** + * This task regenerates msg/json/en.js and msg/json/qqq.js from + * msg/messages.js. + */ +export function messages() { + // Run js_to_json.py + const jsToJsonCmd = `${PYTHON} scripts/i18n/js_to_json.py \ + --input_file ${path.join('msg', 'messages.js')} \ + --output_dir ${path.join('msg', 'json')} \ + --quiet`; + execSync(jsToJsonCmd, {stdio: 'inherit'}); + + console.log(` +Regenerated several flies in msg/json/. Now run + + git diff msg/json/*.json + +and check that operation has not overwritten any modifications made to +hints, etc. by the TranslateWiki volunteers. If it has, backport +their changes to msg/messages.js and re-run 'npm run messages'. + +Once you are satisfied that any new hints have been backported you may +go ahead and commit the changes, but note that the messages script +will have removed the translator credits - be careful not to commit +this removal! +`); +} + +messages();