简体   繁体   中英

Are Native Node Absolute (ie. Root-Relative) Paths Possible With ES Module Named Imports?

Normally in Node one can use NODE_PATH=./src" to make it so that instead of:

import { foo } from '../../../bar'

You can just do:

import { foo } from 'src/bar'

However, that only works if you use the esm package (ie. node -r esm ): NODE_PATH doesn't work with native ES Modules (ie. adding "type": "module" to package.json )... so what is the modern replacement?

I've tried all of the following, and none of them seem to work (though I may not be using them correctly, and would welcome any clarification):

  • local files (ie. `"dependencies": { "src": "file:./src",) - couldn't get this to even work
  • symlinks (ie. adding a symlink from node_modules/src to project-root/src ) - that imports the file as a CommonJS package, not an ES one, which means that named imports don't work
  • workspaces (ie. "workspaces": ["src"], in package.json ) - same issue: no named imports
  • imports (ie. "imports": {"#src": "./src"} ) - ignores the --experimental-specifier-resolution=node flag (so it only works IF I want to go through and manually add .js to every import in my project)
  • custom loaders (ie. making a loader.js file and using node --loader loader.js ) - I couldn't figure out how to make this work, as there is almost no documentation on custom loaders

Ideally, I'd prefer not to have to implement all of Babel/Webpack/Typescript/etc. on my project, just to replace NODE_PATH=./src , but it seems like adding some such tool is the only way now?

It looks like the only viable option... if you want root-relative imports AND you don't want to specify the .js extension... is to use a custom loader.

For reference, the one I made to achieve this was:

import path from 'path';
import fs from 'fs';

export function resolve(originalSpecifier, context, defaultResolver) {
  let specifier = originalSpecifier;

  try {
    // All my root-relative imports start with "src/"; if you 
    // have other folders you'll need to account for them here
    if (specifier.startsWith('src')) {
      specifier = specifier.replace(/^src/, path.resolve('.') + '/src');
      // If the import is for a directory, get its index.js file
      const itExists = fs.existsSync(specifier);
      let isDirectory = false;
      try {
        isDirectory = fs.lstatSync(specifier).isDirectory();
      } catch (err) {}
      specifier = itExists && isDirectory ? `${specifier}/index` : specifier;

      // Add the ".js" extension if not specified
      specifier += specifier.endsWith('.js') ? '' : '.js';

      return {
        format: 'module',
        url: new URL(specifier, context.parentURL).href,
      };
    }
  } catch (err) {
    console.error(err);
  }
  // If we're not handling our special cases, just use the
  // default handler
  return defaultResolver(specifier, context);
}

Then you can use that loader with the --loader option, eg.

node --loader loader.js index.js

However, it's worth noting that the loader stuff is still under development, and could change (making the above loader invalid) in the future. Probably sometime in 2030, given how slow the Node org is with their development;)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM