diff --git a/src/runtimes/node/bundler.js b/src/runtimes/node/bundler.js index 7fd203676..f4b267443 100644 --- a/src/runtimes/node/bundler.js +++ b/src/runtimes/node/bundler.js @@ -40,8 +40,8 @@ const bundleJsFile = async function ({ } } - const externalizedModules = new Set() - const plugins = [externalNativeModulesPlugin(externalizedModules)] + const nativeNodeModules = {} + const plugins = [externalNativeModulesPlugin(nativeNodeModules)] const nodeTarget = getBundlerTarget(config.nodeVersion) try { @@ -58,7 +58,7 @@ const bundleJsFile = async function ({ target: [nodeTarget], }) - return { bundlePath, cleanTempFiles, data, externalizedModules: [...externalizedModules] } + return { bundlePath, cleanTempFiles, data, nativeNodeModules } } catch (error) { error.customErrorInfo = { type: 'functionsBundling', location: { functionName: name } } diff --git a/src/runtimes/node/native_modules/plugin.js b/src/runtimes/node/native_modules/plugin.js index 3b415dfea..d7c5b4ffd 100644 --- a/src/runtimes/node/native_modules/plugin.js +++ b/src/runtimes/node/native_modules/plugin.js @@ -14,8 +14,8 @@ const findNativeModule = (packageJsonPath, cache) => { if (cache[packageJsonPath] === undefined) { // eslint-disable-next-line fp/no-mutation, no-param-reassign, promise/prefer-await-to-then cache[packageJsonPath] = readPackageJson(packageJsonPath).then( - (data) => Boolean(isNativeModule(data)), - () => {}, + (data) => [Boolean(isNativeModule(data), data), data], + () => [], ) } @@ -39,13 +39,21 @@ const externalNativeModulesPlugin = (externalizedModules) => ({ // eslint-disable-next-line fp/no-loops while (true) { if (path.basename(directory) !== 'node_modules') { - const packageJsonPath = path.join(directory, 'node_modules', package[1], 'package.json') + const modulePath = path.join(directory, 'node_modules', package[1]) + const packageJsonPath = path.join(modulePath, 'package.json') // eslint-disable-next-line no-await-in-loop - const isNative = await findNativeModule(packageJsonPath, cache) + const [isNative, packageJsonData] = await findNativeModule(packageJsonPath, cache) // eslint-disable-next-line max-depth if (isNative === true) { - externalizedModules.add(args.path) + // eslint-disable-next-line max-depth + if (externalizedModules[args.path] === undefined) { + // eslint-disable-next-line fp/no-mutation, no-param-reassign + externalizedModules[args.path] = {} + } + + // eslint-disable-next-line fp/no-mutation, no-param-reassign + externalizedModules[args.path][modulePath] = packageJsonData.version return { path: args.path, external: true } } diff --git a/src/runtimes/node/zip_esbuild.js b/src/runtimes/node/zip_esbuild.js index 1ea69f031..335bf1605 100644 --- a/src/runtimes/node/zip_esbuild.js +++ b/src/runtimes/node/zip_esbuild.js @@ -40,7 +40,7 @@ const zipEsbuild = async ({ stat, }) => { const { externalModules, ignoredModules } = await getExternalAndIgnoredModules({ config, srcDir }) - const { bundlePath, data, cleanTempFiles, externalizedModules } = await bundleJsFile({ + const { bundlePath, data, cleanTempFiles, nativeNodeModules = {} } = await bundleJsFile({ additionalModulePaths: pluginsModulesPath ? [pluginsModulesPath] : [], config, destFilename: filename, @@ -53,7 +53,7 @@ const zipEsbuild = async ({ }) const bundlerWarnings = data.warnings.length === 0 ? undefined : data.warnings const { paths: srcFiles } = await getSrcFilesAndExternalModules({ - externalNodeModules: [...externalModules, ...externalizedModules], + externalNodeModules: [...externalModules, ...Object.keys(nativeNodeModules)], bundler: JS_BUNDLER_ESBUILD, mainFile, srcPath, @@ -92,7 +92,7 @@ const zipEsbuild = async ({ srcFiles: [...supportingSrcFiles, bundlePath], }) - return { bundler: JS_BUNDLER_ESBUILD, bundlerWarnings, config, path } + return { bundler: JS_BUNDLER_ESBUILD, bundlerWarnings, config, nativeNodeModules, path } } finally { await cleanTempFiles() } diff --git a/src/zip.js b/src/zip.js index 713f49691..2d91b49fc 100644 --- a/src/zip.js +++ b/src/zip.js @@ -19,7 +19,7 @@ const validateArchiveFormat = (archiveFormat) => { // Takes the result of zipping a function and formats it for output. const formatZipResult = (result) => { - const { bundler, bundlerErrors, bundlerWarnings, config = {}, name, path, runtime } = result + const { bundler, bundlerErrors, bundlerWarnings, config = {}, name, nativeNodeModules, path, runtime } = result return removeFalsy({ bundler, @@ -27,6 +27,7 @@ const formatZipResult = (result) => { bundlerWarnings, config, name, + nativeNodeModules, path, runtime: runtime.name, }) diff --git a/tests/fixtures/node-module-native-buildtime/package.json b/tests/fixtures/node-module-native-buildtime/package.json index c3b380e8d..bb906b245 100644 --- a/tests/fixtures/node-module-native-buildtime/package.json +++ b/tests/fixtures/node-module-native-buildtime/package.json @@ -2,6 +2,6 @@ "name": "node-module-native-buildtime", "version": "1.0.0", "dependencies": { - "test": "1.0.2" + "test": "1.0.0" } } diff --git a/tests/main.js b/tests/main.js index da54fd2af..353201f4e 100644 --- a/tests/main.js +++ b/tests/main.js @@ -67,22 +67,30 @@ testBundlers( 'Handles Node module with native bindings (buildtime marker module)', [ESBUILD, ESBUILD_ZISI, DEFAULT], async (bundler, t) => { - if (bundler === ESBUILD && semver.lt(versions.node, '10.0.0')) { + if (semver.lt(versions.node, '10.0.0')) { t.log('Skipping test for unsupported Node version') return t.pass() } - const { files, tmpDir } = await zipNode(t, 'node-module-native-buildtime', { + const fixtureDir = 'node-module-native-buildtime' + const { files, tmpDir } = await zipNode(t, fixtureDir, { opts: { config: { '*': { nodeBundler: bundler } } }, }) const requires = await getRequires({ filePath: resolve(tmpDir, 'src/function.js') }) const normalizedRequires = new Set(requires.map(unixify)) + const modulePath = resolve(FIXTURES_DIR, `${fixtureDir}/node_modules/test`) - t.true(files.every(({ runtime }) => runtime === 'js')) + t.is(files.length, 1) + t.is(files[0].runtime, 'js') t.true(await pathExists(`${tmpDir}/src/node_modules/test/native.node`)) t.true(await pathExists(`${tmpDir}/src/node_modules/test/side-file.js`)) t.true(normalizedRequires.has('test')) + + // We can only detect native modules when using esbuild. + if (bundler !== DEFAULT) { + t.deepEqual(files[0].nativeNodeModules, { test: { [modulePath]: '1.0.0' } }) + } }, ) @@ -90,22 +98,30 @@ testBundlers( 'Handles Node module with native bindings (runtime marker module)', [ESBUILD, ESBUILD_ZISI, DEFAULT], async (bundler, t) => { - if (bundler === ESBUILD && semver.lt(versions.node, '10.0.0')) { + if (semver.lt(versions.node, '10.0.0')) { t.log('Skipping test for unsupported Node version') return t.pass() } - const { files, tmpDir } = await zipNode(t, 'node-module-native-runtime', { + const fixtureDir = 'node-module-native-runtime' + const { files, tmpDir } = await zipNode(t, fixtureDir, { opts: { config: { '*': { nodeBundler: bundler } } }, }) const requires = await getRequires({ filePath: resolve(tmpDir, 'src/function.js') }) const normalizedRequires = new Set(requires.map(unixify)) + const modulePath = resolve(FIXTURES_DIR, `${fixtureDir}/node_modules/test`) - t.true(files.every(({ runtime }) => runtime === 'js')) + t.is(files.length, 1) + t.is(files[0].runtime, 'js') t.true(await pathExists(`${tmpDir}/src/node_modules/test/native.node`)) t.true(await pathExists(`${tmpDir}/src/node_modules/test/side-file.js`)) t.true(normalizedRequires.has('test')) + + // We can only detect native modules when using esbuild. + if (bundler !== DEFAULT) { + t.deepEqual(files[0].nativeNodeModules, { test: { [modulePath]: '1.0.0' } }) + } }, )