Update scratch-desktop to use webpack v5 and scratch-editor

This commit is contained in:
Kaloyan Manolov 2025-03-26 16:42:08 +02:00
parent ce97c9ecec
commit b09ce5fa9f
17 changed files with 3334 additions and 7997 deletions

2
.gitignore vendored
View file

@ -20,7 +20,7 @@ npm-*
/*.provisionprofile
# don't store the assets downloaded with the `fetch` script
/static/assets/
/static/fetched/
# generated translation files
/translations

View file

@ -7,7 +7,7 @@ publish: # empty provider list = don't publish
files:
- dist/**
- package.json
extraResources:
extraResources: # copy the downloaded static assets to make sure they are available for the artifact
- from: static/
to: static/
filter:

11072
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -39,14 +39,16 @@
"@babel/plugin-transform-optional-chaining": "7.25.9",
"@babel/preset-env": "7.26.0",
"@babel/preset-react": "7.26.3",
"@scratch/scratch-gui": "^11.0.0-beta.2",
"async": "3.2.6",
"autoprefixer": "9.8.8",
"autoprefixer": "10.4.20",
"babel-eslint": "10.1.0",
"babel-loader": "8.4.1",
"babel-loader": "9.2.1",
"babel-plugin-react-intl": "7.9.4",
"copy-webpack-plugin": "5.1.2",
"css-loader": "1.0.1",
"electron": "23.3.13",
"chalk": "^4.1.2",
"copy-webpack-plugin": "^6.4.1",
"css-loader": "5.2.7",
"electron": "25.9.8",
"electron-builder": "22.14.13",
"electron-devtools-installer": "^3.2.1",
"electron-notarize": "1.2.2",
@ -55,19 +57,20 @@
"eslint-config-scratch": "9.0.9",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-react": "7.37.4",
"file-loader": "^6.2.0",
"fs-extra": "9.1.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^4.5.2",
"html-webpack-plugin": "5.6.3",
"intl": "1.2.5",
"lodash.bindall": "4.4.0",
"lodash.defaultsdeep": "4.6.1",
"mini-css-extract-plugin": "^0.9.0",
"lodash.omit": "4.4.0",
"mini-css-extract-plugin": "1.6.2",
"minilog": "3.1.0",
"minimist": "1.2.8",
"mkdirp": "1.0.4",
"nets": "3.2.0",
"postcss-import": "12.0.1",
"postcss-loader": "4.3.0",
"postcss-simple-vars": "5.0.2",
"react": "16.14.0",
"react-dom": "16.14.0",
@ -75,14 +78,13 @@
"react-redux": "5.1.2",
"redux": "3.7.2",
"rimraf": "3.0.2",
"scratch-gui": "github:scratchfoundation/scratch-gui#scratch-desktop-v3.30.1",
"style-loader": "^2.0.0",
"style-loader": "^4.0.0",
"url-loader": "^4.1.1",
"uuid": "8.3.2",
"wait-on": "^8.0.2",
"webpack": "4.47.0",
"webpack-cli": "3.3.12",
"webpack-dev-server": "^3.11.3",
"webpack": "5.97.1",
"webpack-cli": "5.1.4",
"webpack-dev-server": "5.2.0",
"webpack-merge": "4.2.2"
},
"resolutions": {

View file

@ -9,7 +9,7 @@ const libraries = require('./lib/libraries');
const ASSET_HOST = 'cdn.assets.scratch.mit.edu';
const NUM_SIMULTANEOUS_DOWNLOADS = 5;
const OUT_PATH = path.resolve('static', 'assets');
const OUT_PATH = path.resolve('static', 'fetched');
const describe = function (object) {

View file

@ -1,7 +1,7 @@
const backdrops = require('scratch-gui/src/lib/libraries/backdrops.json');
const costumes = require('scratch-gui/src/lib/libraries/costumes.json');
const sounds = require('scratch-gui/src/lib/libraries/sounds.json');
const sprites = require('scratch-gui/src/lib/libraries/sprites.json');
const backdrops = require('@scratch/scratch-gui/backdrops');
const costumes = require('@scratch/scratch-gui/costumes');
const sounds = require('@scratch/scratch-gui/sounds');
const sprites = require('@scratch/scratch-gui/sprites');
const libraries = {
backdrops,

View file

@ -29,27 +29,25 @@ const startRenderer = async () => {
console.log(chalk.cyan('Starting Webpack Dev Server...'));
const compiler = webpack(rendererConfig);
const server = new WebpackDevServer(compiler, {
hot: true,
compress: true,
port: PORT,
headers: { 'Access-Control-Allow-Origin': '*' },
historyApiFallback: true
});
return new Promise((resolve, reject) => {
server.listen(PORT, 'localhost', (err) => {
if (err) {
console.error(chalk.red('Failed to start Webpack Dev Server:', err));
reject(err);
} else {
console.log(chalk.green(`Renderer is running at http://localhost:${PORT}`));
resolve();
}
});
});
};
const server = new WebpackDevServer(
{
hot: true,
compress: true,
port: PORT,
headers: { 'Access-Control-Allow-Origin': '*' },
historyApiFallback: true
},
compiler
);
try {
await server.start();
console.log(chalk.green(`Renderer is running at http://localhost:${PORT}`));
} catch (err) {
console.error(chalk.red('Failed to start Webpack Dev Server:', err));
throw err;
}
}
const startElectron = async () => {
console.log(chalk.cyan('Starting Electron...'));
@ -84,4 +82,4 @@ const start = async () => {
});
};
start();
start();

View file

@ -1,7 +1,7 @@
const fs = require('fs');
const path = require('path');
const staticAssets = path.resolve(__static, 'assets');
const staticAssets = path.resolve(__static, 'fetched');
/**
* Allow the storage module to load files bundled in the Electron application.

View file

@ -1,11 +1,11 @@
import {app, ipcMain} from 'electron';
import defaultsDeep from 'lodash.defaultsdeep';
import {version} from '../../package.json';
import packageJson from '../../package.json';
import TelemetryClient from './telemetry/TelemetryClient';
const EVENT_TEMPLATE = {
version,
version: packageJson.version,
projectName: '',
language: '',
metadata: {

View file

@ -9,7 +9,7 @@ import {getFilterForExtension} from './FileFilters';
import telemetry from './ScratchDesktopTelemetry';
import MacOSMenu from './MacOSMenu';
import log from '../common/log.js';
import {productName, version} from '../../package.json';
import packageJson from '../../package.json';
// suppress deprecation warning; this will be the default in Electron 9
app.allowRendererProcessReuse = true;
@ -20,14 +20,14 @@ telemetry.appWasOpened();
const defaultSize = {width: 1280, height: 800}; // good for MAS screenshots
const isDevelopment = process.env.NODE_ENV !== 'production';
const devToolKey = ((process.platform === 'darwin' || process.platform === 'linux') ?
{ // macOS / linux: command+option+i
const devToolKey = ((process.platform === 'darwin') ?
{ // macOS: command+option+i
alt: true, // option
control: false,
meta: true, // command
shift: false,
code: 'KeyI'
} : { // Windows: control+shift+i
} : { // Windows / linux: control+shift+i
alt: false,
control: true,
meta: false, // Windows key
@ -214,7 +214,7 @@ const createAboutWindow = () => {
height: 400,
parent: _windows.main,
search: 'route=about',
title: `About ${productName}`
title: `About ${packageJson.productName}`
});
return window;
};
@ -225,7 +225,7 @@ const createPrivacyWindow = () => {
height: _windows.main.height * 0.8,
parent: _windows.main,
search: 'route=privacy',
title: `${productName} Privacy Policy`
title: `${packageJson.productName} Privacy Policy`
});
return window;
};
@ -288,7 +288,7 @@ const createMainWindow = () => {
const window = createWindow({
width: defaultSize.width,
height: defaultSize.height,
title: `${productName} ${version}` // something like "Scratch 3.14"
title: `${packageJson.productName} ${packageJson.version}` // something like "Scratch 3.14"
});
const webContents = window.webContents;
@ -359,7 +359,7 @@ const createMainWindow = () => {
webContents.on('will-prevent-unload', ev => {
const choice = dialog.showMessageBoxSync(window, {
title: productName,
title: packageJson.productName,
type: 'question',
message: 'Leave Scratch?',
detail: 'Any unsaved changes will be lost.',
@ -412,12 +412,10 @@ if (process.platform === 'win32') {
// create main BrowserWindow when electron is ready
app.on('ready', () => {
if (isDevelopment) {
// TODO: Debug errors around extensions not loading correctly
import('electron-devtools-installer').then(importedModule => {
const {default: installExtension, ...devToolsExtensions} = importedModule;
const extensionsToInstall = [
devToolsExtensions.REACT_DEVELOPER_TOOLS,
devToolsExtensions.REACT_PERF,
devToolsExtensions.REDUX_DEVTOOLS
];
for (const extension of extensionsToInstall) {
@ -471,6 +469,7 @@ const initialProjectDataPromise = (async () => {
const projectData = await promisify(fs.readFile)(projectPath, null);
return projectData;
} catch (e) {
log.error(`Error loading project data: ${e}`)
dialog.showMessageBox(_windows.main, {
type: 'error',
title: 'Failed to load project',

View file

@ -4,7 +4,6 @@ import omit from 'lodash.omit';
import PropTypes from 'prop-types';
import React from 'react';
import {connect} from 'react-redux';
import GUIComponent from 'scratch-gui/src/components/gui/gui.jsx';
import {
LoadingStates,
@ -13,13 +12,11 @@ import {
defaultProjectId,
requestNewProject,
requestProjectUpload,
setProjectId
} from 'scratch-gui/src/reducers/project-state';
import {
setProjectId,
openLoadingProject,
closeLoadingProject,
openTelemetryModal
} from 'scratch-gui/src/reducers/modals';
} from '@scratch/scratch-gui';
import ElectronStorageHelper from '../common/ElectronStorageHelper';
@ -136,8 +133,8 @@ const ScratchDesktopGUIHOC = function (WrappedComponent) {
onLoadingStarted: PropTypes.func,
onRequestNewProject: PropTypes.func,
onTelemetrySettingsClicked: PropTypes.func,
// using PropTypes.instanceOf(VM) here will cause prop type warnings due to VM mismatch
vm: GUIComponent.WrappedComponent.propTypes.vm
// TODO: Expose this type / find where it is exposed from scratch-gui
vm: PropTypes.object
};
const mapStateToProps = state => {
const loadingState = state.scratchGui.projectState.loadingState;

View file

@ -1,5 +1,5 @@
import React from 'react';
import {productName, version} from '../../package.json';
import packageJson from '../../package.json';
import logo from '../icon/ScratchDesktop.svg';
import styles from './about.css';
@ -7,13 +7,13 @@ import styles from './about.css';
const AboutElement = () => (
<div className={styles.aboutBox}>
<div><img
alt={`${productName} icon`}
alt={`${packageJson.productName} icon`}
src={logo}
className={styles.aboutLogo}
/></div>
<div className={styles.aboutText}>
<h2>{productName}</h2>
Version {version}
<h2>{packageJson.productName}</h2>
Version {packageJson.version}
<table className={styles.aboutDetails}><tbody>
{
['Electron', 'Chrome', 'Node'].map(component => {

View file

@ -1,4 +1,4 @@
/* Adapted from scratch-gui/src/playground/index.css */
/* Adapted from @scratch/scratch-gui/src/playground/index.css */
html,
body,
.app {

View file

@ -1,8 +1,6 @@
import React from 'react';
import {compose} from 'redux';
import GUI from 'scratch-gui/src/index';
import AppStateHOC from 'scratch-gui/src/lib/app-state-hoc.jsx';
import GUI, {AppStateHOC} from '@scratch/scratch-gui';
import ScratchDesktopAppStateHOC from './ScratchDesktopAppStateHOC.jsx';
import ScratchDesktopGUIHOC from './ScratchDesktopGUIHOC.jsx';
@ -13,7 +11,6 @@ appTarget.className = styles.app || 'app';
GUI.setAppElement(appTarget);
// note that redux's 'compose' function is just being used as a general utility to make
// the hierarchy of HOC constructor calls clearer here; it has nothing to do with redux's
// ability to compose reducers.

View file

@ -20,6 +20,7 @@ module.exports = makeConfig(
output: {
filename: '[name].js',
chunkFilename: '[name].bundle.js',
assetModuleFilename: 'static/assets/[name].[hash][ext]',
libraryTarget: 'commonjs2',
path: path.resolve(__dirname, "dist/main")
},

View file

@ -7,11 +7,6 @@ const electronPath = require('electron');
const webpack = require('webpack');
const merge = require('webpack-merge');
// PostCss
const autoprefixer = require('autoprefixer');
const postcssImport = require('postcss-import');
const postcssVars = require('postcss-simple-vars');
const isProduction = (process.env.NODE_ENV === 'production');
const electronVersion = childProcess.execSync(`${electronPath} --version`, {encoding: 'utf8'}).trim();
@ -33,7 +28,6 @@ const makeConfig = function (defaultConfig, options) {
]
};
const sourceFileTest = options.useReact ? /\.jsx?$/ : /\.js$/;
if (options.useReact) {
babelOptions.presets = babelOptions.presets.concat('@babel/preset-react');
babelOptions.plugins.push(['react-intl', {
@ -61,49 +55,55 @@ const makeConfig = function (defaultConfig, options) {
}
const config = merge.smart(defaultConfig, {
devtool: 'cheap-module-eval-source-map',
devtool: 'cheap-module-source-map',
mode: isProduction ? 'production' : 'development',
module: {
rules: [
{
test: sourceFileTest,
test: options.useReact ? /\.jsx?$/ : /\.js$/,
include: options.babelPaths,
loader: 'babel-loader',
options: babelOptions
},
{ // coped from scratch-gui
{
test: /\.css$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[name]_[local]_[hash:base64:5]',
camelCase: true
}
}, {
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: function () {
return [
postcssImport,
postcssVars,
autoprefixer
];
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]_[local]_[hash:base64:5]',
exportLocalsConvention: 'camelCase'
},
importLoaders: 1,
esModule: false
}
}
}]
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import',
'postcss-simple-vars',
'autoprefixer'
]
}
}
},
]
},
{
test: /\.(svg|png|wav|gif|jpg)$/,
loader: 'file-loader',
options: {
outputPath: 'static/assets/'
type: 'asset/resource',
generator: {
filename: 'static/assets/[name].[hash][ext]'
}
},
},
{
test: /\.hex$/,
use: [{
@ -126,14 +126,9 @@ const makeConfig = function (defaultConfig, options) {
resolve: {
cacheWithContext: false,
symlinks: false,
alias: {
// act like scratch-gui has this line in its package.json:
// "browser": "./src/index.js"
'scratch-gui$': path.resolve(__dirname, 'node_modules', 'scratch-gui', 'src', 'index.js'),
},
// attempt to resolve file extensions in this order
// (allows leaving off the extension when importing)
extensions: [".js", ".json", ".node", ".css"],
extensions: [".js", ".jsx", ".json", ".node", ".css"],
}
});

View file

@ -7,8 +7,7 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
const makeConfig = require('./webpack.makeConfig.js');
const getModulePath = moduleName => path.dirname(require.resolve(`${moduleName}/package.json`));
const getModulePath = moduleName => path.dirname(require.resolve(`${moduleName}`));
function generateIndexFile(template) {
let html = template;
@ -36,6 +35,7 @@ module.exports = makeConfig(
],
output: {
filename: '[name].js',
assetModuleFilename: 'static/assets/[name].[hash][ext]',
chunkFilename: '[name].bundle.js',
libraryTarget: 'commonjs2',
path: path.resolve(__dirname, "dist/renderer"),
@ -59,7 +59,7 @@ module.exports = makeConfig(
disableDefaultRulesForExtensions: ['js', 'jsx', 'css', 'svg', 'png', 'wav', 'gif', 'jpg', 'ttf'],
babelPaths: [
path.resolve(__dirname, 'src', 'renderer'),
/node_modules[\\/]+scratch-[^\\/]+[\\/]+src/,
/node_modules[\\/]+@scratch[\\/]+[^\\/]+[\\/]+src/,
/node_modules[\\/]+pify/,
/node_modules[\\/]+@vernier[\\/]+godirect/
],
@ -69,31 +69,23 @@ module.exports = makeConfig(
template: generateIndexFile(template),
minify: false,
}),
new CopyWebpackPlugin([
{
from: path.join(getModulePath('scratch-blocks'), 'media'),
to: 'static/blocks-media/default'
},
{
from: path.join(getModulePath('scratch-blocks'), 'media'),
to: 'static/blocks-media/high-contrast'
},
{
from: path.join(getModulePath('scratch-gui'),
'src', 'lib', 'themes', 'high-contrast', 'blocks-media'),
to: 'static/blocks-media/high-contrast',
force: true
},
{
from: 'extension-worker.{js,js.map}',
context: path.join(getModulePath('scratch-vm'), 'dist', 'web')
},
{
from: path.join(getModulePath('scratch-gui'), 'src', 'lib', 'libraries', '*.json'),
to: 'static/libraries',
flatten: true
}
])
new CopyWebpackPlugin({
patterns: [
{
from: path.join(getModulePath('@scratch/scratch-gui'), 'static'),
to: 'static'
},
{
from: 'extension-worker.{js,js.map}',
context: getModulePath('@scratch/scratch-gui')
},
{
from: path.join(getModulePath('@scratch/scratch-gui'), 'libraries', '*.json'),
to: 'static/libraries',
flatten: true
},
]
}),
]
}
);