|
1 | 1 | const { promises: fsPromises } = require('fs'); |
2 | 2 | const path = require('path'); |
3 | | -const commonPathPrefix = require('common-path-prefix'); |
4 | | -const findUp = require('find-up'); |
5 | 3 |
|
6 | 4 | /** @type {Map<string, string | undefined>} */ |
7 | 5 | let packageJsonTypeMap = new Map(); |
@@ -43,29 +41,81 @@ async function getModuleSystem(ModuleFilenameHelpers, options) { |
43 | 41 | if (/\.mjs$/.test(this.resourcePath)) return 'esm'; |
44 | 42 | if (/\.cjs$/.test(this.resourcePath)) return 'cjs'; |
45 | 43 |
|
46 | | - // Load users' `package.json` - |
47 | | - // We will cache the results in a global variable so it will only be parsed once. |
48 | | - let packageJsonType = packageJsonTypeMap.get(this.rootContext); |
49 | | - if (!packageJsonType) { |
| 44 | + if (typeof this.addMissingDependency !== 'function') { |
| 45 | + // This is Webpack 4 which does not support `import.meta` and cannot use ESM anwyay. We assume .js files |
| 46 | + // are commonjs because the output cannot be ESM anyway |
| 47 | + return 'cjs'; |
| 48 | + } |
| 49 | + |
| 50 | + // We will assume commonjs if we cannot determine otherwise |
| 51 | + let packageJsonType = ''; |
| 52 | + |
| 53 | + // We begin our search for relevant package.json files at the directory of the |
| 54 | + // resource being loaded. |
| 55 | + // These paths should already be resolved but we resolve them again to ensure |
| 56 | + // we are dealing with an aboslute path |
| 57 | + const resourceContext = path.dirname(this.resourcePath); |
| 58 | + let searchPath = resourceContext; |
| 59 | + let previousSearchPath = ''; |
| 60 | + // We start our search just above the root context of the webpack compilation |
| 61 | + const stopPath = path.dirname(this.rootContext); |
| 62 | + |
| 63 | + // if the module context is a resolved symlink outside the rootContext path then we will never find |
| 64 | + // the stopPath so we also halt when we hit the root. Note that there is a potential that the wrong |
| 65 | + // package.json is found in some pathalogical cases like if a folder that is conceptually a package |
| 66 | + // but does not have an ancestor package.json but there is a package.json higher up. This might happen if |
| 67 | + // you have a folder of utility js files that you symlink but did not organize as a package. We consider |
| 68 | + // this an edge case for now |
| 69 | + while (searchPath !== stopPath && searchPath !== previousSearchPath) { |
| 70 | + // If we have already determined the package.json type for this path we can stop searching. We do however |
| 71 | + // still need to cache the found value from the resourcePath folder up to the matching searchPath to avoid |
| 72 | + // retracing these steps when processing sibling resources. |
| 73 | + if (packageJsonTypeMap.has(searchPath)) { |
| 74 | + packageJsonType = packageJsonTypeMap.get(searchPath); |
| 75 | + |
| 76 | + let currentPath = resourceContext; |
| 77 | + while (currentPath !== searchPath) { |
| 78 | + // We set the found type at least level from this.resourcePath folder up to the matching searchPath |
| 79 | + packageJsonTypeMap.set(currentPath, packageJsonType); |
| 80 | + currentPath = path.dirname(currentPath); |
| 81 | + } |
| 82 | + break; |
| 83 | + } |
| 84 | + |
| 85 | + let packageJsonPath = path.join(searchPath, 'package.json'); |
50 | 86 | try { |
51 | | - const commonPath = commonPathPrefix([this.rootContext, this.resourcePath], '/'); |
52 | | - const stopPath = path.resolve(commonPath, '..'); |
53 | | - |
54 | | - const packageJsonPath = await findUp( |
55 | | - (dir) => { |
56 | | - if (dir === stopPath) return findUp.stop; |
57 | | - return 'package.json'; |
58 | | - }, |
59 | | - { cwd: path.dirname(this.resourcePath) } |
60 | | - ); |
61 | | - |
62 | | - const buffer = await fsPromises.readFile(packageJsonPath, { encoding: 'utf-8' }); |
63 | | - const rawPackageJson = buffer.toString('utf-8'); |
64 | | - ({ type: packageJsonType } = JSON.parse(rawPackageJson)); |
65 | | - packageJsonTypeMap.set(this.rootContext, packageJsonType); |
| 87 | + const packageSource = await fsPromises.readFile(packageJsonPath, 'utf-8'); |
| 88 | + try { |
| 89 | + const packageObject = JSON.parse(packageSource); |
| 90 | + |
| 91 | + // Any package.json is sufficient as long as it can be parsed. If it does not explicitly have a type "module" |
| 92 | + // it will be assumed to be commonjs. |
| 93 | + packageJsonType = typeof packageObject.type === 'string' ? packageObject.type : ''; |
| 94 | + packageJsonTypeMap.set(searchPath, packageJsonType); |
| 95 | + |
| 96 | + // We set the type in the cache for all paths from the resourcePath folder up to the |
| 97 | + // matching searchPath to avoid retracing these steps when processing sibling resources. |
| 98 | + let currentPath = resourceContext; |
| 99 | + while (currentPath !== searchPath) { |
| 100 | + packageJsonTypeMap.set(currentPath, packageJsonType); |
| 101 | + currentPath = path.dirname(currentPath); |
| 102 | + } |
| 103 | + } catch (e) { |
| 104 | + // package.json exists but could not be parsed. we track it as a dependency so we can reload if |
| 105 | + // this file changes |
| 106 | + } |
| 107 | + this.addDependency(packageJsonPath); |
| 108 | + |
| 109 | + break; |
66 | 110 | } catch (e) { |
67 | | - // Failed to parse `package.json`, do nothing. |
| 111 | + // package.json does not exist. We track it as a missing dependency so we can reload if this |
| 112 | + // file is added |
| 113 | + this.addMissingDependency(packageJsonPath); |
68 | 114 | } |
| 115 | + |
| 116 | + // try again at the next level up |
| 117 | + previousSearchPath = searchPath; |
| 118 | + searchPath = path.dirname(searchPath); |
69 | 119 | } |
70 | 120 |
|
71 | 121 | // Check `package.json` for the `type` field - |
|
0 commit comments