Skip to content

Commit 7697047

Browse files
Bug/issue 820 deeply relative ESM paths in node modules not working (#832)
* WIP supporting deep import specifier nesting * refactor walkPackageJson to avoid manual relative file handling * refactor our final single dot path replaces * tweaks based on validation testing * refactor bare module check * refactor walk module to read file and handle deeply relative paths for named exports * apply relative path handling across all AST visitor handlers * dont walk packages that already provide export maps * dont walk packages that already provide export maps * force ESM style path separators * tracked TODO item
1 parent 5c2aea0 commit 7697047

File tree

6 files changed

+71
-43
lines changed

6 files changed

+71
-43
lines changed

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"@lion/calendar": "^0.16.7",
5757
"@mapbox/rehype-prism": "^0.5.0",
5858
"@material/mwc-button": "^0.25.2",
59+
"@stencil/core": "^2.12.0",
5960
"@types/trusted-types": "^2.0.2",
6061
"lit": "^2.0.0",
6162
"lit-redux-router": "~0.20.0",

packages/cli/src/plugins/resource/plugin-node-modules.js

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,19 @@ const updateImportMap = (entry, entryPath) => {
2121
entryPath = `${entryPath}.js`;
2222
}
2323

24-
importMap[entry] = entryPath;
24+
// handle WIn v Unix-style path separators and force to /
25+
importMap[entry.replace(/\\/g, '/')] = entryPath.replace(/\\/g, '/');
2526
};
2627

