teleport-code-generators icon indicating copy to clipboard operation
teleport-code-generators copied to clipboard

UIDL Declaration Change: Asset declaration and usage

Open vladnicula opened this issue 5 years ago β€’ 15 comments

Related to the discussion from https://github.com/teleporthq/teleport-code-generators/issues/79

In this ticket I'll describe the change proposal for handling assets in components and the overall project. I'll list the section of this proposal as it might get quite long:

  1. Overview of a component using the new asset and expected behaviours from the component and project generators
  2. What is a asset and how we could group assets in order to better handle them
  3. Declaration of the asset json and it's relationship with global assets
  4. Implementation implications in the assembly line, resolvers and more
  5. Potential support problems and incompatibilities between generators
  6. Overall implementation plan
  7. Potential extension to Prop

1. Quick look at a component using the new asset and expected behaviours from the component and project generators

Below is a valid (I think) uidl representation of a component:

{
  "$schema": "https://raw.githubusercontent.com/teleporthq/teleport-code-generators/master/src/uidl-definitions/schemas/component.json",
  "name": "AuthorImage",
  "propDefinitions": {
    "authorName": {
      "type": "string",
      "defaultValue": "TeleportHQ Rocks"
    },
  },
  "content": {
    "type": "image",
    "name": "img",
    "attrs": {
      "src": "http://lorempixel.com/400/200/",
      "alt": "$props.authorName"
    }
  }
}

The resulting code looks like this:

import React from 'react'
import PropTypes from 'prop-types'

const AuthorImage = props => {
  return <img src="http://lorempixel.com/400/200/" alt={props.authorName} />
}

AuthorImage.defaultProps = {
  authorName: 'TeleportHQ Rocks',
}

AuthorImage.propTypes = {
  authorName: PropTypes.string,
}

export default AuthorImage

The suggested new structure is the following:

The main changed section:

 "assetDefinitions": {
    "myLocalAssetId": {
      "type": "image",
      "url" : "http://lorempixel.com/400/200/",
      "meta": {
        "serve": "local"
      }
    }
  }

and:

"attrs": {
      "src": {
        "type": "asset",
        "id": "myLocalAssetId"
      },
      "alt": "$props.authorName"
    }

the full uidl definition

{
  "$schema": "https://raw.githubusercontent.com/teleporthq/teleport-code-generators/master/src/uidl-definitions/schemas/component.json",
  "name": "AuthorImage",
  "assetDefinitions": {
    "myLocalAssetId": {
      "type": "image",
      "url" : "http://lorempixel.com/400/200/",
      "meta": {
        "serve": "local"
      }
    }
  },
  "propDefinitions": {
    "authorName": {
      "type": "string",
      "defaultValue": "TeleportHQ Rocks"
    }
  },
  "content": {
    "type": "image",
    "name": "img",
    "attrs": {
      "src": {
        "type": "asset",
        "id": "myLocalAssetId"
      },
      "alt": "$props.authorName"
    }
  }
}

Alternative definition, probably useful to support it as well from the get go:

"attrs": {
      "src": {
        "type": "asset",
        "content":  {
            "type": "image",
            "url" : "http://lorempixel.com/400/200/",
            "meta": {
               "serve": "local"
            }
          } 
      },
      "alt": "$props.authorName"
    }

2. What is a asset and how we could group assets in order to better handle them

We can group them by the type of content they have. Fonts, images and svgs are 3 different types handled differently by the browser. They might need different handling in the generators.

We could also group them by the location they will exist in. For example, assets that are supposed to be in the cloud are not going the be downloaded and referenced locally. Resources that are local might need to be moved into the β€œstatic” folder used by the project generator.

3. Declaration of the asset json and it's relationship with global assets

The asset definition is inspired by the global assets definitions found in https://github.com/teleporthq/teleport-code-generators/blob/master/examples/uidl-samples/project-state-components.json#L32.

My proposal is something like:

interface UIDLAssetDefinition {
  type: "image" | "video" | "svg" | "font" | "web:style" | "web:script"
  url: string
  meta: Record<string, number | string | boolean>
}

