Skip to content

Commit da7c7a6

Browse files
authored
feat!: convert all modules running in loader thread to ESM (#210)
1 parent b8fd97e commit da7c7a6

File tree

8 files changed

+70
-31
lines changed

8 files changed

+70
-31
lines changed

hook.js renamed to create-hook.mjs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
//
33
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
44

5-
const { URL, fileURLToPath } = require('url')
6-
const { inspect } = require('util')
7-
const { builtinModules } = require('module')
5+
import { URL, fileURLToPath } from 'url'
6+
import { inspect } from 'util'
7+
import { builtinModules } from 'module'
8+
import { getExports as getExportsImpl } from './lib/get-exports.mjs'
9+
810
const specifiers = new Map()
911
const isWin = process.platform === 'win32'
1012
let experimentalPatchInternals = false
@@ -17,15 +19,15 @@ const NODE_MAJOR = Number(NODE_VERSION[0])
1719
const NODE_MINOR = Number(NODE_VERSION[1])
1820
const HANDLED_FORMATS = new Set(['builtin', 'module', 'commonjs'])
1921

20-
let entrypoint
21-
2222
let getExports
2323
if (NODE_MAJOR >= 20 || (NODE_MAJOR === 18 && NODE_MINOR >= 19)) {
24-
getExports = require('./lib/get-exports.js')
24+
getExports = getExportsImpl
2525
} else {
2626
getExports = (url) => import(url).then(Object.keys)
2727
}
2828

29+
let entrypoint
30+
2931
function hasIitm (url) {
3032
try {
3133
return new URL(url).searchParams.has('iitm')
@@ -35,7 +37,7 @@ function hasIitm (url) {
3537
}
3638

3739
function isIitm (url, meta) {
38-
return url === meta.url || url === meta.url.replace('hook.mjs', 'hook.js')
40+
return url === meta.url || url === meta.url.replace('hook.mjs', 'create-hook.mjs')
3941
}
4042

4143
function deleteIitm (url) {
@@ -284,7 +286,7 @@ function addIitm (url) {
284286
return needsToAddFileProtocol(urlObj) ? 'file:' + urlObj.href : urlObj.href
285287
}
286288

287-
function createHook (meta) {
289+
export function createHook (meta) {
288290
let cachedResolve
289291
const iitmURL = new URL('lib/register.js', meta.url).toString()
290292
let includeModules, excludeModules
@@ -494,5 +496,3 @@ register(${JSON.stringify(realUrl)}, _, set, get, ${JSON.stringify(specifiers.ge
494496
}
495497
}
496498
}
497-
498-
module.exports = { createHook }

hook.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
44

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

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

lib/get-esm-exports.js renamed to lib/get-esm-exports.mjs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict'
22

3-
const { Parser } = require('acorn')
4-
const { importAttributesOrAssertions } = require('acorn-import-attributes')
3+
import { Parser } from 'acorn'
4+
import { importAttributesOrAssertions } from 'acorn-import-attributes'
55

66
const acornOpts = {
77
ecmaVersion: 'latest',
@@ -32,7 +32,7 @@ function warn (txt) {
3232
* @returns {Set<string>} The identifiers exported by the module along with any
3333
* custom directives.
3434
*/
35-
function getEsmExports (moduleSource) {
35+
export default function getEsmExports (moduleSource) {
3636
const exportedNames = new Set()
3737
const tree = parser.parse(moduleSource, acornOpts)
3838
for (const node of tree.body) {
@@ -114,5 +114,3 @@ function parseSpecifiers (node, exportedNames) {
114114
}
115115
}
116116
}
117-
118-
module.exports = getEsmExports

lib/get-exports.js renamed to lib/get-exports.mjs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
'use strict'
22

3-
const getEsmExports = require('./get-esm-exports.js')
4-
const { parse: parseCjs } = require('cjs-module-lexer')
5-
const { readFileSync, existsSync } = require('fs')
6-
const { builtinModules } = require('module')
7-
const { fileURLToPath, pathToFileURL } = require('url')
8-
const { dirname, join } = require('path')
3+
import getEsmExports from './get-esm-exports.mjs'
4+
import { parse as parseCjs, init as parserInit } from 'cjs-module-lexer'
5+
import { readFileSync, existsSync } from 'fs'
6+
import { builtinModules, createRequire } from 'module'
7+
import { fileURLToPath, pathToFileURL } from 'url'
8+
import { dirname, join } from 'path'
9+
10+
let parserInitialized = false
911

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

19+
let require
20+
1721
function getExportsForNodeBuiltIn (name) {
1822
let exports = BUILT_INS.get()
1923

24+
if (!require) {
25+
require = createRequire(import.meta.url)
26+
}
27+
2028
if (!exports) {
2129
exports = new Set(addDefault(Object.keys(require(name))))
2230
BUILT_INS.set(name, exports)
@@ -90,6 +98,10 @@ async function getCjsExports (url, context, parentLoad, source) {
9098
urlsBeingProcessed.add(url)
9199

92100
try {
101+
if (!parserInitialized) {
102+
await parserInit()
103+
parserInitialized = true
104+
}
93105
const result = parseCjs(source)
94106
const full = addDefault(result.exports)
95107

@@ -110,6 +122,10 @@ async function getCjsExports (url, context, parentLoad, source) {
110122
[url, re] = resolved
111123
}
112124

125+
// Resolve the re-exported module relative to the current module.
126+
if (!require) {
127+
require = createRequire(import.meta.url)
128+
}
113129
const newUrl = pathToFileURL(require.resolve(re, { paths: [dirname(fileURLToPath(url))] })).href
114130

115131
if (newUrl.endsWith('.node') || newUrl.endsWith('.json')) {
@@ -144,7 +160,7 @@ async function getCjsExports (url, context, parentLoad, source) {
144160
* Please see {@link getEsmExports} for caveats on special identifiers that may
145161
* be included in the result set.
146162
*/
147-
async function getExports (url, context, parentLoad) {
163+
export async function getExports (url, context, parentLoad) {
148164
// `parentLoad` gives us the possibility of getting the source
149165
// from an upstream loader. This doesn't always work though,
150166
// so later on we fall back to reading it from disk.
@@ -188,5 +204,3 @@ async function getExports (url, context, parentLoad) {
188204
throw err
189205
}
190206
}
191-
192-
module.exports = getExports

test/fixtures/double-loader.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { readFile } from 'fs/promises'
2+
3+
export async function load (url, context, nextLoad) {
4+
const result = await nextLoad(url, context)
5+
if (!result.source && url.startsWith('file:')) {
6+
result.source = await readFile(new URL(url))
7+
}
8+
return result
9+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
// v18.19.0 backported ESM hook execution to a separate thread,
22
// thus being equivalent to >=v20.
3-
require('./v20-get-esm-exports')
3+
import './v20-get-esm-exports.mjs'

test/get-esm-exports/v20-get-esm-exports.js renamed to test/get-esm-exports/v20-get-esm-exports.mjs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
'use strict'
22

3-
const getEsmExports = require('../../lib/get-esm-exports.js')
4-
const fs = require('fs')
5-
const assert = require('assert')
6-
const path = require('path')
3+
import getEsmExports from '../../lib/get-esm-exports.mjs'
4+
import fs from 'fs'
5+
import assert from 'assert'
6+
import path from 'path'
7+
import { fileURLToPath } from 'url'
78

8-
const fixturePath = path.join(__dirname, '../fixtures/esm-exports.txt')
9+
const dirname = path.dirname(fileURLToPath(import.meta.url))
10+
11+
const fixturePath = path.join(dirname, '../fixtures/esm-exports.txt')
912
const fixture = fs.readFileSync(fixturePath, 'utf8')
1013

1114
fixture.split('\n').forEach(line => {

test/other/double-loading.mjs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License.
2+
//
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
4+
5+
import { execSync } from 'child_process'
6+
import { doesNotThrow } from 'assert'
7+
8+
const env = {
9+
...process.env,
10+
NODE_OPTIONS: '--no-warnings --experimental-loader ./test/fixtures/double-loader.mjs --experimental-loader ./hook.mjs'
11+
}
12+
13+
doesNotThrow(() => {
14+
execSync('node -p 0', { env })
15+
})

0 commit comments

Comments
 (0)