28+
// handle ESM paths that have varying levels of nesting, e.g. export * from '../../something.js'
29+
// https://github.com/ProjectEvergreen/greenwood/issues/820
30+
async function resolveRelativeSpecifier(specifier, modulePath, dependency) {
31+
const absoluteNodeModulesLocation = await getNodeModulesLocationForPackage(dependency);
32+
33+
// handle WIn v Unix-style path separators and force to /
34+
return `${dependency}${path.join(path.dirname(modulePath), specifier).replace(/\\/g, '/').replace(absoluteNodeModulesLocation.replace(/\\/g, '/', ''), '')}`;
35+
}
36+
2737
const getPackageEntryPath = async (packageJson) => {
2838
let entry = packageJson.exports
2939
? Object.keys(packageJson.exports) // first favor export maps first
@@ -41,58 +51,64 @@ const getPackageEntryPath = async (packageJson) => {
4151
return entry;
4252
};
4353

44-
const walkModule = async (module, dependency) => {
45-
walk.simple(acorn.parse(module, {
54+
const walkModule = async (modulePath, dependency) => {
55+
const moduleContents = fs.readFileSync(modulePath, 'utf-8');
56+
57+
walk.simple(acorn.parse(moduleContents, {
4658
ecmaVersion: '2020',
4759
sourceType: 'module'
4860
}), {
4961
async ImportDeclaration(node) {
5062
let { value: sourceValue } = node.source;
5163
const absoluteNodeModulesLocation = await getNodeModulesLocationForPackage(dependency);
64+
const isBarePath = sourceValue.indexOf('http') !== 0 && sourceValue.charAt(0) !== '.' && sourceValue.charAt(0) !== path.sep;
65+
const hasExtension = path.extname(sourceValue) !== '';
5266

53-
if (path.extname(sourceValue) === '' && sourceValue.indexOf('http') !== 0 && sourceValue.indexOf('./') < 0) {
67+
if (isBarePath && !hasExtension) {
5468
if (!importMap[sourceValue]) {
55-
// found a _new_ bare import for ${sourceValue}
56-
// we should add this to the importMap and walk its package.json for more transitive deps
5769
updateImportMap(sourceValue, `/node_modules/${sourceValue}`);
5870
}
5971

6072
await walkPackageJson(path.join(absoluteNodeModulesLocation, 'package.json'));
61-
} else if (sourceValue.indexOf('./') < 0) {
62-
// adding a relative import
73+
} else if (isBarePath) {
6374
updateImportMap(sourceValue, `/node_modules/${sourceValue}`);
6475
} else {
6576
// walk this module for all its dependencies
66-
sourceValue = sourceValue.indexOf('.js') < 0
77+
sourceValue = !hasExtension
6778
? `${sourceValue}.js`
6879
: sourceValue;
6980

7081
if (fs.existsSync(path.join(absoluteNodeModulesLocation, sourceValue))) {
71-
const moduleContents = fs.readFileSync(path.join(absoluteNodeModulesLocation, sourceValue));
72-
await walkModule(moduleContents, dependency);
73-
updateImportMap(`${dependency}/${sourceValue.replace('./', '')}`, `/node_modules/${dependency}/${sourceValue.replace('./', '')}`);
82+
const entry = `/node_modules/${await resolveRelativeSpecifier(sourceValue, modulePath, dependency)}`;
83+
await walkModule(path.join(absoluteNodeModulesLocation, sourceValue), dependency);
84+
85+
updateImportMap(path.join(dependency, sourceValue), entry);
7486
}
7587
}
76-
77-
return Promise.resolve();
7888
},
79-
ExportNamedDeclaration(node) {
89+
async ExportNamedDeclaration(node) {
8090
const sourceValue = node && node.source ? node.source.value : '';
8191

8292
if (sourceValue !== '' && sourceValue.indexOf('http') !== 0) {
93+
// handle relative specifier
8394
if (sourceValue.indexOf('.') === 0) {
84-
updateImportMap(`${dependency}/${sourceValue.replace('./', '')}`, `/node_modules/${dependency}/${sourceValue.replace('./', '')}`);
95+
const entry = `/node_modules/${await resolveRelativeSpecifier(sourceValue, modulePath, dependency)}`;
96+
97+
updateImportMap(path.join(dependency, sourceValue), entry);
8598
} else {
99+
// handle bare specifier
86100
updateImportMap(sourceValue, `/node_modules/${sourceValue}`);
87101
}
88102
}
89103
},
90-
ExportAllDeclaration(node) {
104+
async ExportAllDeclaration(node) {
91105
const sourceValue = node && node.source ? node.source.value : '';
92106

93107
if (sourceValue !== '' && sourceValue.indexOf('http') !== 0) {
94108
if (sourceValue.indexOf('.') === 0) {
95-
updateImportMap(`${dependency}/${sourceValue.replace('./', '')}`, `/node_modules/${dependency}/${sourceValue.replace('./', '')}`);
109+
const entry = `/node_modules/${await resolveRelativeSpecifier(sourceValue, modulePath, dependency)}`;
110+
111+
updateImportMap(path.join(dependency, sourceValue), entry);
96112
} else {
97113
updateImportMap(sourceValue, `/node_modules/${sourceValue}`);
98114
}
@@ -107,7 +123,7 @@ const walkPackageJson = async (packageJson = {}) => {
107123
// and walk its package.json for its dependencies
108124

109125
for (const dependency of Object.keys(packageJson.dependencies || {})) {
110-
const dependencyPackageRootPath = path.join(process.cwd(), './node_modules', dependency);
126+
const dependencyPackageRootPath = path.join(process.cwd(), 'node_modules', dependency);
111127
const dependencyPackageJsonPath = path.join(dependencyPackageRootPath, 'package.json');
112128
const dependencyPackageJson = JSON.parse(fs.readFileSync(dependencyPackageJsonPath, 'utf-8'));
113129
const entry = await getPackageEntryPath(dependencyPackageJson);
@@ -137,7 +153,7 @@ const walkPackageJson = async (packageJson = {}) => {
137153
break;
138154
case 'object':
139155
const entryTypes = Object.keys(mapItem);
140-
156+
141157
if (entryTypes.import) {
142158
esmPath = entryTypes.import;
143159
} else if (entryTypes.require) {
@@ -165,31 +181,26 @@ const walkPackageJson = async (packageJson = {}) => {
165181

166182
// use the dependency itself as an entry in the importMap
167183
if (entry === '.') {
168-
updateImportMap(dependency, `/node_modules/${dependency}/${packageExport.replace('./', '')}`);
184+
updateImportMap(dependency, `/node_modules/${path.join(dependency, packageExport)}`);
169185
}
170186
} else if (exportMapEntry.endsWith && (exportMapEntry.endsWith('.js') || exportMapEntry.endsWith('.mjs')) && exportMapEntry.indexOf('*') < 0) {
171187
// is probably a file, so _not_ an export array, package.json, or wildcard export
172188
packageExport = exportMapEntry;
173189
}
174-
190+
175191
if (packageExport) {
176-
const packageExportLocation = path.join(absoluteNodeModulesLocation, packageExport.replace('./', ''));
192+
const packageExportLocation = path.resolve(absoluteNodeModulesLocation, packageExport);
177193

178-
// check all exports of an exportMap entry
179-
// to make sure those deps get added to the importMap
180194
if (packageExport.endsWith('js')) {
181-
const moduleContents = fs.readFileSync(packageExportLocation);
182-
183-
await walkModule(moduleContents, dependency);
184-
updateImportMap(`${dependency}${entry.replace('.', '')}`, `/node_modules/${dependency}/${packageExport.replace('./', '')}`);
195+
updateImportMap(path.join(dependency, entry), `/node_modules/${path.join(dependency, packageExport)}`);
185196
} else if (fs.lstatSync(packageExportLocation).isDirectory()) {
186197
fs.readdirSync(packageExportLocation)
187198
.filter(file => file.endsWith('.js') || file.endsWith('.mjs'))
188199
.forEach((file) => {
189-
updateImportMap(`${dependency}/${packageExport.replace('./', '')}${file}`, `/node_modules/${dependency}/${packageExport.replace('./', '')}${file}`);
200+
updateImportMap(path.join(dependency, packageExport, file), `/node_modules/${path.join(dependency, packageExport, file)}`);
190201
});
191202
} else {
192-
console.warn('Warning, not able to handle export', `${dependency}/${packageExport}`);
203+
console.warn('Warning, not able to handle export', path.join(dependency, packageExport));
193204
}
194205
}
195206
}
@@ -200,10 +211,9 @@ const walkPackageJson = async (packageJson = {}) => {
200211

201212
// sometimes a main file is actually just an empty string... :/
202213
if (fs.existsSync(packageEntryPointPath)) {
203-
const packageEntryModule = fs.readFileSync(packageEntryPointPath, 'utf-8');
204-
205-
await walkModule(packageEntryModule, dependency);
206-
updateImportMap(dependency, `/node_modules/${dependency}/${entry}`);
214+
updateImportMap(dependency, `/node_modules/${path.join(dependency, entry)}`);
215+
216+
await walkModule(packageEntryPointPath, dependency);
207217
await walkPackageJson(dependencyPackageJson);
208218
}
209219
}

packages/cli/test/cases/develop.default/develop.default.spec.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,18 @@ describe('Develop Greenwood With: ', function() {
342342
`${process.cwd()}/node_modules/tslib/*.js`,
343343
`${outputPath}/node_modules/tslib/`
344344
);
345+
const stencilCorePackageJson = await getDependencyFiles(
346+
`${process.cwd()}/node_modules/@stencil/core/package.json`,
347+
`${outputPath}/node_modules/@stencil/core/`
348+
);
349+
const stencilCoreCoreLibs = await getDependencyFiles(
350+
`${process.cwd()}/node_modules/@stencil/core/internal/stencil-core/*.js`,
351+
`${outputPath}/node_modules/@stencil/core/internal/stencil-core/`
352+
);
353+
const stencilCoreClientLibs = await getDependencyFiles(
354+
`${process.cwd()}/node_modules/@stencil/core/internal/client/*.js`,
355+
`${outputPath}/node_modules/@stencil/core/internal/client/`
356+
);
345357

346358
// manually copy all these @babel/runtime files recursively since there are too many of them to do it individually
347359
const babelRuntimeLibs = await rreaddir(`${process.cwd()}/node_modules/@babel/runtime`);
@@ -437,7 +449,10 @@ describe('Develop Greenwood With: ', function() {
437449
...materialThemePackageJson,
438450
...materialThemeLibs,
439451
...tslibPackageJson,
440-
...tslibLibs
452+
...tslibLibs,
453+
...stencilCorePackageJson,
454+
...stencilCoreCoreLibs,
455+
...stencilCoreClientLibs
441456
]);
442457

443458
return new Promise(async (resolve) => {

packages/cli/test/cases/develop.default/import-map.snapshot.json

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,8 @@
166166
"@lion/button": "/node_modules/@lion/button/index.js",
167167
"@lion/core": "/node_modules/@lion/core/index.js",
168168
"@lion/core/differentKeyEventNamesShimIE": "/node_modules/@lion/core/src/differentKeyEventNamesShimIE.js",
169-
"@lion/button/src/LionButton.js": "/node_modules/@lion/button/src/LionButton.js",
170169
"@lion/button/define-button": "/node_modules/@lion/button/lion-button.js",
171-
"@lion/button/src/LionButtonReset.js": "/node_modules/@lion/button/src/LionButtonReset.js",
172170
"@lion/button/define-button-reset": "/node_modules/@lion/button/lion-button-reset.js",
173-
"@lion/button/src/LionButtonSubmit.js": "/node_modules/@lion/button/src/LionButtonSubmit.js",
174171
"@lion/button/define-button-submit": "/node_modules/@lion/button/lion-button-submit.js",
175172
"@lion/button/define": "/node_modules/@lion/button/define.js",
176173
"lit": "/node_modules/lit/index.js",
@@ -258,17 +255,16 @@
258255
"lit-element/decorators/state.js": "/node_modules/lit-element/decorators/state.js",
259256
"lit-element/polyfill-support.js": "/node_modules/lit-element/polyfill-support.js",
260257
"lit-element/private-ssr-support.js": "/node_modules/lit-element/private-ssr-support.js",
261-
"lit-html/lit-html.js": "/node_modules/lit-html/lit-html.js",
262258
"lit-html/polyfill-support.js": "/node_modules/lit-html/polyfill-support.js",
263259
"lit-html/private-ssr-support.js": "/node_modules/lit-html/private-ssr-support.js",
264260
"@lion/calendar": "/node_modules/@lion/calendar/index.js",
265261
"@lion/localize": "/node_modules/@lion/localize/index.js",
266-
"@lion/calendar/src/LionCalendar.js": "/node_modules/@lion/calendar/src/LionCalendar.js",
267262
"@lion/calendar/define": "/node_modules/@lion/calendar/lion-calendar.js",
268263
"@lion/calendar/test-helpers": "/node_modules/@lion/calendar/test-helpers/index.js",
269264
"@lion/localize/test-helpers": "/node_modules/@lion/localize/test-helpers/index.js",
270265
"@bundled-es-modules/message-format/MessageFormat.js": "/node_modules/@bundled-es-modules/message-format/MessageFormat.js",
271266
"@bundled-es-modules/message-format": "/node_modules/@bundled-es-modules/message-format/index.js",
272-
"singleton-manager/src/SingletonManagerClass.js": "/node_modules/singleton-manager/src/SingletonManagerClass.js",
273-
"singleton-manager": "/node_modules/singleton-manager/index.js"
267+
"singleton-manager": "/node_modules/singleton-manager/index.js",
268+
"@stencil/core": "/node_modules/@stencil/core/internal/stencil-core/index.js",
269+
"@stencil/client/index.js": "/node_modules/@stencil/core/internal/client/index.js"
274270
}

packages/cli/test/cases/develop.default/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"@lion/button": "^0.14.3",
77
"@lion/calendar": "^0.16.5",
88
"@material/mwc-button": "^0.25.2",
9+
"@stencil/core": "^2.12.0",
910
"@types/trusted-types": "^2.0.2",
1011
"lit": "^2.0.0"
1112
}

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2481,6 +2481,11 @@
24812481
resolved "https://registry.yarnpkg.com/@rollup/stream/-/stream-2.0.0.tgz#2ada818c2d042e37f63119d7bf8bbfc71792f641"
24822482
integrity sha512-HsCyY/phZMys1zFUYoYlnDJGG9zMmYFfEjDKNQa00CYgjeyGD4cLdO6KNIkBh61AWOZfOsTPuGtNmFCsjQOfFg==
24832483

2484+
"@stencil/core@^2.12.0":
2485+
version "2.12.0"
2486+
resolved "https://registry.yarnpkg.com/@stencil/core/-/core-2.12.0.tgz#5b12517dd367908026692d3b00fa1aab39638ab9"
2487+
integrity sha512-hQlQKh5CUJe8g3L5avLLsfgVu95HMS2LToTtS7gpvvP0eKes1VvAC56uhI+vH4u44GZl9ck/g1rJBVRmMWu0LA==
2488+
24842489
"@stylelint/postcss-css-in-js@^0.37.2":
24852490
version "0.37.2"
24862491
resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz#7e5a84ad181f4234a2480803422a47b8749af3d2"

0 commit comments

Comments
 (0)