With the following mentions:

  1. It's unclear right now how SVG type would work. We could support images that have svg as source.
  2. The url could be renamed as path.
  3. In the meta flag we could discover common usecases that could be upgraded to named properties later on. For example, one flag proposed would be "serve": "local" | "external" which will be used to indicate if we load data from the web or store is locally somewhere.
  4. This representation would pretty much match the global assets.
  5. We could prefix things that only work for web with the web: prefix. For example, the style and script assets work only on the web, they won't work in react native.

4. Implementation implications in the assembly line, resolvers and more

In order to properly generate this kind of assets we need to change the code that :

  • generates the main component generators which handles generation of primitive images,
  • generates the styles for entities that use urls for background images. This would include all react style flavours.
  • for supporting global assets to be referenced directly into the UIDL we might need to alter the dependency pipeline from which import statements are generated, as we will now have dependencies of type asset which are expected to be served from the static folder of the generated project. This is a secondary feature in my opinion and could be left out of the initial proof of concept.
  • the project generator which will be required to download and make sure assets are available in local mode. Just like the previous point, we can commit this from the initial code generation of the component and only work with remote files.

5. Potential support problems and incompatibilities between generators

The work requires changing many style flavours of react. I expect we'll find a way to abstract the work away from the plugins and have them only handle the same type of external variable as they do with normal props. This is a task for exploration.

6. Overall implementation plan

  1. Start of with remote support of images in srcs of components. Make sure the react and vue component generators work without problems in this scenario.
  2. Add support for one react flavour and vue styles for background images. Still remote only.
  3. Identify a way to describe this in a way that does not duplicate the same work regarding asset addressing in all react flavours. Implement that if possible.
  4. Implement capability for asset usage in this way in all plugins.
  5. Start working on global assets support, clarify if there is a need for the dependencies of the pipeline to be altered or not.
  6. Add support for copying assets over and serving them on local style.
  7. Document the expected behavior of the generators, for other to be able to implement this kind of support.

7. Potential extension to Prop

If we look at the asset definition and compare it with a prop definition we can see similarities. We can also see that a prop could be referenced in the same way as an asset:

{
  "$schema": "https://raw.githubusercontent.com/teleporthq/teleport-code-generators/master/src/uidl-definitions/schemas/component.json",
  "name": "AuthorCardContainer",
  "assetDefinitions": {
    "myLocalAssetId": {
      "type": "image",
      "url" : "http://lorempixel.com/400/200/",
      "meta": {
        "serve": "local"
      }
    }
  },
  "propDefinitions": {
    "authorName": {
      "type": "string",
      "defaultValue": "TeleportHQ Rocks"
    }
  },
  "content": {
    "type": "image",
    "name": "img",
    "attrs": {
      "src": {
        "type": "asset",
        "id": "myLocalAssetId"
      },
      "alt":  {
         "type": "prop",
          "id": "authorName"
       }
    }
  }
}

vladnicula avatar Mar 22 '19 14:03 vladnicula

Awesome proposal, both as content and form!

Would it be a good moment to think about variables and constants as well? Besides propDefinitions and assetDefinitions we could also have varDefinitions:

...
varDefinitions: {
  color1: '#fff',
  color2: '#000'
}
... and later on ... 
{
  style: {
    color: $color1,
    border: 'solid 1px $color2'
  }
}

paulbrie avatar Mar 23 '19 21:03 paulbrie

@paulbrie yes, we could end up with a common way of defining the type of values that are attributed to style props and to attributes.

I'm not sure how local variable declarations make sense, since we are completely in control of what the local scope looks like. Local variables in UIDL don't make sense to me right now.

vladnicula avatar Mar 25 '19 06:03 vladnicula

I would suggest against border: 'solid 1px $color2' though. I'd rather use objects to define references to props, vars or assets or anything else that is not a string. We could in the end create a preprocessor that reads $color and generates the boderColor: { type: 'prop', id: 'color2' } for us. This would be a different step.

