URI.js icon indicating copy to clipboard operation
URI.js copied to clipboard

Can't use URI.js from require.js without the optional dependencies

Open borntyping opened this issue 10 years ago • 20 comments

Require.js does not load URI.js unless the IPv6, punycode, and SecondLevelDomains are available. Is there any way to use it without these extensions?

borntyping avatar Oct 08 '13 16:10 borntyping

I have yet to figure out how to handle optional dependencies with RequireJS. If anyone has any ideas about this, shoot!

rodneyrehm avatar Oct 08 '13 23:10 rodneyrehm

Patching URI.js to not register the dependencies worked for me in the end. The only way I can think of to control this might be to check for the presence of a user-set global variable (similar to how require.js checks for require) and use that to decide whether to include the dependencies.

borntyping avatar Oct 09 '13 16:10 borntyping

Optional dependencies go against the concept of require.js with explicit dependency resolution. I think the easiest way to go around this problem is providing dummy IPv6 etc. modules which return null instead of the actual implementation. Users can than decide for themselves whether or not they want the actual implementation.

If you agree with this proposal I would prepare a patch.

ooxi avatar Oct 10 '13 07:10 ooxi

That would still result in three extra http calls for almost empty files. On 10 Oct 2013 08:15, "ooxi" [email protected] wrote:

Optional dependencies goes against the concept of require.js with explicit dependency resolution. I think the easiest way to go around this problem is providing dummy IPv6 etc. modules which return null instead of the actual implementation. Users can than decide for themselves whether or not they want the actual implementation.

If you agree with this proposal I would prepare a patch.

— Reply to this email directly or view it on GitHubhttps://github.com/medialize/URI.js/issues/118#issuecomment-26033410 .

borntyping avatar Oct 10 '13 08:10 borntyping

That would still result in three extra http calls for almost empty files.

No it would not since normal build tools (like r.js) include the configured files automatically. It would however increase the download size of minimized version by a couple bytes.

ooxi avatar Oct 10 '13 08:10 ooxi

I think the real fix should have the build include only the dependencies asked for. If a custom build is allowed with optional dependencies, and AMD is supported, the build should output the correct code.

ghost avatar Dec 09 '13 05:12 ghost

Optional dependencies should be handled using :

define( 'vendors/punycode', function ( ) {
    window.punycode;
} );

This way, if the user wants to use punycode, he can include it before using URI.js, and the wrapper will return the correct object. Furthermore, the library build should be wrapped with almond.js to not interfer with other require systems and should always export a global object - it's the only way to support AMD without making incorrect assumptions about the user environment.

arcanis avatar Jan 21 '14 13:01 arcanis

Thanks for your input. Your solution only works for the browser, though - there is no window in Node. Also it kind of defeats the purpose of modules as stuff is not contained, but again littered all over the global scope.

I was thinking about removing the dependencies from URI and provide a function to register plugins instead. So if you wanted to use SLD and punycode, you could do something like this somewhere in your app:

define(function defineURI() {
  var SLD = require('URIjs/SecondLevelDomains');
  var punycode = require('URIjs/punycode');
  var URI = require('URIjs/URI');
  URI.register('SLD', SLD);
  URI.register('punycode', punycode);
  return URI;
});

I disagree with the almond thing, though. The build is there to be used with <script> includes. Any module system can resolve the UMDs and build it's own optimized file.

rodneyrehm avatar Jan 21 '14 20:01 rodneyrehm

Yep, using an explicit registration service is a solution too. However, I think that the less setup code required, the better. In this case, it could be possible to use window.punycode as a default value, even if undefined in a Node context.

The issue that I have with leaving modules unwrapped is that it is making assumptions about the user environment : what may work for you may not work for me, because I decided to use different vendor locations, names, or anything else. These issues can usually be sorted around by using Require.js map configuration, but it sounds hackish, and can lead to more serious issues (this coupled to the dependencies issues made us incapable of using URI.js).

Imo, the right way to do it would be packaging the library with almond+local wrapper. Compiled with r.js, it would results in something like this (minification omitted) :

