New approach to handling addon libraries-raw and assets

They are now handled with `import` just like everything else in Scratch.
Benefits of this:
 - Addon assets can be cached immutably now
 - No special webpack configuration needed anymore
This commit is contained in:
Thomas Weber committed 2021-01-25 20:02:24 -06:00
1 parent a6831de286
commit 9981995f3e
9 files changed
+130 -87

No files matched your search

+1 -2
View File
@@ -14,7 +14,6 @@ Directory structure:
- addons - the addons (managed by pull.js)
- addons-l10n - addon translations (managed by pull.js)
- libraries - libraries used by addons loaded with `import` (managed by pull.js)
- libraries-raw - libraries used by addons loaded with `addon.tab.loadScript` (managed by pull.js)
- libraries - libraries used by addons (managed by pull.js)
- api-libraries - libraries used by addon API
- settings - the settings page and its translations
+12 -3
View File
@@ -3,6 +3,15 @@
* @license GPLv3.0 (see LICENSE_GPL or https://www.gnu.org/licenses/ for more information)
*/
/* inserted by pull.js */
import _twAsset0 from "./300cats.svg";
import _twAsset1 from "./cat.svg";
const _twGetAsset = (path) => {
if (path === "/300cats.svg") return _twAsset0;
if (path === "/cat.svg") return _twAsset1;
throw new Error(`Unknown asset: ${path}`);
};
export default async function ({ addon, global, console, msg }) {
console.log("clones counter enabled");
@@ -44,10 +53,10 @@ export default async function ({ addon, global, console, msg }) {
if (v === 300) {
count.style.color = "#ff6680";
icon.src = addon.self.dir + "/300cats.svg";
icon.src = /* changed by pull.js */ _twGetAsset("/300cats.svg");
} else {
count.style.color = "";
icon.src = addon.self.dir + "/cat.svg";
icon.src = /* changed by pull.js */ _twGetAsset("/cat.svg");
}
count.innerText = msg("clones", { cloneCount: v });
}
@@ -58,7 +67,7 @@ export default async function ({ addon, global, console, msg }) {
if (addon.tab.editorMode === "editor") {
doCloneChecks(vm.runtime._cloneCounter);
icon.src = addon.self.dir + "/cat.svg";
icon.src = /* changed by pull.js */ _twGetAsset("/cat.svg");
bar.appendChild(countContainerContainer);
}
+8 -1
View File
@@ -3,6 +3,13 @@
* @license GPLv3.0 (see LICENSE_GPL or https://www.gnu.org/licenses/ for more information)
*/
/* inserted by pull.js */
import _twScript0 from "!file-loader!../../libraries/tinycolor-min.js";
const _twGetAsset = (path) => {
if (path === "/tinycolor-min.js") return _twScript0;
throw new Error(`Unknown asset: ${path}`);
};
import codeEditorHandler from "./code-editor.js";
import paintEditorHandler from "./paint-editor.js";
@@ -10,7 +17,7 @@ import paintEditorHandler from "./paint-editor.js";
// Note that we don't await other scripts (they block!)
export default async (api) => {
const { addon } = api;
await addon.tab.loadScript(addon.self.lib + "/tinycolor-min.js");
await addon.tab.loadScript(/* changed by pull.js */ _twGetAsset("/tinycolor-min.js"));
codeEditorHandler(api);
paintEditorHandler(api);
};
+11 -2
View File
@@ -3,6 +3,15 @@
* @license GPLv3.0 (see LICENSE_GPL or https://www.gnu.org/licenses/ for more information)
*/
/* inserted by pull.js */
import _twAsset0 from "./lock.svg";
import _twAsset1 from "./unlock.svg";
const _twGetAsset = (path) => {
if (path === "/lock.svg") return _twAsset0;
if (path === "/unlock.svg") return _twAsset1;
throw new Error(`Unknown asset: ${path}`);
};
export default async function ({ addon, global, console }) {
var placeHolderDiv = null,
lockDisplay = null,
@@ -21,11 +30,11 @@ export default async function ({ addon, global, console }) {
// Lock Img
if (lockDisplay) lockDisplay.remove();
lockDisplay = document.createElement("img");
lockDisplay.src = addon.self.dir + "/unlock.svg";
lockDisplay.src = /* changed by pull.js */ _twGetAsset("/unlock.svg");
lockDisplay.className = "sa-lock-image";
lockDisplay.onclick = () => {
flyoutLock = !flyoutLock;
lockDisplay.src = addon.self.dir + `/${flyoutLock ? "" : "un"}lock.svg`;
lockDisplay.src = /* changed by pull.js */ _twGetAsset(`/${flyoutLock ? "" : "un"}lock.svg`);
};
function positionElements() {
+14 -1
View File
@@ -3,6 +3,19 @@
* @license GPLv3.0 (see LICENSE_GPL or https://www.gnu.org/licenses/ for more information)
*/
/* inserted by pull.js */
import _twAsset0 from "./decrement.svg";
import _twAsset1 from "./increment.svg";
import _twAsset2 from "./settings.svg";
import _twAsset3 from "./toggle.svg";
const _twGetAsset = (path) => {
if (path === "/decrement.svg") return _twAsset0;
if (path === "/increment.svg") return _twAsset1;
if (path === "/settings.svg") return _twAsset2;
if (path === "/toggle.svg") return _twAsset3;
throw new Error(`Unknown asset: ${path}`);
};
export default async function ({ addon, global, console, msg }) {
let project = null;
let paperCanvas = null;
@@ -539,7 +552,7 @@ export default async function ({ addon, global, console, msg }) {
el.className = "sa-onion-image";
el.draggable = false;
el.dataset.image = name;
el.src = addon.self.dir + "/" + name + ".svg";
el.src = /* changed by pull.js */ _twGetAsset("/" + name + ".svg");
return el;
};
+25 -20
View File
@@ -165,17 +165,12 @@ class Settings extends EventTarget {
}
class Self {
constructor (id) {
this.dir = `addon-files/${id}`;
this.lib = 'addon-files/libraries-raw';
// These are removed at build-time by pull.js. Throw if attempting to access them at runtime.
get dir () {
throw new Error(`Addon tried to access addon.self.dir`);
}
}
class Addon {
constructor (addonId, manifest) {
this.tab = new Tab();
this.settings = new Settings(addonId, manifest);
this.self = new Self(addonId);
get lib () {
throw new Error(`Addon tried to access addon.self.lib`);
}
}
@@ -190,7 +185,11 @@ class AddonRunner {
this.publicAPI = {
global,
console,
addon: new Addon(id, manifest),
addon: {
tab: new Tab(),
settings: new Settings(id, manifest),
self: new Self()
},
msg: this.msg.bind(this),
safeMsg: this.safeMsg.bind(this)
};
@@ -238,12 +237,17 @@ class AddonRunner {
}
}
_run () {
async _run () {
this.updateCSSVariables();
if (this.manifest.userstyles) {
for (const userstyle of this.manifest.userstyles) {
const source = require(`./addons/${this.id}/${userstyle.url}`);
const m = await import(
/* webpackInclude: /\.css$/ */
/* webpackMode: "eager" */
`!raw-loader!./addons/${this.id}/${userstyle.url}`
);
const source = m.default;
const style = createStylesheet(source);
style.className = 'scratch-addons-theme';
style.dataset.addonId = this.id;
@@ -254,17 +258,18 @@ class AddonRunner {
if (this.manifest.userscripts) {
for (const userscript of this.manifest.userscripts) {
require(`./addons/${this.id}/${userscript.url}`).default(this.publicAPI);
const m = await import(
/* webpackInclude: /\.js$/ */
/* webpackMode: "eager" */
`./addons/${this.id}/${userscript.url}`
);
m.default(this.publicAPI);
}
}
}
run () {
try {
this._run();
} catch (e) {
console.error('Addon error', e);
}
async run () {
this._run();
}
}
AddonRunner.instances = [];
File renamed without changes.
+59 -21
View File
@@ -51,12 +51,10 @@ rimraf.sync('ScratchAddons');
rimraf.sync('addons');
rimraf.sync('addons-l10n');
rimraf.sync('libraries');
rimraf.sync('libraries-raw');
childProcess.execSync('git clone --depth=1 -b tw https://github.com/GarboMuffin/ScratchAddons ScratchAddons');
fs.mkdirSync('addons', {recursive: true});
fs.mkdirSync('addons-l10n', {recursive: true});
fs.mkdirSync('libraries', {recursive: true});
fs.mkdirSync('libraries-raw', {recursive: true});
const JS_HEADER = `/**!
* Imported from SA
@@ -77,19 +75,6 @@ const includeImportedLibraries = contents => {
}
};
const includeDynamicallyImportedLibraries = contents => {
// Parse things like:
// await addon.tab.loadScript(addon.self.lib + "/tinycolor-min.js");
const matches = [...contents.matchAll(/addon\.self\.lib *\+ *["']\/([\w\d_-]+\.js)["']/g)];
for (const match of matches) {
const libraryFile = match[1];
const oldLibraryPath = pathUtil.join('ScratchAddons', 'libraries', libraryFile);
const newLibraryPath = pathUtil.join('libraries-raw', libraryFile);
const libraryContents = fs.readFileSync(oldLibraryPath, 'utf-8');
fs.writeFileSync(newLibraryPath, libraryContents);
}
};
let fixHardcodedClassesCSS = '/* generated by pull.js */\n';
const includeFixedHardcodedClasses = async contents => {
// Parse things like:
@@ -158,6 +143,56 @@ const includeFixedHardcodedClasses = async contents => {
}
};
const includeImports = (folder, contents) => {
// The first thing we have to do is figure out which files actually need to be loaded.
// Parse things like:
// await addon.tab.loadScript(addon.self.lib + "/tinycolor-min.js");
const matches = [...contents.matchAll(/addon\.self\.lib *\+ *["']\/([\w\d_-]+\.js)["']/g)];
const dynamicLibraries = [];
for (const match of matches) {
const libraryFile = match[1];
dynamicLibraries.push(libraryFile);
const oldLibraryPath = pathUtil.join('ScratchAddons', 'libraries', libraryFile);
const newLibraryPath = pathUtil.join('libraries', libraryFile);
const libraryContents = fs.readFileSync(oldLibraryPath, 'utf-8');
fs.writeFileSync(newLibraryPath, libraryContents);
}
const dynamicAssets = fs.readdirSync(folder)
.filter(file => file.endsWith('.svg'));
// Then we'll generate some JS to import them.
let header = '/* inserted by pull.js */\n';
dynamicAssets.forEach((file, index) => {
header += `import _twAsset${index} from "./${file}";\n`;
});
dynamicLibraries.forEach((file, index) => {
// Load as a file as it will be run through addon.tab.loadScript
header += `import _twScript${index} from "!file-loader!../../libraries/${file}";\n`;
});
header += `const _twGetAsset = (path) => {\n`;
dynamicAssets.forEach((file, index) => {
header += ` if (path === "/${file}") return _twAsset${index};\n`;
});
dynamicLibraries.forEach((file, index) => {
header += ` if (path === "/${file}") return _twScript${index};\n`;
});
header += ' throw new Error(`Unknown asset: ${path}`);\n';
header += '};\n';
header += '\n';
// And now we reroute everything to use our imports.
// Parse things like:
// el.src = addon.self.dir + "/" + name + ".svg";
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ match
// ^^^^^^^^^^^^^^^^^^^ capture group 1
contents = contents.replace(
/addon\.self\.(?:dir|lib) *\+ *([^;]+)/g,
(_fullText, name) => `/* changed by pull.js */ _twGetAsset(${name})`
);
return header + contents;
};
(async () => {
for (const addon of addons) {
const oldDirectory = pathUtil.join('ScratchAddons', 'addons', addon);
@@ -168,17 +203,20 @@ const includeFixedHardcodedClasses = async contents => {
fs.mkdirSync(newDirectory, {recursive: true});
let contents = fs.readFileSync(oldPath, 'utf-8');
if (file.endsWith('.js')) {
includeImportedLibraries(contents);
// includeDynamicallyImportedLibraries(contents);
await includeFixedHardcodedClasses(contents);
if (contents.includes('addon.self.dir') || contents.includes('addon.self.lib')) {
contents = includeImports(oldDirectory, contents);
}
}
// Add a license notice, unless one already exists.
if ((file.endsWith('.js') || file.endsWith('.css')) && !contents.includes('@license')) {
contents = JS_HEADER + contents;
}
if (file.endsWith('.js')) {
includeImportedLibraries(contents);
includeDynamicallyImportedLibraries(contents);
await includeFixedHardcodedClasses(contents);
}
fs.writeFileSync(newPath, contents);
}
}
-37
View File
@@ -22,9 +22,6 @@ const htmlWebpackPluginCommon = {
root: root
};
const addonFolder = path.resolve(__dirname, 'src', 'addons', 'addons');
const rawLibrariesFolder = path.resolve(__dirname, 'src', 'addons', 'libraries-raw');
const base = {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
devtool: process.env.NODE_ENV === 'production' ? false : 'cheap-module-source-map',
@@ -66,9 +63,6 @@ const base = {
/node_modules[\\/]pify/,
/node_modules[\\/]@vernier[\\/]godirect/
],
exclude: [
rawLibrariesFolder
],
options: {
// Explicitly disable babelrc so we don't catch various config
// in much lower dependencies.
@@ -85,9 +79,6 @@ const base = {
},
{
test: /\.css$/,
exclude: [
addonFolder
],
use: [{
loader: 'style-loader'
}, {
@@ -111,24 +102,6 @@ const base = {
}
}
}]
}, {
test: /\.css$/,
include: [
addonFolder
],
loader: 'raw-loader'
},
{
test: /\.svg$/,
include: [
addonFolder
],
loader: 'file-loader',
options: {
name: '[path][name].[ext]',
context: addonFolder,
outputPath: 'addon-files'
}
}]
},
plugins: []
@@ -160,9 +133,6 @@ module.exports = [
{
test: /\.(svg|png|wav|gif|jpg|mp3)$/,
loader: 'file-loader',
exclude: [
addonFolder
],
options: {
outputPath: 'static/assets/'
}
@@ -244,10 +214,6 @@ module.exports = [
new CopyWebpackPlugin([{
from: 'extension-worker.{js,js.map}',
context: 'node_modules/scratch-vm/dist/web'
}]),
new CopyWebpackPlugin([{
from: rawLibrariesFolder,
to: 'addon-files/libraries-raw'
}])
])
})
@@ -274,9 +240,6 @@ module.exports = [
{
test: /\.(svg|png|wav|gif|jpg|mp3)$/,
loader: 'file-loader',
exclude: [
addonFolder
],
options: {
outputPath: 'static/assets/',
publicPath: `${STATIC_PATH}/assets/`