vladnicula avatar Mar 25 '19 06:03 vladnicula

Mentioning #35 as the extensions of asset representation solves that as well

vladnicula avatar Mar 25 '19 08:03 vladnicula

Follow-up from a discussion with @vladnicula:

  • we are proceeding with the proposed asset definition structure at the UIDL level
  • this will be the representation for local and remote assets
  • "inline" definition might be an idea as a fallback, but for now we will enforce the assetsDefinitions in the UIDL
  • style rules which can contain assets will be isolated from the rest of the rules (ex: we won't be using background, but background-image specifically). this means that style processing plugins will have to handle assets using the same schema defined above

Questions/Follow-up explorations:

  • using the same structure for props and state (we have to have a good way of representing the dot notation - ex: props.author.id
  • we have to figure out how to represent text nodes with this structure (ex: "children": ["$props.authorName"])

alexnm avatar Mar 25 '19 14:03 alexnm

I'm having issues just adding assets and not touching props. The MR that I will do will contain the props change as well as it is easier to write one single treatment for assignments of state, props, assets and local vars in one go.

vladnicula avatar Mar 26 '19 13:03 vladnicula

@vladnicula I think local variables could appear inside a component definition. It probably will trigger a new discussion about variables scope management. Maybe to be discussed later.

paulbrie avatar Mar 29 '19 10:03 paulbrie

sure, I'm not certain "local" has any meaning when we have props and "state".

vladnicula avatar Mar 29 '19 12:03 vladnicula

Hey @vladnicula the proposal looks really good, but I have a small doubts

  1. Regarding the attributeserve
"content":  {
            "type": "image",
            "url" : "http://lorempixel.com/400/200/",
            "meta": {
               "serve": "local"
            }
          }

So, I am assuming (if I am not wrong) the serve defines that the asset is a local one which can be found from the generated files and if it is dynamic or something we need to load from an external resource, but I don't see the difference in the way we handle both the cases because we use url to set the src for assets but src is being defined by the user and if the user defines something like this

static/images/user.png

Do we create particular folders and store the assets there instead of our defined path?

  1. How we are going to handle the serve local for componentGenerators since we don't have any specific local folders to refer and its just a component output.

JayaKrishnaNamburu avatar Apr 01 '19 07:04 JayaKrishnaNamburu

But this proposal makes the whole Schema much cleaner and more declarative on handling assets πŸ˜ƒ πŸŽ†

JayaKrishnaNamburu avatar Apr 01 '19 07:04 JayaKrishnaNamburu

@JayaKrishnaNamburu

For 1.

server: local would mean server from local folder, not the internet, meaning download whatever is at that url.

We need this because tools like the visual editor (playground) keeps assets in the cloud, and when we run the generators we need to manually download assets.

  1. The idea is to expand the externalDeps of the component generator to support additional things, like extra instructions or requirements for the project generator. Right now we can only specify required packages to be available in package json, but we could also introduce some new concepts there, like a set of required assets to be downloaded and be made available locally for the component.

Does this answer your questions?

Feel free to chip in with more ideas on this ;) Thanks

vladnicula avatar Apr 01 '19 11:04 vladnicula

Yeah that clears a lot of doubts to me, so we will download the assets and make them available to the user when we are exporting the project right ?

JayaKrishnaNamburu avatar Apr 01 '19 11:04 JayaKrishnaNamburu

Yes, that's the intention. We are already doing that by hand before generating code in our visual editor and would like to move that functionality into a stage of the project generation.

vladnicula avatar Apr 01 '19 11:04 vladnicula

But, downloading external dependencies are affected by the external situations which might affect the generators performance time right? πŸ˜…
Total time = Time taken for the code to generate + time taken for the assets to download

JayaKrishnaNamburu avatar Apr 01 '19 11:04 JayaKrishnaNamburu

Agreed. This is gonna be opt in. And in a separate package. It won't be in the part that generates the code and the regular files - I think -.

vladnicula avatar Apr 01 '19 11:04 vladnicula