jorendorff / js-loaders

Pseudoimplementation of the proposed ES6 module loaders.
54 stars 7 forks source link

A default relative import proposal #100

Closed guybedford closed 10 years ago

guybedford commented 10 years ago

This is for the browser spec only.

If Loader.prototype.import does run normalization (as discussed in https://github.com/jorendorff/js-loaders/issues/89), then there may be an interesting possible role for the default parentName in normalizations.

When creating an application, we have two major types of code - application code and library code.

Typically we can think of these as two folders (say app and lib).

A strong motivation for working with normalized module names, is to try and ensure that users refer to external libraries in the same way.

But with a lib folder, one user might refer to lib/jquery and another user might refer to js/jquery.

The code is now diverging in meaning.

The ideal configuration for the baseURL is actually to set it directly to the library code folder.

This way requires can work easily of the form jquery or underscore.

But the question then is how we refer to application code.

Typically, one would define a new path, that separates the app folder from the baseURL:

System.paths['app/*'] = '/app/*.js';

I'm wondering if we can do this better.

If the System loader acted as if the relative import name was the current document path, then we can use relative names to refer to application code.

For example:

  <script>
    System.import('./my-app-code')
  </script>

Would load "my-app-code.js" from the HTML page path.

But we can also then load from the baseURL as well:

  <script>
    System.baseURL = '/lib';

    // loads application code from the page path
    System.import('./my-app-code');

    // loads library code from the baseURL
    System.import('jquery');
  </script>

The restrictions of such a system are:

  1. This relative loading only works in the HTML document itself. Application modules must all refer to each other relatively (with backtracking if necessary).
  2. Library code won't know how to access application code.

These are strong conventions, but good ones, encouraging modular paths and sensible code separation.

That said, I understand that the spec may not want to impose strong conventions.

I hope I've explained this properly. Any questions just ask. I don't want to push an idea that isn't useful, I just thought maybe this could solve a common problem.

@jrburke @unscriptable @johnjbarton @probins I'd value your feedback too on whether this might be useful. If not, no worries at all.

briandipalma commented 10 years ago

I would be interested in something like this. I have been trying to figure out how to do something close to it.

My guess for the standard future workflow for building large web apps would be structured something like this.

My webapp would be composed of many different components, each being complex enough to be described as a web app feature. So taking examples from the domain I'm most familiar with, an FXTile, a FXMarketTicket, a OrderTicket, a live updating instrument grid (showing FX currencies/Bonds for example), each of these would be a feature of the application.

Each should be developed separately as stand alone npm packages which may depend on other npm packages containing Custom Elements and libraries etc. So I would imagine an FXTile may contain many modules (inside it's src/ directory) which can refer to each other in a relative fashion but which would refer to their npm dependencies absolutely.

FXTile/
    src/
        FxTile.js
    node_modules/
        MyFxTradeFactory/

I would not want to have the value node_modules as part of the identifiers that each of these components will use when referring to required npm dependencies. Inside my FxTile module I would like to write import {FxTradeFactory} from "MyFxTradeFactory"; and not import {FxTradeFactory} from "node_modules/MyFxTradeFactory";.

At the same time I would like to use relative paths within the src/ directories and have them resolve to files within src/ and for any relative import IDs within node_modules/ to resolve to their own local package values. I figured the way to do that would be two loaders one that is rooted at the dependency folder node_modules/ and another at the src/ folder, I just wasn't sure how I'd wire that up as the FxTile could either be used as the src/ root or as another node_modules/ dependency when it's pulled into the main application so it couldn't create a new Loader in it's main package class...

johnjbarton commented 10 years ago

Setting a global value to change the behavior of subsequent function calls is prone to errors, is difficult to debug, and makes composition difficult.

I think you have introduced the problem in part by insisting that dependencies be named with a special prefix 'lib/' or 'js/'. If we drop that requirement, the problem changes. Don't allow the choice 'lib/jquery' or 'js/jquery': insist on 'jquery'. This is the solution that npm uses with great success.

While I'm not keen on the proposed solution, a solution to this issue broadly must be part of the standard.

jrburke commented 10 years ago

The distinction between app and library code is a slippery one, and when CDN deployment is taken into consideration, where the app and library JS may be on the CDN and the index.html is under the main hostname, I do not think this convention will be that helpful vs the mistakes a developer may make trying to use it. I prefer to always use the same resolution logic, and not have this specialized pathway.

guybedford commented 10 years ago

James raises a good point here.

We are actually dealing with two URL concepts here, so perhaps it would make more sense to make this explicit:

  System.baseURL = '/lib';
  System.relativeURL = '/app';

  System.import('jquery') // -> '/lib/jquery.js'
  System.import('./main') // -> '/app/main.js'

I like this idea a lot, and I think it cuts out a lot of issues that developers will have with the loader. The ./ creates a natural hole in our standard baseURL resolution, which is sorely needed for app code.

That said, I know introducing a new concept is not ideal. Feedback still very much welcome.

guybedford commented 10 years ago

Note that the module tag could also use this same default relativeURL convention allowing the following to work:

<script>
  System.baseURL = '/lib';
  System.relativeURL = '/app';
</script>
<module>
  import "jquery";
  import "./local/module";
</module>

By default, both the baseURL and relativeURL would be the current page. With either customizable.

Perhaps it is just more complexity on top of paths, but otherwise why have baseURL at all when the same can be accomplished with:

<script>
  System.paths['*'] = '/lib/*.js';
  System.paths['app/*'] = '/app/*.js';
</script>

The whole point is to try and make the standard use case the simplest. And this is the standard use case.

johnjbarton commented 10 years ago

I'm just quoting you out of order: "A strong motivation for working with normalized module names, is to try and ensure that users refer to external libraries in the same way." "The whole point is to try and make the standard use case the simplest. " "...why have baseURL at all when the same can be accomplished with: " (paths).

I don't see the critical motivation for System.baseURL or System.relativeURL, given that (IMO) we must have paths anyway.

jrburke commented 10 years ago

It depends on how System.paths is defined, but it does seem that either baseURL + paths or just paths is enough, no need for relativeURL.

One note on choosing a System.paths style: System.paths['*'] = '/lib/*.js' gives me some concern because, at least for AMD systems, we have used ID-to-path resolution for strings like 'some/thing.html', where some/thing is a module ID turned to a path, then the .html suffix is added. So that has been useful: just translating a module ID to a path without extension. Specifying a .js in the paths entry seems to complicate the rules around it. Maybe it is workable though.

I don't think it needs further discussion here, just something to consider later when a paths config is getting more worked out. For this bug, it seems like a special relative import setting is not needed on its own.

guybedford commented 10 years ago

I actually agree with both of you here, and was wondering myself today why we even need baseURL.

System.paths does solve the problem very nicely as far as we need, keeping everything within a single model:

  <script>
  System.paths['*'] = 'lib/*';
  System.paths['app/*'] = 'app/*';
</script>
<module>
  import "jquery"; // -> lib/jquery.js
  import "app/local/module"; // -> app/local/module.js
</module>

I'm all for this, keeping these concepts simple. I've summarized the suggestions in https://github.com/jorendorff/js-loaders/issues/25

Proposal retracted!