mirror of
https://github.com/scratchfoundation/scratch-desktop.git
synced 2025-08-28 22:19:55 -04:00
Update scratch-desktop to use webpack v5 and scratch-editor
This commit is contained in:
parent
ce97c9ecec
commit
b09ce5fa9f
17 changed files with 3334 additions and 7997 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -20,7 +20,7 @@ npm-*
|
|||
/*.provisionprofile
|
||||
|
||||
# don't store the assets downloaded with the `fetch` script
|
||||
/static/assets/
|
||||
/static/fetched/
|
||||
|
||||
# generated translation files
|
||||
/translations
|
||||
|
|
|
@ -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
11072
package-lock.json
generated
File diff suppressed because it is too large
Load diff
28
package.json
28
package.json
|
@ -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": {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Adapted from scratch-gui/src/playground/index.css */
|
||||
/* Adapted from @scratch/scratch-gui/src/playground/index.css */
|
||||
html,
|
||||
body,
|
||||
.app {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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")
|
||||
},
|
||||
|
|
|
@ -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"],
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
},
|
||||
]
|
||||
}),
|
||||
]
|
||||
}
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue