monarchwadia / steelbit

2 stars 0 forks source link

External imports #1

Open monarchwadia opened 1 year ago

monarchwadia commented 1 year ago

Really excited about collabbing with you on this, Paul!

How do we handle importing npm modules, user-defined functions, hardcoded libraries, shims, etc.? We want to make them available within steelbit components and stores, while allowing the developer to manage them separately using normal code. Steelbit's goal is to make UX/UI easier to build -- it is not to replace the entire frontend stack -- so how do we make it so that it fits well into an average developer's normal development flow, instead of forcing its own philosophy?

We talked about the solution on our call, yet the implementation is still unclear. So I'm going to record what we talked about; please feel free to add more.

  1. Use flat json files: We are going to continue storing everything as flat JSON files. i.e. plain text files in the format of a JSON file. This will allow for more automation and tooling, where we can define tools to quickly refactor and manipulate the entire application tree without the need for a full runtime environment.
  2. Support quick functions: WYSIWYG UI will eventually have the ability to define quick functions inside components without the need to write those functions externally. These will be stored in a functions.json file. The functions will be eval'd during runtime bootup. They'll be referenced by ID. So, for example, a component might have on: {click: "123xyz"} with a matching function in the functions.json that looks like {id: "123xyz", body: "() => console.log('clicked')"}
  3. Support external functions: We will allow the imports of external functions, npm modules, etc. so that if we import for example a logger, then the click handler received it dependency-injected it like so: ({originalEvent, imports}) => { originalEvent.preventDefault(); imports.logger.log('clicked') }.
  4. Support external imports The same dependency injection method in #3 above can be used to import npm modules.

This is a rough sketch of what we talked about.. what do you think?

pldilley commented 1 year ago

For #2, it may not be necessary to have the functions in a separate file, may as well keep it all in line. I'm not sure I see much advantage of a separate functions.json file that contains just a list of functions. It will make the json harder to debug.

If you have #3, then it doesn't matter because people can DI their own external functions anyway, which I suspect people would opt to do for several reasons (browser optimizations, use of IDE, etc). With DI the developer has flexibility.

We may want to eval all the stringy functions at once, so at runtime we might collect all the strings into one blob and run eval in a single shot. This would be more performant that running it one function at a time. In this case we may want to use some generation to ensure no conflicts.

I want to suggest that if json function args don't get obsecurificated, have the DI mechanism pick them up automatically. So something like this:

// "logger" is available in every managed callback.
({originalEvent, logger}) => { originalEvent.preventDefault(); logger.log('clicked') }

If there's concern about naming conflicts, there's two ways to handle this:

  1. Have imported things pre-pended with something, e.g. $logger. The user can always alias this back to logger:

    ({originalEvent: e, $logger: logger }) => { e.preventDefault(); logger.log('clicked') }
  2. Have the dev be required to register every external library, using the name of their choice, and throw an error there if using reserved names:

    registerExternalDependencies({
    logger: import('web-logger'),
    });
monarchwadia commented 1 year ago

I agree with the first point, a single JSON file will make it easier to write framework code.

monarchwadia commented 1 year ago

Re: external libs, I like the idea of asking the dev to register every external library manually. I.e. I like this snippet of yours

registerExternalDependencies({
  logger: import('web-logger'),
});

After a while, we can come up with smarter and easier methods to make this available to them. For example, folder-based imports. The following is not a suggestion, but just an example... If you have the following files:

src
  -scripts
    -logger.ts
    -apiUtil.ts
  -steelbit.json

and lets say the files look like so:

// src/scripts/logger.ts
export default (...args) => console.log(...args)

// src/scripts/apiUtil.ts
const apiUtil = { get, put, post, delete}
export default apiUtil

Then those become available like so:

// "logger" and "apiUtil" is available in every managed callback.
({originalEvent, logger, apiUtil}) => { originalEvent.preventDefault(); logger.log('clicked'); apiUtil.get("/blah") }
monarchwadia commented 1 year ago

Question: Where will registerExternalDependencies be called? Will we need a steelbit.config.ts file?

pldilley commented 1 year ago

Ok I like the idea of automatic imports based on a folder. Maybe the config should also be in json format?

{
  "dependencies": {
    "manualImport": "web-logger"
  },
  "lazyDependencies": {
    "otherManualImport": "web-vlogger"
  },

Then the runtime loader can translate these two dynamic import statements before loading the rest of the app. A future option could be to allow lazy loading, in which it's only imported when it's being used as a dependency by a function (meaning execution of the function could be delayed).

pldilley commented 1 year ago

Next.js have url navigation based on file paths, which I thought was really cool, including support for url variables.

monarchwadia commented 1 year ago

Agree re: config should be in JSON format, it fits into the greater JS ecosystem's way of doing things.

monarchwadia commented 1 year ago

I think we have enough discussion on this thread to get started with some experiments, yeah?