Skip to content
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

Simplify function injection #73

Open
AndrewRot opened this issue Nov 27, 2019 · 5 comments
Open

Simplify function injection #73

AndrewRot opened this issue Nov 27, 2019 · 5 comments

Comments

@AndrewRot
Copy link
Contributor

AndrewRot commented Nov 27, 2019

So I was trying to use preval to insert functions into my code before run time. What I thought was going to be quite trivial, turned out to be a bit tricky. In order to insert functions, preval implicitly requires you to wrap your exports in an object.

Example function:

function doThing(message) { return message + ' a thing'; }
module.exports = { doThing }; // You must wrap it here [see explanation A below]

Usage of preval:

const doThing = preval.require('./doDigest.js').doThing; // pull out function from object
// In order to insert a function, you must wrap it in an object and then extract it

Generated code:

var doThing = {
  "doThing": function doThing(message) {
    return message + ' a thing';
  }
}.doThing;

explanation A

Preval then creates an object with our function inside. This is a bit funky for users. If you attempt to do this without wrapping your exports as an object, preval will fail to inject the function with the following behavior:

confusing failed scenario to insert function

Example function:

function doThing(message) { return message + ' a thing'; }
module.exports = doThing; // Omission of object wrapper

Usage of preval:

const doThing = preval.require('./doDigest.js');

Generated code:

var doThing = "undefined a thing";

Suggestion

A.) To make this easier for users to insert functions into their code, you may want to consider the way preval requires modules.
B.) Add an example to the README.md that illustrates how this is done.

@kentcdodds
Copy link
Owner

This is actually a feature that should be documented. If you export a function, them preval will call that function and whatever you return will be the thing used for replacement. Meaning you could return a function if you want:

function doThing(message) { return message + ' a thing'; }
module.exports = () => doThing;

Would you like to update your pull request to add documentation for this feature?

@Maycon-Santos
Copy link

Recently I needed to "generate" a function and thought about the following method:

const getAssets = preval`module.exports = require('./build-time/get-assets')`
const assets = preval`module.exports = { run: () => ({${getAssets}}) }`.run()

getAssets returns something like: require('./image-path-1'); require('./image-path-2'); ....
assets puts this string into the function body, generating something like:

{
  run: () => { require('./image-path-1'); require('./image-path-2');  ...}
}

Doubts:

  • Is there an easier way for us to generate a function without having to generate 2 const?
  • Could this method or another better be in the documentation?

Sorry for my English, I'm a beginner at this.

@kentcdodds
Copy link
Owner

You should probably be using babel-plugin-codegen which is more powerful.

@Hideman85
Copy link

I have a similar needs, I was trying out this tool and got nicely surprised at beginning but then I just tried to put a condition and then 💥

Second point super annoying is I would like to preserve my modules from beginning to end and output es2020 code in the end.

Sample used in test

I use esbuild to bundle the content....

// lib.ts
var factory = (str, num) => ({ str, num });
var builder = () => {
  const obj = {};
  const methods = {
    str: (str) => {
      obj.str = str;
      return methods;
    },
    num: (num) => {
      obj.num = num;
      return methods;
    },
    foo: (foo) => {
      obj.foo = foo;
      return methods;
    },
    build: () => {
      return obj;
    }
  };
  return methods;
};
var Builder = class {
  constructor() {
    this.obj = {};
  }
  str(str) {
    this.obj.str = str;
    return this;
  }
  num(num) {
    this.obj.num = num;
    return this;
  }
  foo(foo) {
    this.obj.foo = foo;
    return this;
  }
  build() {
    return this.obj;
  }
};

// test.ts
var a = { foo: "bar", ...factory("Hello", 1) };
var b = builder().foo("bar").str("Hello").num(1).build();
var c = new Builder().foo("bar").str("Hello").num(1).build();
var d = (str) => {
  let cond = new Builder().foo("bar").num(1);
  if (str) {
    cond = cond.str(str);
  }
  return cond.build();
};
var e = (str) => {
  let cond = builder().foo("bar").num(1);
  if (str) {
    cond = cond.str(str);
  }
  return cond.build();
};
module.exports = () => ({
  a,
  b,
  c,
  d,
  e
});

...then I have a simple index file that preval it and exports the content...

const { a, b, c, d, e } = preval.require('./test-bundled')
export { a, b, c, d, e }

...but then the produced result is invalid.

const {
  a,
  b,
  c,
  d,
  e
} = {
  "a": {
    "foo": "bar",
    "str": "Hello",
    "num": 1
  },
  "b": {
    "foo": "bar",
    "str": "Hello",
    "num": 1
  },
  "c": {
    "foo": "bar",
    "str": "Hello",
    "num": 1
  },
  "d": str => {
    let cond = new Builder().foo("bar").num(1);
    if (str) {
      cond = cond.str(str);
    }
    return cond.build();
  },
  "e": str => {
    let cond = builder().foo("bar").num(1);
    if (str) {
      cond = cond.str(str);
    }
    return cond.build();
  }
};
export { a, b, c, d, e };

My goal is to have a kind of builder pattern that could be statically replaced.
Example:

// Builder declared in the spoiler
export const request = (str) => {
  let cond = builder().foo("bar").num(1);
  if (str) {
    return cond.str(str).build();
  }
  return cond.build();
}

// Statically built as
export const request = (str) => ({
  foo: 'bar',
  num: 1,
  ...(str ? { str } : {}),
})

Of course this is a minimal example but each step would impact several attributes in that object and it would be great to be built as a single object with conditionals spreading for instance.

@Hideman85
Copy link

Any help/news on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants