Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
34 changes: 0 additions & 34 deletions config/plugins/esbuild/graphQLImportPlugin.ts

This file was deleted.

28 changes: 0 additions & 28 deletions config/plugins/esbuild/resolveCoreImportsPlugin.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import path from 'node:path'
import crypto from 'crypto'
import minify from 'babel-minify'
import { invariant } from 'outvariant'
import type { Plugin } from 'esbuild'
import copyServiceWorker from '../../copyServiceWorker.js'
import type { TsdownPlugin } from 'tsdown'
import copyServiceWorker from '../../copyServiceWorker.ts'

const SERVICE_WORKER_ENTRY_PATH = path.resolve(
process.cwd(),
Expand All @@ -27,15 +27,16 @@ export function getWorkerChecksum(): string {
return getChecksum(workerContents)
}

export function copyWorkerPlugin(checksum: string): Plugin {
export function copyWorkerPlugin(checksum: string): TsdownPlugin {
return {
name: 'copyWorkerPlugin',
async setup(build) {
async buildStart() {
invariant(
SERVICE_WORKER_ENTRY_PATH,
'Failed to locate the worker script source file',
)

},
async writeBundle() {
if (fs.existsSync(SERVICE_WORKER_OUTPUT_PATH)) {
console.warn(
'Skipped copying the worker script to "%s": already exists',
Expand All @@ -44,31 +45,14 @@ export function copyWorkerPlugin(checksum: string): Plugin {
return
}

// Generate the checksum from the worker script's contents.
// const workerContents = await fs.readFile(workerSourcePath, 'utf8')
// const checksum = getChecksum(workerContents)

build.onLoad({ filter: /mockServiceWorker\.js$/ }, async () => {
return {
// Prevent the worker script from being transpiled.
// But, generally, the worker script is not in the entrypoints.
contents: '',
}
})
// eslint-disable-next-line no-console
console.log('worker script checksum:', checksum)

build.onEnd(() => {
// eslint-disable-next-line no-console
console.log('worker script checksum:', checksum)

// Copy the worker script on the next tick.
process.nextTick(async () => {
await copyServiceWorker(
SERVICE_WORKER_ENTRY_PATH,
SERVICE_WORKER_OUTPUT_PATH,
checksum,
)
})
})
await copyServiceWorker(
SERVICE_WORKER_ENTRY_PATH,
SERVICE_WORKER_OUTPUT_PATH,
checksum,
)
},
}
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,24 @@
import { type Plugin } from 'esbuild'
import type { TsdownPlugin } from 'tsdown'

export const ESM_EXTENSION = '.mjs'
export const CJS_EXTENSION = '.js'

export function forceEsmExtensionsPlugin(): Plugin {
export function forceFileExtensionsPlugin(): TsdownPlugin {
return {
name: 'forceEsmExtensionsPlugin',
setup(build) {
const isEsm = build.initialOptions.format === 'esm'

build.onEnd(async (result) => {
if (result.errors.length > 0) {
return
}

for (const outputFile of result.outputFiles || []) {
// Only target CJS/ESM files.
// This ignores additional files emitted, like sourcemaps ("*.js.map").
if (
!(
outputFile.path.endsWith(ESM_EXTENSION) ||
outputFile.path.endsWith('.mjs')
)
) {
continue
}

const fileContents = outputFile.text
const nextFileContents = modifyRelativeImports(fileContents, isEsm)
name: 'forceFileExtensionsPlugin',
renderChunk(code, chunk, outputOptions) {
const isEsm =
outputOptions.format === 'es' ||
chunk.fileName.endsWith(ESM_EXTENSION)

if (!isEsm) {
return
}

outputFile.contents = Buffer.from(nextFileContents)
}
})
return {
code: modifyRelativeImports(code, isEsm),
map: null,
}
Comment on lines +9 to +21

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Entry condition and transform value use different ESM signals.

Line 12 enters the branch if either chunk.fileName.endsWith(ESM_EXTENSION) or isEsm (format-based) is true, but Line 17 only passes the format-based isEsm into modifyRelativeImports. If a chunk's file name ends in .mjs while outputOptions.format isn't 'es' (or vice versa, an edge case with custom entryFileNames), the function processes the chunk but rewrites imports using the wrong assumption of module format.

♻️ Proposed fix — compute effective ESM state once
     renderChunk(code, chunk, outputOptions) {
-      const isEsm = outputOptions.format === 'es'
+      const isEsm =
+        outputOptions.format === 'es' || chunk.fileName.endsWith(ESM_EXTENSION)

-      if (!(chunk.fileName.endsWith(ESM_EXTENSION) || isEsm)) {
+      if (!isEsm) {
         return
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
renderChunk(code, chunk, outputOptions) {
const isEsm = outputOptions.format === 'es'
build.onEnd(async (result) => {
if (result.errors.length > 0) {
return
}
for (const outputFile of result.outputFiles || []) {
// Only target CJS/ESM files.
// This ignores additional files emitted, like sourcemaps ("*.js.map").
if (
!(
outputFile.path.endsWith(ESM_EXTENSION) ||
outputFile.path.endsWith('.mjs')
)
) {
continue
}
const fileContents = outputFile.text
const nextFileContents = modifyRelativeImports(fileContents, isEsm)
if (!(chunk.fileName.endsWith(ESM_EXTENSION) || isEsm)) {
return
}
outputFile.contents = Buffer.from(nextFileContents)
}
})
return {
code: modifyRelativeImports(code, isEsm),
map: null,
}
renderChunk(code, chunk, outputOptions) {
const isEsm =
outputOptions.format === 'es' || chunk.fileName.endsWith(ESM_EXTENSION)
if (!isEsm) {
return
}
return {
code: modifyRelativeImports(code, isEsm),
map: null,
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@config/plugins/rolldown/forceFileExtensionsPlugin.ts` around lines 9 - 19,
The ESM check in renderChunk uses two different signals, which can cause imports
to be rewritten with the wrong module assumption. Compute a single effective ESM
flag in forceFileExtensionsPlugin using both chunk.fileName and
outputOptions.format, then use that same value for both the early return
condition and the modifyRelativeImports call so the branch decision and
transform input stay consistent.

},
}
}
Expand Down
26 changes: 26 additions & 0 deletions config/plugins/rolldown/graphQLImportPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { TsdownPlugin } from 'tsdown'

/**
* A plugin to replace `require('graphql')` statements with `await import('graphql')`
* only for ESM bundles. This makes the GraphQL module to be imported lazily
* while maintaining the CommonJS compatibility.
* @see https://github.com/mswjs/msw/issues/2254
*/
export function graphqlImportPlugin(): TsdownPlugin {
return {
name: 'graphql-import-plugin',
transform(code, id) {
if (!id.endsWith('.ts') || !code.includes(`require('graphql')`)) {
return
}

return {
code: code.replace(
/require\(['"]graphql['"]\)/g,
`await import('graphql').catch((error) => {console.error('[MSW] Failed to parse a GraphQL query: cannot import the "graphql" module. Please make sure you install it if you wish to intercept GraphQL requests. See the original import error below.'); throw error})`,
),
map: null,
}
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
19 changes: 19 additions & 0 deletions config/plugins/rolldown/resolveCoreImportsPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import path from 'node:path'
import type { TsdownPlugin } from 'tsdown'
import { replaceCoreImports } from '../../replaceCoreImports.js'
import { ESM_EXTENSION } from './forceFileExtensionsPlugin.ts'

export function resolveCoreImportsPlugin(): TsdownPlugin {
return {
name: 'resolveCoreImportsPlugin',
renderChunk(code, chunk, outputOptions) {
const isEsm = chunk.fileName.endsWith(ESM_EXTENSION)
const moduleFilePath = path.resolve(outputOptions.dir!, chunk.fileName)

return {
code: replaceCoreImports(moduleFilePath, code, isEsm),
map: null,
}
},
}
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,10 @@
"node": ">=22"
},
"scripts": {
"start": "tsup --watch",
"start": "tsdown --watch",
"clean": "rimraf ./lib",
"lint": "oxlint ./cli ./src ./test",
"build": "pnpm clean && cross-env NODE_ENV=production tsup && pnpm patch:dts",
"build": "pnpm clean && cross-env NODE_ENV=production tsdown && pnpm patch:dts",
"patch:dts": "node \"./config/scripts/patch-ts.js\"",
"publint": "publint",
"test": "pnpm test:unit && pnpm test:node && pnpm test:browser && pnpm test:native && pnpm test:memory",
Expand Down Expand Up @@ -303,7 +303,7 @@
"regenerator-runtime": "^0.14.1",
"rimraf": "^6.1.3",
"simple-git-hooks": "^2.13.1",
"tsup": "^8.5.1",
"tsdown": "^0.22.3",
"typescript": "^5.9.3",
"undici": "^8.7.0",
"url-loader": "^4.1.1",
Expand Down
Loading