Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: ES module dummy loader resolve hook bug on Windows #29610

Closed
DerekNonGeneric opened this issue Sep 19, 2019 · 28 comments
Closed

doc: ES module dummy loader resolve hook bug on Windows #29610

DerekNonGeneric opened this issue Sep 19, 2019 · 28 comments

Comments

@DerekNonGeneric
Copy link
Contributor

DerekNonGeneric commented Sep 19, 2019

  • Version: 12.10.0
  • Platform: Windows Server 2019
  • Subsystem: url

An error occurs after creating the custom-loader.mjs containing the dummy loader code as directed by the documentation, an x.js file, and running the following command in PowerShell.

node --experimental-modules --loader ./custom-loader.mjs x.js

By the way, this command diverges from the one provided in the documentation to be used on Windows as NODE_OPTIONS='--experimental-modules --loader ./custom-loader.mjs' node x.js fails in PowerShell.

(node:1364) ExperimentalWarning: The ESM module loader is experimental.
(node:1364) ExperimentalWarning: --loader is an experimental feature. This feature could change at any time
internal/modules/cjs/loader.js:992
      internalBinding('errors').triggerUncaughtException(
                                ^

TypeError [ERR_INVALID_URL]: Invalid URL: /C:/Users/Administrator/source/repos/esm/x.js
    at onParseError (internal/url.js:243:9)
    at new URL (internal/url.js:319:5)
    at resolve (file:///C:/Users/Administrator/source/repos/esm/custom-loader.mjs:23:20)
    at Loader.resolve (internal/modules/esm/loader.js:73:33)
    at Loader.getModuleJob (internal/modules/esm/loader.js:152:40)
    at Loader.import (internal/modules/esm/loader.js:136:28)
    at internal/modules/cjs/loader.js:989:27 {
  input: '/C:/Users/Administrator/source/repos/esm/x.js'
}

The x.js file certainly exists. It turns out that there is a Windows-specific error where a leading forward slash causes this example to break. I was able to resolve this by adding:

  specifier = cleanPath(specifier);
  specifier = url.pathToFileURL(specifier).href;

prior to (from the dummy loader code):

 const resolved = new URL(specifier, parentModuleURL);

and including the following function:

/**
 * Path sanitizer that removes a leading slash if followed by Windows drive sepcifier.
 * @param {string} specifier URL path to a file
 * @returns {string} Cleaned specifier URL path to a file
 */
function cleanPath(specifier) {
  const specifierDir = path.parse(specifier).dir;

  if (
    specifierDir.length >= 3 &&
    specifierDir.charAt(0) == '/' &&
    specifierDir.charAt(1).toUpperCase() !=
      specifierDir.charAt(1).toLowerCase() && // Check if alphabetic
    specifierDir.charAt(2) == ':'
  ) {
    specifier = specifier.substring(1);
  }

  return specifier;
}

P.S. This also needs import url from 'url'; added to the top of custom-loader.mjs.

@joaocgreis
Copy link
Member

cc @nodejs/modules-active-members

@bmeck
Copy link
Member

bmeck commented Sep 25, 2019

This looks like a bug with resolving argv[1] during bootstrapping.

Edit: https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L1005 looks suspect

@bmeck
Copy link
Member

bmeck commented Sep 25, 2019

@DerekNonGeneric do you have your code for custom-loader.mjs, Are you using url.fileURLToPath() ?

@DerekNonGeneric
Copy link
Contributor Author

DerekNonGeneric commented Sep 25, 2019

Thanks for looking into this @bmeck. I am indeed using url.fileURLToPath(). My code for custom-loader.mjs has changed significantly since I opened this issue, but I've tried to bring it back down to a comprehensible reduction to help out anyone looking into this. Apologies in advance for changing the formatting (different style guide).

https://gist.github.com/DerekNonGeneric/bdf314493c3262c93ceab6fda1c7695b

@guybedford
Copy link
Contributor

Changing https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L1005 to use .href over .pathname seems like it would fix the issue here to me. Thanks for finding this bug @DerekNonGeneric.

@DerekNonGeneric
Copy link
Contributor Author

DerekNonGeneric commented Oct 1, 2019

A few documentation-related errors I see:

  • The error stated is incorrect.

    imports must begin with '/', './', or '../'; '${specifier}' does not
    

    According to the Node.js documentation specifiers may not begin with /.

  • URL does not need to be imported, since it is already in scope.

  • The JSDoc comment at the top should be of type {string=} as it is optional parameter, yet it reads:

     * @param {string} parentModuleURL
    
  • The command given to execute this code is not cross-platform. Environment variables cannot be passed to commands like this in PowerShell.

    NODE_OPTIONS='--experimental-modules --loader ./custom-loader.mjs' node x.js

    it should instead read:

    node --experimental-modules --loader ./custom-loader.mjs x.js

    or possibly

    node --experimental-modules --experimental-loader ./custom-loader.mjs x.js

/to @bmeck, @guybedford Do y'all mind if I make a PR to fix these?

@bmeck
Copy link
Member

bmeck commented Oct 1, 2019

I'd avoid the JSDoc change, there is some disagreement and I lean towards TS syntax which does not enforce non-undefined value:

/**
 * @param {string} x
 */
function foo(x) {
  return x;
}
foo(undefined);

@jkrems
Copy link
Contributor

jkrems commented Oct 1, 2019

TS syntax which does not enforce non-undefined value

Not sure how relevant that is here but with strict: true (or just strict null checks), TS does mark your sample as an error. I think it's reasonable to be explicit about optional and/or nullable parameters in the API.

@weswigham
Copy link

FYI, that does enforce non-undefined if strict (or at least strictNullChecks) is on. (And it should be on.) It should be string= if it's optional, which the TS checker'll also recognize.

@bmeck
Copy link
Member

bmeck commented Oct 1, 2019

I'm not really cozy on relying on non-default configuration to determine that.

@jkrems
Copy link
Contributor

jkrems commented Oct 1, 2019

It doesn't break in the default configuration to use string=. And, as Wes said, strictNullCheck isn't an obscure setting but pretty close to an accepted best practice. Your sample relies on the fact that TS actively ignores (some) null errors in the default configuration to make transitions easier (I assume).

@bmeck
Copy link
Member

bmeck commented Oct 1, 2019

even if we have a recommendation, we should not do something if it not the default. E.g. don't share strict mode code if the default is non-strict unless you express that it should be strict. We could argue about this a bit but overall it isn't as important, I'd be fine with string | undefined since it always populated however. I'd not be ok with treating it as optional if it always does have a value passed in.

@weswigham
Copy link

weswigham commented Oct 1, 2019

string | undefined works, yeah. If it's always supposed to be provided, i'd recommend that - and the closure compiler typesystem would, too.

don't share strict mode code if the default is non-strict unless you express that it should be strict.

You can drop a jsconfig.json (or tsconfig.json) somewhere at the root of the js source to set options for vscode and other TS-based JS's IDEs. The default for a new one generated by tsc is strict: true, but that'll pepper errors all over the codebase, likely, since so much internal stuff is missing docs. Then again, those errors won't appear anywhere unless you set the checkJs option or drop a //@ts-check in a file, so... Eh.

Point is, the intention can be conveyed, if you'd like.

@bmeck
Copy link
Member

bmeck commented Oct 1, 2019

Point is, the intention can be conveyed, if you'd like.

I'm not eager to add a tripleslash to our docs for Node.js itself.

@weswigham
Copy link

I'm not eager to add a tripleslash to our docs for Node.js itself.

? "A tripleslash"?

@bmeck
Copy link
Member

bmeck commented Oct 1, 2019

@weswigham https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html

/// <reference strict="true"/>

That would be how I'd assume we would show intent in the docs if we expect it to be strict TS, as I don't think explaining how to add a JSON config for typescript is reasonable.

@weswigham
Copy link

weswigham commented Oct 1, 2019

We don't support anything like that right now. Triple slash directives only exist to specify file dependency ordering in legacy concatenate type code, mostly. (You can't set arbitrary compiler options)

as I don't think explaining how to add a JSON config for typescript is reasonable.

Rather than explaining, you could just... Add it. Like the lint config, to which it is very similar.

@bmeck
Copy link
Member

bmeck commented Oct 1, 2019

@weswigham I don't understand. This is the documentation of the function, what do you mean "add it"

@weswigham
Copy link

Oh, I mean "add the config file to specify that you'd prefer stricter checks throughout the project".

@bmeck
Copy link
Member

bmeck commented Oct 1, 2019 via email

@devsnek
Copy link
Member

devsnek commented Oct 1, 2019

I think this is a bit of a red herring. We should not have jsdoc comments in code samples. The types are already specified exactly in the documentation.

@weswigham
Copy link

jsconfig.json

The tool may be named TypeScript, but ultimately there's no required exposure to TS as a language (unless you'd like to be pedantic and say the type documentation syntax is TS, but since it supports all the closure compiler syntax, too, I don't think that's really a problem), especially if you're just configuring IDE features when used this way.❤️ It's not much different from your eslint config file, really - it's a config file for a tool that runs checks over js files, the primary difference being this one does semantic checks and improves IDE features in some popular editors - especially if you're only using it for the editor support, and not actually running the tool for errors in CI. I somehow doubt we'd be having quite the same discussion if the js checker and the ts checker were different libraries, but they're not (since they're almost identical), so here we are.

Plus, I, at least, think a single config file is a bit nicer than inline pragmas for configuration - there's a reason every file doesn't list the lint rules it enables at the top; it's a bit messy, and usually repetitive. (Which, I guess, is why you said you wouldn't want to add one, if one worked, yeah?)

