lasso-js / lasso

Advanced JavaScript module bundler, asset pipeline and optimizer
580 stars 75 forks source link

Reference optimizer.json within tag? #37

Open viviangledhill opened 9 years ago

viviangledhill commented 9 years ago

The marko-taglib.json references the components dir. You add your tag to your page and in addition to that you have to link every single component optimizer.json in the page optimizer.json

Is there a way to avoid this duplicate linking? The tag itself and the link in the optimizer.json?

For example add an attribute to the tag similar to w-bind instead of the optimizer.json link?

Thanks -Vivian

patrick-steele-idem commented 9 years ago

Hi @viviangledhill, that's a good question. The short answer is that there is probably no good/clean way to avoid the duplication. Right now, front-end resource optimization and template rendering are separate steps and there is no tight coupling between Marko and the Optimizer.

We considered various options, but we found a lot caveats with each option so we have not tried to implement a solution. I've included various options below that we can discuss further:

Option 1) Introduce more Optimizer tags to record additional JS and CSS dependencies at render time

For example:

<optimizer-dependency path="./optimizer.json" />

At render time, capture the list of rendered UI components and have the Optimizer use that information to determine the required JS and CSS at the very end of rendering. After the entire page has been rendered go back and optimize the page based on all of the referenced optimizer.json files and insert the required <link> and <script> tags into the output HTML.

Pros:

Cons:

Option 2) Use static code analysis to look for the usage of custom tags inside Marko template files and automatically include optimizer.json files with custom tags

During optimization, parse and scan Marko template files to find and resolve custom tags to optimizer.json files. If a custom tag resolves to a directory with another template then that template should also be scanned, recursively.

For example, in your optimizer.json:

{
  "dependencies": [
    "marko-tag-dependencies: ./template.marko",
  ]
}

Pros:

Cons:

Option 3) Introduce a new UI component dependency type

For example, given the following UI component directory:

src/components/app-foo/
├── assets/
|   └── ...
├── index.js # The module exports
├── marko-tag.json # Describes the custom tag for use in Marko templates
├── optimizer.json # Client-side dependencies
├── README.md # Documentation
├── renderer.js # HTML renderer
├── style.(css|less|styl|scss) # UI Component styling
├── template.marko # HTML template
├── test.js # Any unit tests for the UI component
└── widget.js # Client-side behavior

In your optimizer.json:

{
  "dependencies": [
    "ui-component-dependencies:./"
  ]
}

The custom ui-component-dependencies dependency will automatically scan the directory and include the required client-side dependencies based on a convention such that it would be equivalent to the following (based on the found files):

{
  "dependencies": [
    "marko-tag-dependencies: ./template.marko",
    "require: ./widget",
    "style.css"
  ]
}

Pros:

Cons:


What are your thoughts on the above options? Are there any options that I missed?

I think we should bring more people into this discussion to see if we can come up with a solution worth implementing.

Thanks, Patrick

patrick-steele-idem commented 9 years ago

@philidem Thoughts?

peterdotjs commented 9 years ago

Hey Patrick,

Option 1: This fits our needs because we need to ability to use different taglib components during runtime based on the service response.

What is the performance hit we'll take on this approach versus using optimizer? I'm assuming that after the initial rendering the resources will be cached so performance may not be an issue.

Option 2: May not be a great option since there are a lot of corner cases to handle as you stated. Option 3: This would also work if not for our requirement of dynamic components at runtime.

Thanks, Peter

patrick-steele-idem commented 9 years ago

@peterdotjs Going with Option 1 would introduce more problems and prevent things like automatic code splitting for dependencies that are shared across pages. Code splitting requires the ability to know the set of page dependencies for all pages from static analysis and that would break if the page's dependencies can vary by rendering. Code splitting is just one example. Testing and pre-optimization (without page rendering) would also be more difficult.

At this point, I would recommend doing more upfront work to figure out what your page optimization output could vary by and set the appropriate optimizer flags to allow for conditional dependencies. The page's HTML templates could use those same flags to control conditional rendering of the page. I realize that is kind of a pain, but I really don't think we want to go down the rabbit hole that comes with coupling page optimization with page rendering. Also, keep in mind that just because your page's HTML output will vary does not mean that you want to always include different JS and CSS. Sometimes it is better just to include the superset of dependencies for better cache hits. For example, if you have a search results page that supports "list view" and "grid view" with different JS/CSS, it is probably better to include the CSS and JS to support both a "list view" and a "grid view" so that the page JS and CSS do not change if the user toggles the view.

I want to keep the discussion open, but I definitely lean towards solutions that allow for all dependencies to be discovered independent of rendering HTML.