( function ( builder ) {

    if ( typeof module !== 'undefined' ) {
        module.exports = builder( );
    } else {
        window.URI = builder( );
    }

} )( function ( ) {

    /*Almond part*/
    var require = ...;
    var define = ...;
    /*End of almond part*/

    /*URI.js part*/
    define( 'punycode', function ( ) {
        return typeof window !== 'undefined'
            ? window.punycode : undefined;
    } );
    define( 'URI', [ 'punycode' ], function ( punycode ) {
        return { setPunycode : function ( instance ) {
            punycode = instance;
        } };
    } );
    /*End of URI.js part*/

    return require( 'URI' );

} );

Notice that there is no global defining - even if the user is using Require.js, the library would not export itself to it. Then the user would just have to set a shim in its configuration to tell Require.js where is URI.js and which variable it is exporting to. It would have a full control over its libraries paths, names, exports, ...

arcanis avatar Jan 22 '14 10:01 arcanis

You're welcome to fork the repo and provide a PR with a grunt configuration.

rodneyrehm avatar Jan 22 '14 10:01 rodneyrehm

fyi I solved the problem by using the unminified URI.js with the following requirejs configuration

{
    "map": {
        "uri": {
            "IPv6": "false",
            "punycode": "false",
            "SecondLevelDomains": "false"
        }
}

Where false is available on bower.

ooxi avatar Mar 04 '14 14:03 ooxi

you're loading URI.js through bower as well, right?

rodneyrehm avatar Mar 04 '14 15:03 rodneyrehm

Yes, I'm loading URI.js through the browser, but r.js optimizes the mapping so the browser will only fetch URI.js but not false.js

ooxi avatar Mar 04 '14 15:03 ooxi

What I meant was replicating your setup requires bower.json:

{
  "name": "Your App",
  "description": "... is a unicorn",
  "version": "0.0.0",
  "dependencies": {
    "false": "~0.1.1",
    "URIjs": "~1.12.0"
  }
}

and require main file:

require.config({
  paths: {
    'URI': '-path-to-/bower_components/URIjs/src/URI',
    'false': '-path-to-/bower_components/false/false'
  },
  map: {
    'uri': {
      'IPv6': 'false',
      'punycode': 'false',
      'SecondLevelDomains': 'false'
    }
  }
});

rodneyrehm avatar Mar 04 '14 15:03 rodneyrehm

Yes that's right! I misread bower as browser ;)

ooxi avatar Mar 04 '14 15:03 ooxi

+1 for this issue. I straight away hit in to it when using bower + requirejs. One thing I notice is that files pulled by bower doesn't have build files. I think its a good idea to include your build files in bower. Common standard I see around seems to be including dist folder like this:

dist\
  mylib.full.js           //unminified full version
  mylib.full.min.js    //minified full version version
  mylib.js                //unminified recommended version
  mylib.min.js         //minified recommended version

This might be simple fix to get bower + requirejs working.

@ooxi Thanks for the clever workaround!

sytelus avatar Apr 18 '14 23:04 sytelus

I still haven't figured out how to do this exactly. There are a couple of options, each of them having their own downsides. If there is a consensus on how to tackle this (which I haven't been able to find), hit me!

As for including dist in the release: I was acting on the assumption that if you were using a package manager, you'd also be using Require or Browserify or something similar. In that case the internal dependencies would've been resolved by that tool. That tool would also have been the responsible part for minifying the code. That's why I decided to not include a built and minified distributable to package managers.

What's the reason you need this?

rodneyrehm avatar Apr 19 '14 12:04 rodneyrehm

Other solution, not the best but for now... Try loading URI.js before requirejs with two differents script tags :

<script src="path/to/URI.js"></script>
<script src="path/to/require.js" data-main=""></script>

This is not requirejs config but it's working.

Jisay avatar Oct 21 '14 07:10 Jisay

I think the "best" solution to this (from an AMD perspective) is to not think of these things as "optional dependencies" (which AMD doesn't really support, as noted above), but instead like "plugins" that work similar to URI.fragementQuery and URI.fragmentURI. The difference being that "plugins" add functionality to URI so the main URI.js doesn't need to know about them in advance.

ebeeson avatar Apr 29 '15 18:04 ebeeson

One note to save some folks from wasting time: when installing false 0.1.1 per the above solution it took me a while to realize I had to add a double dash like this, otherwise nothing useful happened:

bower install --save -- 'false'

nsoft avatar Jan 04 '21 17:01 nsoft