Skip to content

Proposal: Package Importer and built-in Node implementation#3660

Merged
nex3 merged 66 commits intosass:mainfrom
oddbird:package-loads
Sep 22, 2023
Merged

Proposal: Package Importer and built-in Node implementation#3660
nex3 merged 66 commits intosass:mainfrom
oddbird:package-loads

Conversation

@jamesnw
Copy link
Contributor

@jamesnw jamesnw commented Aug 18, 2023

See #2739

jamesnw and others added 30 commits June 29, 2023 22:40
* main:
  [Calculation API] Fix some issues brought up during implementation (sass#3631)
  Bump tj-actions/changed-files from 37.0.5 to 37.1.0 (sass#3632)
  [Calc Functions] Match the spec's behavior better for percentages (sass#3630)
  Bump tj-actions/changed-files from 37.0.2 to 37.0.5 (sass#3627)
  Fix a reference to a dead link (sass#3625)
Co-authored-by: Ed Rivas <ed@jerivas.com>
@jamesnw jamesnw requested a review from nex3 September 14, 2023 01:37
@ntkme
Copy link
Contributor

ntkme commented Sep 14, 2023

One thing that might be missing in the embedded protocol is the current working directory. I suppose we want to lookup node_modules starting from the directory containing the input file. However if the input is a string that does not have a URL, the lookup should start from the working directory. As for a persisted and reused compiler started previously, the host may have changed its directory and thus not having the same working directory. So I think we need to pass the working directory to compiler via embedded protocol as a context information for nodePkgImporter.

@jamesnw
Copy link
Contributor Author

jamesnw commented Sep 14, 2023

One thing that might be missing in the embedded protocol is the current working directory. I suppose we want to lookup node_modules starting from the directory containing the input file. However if the input is a string that does not have a URL, the lookup should start from the working directory. As for a persisted and reused compiler started previously, the host may have changed its directory and thus not having the same working directory. So I think we need to pass the working directory to compiler via embedded protocol as a context information for nodePkgImporter.

@ntkme Does the Containing Url proposal address this need? I think has been accepted but not implemented.

@ntkme
Copy link
Contributor

ntkme commented Sep 14, 2023

The containing url proposal was to provide the containing url of a import to custom importer functions (from compiler to host). Here we're considering providing the working directory for input that does not have a url (from host to compiler).

E.g. In node REPL, you can run const sass = require('sass') in a working directory that has node_modules/sass, because node would begin the lookup from the current working directory. So in sass, if we're running a string compilation that does not have a URL (filename) like sass.compileString('@use "pkg:bootstrap";'), it should to be treated similarly I suppose.

@nex3
Copy link
Contributor

nex3 commented Sep 14, 2023

I agree with @ntkme, we need to have some way to pass in an entryPointURL to the package importer.

```proto
message CompileRequest {
message Importer {
PackageImporter packageImporter = 14;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a new member of the Importer.importer oneof, since it's a new type of importer. Also, it doesn't need index 14; the index just needs to be unique within the Importer record.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right, I forgot to update to mirror the move in the JS API from a flag to a Importer object.

I updated this in 8658bc3.

For the entryPointUrl, I added a NodePackageImporter message with a string entry_point_url. The other option I considered but rejected was flattening the object with a single key into-

    oneof importer {
      string node_package_importer_entry_point_url = 4;
    }

The nested message style I went with would allow for easier future additions, but I'm fine with either option (or something else).

@ntkme Thanks for catching and explaining this- does this address the need?

@ntkme
Copy link
Contributor

ntkme commented Sep 15, 2023

Do we want to support loading from NODE_PATH or GLOBAL_FOLDERS? For example, loading a package installed globally like npm install -g bootstrap.

In node NODE_PATH + GLOBAL_FOLDERS are searched after local node_modules. A few implementation details in node:

  • NODE_PATH + GLOBAL_FOLDERS can be retrieved from require("module").globalPaths.
  • Mutating NODE_PATH during node runtime does not affect the load path, however mutating require("module").globalPaths does affect the load path.
    • This means if we want to be 100% compatible with node resolve we need to pass require("module").globalPaths in embedded protocol for each compilation to be accurate.

https://nodejs.org/api/modules.html#requireresolverequest-options
https://nodejs.org/api/modules.html#loading-from-the-global-folders
https://nodejs.org/api/modules.html#all-together
https://nodejs.org/api/esm.html#importmetaresolvespecifier

https://github.com/nodejs/node/blob/v20.0.0/lib/internal/modules/cjs/loader.js#L1397-L1425

@nex3 @jamesnw Any thoughts?

Comment on lines +241 to +242
* Let `pkgImporter` be a [Node Package Importer] with an associated
`entryPointURL` of `require.main.filename`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When running node REPL require.main is null. Node has a hack internally to make the import work by adding the working directory as the local search path:

https://github.com/nodejs/node/blob/e7618fb5a5fc25d76b6474e2a6607f04fd6f10e0/lib/internal/modules/cjs/loader.js#L824-L832

I suppose we can set entryPointURL to file://${process.cwd()}/ (node the slash at the end is important), which will give us enough information on the compiler side.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ntkme I updated this for the Embedded Protocol in 3e3c5f3. Thanks!

@nex3
Copy link
Contributor

nex3 commented Sep 16, 2023

My instinct is that trying to resolve from globally-installed packages will cause more headache than value for users. @alexander-akait what's the prior art here with sass-loader? Does it look for dependencies from NODE_PATH and GLOBAL_FOLDERS?

Comment on lines +508 to +509
is instantiated with an associated `entry_point_url` set to the value of
`file://${process.cwd()}/`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is confusing—if the entry_point_url is a field of the proto, isn't it up to the host to provide it rather than the spec?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes. Does this need to be specified elsewhere for the host, or is that not part of the spec?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JS host isn't part of the spec, except in that it has to follow the JS API spec.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the host in d14aba8.

@nex3
Copy link
Contributor

nex3 commented Sep 20, 2023

Not hearing from @alexander-akait, I think the safest bet is to just not support global load paths for now. It'd be easier to add them later than it would be to remove them. @jamesnw can you add a design note documenting the rationale here?

@jamesnw
Copy link
Contributor Author

jamesnw commented Sep 21, 2023

Not hearing from @alexander-akait, I think the safest bet is to just not support global load paths for now. It'd be easier to add them later than it would be to remove them. @jamesnw can you add a design note documenting the rationale here?

I added this in e733b78.

ECMAScript modules (used with import) do not support NODE_PATHs, but it is still supported (but discouraged) in CommonJS modules (used with require()). So I documented the decision to choose ECMAScript resolution, and noted no NODE_PATH as a result of that.

I suspect that Webpack would use CommonJS resolution for sass-loader, so this may be a surprise for some. But because Node docs discourage using NODE_PATHS and there's a straightforward workaround with symlinks, I don't think we need to support it.

@jamesnw
Copy link
Contributor Author

jamesnw commented Sep 22, 2023

@nex3 I've made the changes we talked about yesterday, and this is ready for another review. Thanks!

@nex3 nex3 merged commit d6c7d94 into sass:main Sep 22, 2023
@jgerigmeyer jgerigmeyer deleted the package-loads branch September 25, 2023 17:59
@nex3 nex3 mentioned this pull request Feb 1, 2024
6 tasks
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 this pull request may close these issues.

5 participants