Skip to content

Add TS support and use babel to transpile newer features. #1989

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const path = require("path");

module.exports = function (api) {
api.cache(true);
return {
presets: [],
plugins: [
"@babel/plugin-transform-logical-assignment-operators",
path.resolve("./development/@babel/transform-object-hasown"),
"@babel/plugin-syntax-typescript",
],
};
};
3 changes: 3 additions & 0 deletions development/@babel/fake.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
cache: () => {},
};
18 changes: 18 additions & 0 deletions development/@babel/transform-imports/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* eslint-disable */
/** @todo Make this work */
module.exports = function () {
return {
visitor: {
async CallExpression(path) {
if (!path.node.callee || path.node.callee.type !== "Import") {
return;
}
const url = path.get("arguments.0");
if (url.type !== "StringLiteral") {
url.node.value = "";
}
url.node.value += "";
},
},
};
};
3 changes: 3 additions & 0 deletions development/@babel/transform-imports/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"main": "./index.js"
}
16 changes: 16 additions & 0 deletions development/@babel/transform-object-hasown/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const t = require("@babel/core").types;

module.exports = function () {
return {
visitor: {
CallExpression(path) {
if (!path.get("callee").matchesPattern("Object.hasOwn")) {
return;
}
path
.get("callee")
.replaceWith(t.identifier("Object.prototype.hasOwnProperty.call"));
},
},
};
};
3 changes: 3 additions & 0 deletions development/@babel/transform-object-hasown/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"main": "./index.js"
}
82 changes: 74 additions & 8 deletions development/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ const ExtendedJSON = require("@turbowarp/json");
const compatibilityAliases = require("./compatibility-aliases");
const parseMetadata = require("./parse-extension-metadata");
const { mkdirp, recursiveReadDirectory } = require("./fs-utils");
const typescript = require("typescript");
const tsCompilerOptions = typescript.parseConfigFileTextToJson(
"tsconfig.json",
fs.readFileSync("tsconfig.json", "utf-8")
).config;
const babelOptions = require("../babel.config")(require("./@babel/fake"));
const babelCore = require("@babel/core");

/**
* @typedef {'development'|'production'|'desktop'} Mode
Expand Down Expand Up @@ -103,6 +110,7 @@ const insertAfterCommentsBeforeCode = (oldCode, insertCode) => {

class BuildFile {
constructor(source) {
this.realSourcePath = source;
this.sourcePath = source;
}

Expand All @@ -111,11 +119,11 @@ class BuildFile {
}

getLastModified() {
return fs.statSync(this.sourcePath).mtimeMs;
return fs.statSync(this.realSourcePath).mtimeMs;
}

read() {
return fs.readFileSync(this.sourcePath);
return fs.readFileSync(this.realSourcePath);
}

validate() {
Expand All @@ -131,7 +139,55 @@ class BuildFile {
}
}

class ExtensionFile extends BuildFile {
class JSFile extends BuildFile {
/**
* @param {string} filename Path of a TS file to set the extension of
* @returns {2 | 3 | false} 0 if its not a JS file extension, otherwise the length of the extension
*/
static jsExtension(filename) {
if (filename.endsWith(".js") || filename.endsWith(".ts")) return 2;
if (filename.endsWith(".mjs")) return 3;
return false;
}
/**
* @param {string} filename Path of a TS file to set the extension of
*/
static setExtension(filename) {
return (
filename.substring(
0,
filename.length - (JSFile.jsExtension(filename) + 1)
) + ".js"
);
}
/**
* @param {string} absolutePath Full path to the .js file, eg. /home/.../extensions/fetch.js
*/
constructor(absolutePath) {
super(absolutePath);
/** @type {boolean} */
this.isTS = this.sourcePath.endsWith(".ts");
this.isModule = !this.isTS && this.sourcePath.endsWith(".mjs");
this.sourcePath = JSFile.setExtension(this.sourcePath);
this.babelOptions = Object.assign(babelOptions, {
sourceType: this.isModule ? "module" : "unambiguous",
filename: this.realSourcePath,
});
}
read() {
return this.doCompile();
}
doCompile() {
let data = fs.readFileSync(this.realSourcePath, "utf-8");
if (this.isTS) {
data = typescript.transpileModule(data, tsCompilerOptions).outputText;
}
data = babelCore.transformSync(data, this.babelOptions).code;
return data;
}
}

