Make sure to actually use yarn to install the main-packages, otherwise the packages.json#resolutions property will not be used and @babel/helpers would not get overruled

Overruled @babel/helpers to fix how initializers play with decorated properties. Thus circumventing the imperformant and lengthy code being generated by babel in the non-legacy option.
This commit is contained in:
2019-11-05 21:11:01 +01:00
parent 5ca9bec93a
commit 72f38c1137
47 changed files with 5004 additions and 80 deletions

View File

@@ -0,0 +1,3 @@
src
test
*.log

View File

@@ -0,0 +1,19 @@
# @babel/helpers
> Collection of helper functions used by Babel transforms.
See our website [@babel/helpers](https://babeljs.io/docs/en/next/babel-helpers.html) for more information.
## Install
Using npm:
```sh
npm install --save-dev @babel/helpers
```
or using yarn:
```sh
yarn add @babel/helpers --dev
```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,276 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.get = get;
exports.minVersion = minVersion;
exports.getDependencies = getDependencies;
exports.ensure = ensure;
exports.default = exports.list = void 0;
var _traverse = _interopRequireDefault(require("@babel/traverse"));
var t = _interopRequireWildcard(require("@babel/types"));
var _helpers = _interopRequireDefault(require("./helpers"));
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; if (obj != null) { var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function makePath(path) {
const parts = [];
for (; path.parentPath; path = path.parentPath) {
parts.push(path.key);
if (path.inList) parts.push(path.listKey);
}
return parts.reverse().join(".");
}
function getHelperMetadata(file) {
const globals = new Set();
const localBindingNames = new Set();
const dependencies = new Map();
let exportName;
let exportPath;
const exportBindingAssignments = [];
const importPaths = [];
const importBindingsReferences = [];
(0, _traverse.default)(file, {
ImportDeclaration(child) {
const name = child.node.source.value;
if (!_helpers.default[name]) {
throw child.buildCodeFrameError(`Unknown helper ${name}`);
}
if (child.get("specifiers").length !== 1 || !child.get("specifiers.0").isImportDefaultSpecifier()) {
throw child.buildCodeFrameError("Helpers can only import a default value");
}
const bindingIdentifier = child.node.specifiers[0].local;
dependencies.set(bindingIdentifier, name);
importPaths.push(makePath(child));
},
ExportDefaultDeclaration(child) {
const decl = child.get("declaration");
if (decl.isFunctionDeclaration()) {
if (!decl.node.id) {
throw decl.buildCodeFrameError("Helpers should give names to their exported func declaration");
}
exportName = decl.node.id.name;
}
exportPath = makePath(child);
},
ExportAllDeclaration(child) {
throw child.buildCodeFrameError("Helpers can only export default");
},
ExportNamedDeclaration(child) {
throw child.buildCodeFrameError("Helpers can only export default");
},
Statement(child) {
if (child.isModuleDeclaration()) return;
child.skip();
}
});
(0, _traverse.default)(file, {
Program(path) {
const bindings = path.scope.getAllBindings();
Object.keys(bindings).forEach(name => {
if (name === exportName) return;
if (dependencies.has(bindings[name].identifier)) return;
localBindingNames.add(name);
});
},
ReferencedIdentifier(child) {
const name = child.node.name;
const binding = child.scope.getBinding(name, true);
if (!binding) {
globals.add(name);
} else if (dependencies.has(binding.identifier)) {
importBindingsReferences.push(makePath(child));
}
},
AssignmentExpression(child) {
const left = child.get("left");
if (!(exportName in left.getBindingIdentifiers())) return;
if (!left.isIdentifier()) {
throw left.buildCodeFrameError("Only simple assignments to exports are allowed in helpers");
}
const binding = child.scope.getBinding(exportName);
if (binding && binding.scope.path.isProgram()) {
exportBindingAssignments.push(makePath(child));
}
}
});
if (!exportPath) throw new Error("Helpers must default-export something.");
exportBindingAssignments.reverse();
return {
globals: Array.from(globals),
localBindingNames: Array.from(localBindingNames),
dependencies,
exportBindingAssignments,
exportPath,
exportName,
importBindingsReferences,
importPaths
};
}
function permuteHelperAST(file, metadata, id, localBindings, getDependency) {
if (localBindings && !id) {
throw new Error("Unexpected local bindings for module-based helpers.");
}
if (!id) return;
const {
localBindingNames,
dependencies,
exportBindingAssignments,
exportPath,
exportName,
importBindingsReferences,
importPaths
} = metadata;
const dependenciesRefs = {};
dependencies.forEach((name, id) => {
dependenciesRefs[id.name] = typeof getDependency === "function" && getDependency(name) || id;
});
const toRename = {};
const bindings = new Set(localBindings || []);
localBindingNames.forEach(name => {
let newName = name;
while (bindings.has(newName)) newName = "_" + newName;
if (newName !== name) toRename[name] = newName;
});
if (id.type === "Identifier" && exportName !== id.name) {
toRename[exportName] = id.name;
}
(0, _traverse.default)(file, {
Program(path) {
const exp = path.get(exportPath);
const imps = importPaths.map(p => path.get(p));
const impsBindingRefs = importBindingsReferences.map(p => path.get(p));
const decl = exp.get("declaration");
if (id.type === "Identifier") {
if (decl.isFunctionDeclaration()) {
exp.replaceWith(decl);
} else {
exp.replaceWith(t.variableDeclaration("var", [t.variableDeclarator(id, decl.node)]));
}
} else if (id.type === "MemberExpression") {
if (decl.isFunctionDeclaration()) {
exportBindingAssignments.forEach(assignPath => {
const assign = path.get(assignPath);
assign.replaceWith(t.assignmentExpression("=", id, assign.node));
});
exp.replaceWith(decl);
path.pushContainer("body", t.expressionStatement(t.assignmentExpression("=", id, t.identifier(exportName))));
} else {
exp.replaceWith(t.expressionStatement(t.assignmentExpression("=", id, decl.node)));
}
} else {
throw new Error("Unexpected helper format.");
}
Object.keys(toRename).forEach(name => {
path.scope.rename(name, toRename[name]);
});
for (const path of imps) path.remove();
for (const path of impsBindingRefs) {
const node = t.cloneNode(dependenciesRefs[path.node.name]);
path.replaceWith(node);
}
path.stop();
}
});
}
const helperData = Object.create(null);
function loadHelper(name) {
if (!helperData[name]) {
const helper = _helpers.default[name];
if (!helper) {
throw Object.assign(new ReferenceError(`Unknown helper ${name}`), {
code: "BABEL_HELPER_UNKNOWN",
helper: name
});
}
const fn = () => {
return t.file(helper.ast());
};
const metadata = getHelperMetadata(fn());
helperData[name] = {
build(getDependency, id, localBindings) {
const file = fn();
permuteHelperAST(file, metadata, id, localBindings, getDependency);
return {
nodes: file.program.body,
globals: metadata.globals
};
},
minVersion() {
return helper.minVersion;
},
dependencies: metadata.dependencies
};
}
return helperData[name];
}
function get(name, getDependency, id, localBindings) {
return loadHelper(name).build(getDependency, id, localBindings);
}
function minVersion(name) {
return loadHelper(name).minVersion();
}
function getDependencies(name) {
return Array.from(loadHelper(name).dependencies.values());
}
function ensure(name) {
loadHelper(name);
}
const list = Object.keys(_helpers.default).map(name => name.replace(/^_/, "")).filter(name => name !== "__esModule");
exports.list = list;
var _default = get;
exports.default = _default;

View File

@@ -0,0 +1,21 @@
{
"name": "@babel/helpers",
"version": "7.7.0",
"description": "Collection of helper functions used by Babel transforms.",
"author": "Sebastian McKenzie <sebmck@gmail.com>",
"homepage": "https://babeljs.io/",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"repository": "https://github.com/babel/babel/tree/master/packages/babel-helpers",
"main": "lib/index.js",
"dependencies": {
"@babel/template": "^7.7.0",
"@babel/traverse": "^7.7.0",
"@babel/types": "^7.7.0"
},
"devDependencies": {
"@babel/helper-plugin-test-runner": "^7.0.0"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,291 @@
import traverse from "@babel/traverse";
import * as t from "@babel/types";
import helpers from "./helpers";
function makePath(path) {
const parts = [];
for (; path.parentPath; path = path.parentPath) {
parts.push(path.key);
if (path.inList) parts.push(path.listKey);
}
return parts.reverse().join(".");
}
/**
* Given a file AST for a given helper, get a bunch of metadata about it so that Babel can quickly render
* the helper is whatever context it is needed in.
*/
function getHelperMetadata(file) {
const globals = new Set();
const localBindingNames = new Set();
// Maps imported identifier -> helper name
const dependencies = new Map();
let exportName;
let exportPath;
const exportBindingAssignments = [];
const importPaths = [];
const importBindingsReferences = [];
traverse(file, {
ImportDeclaration(child) {
const name = child.node.source.value;
if (!helpers[name]) {
throw child.buildCodeFrameError(`Unknown helper ${name}`);
}
if (
child.get("specifiers").length !== 1 ||
!child.get("specifiers.0").isImportDefaultSpecifier()
) {
throw child.buildCodeFrameError(
"Helpers can only import a default value",
);
}
const bindingIdentifier = child.node.specifiers[0].local;
dependencies.set(bindingIdentifier, name);
importPaths.push(makePath(child));
},
ExportDefaultDeclaration(child) {
const decl = child.get("declaration");
if (decl.isFunctionDeclaration()) {
if (!decl.node.id) {
throw decl.buildCodeFrameError(
"Helpers should give names to their exported func declaration",
);
}
exportName = decl.node.id.name;
}
exportPath = makePath(child);
},
ExportAllDeclaration(child) {
throw child.buildCodeFrameError("Helpers can only export default");
},
ExportNamedDeclaration(child) {
throw child.buildCodeFrameError("Helpers can only export default");
},
Statement(child) {
if (child.isModuleDeclaration()) return;
child.skip();
},
});
traverse(file, {
Program(path) {
const bindings = path.scope.getAllBindings();
Object.keys(bindings).forEach(name => {
if (name === exportName) return;
if (dependencies.has(bindings[name].identifier)) return;
localBindingNames.add(name);
});
},
ReferencedIdentifier(child) {
const name = child.node.name;
const binding = child.scope.getBinding(name, /* noGlobal */ true);
if (!binding) {
globals.add(name);
} else if (dependencies.has(binding.identifier)) {
importBindingsReferences.push(makePath(child));
}
},
AssignmentExpression(child) {
const left = child.get("left");
if (!(exportName in left.getBindingIdentifiers())) return;
if (!left.isIdentifier()) {
throw left.buildCodeFrameError(
"Only simple assignments to exports are allowed in helpers",
);
}
const binding = child.scope.getBinding(exportName);
if (binding && binding.scope.path.isProgram()) {
exportBindingAssignments.push(makePath(child));
}
},
});
if (!exportPath) throw new Error("Helpers must default-export something.");
// Process these in reverse so that mutating the references does not invalidate any later paths in
// the list.
exportBindingAssignments.reverse();
return {
globals: Array.from(globals),
localBindingNames: Array.from(localBindingNames),
dependencies,
exportBindingAssignments,
exportPath,
exportName,
importBindingsReferences,
importPaths,
};
}
/**
* Given a helper AST and information about how it will be used, update the AST to match the usage.
*/
function permuteHelperAST(file, metadata, id, localBindings, getDependency) {
if (localBindings && !id) {
throw new Error("Unexpected local bindings for module-based helpers.");
}
if (!id) return;
const {
localBindingNames,
dependencies,
exportBindingAssignments,
exportPath,
exportName,
importBindingsReferences,
importPaths,
} = metadata;
const dependenciesRefs = {};
dependencies.forEach((name, id) => {
dependenciesRefs[id.name] =
(typeof getDependency === "function" && getDependency(name)) || id;
});
const toRename = {};
const bindings = new Set(localBindings || []);
localBindingNames.forEach(name => {
let newName = name;
while (bindings.has(newName)) newName = "_" + newName;
if (newName !== name) toRename[name] = newName;
});
if (id.type === "Identifier" && exportName !== id.name) {
toRename[exportName] = id.name;
}
traverse(file, {
Program(path) {
// We need to compute these in advance because removing nodes would
// invalidate the paths.
const exp = path.get(exportPath);
const imps = importPaths.map(p => path.get(p));
const impsBindingRefs = importBindingsReferences.map(p => path.get(p));
const decl = exp.get("declaration");
if (id.type === "Identifier") {
if (decl.isFunctionDeclaration()) {
exp.replaceWith(decl);
} else {
exp.replaceWith(
t.variableDeclaration("var", [t.variableDeclarator(id, decl.node)]),
);
}
} else if (id.type === "MemberExpression") {
if (decl.isFunctionDeclaration()) {
exportBindingAssignments.forEach(assignPath => {
const assign = path.get(assignPath);
assign.replaceWith(t.assignmentExpression("=", id, assign.node));
});
exp.replaceWith(decl);
path.pushContainer(
"body",
t.expressionStatement(
t.assignmentExpression("=", id, t.identifier(exportName)),
),
);
} else {
exp.replaceWith(
t.expressionStatement(t.assignmentExpression("=", id, decl.node)),
);
}
} else {
throw new Error("Unexpected helper format.");
}
Object.keys(toRename).forEach(name => {
path.scope.rename(name, toRename[name]);
});
for (const path of imps) path.remove();
for (const path of impsBindingRefs) {
const node = t.cloneNode(dependenciesRefs[path.node.name]);
path.replaceWith(node);
}
// We only use "traverse" for all the handy scoping helpers, so we can stop immediately without
// actually doing the traversal.
path.stop();
},
});
}
const helperData = Object.create(null);
function loadHelper(name) {
if (!helperData[name]) {
const helper = helpers[name];
if (!helper) {
throw Object.assign(new ReferenceError(`Unknown helper ${name}`), {
code: "BABEL_HELPER_UNKNOWN",
helper: name,
});
}
const fn = () => {
return t.file(helper.ast());
};
const metadata = getHelperMetadata(fn());
helperData[name] = {
build(getDependency, id, localBindings) {
const file = fn();
permuteHelperAST(file, metadata, id, localBindings, getDependency);
return {
nodes: file.program.body,
globals: metadata.globals,
};
},
minVersion() {
return helper.minVersion;
},
dependencies: metadata.dependencies,
};
}
return helperData[name];
}
export function get(
name,
getDependency?: string => ?t.Expression,
id?,
localBindings?: string[],
) {
return loadHelper(name).build(getDependency, id, localBindings);
}
export function minVersion(name: string) {
return loadHelper(name).minVersion();
}
export function getDependencies(name: string): $ReadOnlyArray<string> {
return Array.from(loadHelper(name).dependencies.values());
}
export function ensure(name: string) {
loadHelper(name);
}
export const list = Object.keys(helpers)
.map(name => name.replace(/^_/, ""))
.filter(name => name !== "__esModule");
export default get;

View File

@@ -0,0 +1 @@
REPLACE_ME;

View File

@@ -0,0 +1,3 @@
{
"plugins": ["./plugin"]
}

View File

@@ -0,0 +1,5 @@
function _$_basic_main() { return _$_basic_dependency(); }
function _$_basic_dependency() {}
_$_basic_main;

View File

@@ -0,0 +1,25 @@
const defineHelper = require("../../../helpers/define-helper").default;
const dependency = defineHelper(__dirname, "dependency", `
export default function fn() {}
`);
const main = defineHelper(__dirname, "main", `
import dep from "${dependency}";
export default function helper() {
return dep();
}
`);
module.exports = function() {
return {
visitor: {
Identifier(path) {
if (path.node.name !== "REPLACE_ME") return;
const helper = this.addHelper(main);
path.replaceWith(helper);
},
},
};
};

View File

@@ -0,0 +1 @@
REPLACE_ME;

View File

@@ -0,0 +1,3 @@
{
"plugins": ["./plugin"]
}

View File

@@ -0,0 +1,7 @@
function _$_deep_main() { return _$_deep_dependency(); }
function _$_deep_dependency() { return _$_deep_dependencyDeep; }
function _$_deep_dependencyDeep() {}
_$_deep_main;

View File

@@ -0,0 +1,30 @@
const defineHelper = require("../../../helpers/define-helper").default;
const dependencyDeep = defineHelper(__dirname, "dependencyDeep", `
export default function fn() {}
`)
const dependency = defineHelper(__dirname, "dependency", `
import f from "${dependencyDeep}";
export default function fn() { return f; }
`);
const main = defineHelper(__dirname, "main", `
import dep from "${dependency}";
export default function helper() {
return dep();
}
`);
module.exports = function() {
return {
visitor: {
Identifier(path) {
if (path.node.name !== "REPLACE_ME") return;
const helper = this.addHelper(main);
path.replaceWith(helper);
},
},
};
};

View File

@@ -0,0 +1 @@
REPLACE_ME;

View File

@@ -0,0 +1,4 @@
{
"plugins": ["./plugin"],
"throws": " "
}

View File

@@ -0,0 +1,21 @@
const defineHelper = require("../../../helpers/define-helper").default;
const main = defineHelper(__dirname, "main", `
import dep from "(!!!)%-..a,4892 missing";
export default function helper() {
return dep();
}
`);
module.exports = function() {
return {
visitor: {
Identifier(path) {
if (path.node.name !== "REPLACE_ME") return;
const helper = this.addHelper(main);
path.replaceWith(helper);
},
},
};
};

View File

@@ -0,0 +1 @@
REPLACE_ME;

View File

@@ -0,0 +1,3 @@
{
"plugins": ["./plugin"]
}

View File

@@ -0,0 +1,7 @@
function _$_multiple_main() { return _$_multiple_dependency() + _$_multiple_dependency2(); }
function _$_multiple_dependency2() { 1; }
function _$_multiple_dependency() { 0; }
_$_multiple_main;

View File

@@ -0,0 +1,30 @@
const defineHelper = require("../../../helpers/define-helper").default;
const dependency1 = defineHelper(__dirname, "dependency1", `
export default function fn() { 0; }
`);
const dependency2 = defineHelper(__dirname, "dependency2", `
export default function fn() { 1; }
`);
const main = defineHelper(__dirname, "main", `
import dep1 from "${dependency1}";
import dep2 from "${dependency2}";
export default function helper() {
return dep1() + dep2();
}
`);
module.exports = function() {
return {
visitor: {
Identifier(path) {
if (path.node.name !== "REPLACE_ME") return;
const helper = this.addHelper(main);
path.replaceWith(helper);
},
},
};
};

View File

@@ -0,0 +1 @@
REPLACE_ME;

View File

@@ -0,0 +1,3 @@
{
"plugins": ["./plugin"]
}

View File

@@ -0,0 +1,9 @@
let _foo = "main";
function _$_renameBindingEqual_main() { return _$_renameBindingEqual_dependency() + _foo; }
let foo = "dependency";
function _$_renameBindingEqual_dependency() { return foo; }
_$_renameBindingEqual_main;

View File

@@ -0,0 +1,28 @@
const defineHelper = require("../../../helpers/define-helper").default;
const dependency = defineHelper(__dirname, "dependency", `
let foo = "dependency";
export default function fn() { return foo }
`);
const main = defineHelper(__dirname, "main", `
import dep from "${dependency}";
let foo = "main";
export default function helper() {
return dep() + foo;
}
`);
module.exports = function() {
return {
visitor: {
Identifier(path) {
if (path.node.name !== "REPLACE_ME") return;
const helper = this.addHelper(main);
path.replaceWith(helper);
},
},
};
};

View File

@@ -0,0 +1,3 @@
REPLACE_ME;
let Promise = "I will be a good guy!";

View File

@@ -0,0 +1,3 @@
{
"plugins": ["./plugin"]
}

View File

@@ -0,0 +1,6 @@
function _$_renameDeepGlobal_main() { return _$_renameDeepGlobal_dependency() || Promise; }
function _$_renameDeepGlobal_dependency() { return Promise; }
_$_renameDeepGlobal_main;
let _Promise = "I will be a good guy!";

View File

@@ -0,0 +1,27 @@
const defineHelper = require("../../../helpers/define-helper").default;
const dependency = defineHelper(__dirname, "dependency", `
export default function fn() {
return Promise;
}
`);
const main = defineHelper(__dirname, "main", `
import dep from "${dependency}";
export default function helper() {
return dep() || Promise;
}
`);
module.exports = function() {
return {
visitor: {
Identifier(path) {
if (path.node.name !== "REPLACE_ME") return;
const helper = this.addHelper(main);
path.replaceWith(helper);
},
},
};
};

View File

@@ -0,0 +1,2 @@
REPLACE_ME_1;
REPLACE_ME_2;

View File

@@ -0,0 +1,3 @@
{
"plugins": ["./plugin"]
}

View File

@@ -0,0 +1,6 @@
function _$_reuseDependency_main() { return _$_reuseDependency_dependency(); }
function _$_reuseDependency_dependency() { 0; }
_$_reuseDependency_main;
_$_reuseDependency_dependency;

View File

@@ -0,0 +1,29 @@
const defineHelper = require("../../../helpers/define-helper").default;
const dependency = defineHelper(__dirname, "dependency", `
export default function fn() { 0; }
`);
const main = defineHelper(__dirname, "main", `
import dep from "${dependency}";
export default function helper() {
return dep();
}
`);
module.exports = function() {
return {
visitor: {
Identifier(path) {
if (path.node.name === "REPLACE_ME_1") {
const mainHelper = this.addHelper(main);
path.replaceWith(mainHelper);
} else if (path.node.name === "REPLACE_ME_2") {
const dependencyHelper = this.addHelper(dependency);
path.replaceWith(dependencyHelper);
}
},
},
};
};

View File

@@ -0,0 +1 @@
REPLACE_ME;

View File

@@ -0,0 +1,3 @@
{
"plugins": ["./plugin"]
}

View File

@@ -0,0 +1,5 @@
function _$_variableSameNameDependency_main() { let x = _$_variableSameNameDependency_dependency; return function (dep) { return x() + dep; }; }
function _$_variableSameNameDependency_dependency() {}
_$_variableSameNameDependency_main;

View File

@@ -0,0 +1,28 @@
const defineHelper = require("../../../helpers/define-helper").default;
const dependency = defineHelper(__dirname, "dependency", `
export default function fn() {}
`);
const main = defineHelper(__dirname, "main", `
import dep from "${dependency}";
export default function helper() {
let x = dep;
return function (dep) {
return x() + dep;
}
}
`);
module.exports = function() {
return {
visitor: {
Identifier(path) {
if (path.node.name !== "REPLACE_ME") return;
const helper = this.addHelper(main);
path.replaceWith(helper);
},
},
};
};

View File

@@ -0,0 +1,26 @@
import path from "path";
import template from "@babel/template";
import helpers from "../../lib/helpers";
function getHelperId(dir, name) {
const testName = path.basename(dir);
return `_$_${testName}_${name}`;
}
export default function defineHelper(
dir: string,
name: string,
code: string,
): string {
const id = getHelperId(dir, name);
if (id in helpers) {
throw new Error(`The ${id} helper is already defined.`);
}
Object.defineProperty(helpers, id, {
value: {
minVersion: "7.0.0-beta.0",
ast: template.program(code),
},
});
return id;
}

View File

@@ -0,0 +1,3 @@
import runner from "@babel/helper-plugin-test-runner";
runner(__dirname);

View File

@@ -2,7 +2,7 @@
"presets": [
],
"plugins": [
[ "@babel/plugin-proposal-decorators" , { "decoratorsBeforeExport": true }],
[ "@babel/plugin-proposal-decorators" , { "legacy": true }],
[ "@babel/plugin-proposal-class-properties", { "loose": true } ],
[ "@babel/plugin-proposal-private-methods", {"loose": true } ],
[ "@babel/plugin-proposal-optional-chaining" ],

View File

@@ -1,43 +1,56 @@
import {render} from "../vdom";
/** Helper class to mark an initializer-value to be used on first get of a value**/
class InitializerValue{ constructor(value){ this.value = value; } }
/**
* The decorators proposal has changed since @babel implemented it. This code will need to change at some point...
* THIS IS TOTALLY FIGGIN BROKEN!! valueMap used to be just value, but it turns out is not unique amongst decorated props.
* (it appears to be run once per class definition, and thus multiple instances would share the same value-reference)
*/
export function State() {
return function decorator(target){
let key = target.key;
let descriptor = target.descriptor;
let valueMap = new WeakMap();
return function decorator(target, key, descriptor){
let {get: oldGet, set: oldSet} = descriptor;
let valueKey='__'+key;
// Add a getter/setter or replace if they're already there with something that intercepts it (this gets a whole lot easyer in the new proposal if i'm not mistaken)
descriptor['get'] = oldGet || (function(){
return valueMap.get(this)
});
descriptor['set'] = function(newValue){
let oldValue = descriptor.get.call(this);
if(newValue!==oldValue){
valueMap.set(this,newValue);
this.markDirty && this.markDirty();
// Rewrite the property as if using getters and setters (if needed)
descriptor.get = oldGet = oldGet || function(){
let val = this[valueKey];
if(val instanceof InitializerValue){
this[valueKey] = val = val.value.call(this);
}
if(oldSet) return oldSet.call(this, newValue);
return val;
};
// CAUTION: this is dangerous. We need intend to conver regular fields to get/set methods here.
delete descriptor.writable;
oldSet = oldSet || function(newVal){
this[valueKey]=newVal;
return newVal;
};
// Overwrite the setter to call markDirty whenever it is used
descriptor.set = function(newValue){
let result = oldSet.call(this, newValue);
this.markDirty && this.markDirty();
return result;
};
// Update the descriptor to match with using getters and setters
target.kind = 'method'; // update to get and set if need be..
delete descriptor.writable;
// CAUTION: this is again dangerous, the initialize function should be called right before the constructor, but after it was fully defined.
if(target.initializer){
valueMap.set(target, target.initializer(target));
delete target.initializer;
// Catch usage of initial value or initalizers
if(descriptor.value){
Object.defineProperty(target, valueKey, {
writable: true,
value: descriptor.value
});
delete descriptor.value;
}else if(descriptor.initializer){
Object.defineProperty(target, valueKey, {
writable: true,
value: new InitializerValue(descriptor.initializer)
});
delete descriptor.initializer;
}
return target;
return descriptor;
}
}

View File

@@ -2,21 +2,16 @@
* The decorators proposal has changed since @babel implemented it. This code will need to change at some point...
*/
export function defineElement(tagName, options) {
return function decorator(target) {
// Queue defining element in a finisher, because apparantly thats how the non-legacy decorator proposal works (again, new proposal will be different...)
target.finisher = (finishedTarget)=>{
// Register the tagName as a custom-element with the browser
window.customElements.define(tagName, finishedTarget, options);
// Define the chosen tagName on the class itself so our vdom.render-function knows what DOM-Element to create
Object.defineProperty(finishedTarget, 'tagName', {
value: tagName,
writable: false,
enumerable: false,
configurable: false
});
return finishedTarget;
};
return target;
return function decorator(targetClass) {
Object.defineProperty(targetClass, 'tagName', {
value: tagName,
writable: false,
enumerable: false,
configurable: false
});
// Register the tagName as a custom-element with the browser
window.customElements.define(tagName, targetClass, options); // Define the chosen tagName on the class itself so our vdom.render-function knows what DOM-Element to create
return targetClass;
};
}