diff --git a/jest.config.js b/jest.config.js index 96682b8402b6fcf..726f0371d6f52bc 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,3 +1,5 @@ +const path = require('path') + module.exports = { testMatch: ['**/*.test.js', '**/*.test.ts', '**/*.test.tsx'], setupFilesAfterEnv: ['/jest-setup-after-env.ts'], @@ -8,7 +10,7 @@ module.exports = { transform: { '.+\\.(t|j)sx?$': [ // this matches our SWC options used in https://github.com/vercel/next.js/blob/canary/packages/next/taskfile-swc.js - '@swc/jest', + path.join(__dirname, './packages/next/jest.js'), { sourceMaps: 'inline', module: { diff --git a/package.json b/package.json index 52d82b750b8dfe8..3cb2fb175e7c0c1 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "@svgr/webpack": "5.5.0", "@swc/cli": "0.1.49", "@swc/core": "1.2.97", - "@swc/jest": "0.2.3", "@testing-library/react": "11.2.5", "@types/cheerio": "0.22.16", "@types/fs-extra": "8.1.0", diff --git a/packages/next/build/swc/jest.js b/packages/next/build/swc/jest.js new file mode 100644 index 000000000000000..2e845c512c7de85 --- /dev/null +++ b/packages/next/build/swc/jest.js @@ -0,0 +1,133 @@ +/* +Copyright (c) 2021 The swc Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +const fs = require('fs') +const path = require('path') +const vm = require('vm') +const { transformSync } = require('./index') + +/** + * Loads closes package.json in the directory hierarchy + */ +function loadClosesPackageJson(attempts = 1) { + if (attempts > 5) { + throw new Error("Can't resolve main package.json file") + } + var mainPath = attempts === 1 ? './' : Array(attempts).join('../') + try { + return require(mainPath + 'package.json') + } catch (e) { + return loadClosesPackageJson(attempts + 1) + } +} + +const packageConfig = loadClosesPackageJson() +const isEsmProject = packageConfig.type === 'module' + +// Jest use the `vm` [Module API](https://nodejs.org/api/vm.html#vm_class_vm_module) for ESM. +// see https://github.com/facebook/jest/issues/9430 +const isSupportEsm = 'Module' in vm + +let swcTransformOpts + +module.exports = { + process(src, filename, jestOptions) { + if (!/\.[jt]sx?$/.test(filename)) { + return src + } + + if (!swcTransformOpts) { + swcTransformOpts = buildSwcTransformOpts(jestOptions) + } + + if (isSupportEsm) { + set( + swcTransformOpts, + 'module.type', + isEsm(filename, jestOptions) ? 'es6' : 'commonjs' + ) + } + + return transformSync(src, { ...swcTransformOpts, filename }) + }, +} + +function buildSwcTransformOpts(jestOptions) { + let swcOptions = getSwcTransformConfig(jestOptions) + + if (!swcOptions) { + const swcrc = path.join(process.cwd(), '.swcrc') + swcOptions = fs.existsSync(swcrc) + ? JSON.parse(fs.readFileSync(swcrc, 'utf-8')) + : {} + } + + if (!isSupportEsm) { + set(swcOptions, 'module.type', 'commonjs') + } + + set(swcOptions, 'jsc.transform.hidden.jest', true) + + return swcOptions +} + +function getSwcTransformConfig(jestConfig) { + return getJestConfig(jestConfig).transform.find(([, transformerPath]) => + transformerPath.includes('next/jest') + )?.[2] +} + +function getJestConfig(jestConfig) { + return 'config' in jestConfig + ? // jest 27 + jestConfig.config + : // jest 26 + jestConfig +} + +function isEsm(filename, jestOptions) { + return ( + (/\.jsx?$/.test(filename) && isEsmProject) || + getJestConfig(jestOptions).extensionsToTreatAsEsm?.find((ext) => + filename.endsWith(ext) + ) + ) +} + +function set(obj, filepath, value) { + let o = obj + const parents = filepath.split('.') + const key = parents.pop() + + for (const prop of parents) { + if (o[prop] == null) o[prop] = {} + o = o[prop] + } + + o[key] = value +} diff --git a/packages/next/jest.js b/packages/next/jest.js new file mode 100644 index 000000000000000..338e34d677b321f --- /dev/null +++ b/packages/next/jest.js @@ -0,0 +1 @@ +module.exports = require('./dist/build/swc/jest') diff --git a/yarn.lock b/yarn.lock index b1927b912cf769d..6818d606613b3ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4111,11 +4111,6 @@ "@swc/core-win32-ia32-msvc" "1.2.97" "@swc/core-win32-x64-msvc" "1.2.97" -"@swc/jest@0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.3.tgz#5c32aaa6298267a955d35eb67094edabd5db598f" - integrity sha512-ARZIY5OkXdFRQLHc/1i+yKrl0H3B1sa7Bu9XE8yTvYZZ4G5Ewu6oyyJBM52TiROP6EpMcF7ZeQQsKMZvzuKkNw== - "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"