In any case, I wouldn't write inaccurate documentation, checked or no - the docs should include the undefined-ness of a param in some way. 🤷‍♂️

@weswigham
Copy link

weswigham commented Oct 1, 2019

The types are already specified exactly in the documentation.

Aren't we talking about the code comments in the implementation? Oh, I had no idea we were talking about the doc sample, totally misread that. 🤦 (I thought we were talking about docstrings on the actual internals, which I'd love to see more of, cause they're pretty rare right now.)

I think stating the types in the sample is convenient (since it condenses what you need to read to understand the functionality), it's just unfortunately hard to keep it in sync with the API's types. Oh, and it being inaccurate messes with editor intellisense when people copy & paste it into their codebase, so if it's there, it should be precise~

@DerekNonGeneric
Copy link
Contributor Author

Opinions about --experimental-loader vs --loader would be appreciated. The flag just landed in v12.11.1 (#29796). It seems like the docs are recommending it although both flags seem viable.

@bmeck
Copy link
Member

bmeck commented Oct 1, 2019

@DerekNonGeneric I'd use the --experimental-loader

@DerekNonGeneric
Copy link
Contributor Author

@devsnek, should I modify my PR to remove the type annotations?

@devsnek
Copy link
Member

devsnek commented Oct 4, 2019

@DerekNonGeneric i'm not planning to block it or anything, but i don't really see the point of including them.

@DerekNonGeneric
Copy link
Contributor Author

The ES module dummy loader has since been removed from this document and replaced with several wonderful examples. 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants