diff --git a/.changeset/fuzzy-gorillas-wait.md b/.changeset/fuzzy-gorillas-wait.md new file mode 100644 index 0000000000..14a6ec1e17 --- /dev/null +++ b/.changeset/fuzzy-gorillas-wait.md @@ -0,0 +1,5 @@ +--- +'@lit-labs/ssr': minor +--- + +Module resolution within SSR now supports package exports (via `package.json`) diff --git a/package-lock.json b/package-lock.json index 4c79cf1a5e..0ee87dbc6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9715,6 +9715,18 @@ } } }, + "node_modules/enhanced-resolve": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -21337,6 +21349,14 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -24097,12 +24117,12 @@ "@lit-labs/ssr-client": "^1.0.0", "@lit/reactive-element": "^1.4.0", "@types/node": "^16.0.0", + "enhanced-resolve": "^5.10.0", "lit": "^2.3.0", "lit-element": "^3.1.0", "lit-html": "^2.3.0", "node-fetch": "^3.2.8", - "parse5": "^6.0.1", - "resolve": "^1.10.1" + "parse5": "^6.0.1" }, "devDependencies": { "@koa/router": "^12.0.0", @@ -26621,6 +26641,7 @@ "@webcomponents/template-shadowroot": "^0.1.0", "command-line-args": "^5.1.1", "deepmerge": "^4.2.2", + "enhanced-resolve": "^5.10.0", "koa": "^2.7.0", "koa-cors": "^0.0.16", "koa-node-resolve": "^1.0.0-pre.5", @@ -26629,8 +26650,7 @@ "lit-element": "^3.1.0", "lit-html": "^2.3.0", "node-fetch": "^3.2.8", - "parse5": "^6.0.1", - "resolve": "^1.10.1" + "parse5": "^6.0.1" }, "dependencies": { "node-fetch": { @@ -32549,6 +32569,15 @@ "has-binary2": "~1.0.2" } }, + "enhanced-resolve": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, "enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -41489,6 +41518,11 @@ } } }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" + }, "tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", diff --git a/packages/labs/ssr/package.json b/packages/labs/ssr/package.json index 1793699975..bb16a9cf10 100644 --- a/packages/labs/ssr/package.json +++ b/packages/labs/ssr/package.json @@ -134,9 +134,9 @@ "@open-wc/testing-karma": "^4.0.9", "@types/command-line-args": "^5.0.0", "@types/koa": "^2.0.49", + "@types/koa__router": "^8.0.2", "@types/koa-cors": "*", "@types/koa-static": "^4.0.1", - "@types/koa__router": "^8.0.2", "@types/parse5": "^6.0.1", "@types/resolve": "^1.20.2", "@webcomponents/template-shadowroot": "^0.1.0", @@ -151,12 +151,12 @@ "@lit-labs/ssr-client": "^1.0.0", "@lit/reactive-element": "^1.4.0", "@types/node": "^16.0.0", + "enhanced-resolve": "^5.10.0", "lit": "^2.3.0", "lit-element": "^3.1.0", "lit-html": "^2.3.0", "node-fetch": "^3.2.8", - "parse5": "^6.0.1", - "resolve": "^1.10.1" + "parse5": "^6.0.1" }, "engines": { "node": ">=13.9.0" diff --git a/packages/labs/ssr/src/lib/module-loader.ts b/packages/labs/ssr/src/lib/module-loader.ts index b6b6e00e3a..26b5c24df7 100644 --- a/packages/labs/ssr/src/lib/module-loader.ts +++ b/packages/labs/ssr/src/lib/module-loader.ts @@ -8,11 +8,9 @@ import * as path from 'path'; import {promises as fs} from 'fs'; import {URL, fileURLToPath, pathToFileURL} from 'url'; import * as vm from 'vm'; -import resolveAsync from 'resolve'; +import enhancedResolve from 'enhanced-resolve'; import {builtinModules} from 'module'; -type PackageJSON = {main?: string; module?: string; 'jsnext:main'?: string}; - const builtIns = new Set(builtinModules); const specifierMatches = (specifier: string, match: string) => @@ -276,17 +274,11 @@ export const resolveSpecifier = async ( // a single version. referrerPath = fileURLToPath(import.meta.url); } - const modulePath = await resolve(specifier, { - basedir: path.dirname(referrerPath), - moduleDirectory: ['node_modules'], + const modulePath = await resolve(specifier, path.dirname(referrerPath), { + modules: ['node_modules'], extensions: ['.js'], - // Some packages use a non-standard alternative to the "main" field - // in their package.json to differentiate their ES module version. - packageFilter: (packageJson: PackageJSON) => { - packageJson.main = - packageJson.module ?? packageJson['jsnext:main'] ?? packageJson.main; - return packageJson; - }, + mainFields: ['module', 'jsnext:main', 'main'], + conditionNames: ['node'], }); return pathToFileURL(modulePath); } @@ -301,10 +293,12 @@ const initializeImportMeta = (meta: {url: string}, module: vm.Module) => { const resolve = async ( id: string, - opts: resolveAsync.AsyncOpts + path: string, + opts: Partial ): Promise => { + const resolver = enhancedResolve.create(opts); return new Promise((res, rej) => { - resolveAsync(id, opts, (err, resolved) => { + resolver({}, path, id, {}, (err: unknown, resolved?: string) => { if (err != null) { rej(err); } else { diff --git a/packages/labs/ssr/src/test/lib/module-loader_test.ts b/packages/labs/ssr/src/test/lib/module-loader_test.ts index d0aad0ab69..b010be7bd7 100644 --- a/packages/labs/ssr/src/test/lib/module-loader_test.ts +++ b/packages/labs/ssr/src/test/lib/module-loader_test.ts @@ -53,4 +53,38 @@ test('loads a module with a built-in import', async () => { assert.ok(module.namespace.join); }); +test('resolves an exact exported path', async () => { + const loader = new ModuleLoader({global: window}); + const result = await loader.importModule('./lit-import.js', testIndex); + const {module, path: modulePath} = result; + assert.is(module.namespace.litIsServer, true); + assert.ok(loader.cache.has(modulePath)); + const isServerPath = path.resolve( + path.dirname(testIndex), + '../../../../../lit-html/node/is-server.js' + ); + assert.ok(loader.cache.has(isServerPath)); +}); + +test('resolves a root exported path (.)', async () => { + const loader = new ModuleLoader({global: window}); + const result = await loader.importModule( + './lit-import-from-root.js', + testIndex + ); + const {module, path: modulePath} = result; + assert.is(module.namespace.litIsServer, true); + assert.ok(loader.cache.has(modulePath)); + const litPath = path.resolve( + path.dirname(testIndex), + '../../../../../lit/index.js' + ); + const isServerPath = path.resolve( + path.dirname(testIndex), + '../../../../../lit-html/node/is-server.js' + ); + assert.ok(loader.cache.has(litPath)); + assert.ok(loader.cache.has(isServerPath)); +}); + test.run(); diff --git a/packages/labs/ssr/src/test/test-files/module-loader/lit-import-from-root.js b/packages/labs/ssr/src/test/test-files/module-loader/lit-import-from-root.js new file mode 100644 index 0000000000..0a1c784a5d --- /dev/null +++ b/packages/labs/ssr/src/test/test-files/module-loader/lit-import-from-root.js @@ -0,0 +1,2 @@ +import {isServer} from 'lit'; +export const litIsServer = isServer; diff --git a/packages/labs/ssr/src/test/test-files/module-loader/lit-import.js b/packages/labs/ssr/src/test/test-files/module-loader/lit-import.js new file mode 100644 index 0000000000..9054221c54 --- /dev/null +++ b/packages/labs/ssr/src/test/test-files/module-loader/lit-import.js @@ -0,0 +1,2 @@ +import {isServer} from 'lit-html/is-server.js'; +export const litIsServer = isServer;