class ExtensionFile extends JSFile {
/**
* @param {string} absolutePath Full path to the .js file, eg. /home/.../extensions/fetch.js
* @param {string} slug Just the extension ID from the path, eg. fetch
Expand All @@ -152,7 +208,7 @@ class ExtensionFile extends BuildFile {
}

read() {
const data = fs.readFileSync(this.sourcePath, "utf-8");
const data = super.read();

if (this.mode !== "development") {
const translations = filterTranslationsByPrefix(
Expand All @@ -173,7 +229,7 @@ class ExtensionFile extends BuildFile {
}

getMetadata() {
const data = fs.readFileSync(this.sourcePath, "utf-8");
const data = fs.readFileSync(this.realSourcePath, "utf-8");
return parseMetadata(data);
}

Expand Down Expand Up @@ -265,7 +321,7 @@ class ExtensionFile extends BuildFile {
};

const parseTranslations = require("./parse-extension-translations");
const jsCode = fs.readFileSync(this.sourcePath, "utf-8");
const jsCode = fs.readFileSync(this.realSourcePath, "utf-8");
const unprefixedRuntimeStrings = parseTranslations(jsCode);
const runtimeStrings = Object.fromEntries(
Object.entries(unprefixedRuntimeStrings).map(([key, value]) => [
Expand Down Expand Up @@ -727,7 +783,7 @@ class Builder {
for (const [filename, absolutePath] of recursiveReadDirectory(
this.extensionsRoot
)) {
if (!filename.endsWith(".js")) {
if (!JSFile.jsExtension(filename)) {
continue;
}
const extensionSlug = filename.split(".")[0];
Expand All @@ -740,7 +796,11 @@ class Builder {
this.mode
);
extensionFiles[extensionSlug] = file;
build.files[`/${filename}`] = file;
if (file.isTS || file.isModule) {
build.files[`/${JSFile.setExtension(filename)}`] = file;
} else {
build.files[`/${filename}`] = file;
}
}

/** @type {Record<string, ImageFile>} */
Expand Down Expand Up @@ -787,6 +847,12 @@ class Builder {
for (const [filename, absolutePath] of recursiveReadDirectory(
this.websiteRoot
)) {
if (JSFile.jsExtension(filename)) {
build.files[`/${JSFile.setExtension(filename)}`] = new JSFile(
absolutePath
);
continue;
}
build.files[`/${filename}`] = new BuildFile(absolutePath);
}

Expand Down
13 changes: 8 additions & 5 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ module.exports = [
js.configs.recommended,

// Common for all files
{
files: ["*.mjs"],
languageOptions: {
ecmaVersion: 2022,
sourceType: "module",
},
},
{
languageOptions: {
ecmaVersion: 2022,
Expand Down Expand Up @@ -121,10 +128,6 @@ module.exports = [
extension: {
rules: {
"no-new-syntax": createQueryRule([
{
selector: 'AssignmentExpression[operator="??="]',
message: "x ??= y syntax is too new; use x = x ?? y intead",
},
{
selector:
"MemberExpression[object.name=Object][property.name=hasOwn]",
Expand Down Expand Up @@ -304,7 +307,7 @@ module.exports = [
"Use Scratch.vm instead of the global vm object. You also can use const vm = Scratch.vm;",
},
],
"extension/no-new-syntax": "error",
"extension/no-new-syntax": "warn",
"extension/no-xmlhttprequest": "error",
"extension/iife": "error",
"extension/use-scratch-vm": "error",
Expand Down
22 changes: 22 additions & 0 deletions extensions/Mio/typescriptTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copy of the typescript test, but in the extensions folder
(function (Scratch: typeof globalThis.Scratch) {
"use strict";
const extensionId: string = "testtypescript";
const vm: VM = Scratch.vm;
vm.greenFlag();
class Test {
getInfo() {
return {
id: extensionId,
name: "Typescript",
blocks: [
{
blockType: Scratch.BlockType.LABEL,
text: "Loaded",
},
],
};
}
}
Scratch.extensions.register(new Test());
})(Scratch);
Loading