123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- const { promises: fsPromises } = require('fs');
- const path = require('path');
- /** @type {Map<string, string | undefined>} */
- let packageJsonTypeMap = new Map();
- /**
- * Infers the current active module system from loader context and options.
- * @this {import('webpack').loader.LoaderContext}
- * @param {import('webpack').ModuleFilenameHelpers} ModuleFilenameHelpers Webpack's module filename helpers.
- * @param {import('../types').NormalizedLoaderOptions} options The normalized loader options.
- * @return {Promise<'esm' | 'cjs'>} The inferred module system.
- */
- async function getModuleSystem(ModuleFilenameHelpers, options) {
- // Check loader options -
- // if `esModule` is set we don't have to do extra guess work.
- switch (typeof options.esModule) {
- case 'boolean': {
- return options.esModule ? 'esm' : 'cjs';
- }
- case 'object': {
- if (
- options.esModule.include &&
- ModuleFilenameHelpers.matchPart(this.resourcePath, options.esModule.include)
- ) {
- return 'esm';
- }
- if (
- options.esModule.exclude &&
- ModuleFilenameHelpers.matchPart(this.resourcePath, options.esModule.exclude)
- ) {
- return 'cjs';
- }
- break;
- }
- default: // Do nothing
- }
- // Check current resource's extension
- if (/\.mjs$/.test(this.resourcePath)) return 'esm';
- if (/\.cjs$/.test(this.resourcePath)) return 'cjs';
- if (typeof this.addMissingDependency !== 'function') {
- // This is Webpack 4 which does not support `import.meta`.
- // We assume `.js` files are CommonJS because the output cannot be ESM anyway.
- return 'cjs';
- }
- // We will assume CommonJS if we cannot determine otherwise
- let packageJsonType = '';
- // We begin our search for relevant `package.json` files,
- // at the directory of the resource being loaded.
- // These paths should already be resolved,
- // but we resolve them again to ensure we are dealing with an aboslute path.
- const resourceContext = path.dirname(this.resourcePath);
- let searchPath = resourceContext;
- let previousSearchPath = '';
- // We start our search just above the root context of the webpack compilation
- const stopPath = path.dirname(this.rootContext);
- // If the module context is a resolved symlink outside the `rootContext` path,
- // then we will never find the `stopPath` - so we also halt when we hit the root.
- // Note that there is a potential that the wrong `package.json` is found in some pathalogical cases,
- // such as a folder that is conceptually a package + does not have an ancestor `package.json`,
- // but there exists a `package.json` higher up.
- // This might happen if you have a folder of utility JS files that you symlink but did not organize as a package.
- // We consider this an unsupported edge case for now.
- while (searchPath !== stopPath && searchPath !== previousSearchPath) {
- // If we have already determined the `package.json` type for this path we can stop searching.
- // We do however still need to cache the found value,
- // from the `resourcePath` folder up to the matching `searchPath`,
- // to avoid retracing these steps when processing sibling resources.
- if (packageJsonTypeMap.has(searchPath)) {
- packageJsonType = packageJsonTypeMap.get(searchPath);
- let currentPath = resourceContext;
- while (currentPath !== searchPath) {
- // We set the found type at least level from `resourcePath` folder up to the matching `searchPath`
- packageJsonTypeMap.set(currentPath, packageJsonType);
- currentPath = path.dirname(currentPath);
- }
- break;
- }
- let packageJsonPath = path.join(searchPath, 'package.json');
- try {
- const packageSource = await fsPromises.readFile(packageJsonPath, 'utf-8');
- try {
- const packageObject = JSON.parse(packageSource);
- // Any package.json is sufficient as long as it can be parsed.
- // If it does not explicitly have a `type: "module"` it will be assumed to be CommonJS.
- packageJsonType = typeof packageObject.type === 'string' ? packageObject.type : '';
- packageJsonTypeMap.set(searchPath, packageJsonType);
- // We set the type in the cache for all paths from the `resourcePath` folder,
- // up to the matching `searchPath` to avoid retracing these steps when processing sibling resources.
- let currentPath = resourceContext;
- while (currentPath !== searchPath) {
- packageJsonTypeMap.set(currentPath, packageJsonType);
- currentPath = path.dirname(currentPath);
- }
- } catch (e) {
- // `package.json` exists but could not be parsed.
- // We track it as a dependency so we can reload if this file changes.
- }
- this.addDependency(packageJsonPath);
- break;
- } catch (e) {
- // `package.json` does not exist.
- // We track it as a missing dependency so we can reload if this file is added.
- this.addMissingDependency(packageJsonPath);
- }
- // Try again at the next level up
- previousSearchPath = searchPath;
- searchPath = path.dirname(searchPath);
- }
- // Check `package.json` for the `type` field -
- // fallback to use `cjs` for anything ambiguous.
- return packageJsonType === 'module' ? 'esm' : 'cjs';
- }
- module.exports = getModuleSystem;
|