Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions hook.js → create-hook.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
//
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.

const { URL, fileURLToPath } = require('url')
const { inspect } = require('util')
const { builtinModules } = require('module')
import { URL, fileURLToPath } from 'url'
import { inspect } from 'util'
import { builtinModules } from 'module'

const specifiers = new Map()
const isWin = process.platform === 'win32'
let experimentalPatchInternals = false
Expand All @@ -21,7 +22,8 @@ let entrypoint

let getExports
if (NODE_MAJOR >= 20 || (NODE_MAJOR === 18 && NODE_MINOR >= 19)) {
getExports = require('./lib/get-exports.js')
// We'll load this lazily in `processModule` since Node.js 12 doesn't support top-level await.
getExports = null
} else {
getExports = (url) => import(url).then(Object.keys)
}
Expand All @@ -35,7 +37,7 @@ function hasIitm (url) {
}

function isIitm (url, meta) {
return url === meta.url || url === meta.url.replace('hook.mjs', 'hook.js')
return url === meta.url || url === meta.url.replace('hook.mjs', 'create-hook.mjs')
}

function deleteIitm (url) {
Expand Down Expand Up @@ -197,6 +199,9 @@ function emitWarning (err) {
* from the module and any transitive export all modules.
*/
async function processModule ({ srcUrl, context, parentGetSource, parentResolve, excludeDefault }) {
if (!getExports) {
getExports = (await import('./lib/get-exports.mjs')).default
}
const exportNames = await getExports(srcUrl, context, parentGetSource)
const starExports = new Set()
const setters = new Map()
Expand Down Expand Up @@ -284,7 +289,7 @@ function addIitm (url) {
return needsToAddFileProtocol(urlObj) ? 'file:' + urlObj.href : urlObj.href
}

function createHook (meta) {
export function createHook (meta) {
let cachedResolve
const iitmURL = new URL('lib/register.js', meta.url).toString()
let includeModules, excludeModules
Expand Down Expand Up @@ -432,6 +437,7 @@ register(${JSON.stringify(realUrl)}, _, set, get, ${JSON.stringify(specifiers.ge
`
}
} catch (cause) {
process._rawDebug(cause)
// If there are other ESM loader hooks registered as well as iitm,
// depending on the order they are registered, source might not be
// JavaScript.
Expand Down Expand Up @@ -494,5 +500,3 @@ register(${JSON.stringify(realUrl)}, _, set, get, ${JSON.stringify(specifiers.ge
}
}
}

module.exports = { createHook }
2 changes: 1 addition & 1 deletion hook.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.

import { createHook } from './hook.js'
import { createHook } from './create-hook.mjs'

const { initialize, load, resolve, getFormat, getSource } = createHook(import.meta)

Expand Down
8 changes: 3 additions & 5 deletions lib/get-esm-exports.js → lib/get-esm-exports.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict'

const { Parser } = require('acorn')
const { importAttributesOrAssertions } = require('acorn-import-attributes')
import { Parser } from 'acorn'
import { importAttributesOrAssertions } from 'acorn-import-attributes'

const acornOpts = {
ecmaVersion: 'latest',
Expand Down Expand Up @@ -32,7 +32,7 @@ function warn (txt) {
* @returns {Set<string>} The identifiers exported by the module along with any
* custom directives.
*/
function getEsmExports (moduleSource) {
export default function getEsmExports (moduleSource) {
const exportedNames = new Set()
const tree = parser.parse(moduleSource, acornOpts)
for (const node of tree.body) {
Expand Down Expand Up @@ -114,5 +114,3 @@ function parseSpecifiers (node, exportedNames) {
}
}
}

module.exports = getEsmExports
27 changes: 18 additions & 9 deletions lib/get-exports.js → lib/get-exports.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
'use strict'

const getEsmExports = require('./get-esm-exports.js')
const { parse: parseCjs } = require('cjs-module-lexer')
const { readFileSync } = require('fs')
const { builtinModules } = require('module')
const { fileURLToPath, pathToFileURL } = require('url')
const { dirname } = require('path')
import getEsmExports from './get-esm-exports.mjs'
import { parse as parseCjs, init as parserInit } from 'cjs-module-lexer'
import { readFileSync } from 'fs'
import { builtinModules, createRequire } from 'module'
import { fileURLToPath, pathToFileURL } from 'url'
import { dirname } from 'path'

await parserInit()

function addDefault (arr) {
return new Set(['default', ...arr])
Expand All @@ -14,9 +16,15 @@ function addDefault (arr) {
// Cached exports for Node built-in modules
const BUILT_INS = new Map()

let require

function getExportsForNodeBuiltIn (name) {
let exports = BUILT_INS.get()

if (!require) {
require = createRequire(import.meta.url)
}

if (!exports) {
exports = new Set(addDefault(Object.keys(require(name))))
BUILT_INS.set(name, exports)
Expand Down Expand Up @@ -47,6 +55,9 @@ async function getCjsExports (url, context, parentLoad, source) {
re = './'
}
// Resolve the re-exported module relative to the current module.
if (!require) {
require = createRequire(import.meta.url)
}
const newUrl = pathToFileURL(require.resolve(re, { paths: [dirname(fileURLToPath(url))] })).href

if (newUrl.endsWith('.node') || newUrl.endsWith('.json')) {
Expand Down Expand Up @@ -81,7 +92,7 @@ async function getCjsExports (url, context, parentLoad, source) {
* Please see {@link getEsmExports} for caveats on special identifiers that may
* be included in the result set.
*/
async function getExports (url, context, parentLoad) {
export default async function getExports (url, context, parentLoad) {
// `parentLoad` gives us the possibility of getting the source
// from an upstream loader. This doesn't always work though,
// so later on we fall back to reading it from disk.
Expand Down Expand Up @@ -125,5 +136,3 @@ async function getExports (url, context, parentLoad) {
throw err
}
}

module.exports = getExports
9 changes: 9 additions & 0 deletions test/fixtures/double-loader.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { readFile } from 'fs/promises'

export async function load (url, context, nextLoad) {
const result = await nextLoad(url, context)
if (!result.source && url.startsWith('file:')) {
result.source = await readFile(new URL(url))
}
return result
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// v18.19.0 backported ESM hook execution to a separate thread,
// thus being equivalent to >=v20.
require('./v20-get-esm-exports')
import './v20-get-esm-exports.mjs'
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
'use strict'

const getEsmExports = require('../../lib/get-esm-exports.js')
const fs = require('fs')
const assert = require('assert')
const path = require('path')
import getEsmExports from '../../lib/get-esm-exports.mjs'
import fs from 'fs'
import assert from 'assert'
import path from 'path'
import { fileURLToPath } from 'url'

const fixturePath = path.join(__dirname, '../fixtures/esm-exports.txt')
const dirname = path.dirname(fileURLToPath(import.meta.url))

const fixturePath = path.join(dirname, '../fixtures/esm-exports.txt')
const fixture = fs.readFileSync(fixturePath, 'utf8')

fixture.split('\n').forEach(line => {
Expand Down
15 changes: 15 additions & 0 deletions test/other/double-loading.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License.
//
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.

import { execSync } from 'child_process'
import { doesNotThrow } from 'assert'

const env = {
...process.env,
NODE_OPTIONS: '--no-warnings --experimental-loader ./test/fixtures/double-loader.mjs --experimental-loader ./hook.mjs'
}

doesNotThrow(() => {
execSync('node -p 0', { env })
})
Loading