Skip to content

Instantly share code, notes, and snippets.

@dfreeman
Last active April 5, 2017 18:31
Show Gist options
  • Save dfreeman/c5fdb07a68868fce84835dcea0b60026 to your computer and use it in GitHub Desktop.
Save dfreeman/c5fdb07a68868fce84835dcea0b60026 to your computer and use it in GitHub Desktop.
flow
import Ember from 'ember';
import Scope from 'twiddle/cf/scope';
import buildModule from 'twiddle/cf/build-module';
const {
Controller,
computed,
} = Ember;
export default Controller.extend({
context: computed(function() {
return {
items: [
{ label: 'a' },
{ label: 'b' },
{ label: 'c' },
]
};
}),
rootScope: computed('context', function() {
const context = this.get('context');
return new Scope({ context });
}),
viewModule: buildModule({
module: 'each',
config: {
items: { bind: 'context.items' },
yield: 'item',
eachModule: {
module: 'text',
config: {
content: [{ bind: 'item.label' }]
}
}
}
}),
editModule: buildModule({
module: 'each',
config: {
items: { bind: 'context.items' },
yield: 'item',
eachModule: {
module: 'input',
config: {
value: { bind: 'item.label' },
}
}
}
}),
// Nasty hacks below here to keep the stringified context up to date
init() {
this._super(...arguments);
const context = this.get('context');
let previous = JSON.stringify(context);
this.interval = setInterval(() => {
const current = JSON.stringify(context);
if (previous !== current) {
previous = current;
this.notifyPropertyChange('jsonContext');
}
}, 20);
},
willDestroy() {
clearInterval(this.interval);
this._super(...arguments);
},
jsonContext: computed(function() {
return JSON.stringify(this.get('context'), null, 2);
}),
});
See <code>application/controller.js</code> for details on the module configuration and <code>application/template.hbs</code> to see how they're invoked.
<h3>Context</h3>
<pre>{{jsonContext}}</pre>
<h3>Display</h3>
{{invoke-module
scope=rootScope
module=viewModule
}}
<h3>Edit</h3>
{{invoke-module
scope=rootScope
module=editModule
}}
import Ember from 'ember';
import { read, write } from './manipulators';
export default class Binding {
constructor(path) {
const firstDot = path.indexOf('.');
if (firstDot === -1) {
this.scopeKey = path;
this.path = null;
} else {
this.scopeKey = path.substring(0, firstDot);
this.path = path.substring(firstDot + 1);
}
}
read(options) {
const value = options.scope.lookup(this.scopeKey);
if (this.path) {
return read(value, this.path, options);
} else {
return value;
}
}
write(value, options) {
if (!this.path) throw new Error(`Don't do that.`);
const target = options.scope.lookup(this.scopeKey);
write(target, this.path, value, options);
}
validate(options) {
// TODO ¯\_(ツ)_/¯
}
}
import Binding from 'twiddle/cf/binding';
import { transform, rule, simple, subtree } from 'botanist';
export default transform([
rule({
bind: simple('name')
}, ({ name }) => {
return new Binding(name);
}),
rule({
module: simple('moduleName'),
config: subtree('moduleConfig')
}, ({ moduleName, moduleConfig }) => {
return {
componentPath: `cf-modules/${moduleName}`,
config: moduleConfig
};
})
]);
import Ember from 'ember';
import { read, write } from './manipulators';
const {
computed,
addObserver,
propertyDidChange,
} = Ember;
export function data(key, options = {}) {
return computed(key, 'scope', {
get() {
const scope = this.get('scope');
const onResolve = (object, property) => registerDependentProperty(object, property, this, key);
return read(this, key, { ...options, scope, onResolve });
},
set(localKey, value) {
const scope = this.get('scope');
write(this, key, value, { scope });
return value;
}
});
}
const SOURCES = window.SOURCES = new WeakMap();
function registerDependentProperty(sourceObj, sourceKey, dependentObj, dependentKey) {
const dependentObjects = dependentObjectsFor(sourceObj, sourceKey);
let dependentKeys = dependentObjects.get(dependentObj);
if (!dependentKeys) {
dependentKeys = new Set();
dependentObjects.set(dependentObj, dependentKeys);
addObserver(sourceObj, sourceKey, dependentObj, () => {
for (const key of dependentKeys) {
propertyDidChange(dependentObj, key);
}
});
}
dependentKeys.add(dependentKey);
}
function dependentObjectsFor(sourceObj, sourceKey) {
let keysForSource = SOURCES.get(sourceObj);
if (!keysForSource) {
keysForSource = new Map();
SOURCES.set(sourceObj, keysForSource);
}
let dependentObjects = keysForSource.get(sourceKey);
if (!dependentObjects) {
dependentObjects = new WeakMap();
keysForSource.set(sourceKey, dependentObjects);
}
return dependentObjects;
}
import Ember from 'ember';
import Binding from './binding';
const {
get,
set,
propertyDidChange,
} = Ember;
export function read(obj, path, options = {}) {
const { scope, onResolve } = options;
const segments = path.split('.');
let parent = null;
let current = obj;
for (const segment of segments) {
parent = current;
current = get(current, segment);
if (current instanceof Binding) {
current = current.read(options);
}
if (current === undefined && 'default' in options) {
return options.default;
}
// TODO this might be a terrible idea
if (Array.isArray(current)) {
current = current.map((item) => {
return item instanceof Binding ? item.read(options) : item;
});
}
}
const last = segments[segments.length - 1];
if (onResolve && !(get(parent, last) instanceof Binding)) {
onResolve(parent, last);
}
return current;
}
export function write(obj, path, value, options = {}) {
const lastDot = path.lastIndexOf('.');
const readPath = path.substring(0, lastDot);
const writeKey = path.substring(lastDot + 1);
let parent = obj;
if (readPath) {
parent = read(obj, readPath, options);
}
const existingValue = get(parent, writeKey);
if (existingValue instanceof Binding) {
existingValue.write(value, options);
propertyDidChange(parent, writeKey);
} else {
set(parent, writeKey, value);
}
}
export default class Scope {
constructor(bound = Object.create(null)) {
this.bound = bound;
}
lookup(key) {
if (key in this.bound) {
return this.bound[key];
} else {
throw new Error(`Unbound identifier '${key}'`);
}
}
extend(values) {
const newBound = Object.create(this.bound);
Object.assign(newBound, values);
return new Scope(newBound);
}
}
import Ember from 'ember';
import { data } from 'twiddle/cf/computed-macros';
const {
Component,
computed,
} = Ember;
export default Component.extend({
items: data('config.items'),
yield: data('config.yield'),
childModule: data('config.eachModule'),
itemScopes: computed('items.[]', 'scope', function() {
const scope = this.get('scope');
const key = this.get('yield');
return this.get('items').map(item => scope.extend({ [key]: item }));
})
});
{{#each itemScopes as |itemScope|}}
{{yield childModule itemScope}}
{{/each}}
import Ember from 'ember';
import { data } from 'twiddle/cf/computed-macros';
const {
Component,
computed,
} = Ember;
export default Component.extend({
value: data('config.value'),
type: data('config.type', { default: 'text' }),
placeholder: data('config.placeholder')
});
<input
type={{type}}
placeholder={{placeholder}}
value={{value}}
oninput={{action (mut value) value='target.value'}}
>
import Ember from 'ember';
import { data } from 'twiddle/cf/computed-macros';
const {
Component,
computed,
} = Ember;
export default Component.extend({
content: data('config.content'),
text: computed('content', function() {
return this.get('content').join('');
})
});
import Ember from 'ember';
export default Ember.Component.extend({
willRender() {
console.time(`Module: ${this.get('module.componentPath')}`);
this._super(...arguments);
},
didRender() {
this._super(...arguments);
console.timeEnd(`Module: ${this.get('module.componentPath')}`);
}
});
{{#component module.componentPath
config=module.config
scope=scope
as |childModule childScope|
}}
{{~invoke-module module=childModule scope=(or childScope scope)~}}
{{/component}}
import 'twiddle/vendor';
import Ember from 'ember';
import config from './config/environment';
const Router = Ember.Router.extend({
location: 'none',
rootURL: config.rootURL
});
Router.map(function() {
});
export default Router;
{
"version": "0.12.1",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js",
"ember": "2.12.0",
"ember-template-compiler": "2.12.0",
"ember-testing": "2.12.0"
},
"addons": {
"ember-truth-helpers": "*"
}
}
define('botanist', ['exports'], function(exports) {
var botanist =
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function(value) { return value; };
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 5);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (immutable) */ __webpack_exports__["d"] = isSimple;
/* harmony export (immutable) */ __webpack_exports__["b"] = isArray;
/* harmony export (immutable) */ __webpack_exports__["a"] = isObject;
/* harmony export (immutable) */ __webpack_exports__["c"] = isFunction;
/**
* Indicates whether a given value is "simple", i.e. it should be matched by the
* simple() binding directive, and will not have recursive rules applied against it.
*/
function isSimple(value) {
return !isArray(value) && !isObject(value) && !isFunction(value);
}
/**
* Indicates whether the given value is an array.
*/
function isArray(value) {
return Array.isArray(value);
}
/**
* Indicates whether the given value is a POJO, typically created via an object literal.
*/
function isObject(value) {
if (!value || typeof value !== 'object') return false;
let proto = Object.getPrototypeOf(value);
return proto === null || proto === Object.prototype;
}
/**
* Indicates whether the given value is a function
*/
function isFunction(value) {
return typeof value === 'function';
}
/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__utils__ = __webpack_require__(0);
/* harmony export (immutable) */ __webpack_exports__["a"] = simple;
/* harmony export (immutable) */ __webpack_exports__["e"] = choice;
/* harmony export (immutable) */ __webpack_exports__["b"] = match;
/* harmony export (immutable) */ __webpack_exports__["c"] = sequence;
/* harmony export (immutable) */ __webpack_exports__["d"] = subtree;
/**
* Matches any simple value and binds it to the given name in the rule handler.
* The following would match any object with only a `key` property whose value
* was a string, number, boolean, etc. It would also match non-POJO objects, such
* as Ember class instances.
*
* @rule({ key: simple('name') })
* shoutName({ name }) {
* return { key: name.toUpperCase() };
* }
*
* Input: { foo: { key: 'one' }, bar: { key: 'two' } }
* Output: { foo: { key: 'ONE' }, bar: { key: 'TWO' } }}
*/
function simple(name) {
return binder(name, node => __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__utils__["d" /* isSimple */])(node) ? node : NO_MATCH);
}
/**
* Matches any simple value that is in the given list.
*
* @rule({ measure: simple('measure'), unit: choice(['in', 'ft'], 'unit') })
* convertToMetric({ measure, unit }) {
* let factor = unit === 'ft' ? 30.48 : 2.54;
* return { measure: measure * factor, unit: 'cm' };
* }
*
* Input: { measure: 3, unit: 'in' }
* Output: { measure: 7.62, unit: 'cm' }
*
* Input: { measure: 3, unit: 'cm' }
* Output: { measure: 3, unit: 'cm' }
*/
function choice(options, name) {
return binder(name, node => __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__utils__["d" /* isSimple */])(node) && options.indexOf(node) !== -1 ? node : NO_MATCH);
}
/**
* Matches any string that matches the given regex, binding the results of that
* regex's execution to the given name.
*
* @rule({ key: match(/^(foo|bar)(baz|qux)$/, 'keyParts') })
* shoutName({ keyParts: [first, second, everything] }) {
* return { first, second, everything };
* }
*
* Input: { foo: { key: 'fooqux' } }
* Output: { foo: { first: 'foo', second: 'qux', everything: 'fooqux' } }
*/
function match(regex, name) {
return binder(name, (node) => {
let matchResult;
if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__utils__["d" /* isSimple */])(node) && (matchResult = regex.exec(node))) {
return matchResult.slice(1).concat(matchResult[0]);
} else {
return NO_MATCH;
}
});
}
/**
* Matches an array of simple values, binding it to the given name.
*
* @rule({ numbers: sequence('numbers') })
* increment({ numbers }) {
* return numbers.map(n => n + 1);
* }
*
* Input: { numbers: [1, 2, 3] }
* Output: { numbers: [2, 3, 4] }
*/
function sequence(name) {
return binder(name, node => __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__utils__["b" /* isArray */])(node) && node.every(__WEBPACK_IMPORTED_MODULE_0__utils__["d" /* isSimple */]) ? node : NO_MATCH);
}
/**
* Matches any subtree, including complex values, binding it to the given name. Note
* that any such rule will be applied AFTER rules that match the subtree.
*
* @rule({ key: simple('key') })
* shout({ key }) {
* return { key: key.toUpperCase() };
* }
*
* @rule({ root: subtree('root') })
* values({ root }) {
* return { root: Object.values(root) };
* }
*
* Input: { root: { key: 'hello' } }
* Output: { root: ['HELLO'] }
*/
function subtree(name) {
return binder(name, node => node);
}
const NO_MATCH = Object.freeze({});
function binder(name, test) {
return (node, context) => {
let result = test(node);
if (result !== NO_MATCH) {
return !name || context.bind(name, result);
}
};
}
/***/ }),
/* 2 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__context__ = __webpack_require__(4);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__compile_matcher__ = __webpack_require__(3);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__utils__ = __webpack_require__(0);
/* harmony export (immutable) */ __webpack_exports__["b"] = applyRules;
/* harmony export (immutable) */ __webpack_exports__["a"] = extractRules;
/* harmony export (immutable) */ __webpack_exports__["c"] = compileRule;
/* harmony export (immutable) */ __webpack_exports__["d"] = memoizeRule;
/**
* Recursively applies the given set of rules to the given AST.
*/
function applyRules(ast, rules, options) {
if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__utils__["a" /* isObject */])(ast)) {
let result = Object.create(null);
Object.keys(ast).forEach((key) => {
result[key] = applyRules(ast[key], rules, options);
});
return applyMatchingRule(result, rules, options);
} else if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__utils__["b" /* isArray */])(ast)) {
let result = ast.map(node => applyRules(node, rules, options));
return applyMatchingRule(result, rules, options);
} else {
return ast;
}
}
/**
* Given an object or array containing a combination of rule definitions and other such
* arrays/objects, returns a flattened array of canonicalized rules.
*/
function extractRules(object) {
let rules = rulesFromObject(object);
if (!__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__utils__["b" /* isArray */])(rules)) return [object];
let flattened = [];
for (let i = 0, len = rules.length; i < len; i++) {
flattened.push(...extractRules(rules[i]));
}
return flattened;
}
/**
* Given a matcher spec and a transform function, immediately compiles the pair into a rule.
*/
function compileRule(spec, transform) {
let match = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__compile_matcher__["a" /* default */])(spec);
return { match, transform };
}
/**
* Given an object, a matcher spec, and the name of a method on that object representing an
* AST transform, compiles and stores the matcher on that object for later retrieval.
*/
function memoizeRule(object, transformKey, spec) {
if (!(RULES in object)) object[RULES] = [];
let match = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__compile_matcher__["a" /* default */])(spec);
object[RULES].push({ match, transformKey });
}
// Key where rule annotations are stashed on a decorated object. We don't use a Symbol because
// they're never exposed in for-in loops, so it makes working with e.g. Ember objects unwieldy.
const RULES = '__botanist-rules';
// Find a rule matching the given node and apply it, or just return the node
function applyMatchingRule(node, rules, options) {
let context = new __WEBPACK_IMPORTED_MODULE_0__context__["a" /* default */]();
for (let i = 0, len = rules.length; i < len; i++) {
let rule = rules[i];
if (rule.match(node, context)) {
return rule.transform.call(null, context.expose(), options, node);
}
}
return node;
}
// Extract an array of canonicalized rules from an object
function rulesFromObject(object) {
if (object[RULES]) {
return object[RULES].map(({ match, transformKey }) => ({
match,
transform: (...params) => object[transformKey](...params)
}));
} else if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__utils__["b" /* isArray */])(object)) {
return object;
}
}
/***/ }),
/* 3 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__utils__ = __webpack_require__(0);
/* harmony export (immutable) */ __webpack_exports__["a"] = compileMatcher;
/**
* Given a specification for a matcher, compiles that spec into a function that will
* take an AST node and a binding context and return a boolean indicating whether that
* AST node matches the original spec.
*
* If the matcher returns true and the spec included binding directives, the context
* will also be updated with bound values for those directives.
*/
function compileMatcher(spec) {
if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__utils__["b" /* isArray */])(spec)) {
return buildArrayMatcher(spec);
} else if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__utils__["a" /* isObject */])(spec)) {
return buildObjectMatcher(spec);
} else if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__utils__["c" /* isFunction */])(spec)) {
return spec;
} else {
return node => node === spec;
}
}
function buildObjectMatcher(object) {
let requiredKeys = Object.keys(object).sort();
let matchers = requiredKeys.map(key => compileMatcher(object[key]));
return (node, context) => {
if (!node || typeof node !== 'object') return false;
let actualKeys = Object.keys(node).sort();
if (!arrayEqual(requiredKeys, actualKeys)) return false;
return matchAll(context, matchers, requiredKeys.map(key => node[key]));
};
}
function buildArrayMatcher(spec) {
let matchers = spec.map(value => compileMatcher(value));
return (node, context) => __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__utils__["b" /* isArray */])(node) && matchAll(context, matchers, node);
}
function arrayEqual(left, right) {
if (left.length !== right.length) return false;
for (let i = 0, len = left.length; i < len; i++) {
if (left[i] !== right[i]) return false;
}
return true;
}
function matchAll(context, matchers, values) {
if (matchers.length !== values.length) return false;
let provisionalContext = context.createProvisionalContext();
for (let i = 0, len = matchers.length; i < len; i++) {
if (!matchers[i](values[i], provisionalContext)) return false;
}
provisionalContext.commit();
return true;
}
/***/ }),
/* 4 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/**
* Represents the context of bound variables for a given AST matcher.
*/
class Context {
constructor(parent = null) {
this.parent = parent;
this.storage = Object.create(parent && parent.storage);
}
/**
* Binds a value in this context, if possible. Returns a boolean indicating whether the
* binding was successful (i.e., either the key was previously undefined or it already
* contained the given value).
*/
bind(key, value) {
if (key in this.storage) {
if (this.storage[key] === value) {
return true;
}
} else {
this.storage[key] = value;
return true;
}
}
/**
* Exposes the bound values on this context in a POJO.
*/
expose() {
return Object.create(this.storage);
}
/**
* Creates a provisional version of this context to bind values into. Those bound values
* can later be committed to the context to make them permanent.
*/
createProvisionalContext() {
return new Context(this);
}
/**
* Applies all values bound on this provisional context to the parent that originally begat it.
*/
commit() {
Object.keys(this.storage).forEach((key) => {
this.parent.storage[key] = this.storage[key];
});
}
}
/* harmony export (immutable) */ __webpack_exports__["a"] = Context;
/***/ }),
/* 5 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__rules__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__binding_matchers__ = __webpack_require__(1);
/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "simple", function() { return __WEBPACK_IMPORTED_MODULE_1__binding_matchers__["a"]; });
/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "match", function() { return __WEBPACK_IMPORTED_MODULE_1__binding_matchers__["b"]; });
/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "sequence", function() { return __WEBPACK_IMPORTED_MODULE_1__binding_matchers__["c"]; });
/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "subtree", function() { return __WEBPACK_IMPORTED_MODULE_1__binding_matchers__["d"]; });
/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "choice", function() { return __WEBPACK_IMPORTED_MODULE_1__binding_matchers__["e"]; });
/* harmony export (immutable) */ __webpack_exports__["transform"] = transform;
/* harmony export (immutable) */ __webpack_exports__["rule"] = rule;
function transform(ruleSpecs) {
let rules = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__rules__["a" /* extractRules */])(ruleSpecs);
return (ast, options) => __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__rules__["b" /* applyRules */])(ast, rules, options);
}
function rule(spec, transformFunction) {
if (transformFunction) {
return __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__rules__["c" /* compileRule */])(spec, transformFunction);
} else {
return (target, key, descriptor) => {
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__rules__["d" /* memoizeRule */])(target, key, spec);
return descriptor;
};
}
}
/***/ })
/******/ ]);
Object.assign(exports, botanist);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment