-
Notifications
You must be signed in to change notification settings - Fork 18
Conversation
This adds require to the import.meta object This can be used as a mechanism to get interoperability between common.js and esm.
Thanks for the example @michael-ciniawsky , I was wondering what it would look like. What's the value in making users perform this Generally speaking, if we provide a feature because users need it, then we shouldn't also punish them for using it by intentionally making it difficult to use. |
It's definitely debatable and if users absolute dislike (e.g shown by a survey) it, it's definitely better to favor DX and UX over long-term concerns in regard to potential technical debt etc. Still |
i'd like to make sure we have this repo set up to automatically pull updates from nodejs/node before we're merging changes |
@zenparsing This is not just about UX/DX. 1. Web Compatibility Path:
import {createRequireFunction} from 'module';
createRequireFunction(import.meta.url); // web 'module'
let base = import.meta.url;
let bundle = {
'/foo.js': ...
}
export function createRequireFunction(url) {
return function require(path) {
return bundle[resolve(base, path)];
};
}
2. Static analysis
// don't bail out here
import.meta.require('fs');
import.meta.require = function () {};
// bail out here
import.meta.require('fs');
// using window.require
require('fs');
// import.meta.require
var require = import.meta.require; This is only a realistic compatibility concern if people choose to have incompatible implementations of 3. Simpler code mods / upgrade pathTools and/or humans can add a simple prefix to files if they wish to use import {createRequireFunction} from 'module';
var require = createRequireFunction(import.meta.require); Doing so with 4. Better Errors
db.onconnect = (conn) => {
// errors here
const models = import.meta.require('models')(conn);
}; The more conventional way of using // errors before any evaluation
import {createRequireFunction} from 'module';
var require = createRequireFunction(import.meta.require);
db.onconnect = (conn) => {
const models = require('models')(conn);
} It should be noted that // I don't know of any reason to do this?
// maybe just to see if `module` exists???
// but if you need to use `require` what help is that
var {createRequireFunction} = await import('module'); 5. More complete feature detectionEven though it is statically analyzable, sometime feature detection is desirable for modules. This feature is still preserved using module namespaces. import * as module from 'module';
// note that this is able to be analyzed even if `module` is a shim
// this is because we can statically analyze what `module` exports
if (module.createRequireFunction) {
// ...
}
In addition, even with the added utility, To disallow code that uses {
const _require = import.meta.require;
// wrap it or replace it:
import.meta.require = wrap(_require);
} However, these once again reach bailout considerations mentioned above. Overall, we should encourage simple usage of code that migrates towards compatibility stories in multiple environments and tooling. However, I do not believe that |
Remind me never to get into a debate with @bmeck. |
@bmeck can we call it module.createRequire(url) instead of createRequireFunction? (+1 on the approach anyway) |
The usage of synchronous XHR is discourage and the creation of that No Web developer ever used The static analysis compares apples to oranges and gives for granted every developer will implement properly that The moment you have to tell developers how to properly create such function is the moment @zenparsing is right.
The TL;DR all @bmeck points are very subjective and some point does not solve any real-world use case while |
I've quite a few usage of Consider this code (full example at https://github.com/fastify/fastify/blob/master/test/internals/handleRequest.test.js): const t = require('tap')
const test = t.test
const internals = require('../../lib/handleRequest')[Symbol.for('internals')]
const Request = require('../../lib/request')
const Reply = require('../../lib/reply')
const buildSchema = require('../../lib/validation').build
const Schemas = require('../../lib/schemas') Would it be better if rewritten as: const t = require('tap')
const test = t.test
const project = require('module').createRequire(new URL(`file://${__dirname}/../../`)
// this block can now be copy-pasted in various part of the tests, and it is independent of
// the location of the file itself
const internals = project('./lib/handleRequest')[Symbol.for('internals')]
const Request = project('./lib/request')
const Reply = project('./lib/reply')
const buildSchema = project('./lib/validation').build
const Schemas = project('./lib/schemas')
|
I agree with Matteo, the two fearures are not mutually exclusive. I'd personally like to see makeRequire land as part of minimal kernel (potentially land on core soon). We can then dig into specifics of import meta require as part of a larger vision... We could also implement import.meta.require with makeRequire 😂 |
@mcollina this const project = require('module').createRequire(new URL(`file://${__dirname}/../../`) is basically const project = source => require(`${__dirname}/../../${source}`); which is even shorter to write. That doesn't look like a compelling reason to block shipment of I am also not saying there are no use cases whatsoever for a |
@WebReflection const project = require('module').createRequire(new URL(`file://${__dirname}/../../`)
const myLibrary = project('awesome-library') // is resolved from `file://${__dirname}/../../` This could be very helpful in a lot of cases (CLI tools etc). A similar capability is available in the ecosystem through https://github.com/sindresorhus/resolve-from which is downloaded 6 millions time per month, and it's currently based on undocumented functions: https://github.com/sindresorhus/resolve-from/blob/60cd04e69135b96b98b848fff719b1276a5610c0/index.js#L29. IMHO we should just add this feature to core, as it will have the side benefit of reducing the usage of private functionality. I'd kindly ask @bmeck to send a PR to core for that if he is willing.
Have you got any evidence of that? How can we collect evidence? |
Currently every project behind bundlers use either Everything a bundler or synchronous loader, needs to do with This is a migration path that requires zero effort for users and tiny effort for bundlers. Anything else that needs user-land solution won't be as simple, straight forward, and effective as Goodbye tree shaking, code splitting, and ease of parsing if we push out a user-land based, potentially always different per project/company, module loader. This is my logical evidence. |
@bmeck Thanks for the counter-points, we'll definitely need to weigh them. I'm not sure why we're discussing extending Node core here. Shouldn't that be out of scope? The only other comment I'd like to make is that |
This comment has been minimized.
This comment has been minimized.
@zenparsing all action here require extending core to support ESM. I consider If the counterpoint to all the problematic behavior listed above is solely that it requires adding an API, I see no validity to the counterpoint given that we are extending/implementing all sorts of new things already by exposing ESM. |
I'm not sure this really relates as bundlers implement their own form of
This tailors the experience to the bundler, I am more concerned with allowing the user to be encouraged down a path of maximal compatibility than pleasing tool makers.
Bundlers could easy boilerplate out the bundle example I have above. This doesn't seem to discount that nor does it address that users must also migrate all code to
I don't understand how this relates when bundlers could just alter output formats. This also seems to be a claim about CJS being hard to analyze that is applied to both
This doesn't make sense.
I'd once again encourage you to not claim your own point as a conclusion at the end of your statements. It often creates both friction and confusion. In this case, mostly confusion on my part. |
At least I always state it makes sense and it's logical for me, I don't claim my personal point of view as the only truth out there, which is how I often interpret your statements. Your post is subjective as much as mine, if you want, and specially the static analysis one is a deviation of how So, I guess we are both confused on each other statements, but if you want a concrete example on how I can statically find |
P.S. it would be very valuable if bundlers related developers would say something about this too, i.e. @TheLarkInn |
Bundlers mainly care about the |
@bmeck I just mean that we should try to resist the temptation to justify things one way or the other by supposing that we're solving problems that are out of scope. As far as the general issue goes, my opinion is that the benefits of "createRequire" are pretty well non-existent considering that the "require" problem (CJS-in-the-browser) was solved a long time ago by bundlers. Let's say that I want to create an ESM entry point for a CJS package with a default and named exports. With // An ESM entry point for my CJS package
import { createRequire } from 'module';
const exports = createRequire(import.meta.url)('./index.js');
export default exports;
export const { a, b, c } = exports; With // An ESM entry point for my CJS package
const exports = import.meta.require('./index.js');
export default exports;
export const { a, b, c } = exports; Either way, it's not that big of a deal. As a user, I would think the first is OK, but I would like the second better. Bundlers are going to have an easier time with For me, the scale seems to tip slightly toward |
Thanks @michael-ciniawsky , that underlines my statement: |
Imagine teaching someone new to Node.js how to use modules in Node. They're writing an ES module, and want to run it in both the browser and Node.js, and they are loading a third-party package, which they've installed from npm, but that also available on a CDN for the browser. How would you explain |
@domenic |
just pointing out that others favorited |
how about some loader hooks https://gist.github.com/devsnek/cd88c66d7beb36ea6f74694a609fb40a |
@WebReflection we are all well aware that |
@domenic Thanks (and apologies if you now get pinged on this thread!) We'll have the same problem with any of these variations, I think. If I suppose the principle at play here is that properties on |
@zenparsing loader assumes you're just on the filesystem because this behaviour anywhere else is objectively terrible, which is why i don't want it by default in node. A future where everyone wraps accessing properties from import.meta in try/catch because we couldn't design a proper system of integration sounds terrible to me. My process for designing our implementation is figuring out 1) defaults that work in all cases and then 2) how to allow people to override those defaults for their own specific needs as painlessly as possible. |
Don't users have to do the same thing with There's another tool for discouraging certain patterns: linters. Linters can warn if you're using |
const { require, __dirname, __filename } = import.meta.node // or (depending on individual feature usage and use cases)
const require = import.meta.require
const { __filename, __dirname } = import.meta.node |
Linters can also warn against usage of any object properties, any global variables, and any imports or requires - |
@michael-ciniawsky we already own the |
It is possible to write an implementation of |
@guybedford |
What is the concrete problem then ? Why is something like import { getPathFromFileURL } from 'url';
import fs from 'fs';
fs.readFile(getPathFromFileURL(import.meta.url)) Why not import fs from 'fs'
const { pathname } = new URL(import.meta.url)
fs.readFile(pathname + 'file.ext', cb) What is the difference ? |
🙄 const url = import.meta.url
const file = path.from(url) @devsnek thx |
The difference is that |
Can't the various 🤢import url from 'url'
import path from 'path'
const p = path.from(import.meta.url)
const p = url.getPathFromFileURL(import.meta.url)
// etc etc...
const file = p + 'file.ext'
fs.readFile(file, cb) 😃const file = url || path + 'file.ext'
fs.readFile(file, cb) But I think this should ideally be a separate topic and issue, I'm for e.g currently confused why && where File URL usage is even needed, since e.g the |
They already do, to an extent. You can pass a File URL to |
So if |
and for the record, |
@bmeck I think from an interop standpoint, filename and dirname are not file-system specific, they could be URL derived outside of node with roughly Ultimately, the fallback is |
Explicitly adding blocked to make it clear there is not an expectation for this to land right now, but rather this is a archive and documentation of the work on import.meta.require so it doesn't need to be done again in the future. Open to closing the PR if people think that is a better appraoch |
@MylesBorins Can you clarify why "blocked" makes its was to this PR? Is that the direct result of module: add createRequireFunction method? Can you help me connect the dots please. |
@SMotaal as we have agreed to focus on the minimal kernel there is no reason to land this right now. I don't think it makes sense to close it and lose the work. As such the blocked label clearly communicates there is not a current intent to merge this edit: worth being explicit that I don't think |
Awesome… makes sense.
… On Sep 18, 2018, at 5:01 PM, Myles Borins ***@***.***> wrote:
@SMotaal <https://github.com/SMotaal> as we have agreed to focus on the minimal kernel there is no reason to land this right now. I don't think it makes sense to close it and lose the work. As such the blocked label clearly communicates there is not a current intent to merge this
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub <#1 (comment)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AAz2za6NwV3V9r6vBywFQ5H6sbL6hWdPks5ucV9EgaJpZM4WIhtS>.
|
This is not likely landing anytime soon, we can revisit another time |
Reverting this enables us to provide slower, but longer-lasting replacements for the deprecated APIs. Original commit message: Put back deleted V8_DEPRECATE_SOON methods This partially reverts https://chromium-review.googlesource.com/c/v8/v8/+/1177861, which deleted many V8_DEPRECATE_SOON methods rather than moving them to V8_DEPRECATED first. This puts them back and marks them V8_DEPRECATED. Note V8_DEPRECATED that were deleted in the same CL stay deleted. NOTRY=true NOPRESUBMIT=true NOTREECHECKS=true Bug: v8:7786, v8:8240 Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng Change-Id: I00330036d957f98dab403465b25e30d8382aac22 Reviewed-on: https://chromium-review.googlesource.com/1251422 Commit-Queue: Dan Elphick <delphick@chromium.org> Reviewed-by: Yang Guo <yangguo@chromium.org> Reviewed-by: Michael Hablich <hablich@chromium.org> Cr-Commit-Position: refs/branch-heads/7.0@{#49} Cr-Branched-From: 6e2adae6f7f8e891cfd01f3280482b20590427a6-refs/heads/7.0.276@{#1} Cr-Branched-From: bc08a8624cbbea7a2d30071472bc73ad9544eadf-refs/heads/master@{#55424} Refs: v8/v8@9136dd8 Refs: nodejs/node#23122 PR-URL: nodejs/node#23158 Reviewed-By: Yang Guo <yangguo@chromium.org> Reviewed-By: Michaël Zasso <targos@protonmail.com>
From nodejs/node#21317