Skip to content

Commit

Permalink
Merge branch 'main' into feat/ws
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito committed Sep 17, 2024
2 parents e006296 + 5342c19 commit c5b1581
Show file tree
Hide file tree
Showing 13 changed files with 526 additions and 76 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ tmp
/yarn.lock
stats.html
.vscode
.idea
msw-*.tgz
.husky/_
.env
Expand Down
1 change: 0 additions & 1 deletion cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ yargs
'Initializes Mock Service Worker at the specified directory',
(yargs) => {
yargs

.positional('publicDir', {
type: 'string',
description: 'Relative path to the public directory',
Expand Down
90 changes: 59 additions & 31 deletions cli/init.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const { until } = require('@open-draft/until')
const confirm = require('@inquirer/confirm').default
const invariant = require('./invariant')
const { SERVICE_WORKER_BUILD_PATH } = require('../config/constants')

module.exports = async function init(args) {
const [, publicDir] = args._
const CWD = args.cwd || process.cwd()
const publicDir = args._[1] ? normalizePath(args._[1]) : undefined

const packageJsonPath = path.resolve(CWD, 'package.json')
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
const savedWorkerDirectories = Array.prototype.concat(
(packageJson.msw && packageJson.msw.workerDirectory) || [],
)
const savedWorkerDirectories = Array.prototype
.concat((packageJson.msw && packageJson.msw.workerDirectory) || [])
.map(normalizePath)

if (publicDir) {
// If the public directory was provided, copy the worker script
// to that directory only. Even if there are paths stored in "msw.workerDirectory",
// those will not be touched.
await copyWorkerScript(publicDir, CWD)
const relativePublicDir = toRelative(publicDir, CWD)
const relativePublicDir = path.relative(CWD, publicDir)
printSuccessMessage([publicDir])

if (args.save) {
Expand Down Expand Up @@ -52,8 +51,7 @@ module.exports = async function init(args) {
return
}

// Calling "init" without a public directory but with the "--save" flag
// is no-op.
// Calling "init" without a public directory but with the "--save" flag is a no-op.
invariant(
args.save == null,
'Failed to copy the worker script: cannot call the "init" command without a public directory but with the "--save" flag. Either drop the "--save" flag to copy the worker script to all paths listed in "msw.workerDirectory", or add an explicit public directory to the command, like "npx msw init ./public".',
Expand All @@ -69,7 +67,7 @@ module.exports = async function init(args) {
return copyWorkerScript(destination, CWD).catch((error) => {
// Inject the absolute destination path onto the copy function rejections
// so it's available in the failed paths array below.
throw [toAbsolute(destination, CWD), error]
throw [toAbsolutePath(destination, CWD), error]
})
}),
)
Expand All @@ -92,51 +90,61 @@ module.exports = async function init(args) {
}
}

function toRelative(absolutePath, cwd) {
return path.relative(cwd, absolutePath)
}

function toAbsolute(maybeAbsolutePath, cwd) {
/**
* @param {string} maybeAbsolutePath
* @param {string} cwd
* @returns {string}
*/
function toAbsolutePath(maybeAbsolutePath, cwd) {
return path.isAbsolute(maybeAbsolutePath)
? maybeAbsolutePath
: path.resolve(cwd, maybeAbsolutePath)
}

/**
* @param {string} destination
* @param {string} cwd
* @returns {Promise<string>}
*/
async function copyWorkerScript(destination, cwd) {
// When running as a part of "postinstall" script, "cwd" equals the library's directory.
// The "postinstall" script resolves the right absolute public directory path.
const absolutePublicDir = toAbsolute(destination, cwd)
const absolutePublicDir = toAbsolutePath(destination, cwd)

if (!fs.existsSync(absolutePublicDir)) {
// Try to create the directory if it doesn't exist
const createDirectoryResult = await until(() =>
fs.promises.mkdir(absolutePublicDir, { recursive: true }),
)

invariant(
createDirectoryResult.error == null,
'Failed to copy the worker script at "%s": directory does not exist and could not be created.\nMake sure to include a relative path to the public directory of your application.\n\nSee the original error below:\n%s',
absolutePublicDir,
createDirectoryResult.error,
)
await fs.promises
.mkdir(absolutePublicDir, { recursive: true })
.catch((error) => {
throw new Error(
invariant(
false,
'Failed to copy the worker script at "%s": directory does not exist and could not be created.\nMake sure to include a relative path to the public directory of your application.\n\nSee the original error below:\n\n%s',
absolutePublicDir,
error,
),
)
})
}

console.log('Copying the worker script at "%s"...', absolutePublicDir)

const serviceWorkerFilename = path.basename(SERVICE_WORKER_BUILD_PATH)
const swDestFilepath = path.resolve(absolutePublicDir, serviceWorkerFilename)
const workerFilename = path.basename(SERVICE_WORKER_BUILD_PATH)
const workerDestinationPath = path.resolve(absolutePublicDir, workerFilename)

fs.copyFileSync(SERVICE_WORKER_BUILD_PATH, swDestFilepath)
fs.copyFileSync(SERVICE_WORKER_BUILD_PATH, workerDestinationPath)

return swDestFilepath
return workerDestinationPath
}

/**
* @param {Array<string>} paths
*/
function printSuccessMessage(paths) {
console.log(`
${chalk.green('Worker script successfully copied!')}
${paths.map((path) => chalk.gray(` - ${path}\n`))}
Continue by describing the network in your application:
${chalk.cyan.bold('https://mswjs.io/docs/getting-started')}
`)
Expand All @@ -151,6 +159,10 @@ ${pathsWithErrors
`)
}

/**
* @param {string} packageJsonPath
* @param {string} publicDir
*/
function saveWorkerDirectory(packageJsonPath, publicDir) {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))

Expand Down Expand Up @@ -179,6 +191,12 @@ function saveWorkerDirectory(packageJsonPath, publicDir) {
)
}

/**
* @param {string} message
* @param {string} packageJsonPath
* @param {string} publicDir
* @returns {void}
*/
function promptWorkerDirectoryUpdate(message, packageJsonPath, publicDir) {
return confirm({
theme: {
Expand All @@ -191,3 +209,13 @@ function promptWorkerDirectoryUpdate(message, packageJsonPath, publicDir) {
}
})
}

/**
* Normalizes the given path, replacing ambiguous path separators
* with the platform-specific path separator.
* @param {string} input Path to normalize.
* @returns {string}
*/
function normalizePath(input) {
return input.replace(/[\\|\/]+/g, path.sep)
}
2 changes: 1 addition & 1 deletion cli/invariant.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const chalk = require('chalk')

module.exports = function (predicate, message, ...args) {
module.exports = function invariant(predicate, message, ...args) {
if (!predicate) {
console.error(chalk.red(message), ...args)
process.exit(1)
Expand Down
34 changes: 34 additions & 0 deletions config/plugins/esbuild/graphQLImportPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import fs from 'fs/promises'
import type { Plugin } from 'esbuild'

/**
* 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(): Plugin {
return {
name: 'graphql-import-plugin',
setup(build) {
if (build.initialOptions.format !== 'esm') {
return
}

build.onLoad({ filter: /\.ts$/ }, async (args) => {
const contents = await fs.readFile(args.path, 'utf-8')
const match = /require\(['"]graphql['"]\)/g.exec(contents)

if (match) {
return {
loader: 'ts',
contents:
contents.slice(0, match.index - 1) +
`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})` +
contents.slice(match.index + match[0].length),
}
}
})
},
}
}
18 changes: 18 additions & 0 deletions decisions/jest-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Jest support

With the introduction of [Mock Service Worker 2.0](https://mswjs.io/blog/introducing-msw-2.0), the library has made a significant step forward in the effort of embracing and promoting web standards. Since that release, its contributors have reported multiple issues with Node.js simply because MSW exposed developers to using standard Node.js APIs.

Betting and advancing the web standards is one of the goals behind this project. One of such standards is ESM. It's the present and the future of JavaScript, and we are planning on switching to ESM-only in the years to come. For that transition to happen, we need to prioritize and, at times, make hard decisions.

**MSW offers no official support for Jest.** It doesn't mean MSW cannot be used in Jest. We maintain usage examples of both [Jest](https://github.com/mswjs/examples/tree/main/examples/with-jest) and [Jest+JSDOM](https://github.com/mswjs/examples/tree/main/examples/with-jest-jsdom) to attest to that. Although it's necessary to mention that those examples require additional setup to tackle underlying Jest or JSDOM issues.

What this means is that **we are not going to address any issues specific to Jest or JSDOM**. Those pose a significant time investment just to uncover another inconsistency between the browser and JSDOM, or the lacking features in Jest, like proper ESM support. That is not a reasonable use of the limited contributors' time. You will have a far better chance of getting your issue solved by reporting it to the Jest or JSDOM repo for the respective teams to address it.

## What's next?

> [!IMPORTANT]
> If you are experiencing issues with using MSW in Jest, **please verify them outside of Jest before reporting them**.
You can verify the issue in a plain Node.js script, or by copying your problematic test to [Vitest](https://vitest.dev/). Please note that we do not support issue reports from non-standard environments, like Deno or Bun either.

You can use one of our existing [Usage examples](https://github.com/mswjs/examples) as a template project to reproduce your issue.
14 changes: 5 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,16 @@
"@bundled-es-modules/statuses": "^1.0.1",
"@bundled-es-modules/tough-cookie": "^0.1.6",
"@inquirer/confirm": "^3.0.0",
"@mswjs/interceptors": "^0.34.0",
"@mswjs/interceptors": "^0.35.6",
"@open-draft/until": "^2.1.0",
"@types/cookie": "^0.6.0",
"@types/statuses": "^2.0.4",
"chalk": "^4.1.2",
"graphql": "^16.8.1",
"headers-polyfill": "^4.0.2",
"is-node-process": "^1.2.0",
"outvariant": "^1.4.2",
"path-to-regexp": "^6.2.0",
"path-to-regexp": "^6.3.0",
"strict-event-emitter": "^0.5.1",
"type-fest": "^4.9.0",
"yargs": "^17.7.2"
Expand Down Expand Up @@ -177,12 +178,11 @@
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"express": "^4.18.2",
"express": "^5.0.0",
"fastify": "^4.26.0",
"fs-extra": "^11.2.0",
"fs-teardown": "^0.3.0",
"glob": "^10.3.10",
"graphql": "^16.8.1",
"jsdom": "^23.2.0",
"json-bigint": "^1.0.0",
"lint-staged": "^15.2.0",
Expand All @@ -202,13 +202,9 @@
"webpack-http-server": "^0.5.0"
},
"peerDependencies": {
"graphql": ">= 16.8.x",
"typescript": ">= 4.7.x"
"typescript": ">= 4.8.x"
},
"peerDependenciesMeta": {
"graphql": {
"optional": true
},
"typescript": {
"optional": true
}
Expand Down
Loading

0 comments on commit c5b1581

Please sign in to comment.