Skip to content

Commit

Permalink
Add next-swc jest transform (vercel#30993)
Browse files Browse the repository at this point in the history
Co-authored-by: JJ Kasper <[email protected]>
Co-authored-by: Tim Neutkens <[email protected]>
  • Loading branch information
3 people authored Nov 8, 2021
1 parent 83cd452 commit bc88831
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 124 deletions.
7 changes: 7 additions & 0 deletions errors/experimental-jest-transformer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# "next/jest" Experimental

#### Why This Message Occurred

You are using `next/jest` which is currently an experimental feature of Next.js. In a future version of Next.js `next/jest` will be marked as stable.

If you have any feedback about the transformer you can share it on this discussion: https://github.com/vercel/next.js/discussions/31152.
4 changes: 4 additions & 0 deletions errors/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,10 @@
{
"title": "middleware-new-signature",
"path": "/errors/middleware-new-signature.md"
},
{
"title": "experimental-jest-transformer",
"path": "/errors/experimental-jest-transformer.md"
}
]
}
Expand Down
33 changes: 3 additions & 30 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const path = require('path')

module.exports = {
testMatch: ['**/*.test.js', '**/*.test.ts', '**/*.test.tsx'],
setupFilesAfterEnv: ['<rootDir>/jest-setup-after-env.ts'],
Expand All @@ -8,36 +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',
{
sourceMaps: 'inline',
module: {
type: 'commonjs',
},
env: {
targets: {
node: '12.0.0',
},
},
jsc: {
loose: true,

parser: {
syntax: 'typescript',
dynamicImport: true,
tsx: true,
},
transform: {
react: {
pragma: 'React.createElement',
pragmaFrag: 'React.Fragment',
throwIfNamespace: true,
development: false,
useBuiltins: true,
},
},
},
},
path.join(__dirname, './packages/next/jest.js'),
],
},
}
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
89 changes: 89 additions & 0 deletions packages/next/build/swc/jest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
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.
*/

import vm from 'vm'
import { transformSync } from './index'
import { getJestSWCOptions } from './options'

console.warn(
'"next/jest" is currently experimental. https://nextjs.org/docs/messages/experimental-jest-transformer'
)

/**
* Loads closest package.json in the directory hierarchy
*/
function loadClosestPackageJson(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 loadClosestPackageJson(attempts + 1)
}
}

const packageConfig = loadClosestPackageJson()
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

module.exports = {
process(src, filename, jestOptions) {
if (!/\.[jt]sx?$/.test(filename)) {
return src
}

let swcTransformOpts = getJestSWCOptions({
filename,
esm: isSupportEsm && isEsm(filename, jestOptions),
})

return transformSync(src, { ...swcTransformOpts, filename })
},
}

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)
)
)
}
126 changes: 126 additions & 0 deletions packages/next/build/swc/options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
const nextDistPath =
/(next[\\/]dist[\\/]shared[\\/]lib)|(next[\\/]dist[\\/]client)|(next[\\/]dist[\\/]pages)/

function getBaseSWCOptions({
filename,
development,
hasReactRefresh,
globalWindow,
}) {
const isTSFile = filename.endsWith('.ts')
const isTypeScript = isTSFile || filename.endsWith('.tsx')

return {
jsc: {
parser: {
syntax: isTypeScript ? 'typescript' : 'ecmascript',
dynamicImport: true,
// Exclude regular TypeScript files from React transformation to prevent e.g. generic parameters and angle-bracket type assertion from being interpreted as JSX tags.
[isTypeScript ? 'tsx' : 'jsx']: isTSFile ? false : true,
},

transform: {
react: {
runtime: 'automatic',
pragma: 'React.createElement',
pragmaFrag: 'React.Fragment',
throwIfNamespace: true,
development: development,
useBuiltins: true,
refresh: hasReactRefresh,
},
optimizer: {
simplify: false,
globals: {
typeofs: {
window: globalWindow ? 'object' : 'undefined',
},
},
},
regenerator: {
importPath: require.resolve('regenerator-runtime'),
},
},
},
}
}

export function getJestSWCOptions({ filename, esm }) {
let baseOptions = getBaseSWCOptions({
filename,
development: false,
hasReactRefresh: false,
globalWindow: false,
})

const isNextDist = nextDistPath.test(filename)

return {
...baseOptions,
env: {
targets: {
// Targets the current version of Node.js
node: process.versions.node,
},
},
module: {
type: esm && !isNextDist ? 'es6' : 'commonjs',
},
disableNextSsg: true,
disablePageConfig: true,
}
}

export function getLoaderSWCOptions({
filename,
development,
isServer,
pagesDir,
isPageFile,
hasReactRefresh,
}) {
let baseOptions = getBaseSWCOptions({
filename,
development,
globalWindow: !isServer,
hasReactRefresh,
})

const isNextDist = nextDistPath.test(filename)

if (isServer) {
return {
...baseOptions,
// Disables getStaticProps/getServerSideProps tree shaking on the server compilation for pages
disableNextSsg: true,
disablePageConfig: true,
isDevelopment: development,
pagesDir,
isPageFile,
env: {
targets: {
// Targets the current version of Node.js
node: process.versions.node,
},
},
}
} else {
// Matches default @babel/preset-env behavior
baseOptions.jsc.target = 'es5'
return {
...baseOptions,
// Ensure Next.js internals are output as commonjs modules
...(isNextDist
? {
module: {
type: 'commonjs',
},
}
: {}),
disableNextSsg: !isPageFile,
isDevelopment: development,
pagesDir,
isPageFile,
}
}
}
90 changes: 2 additions & 88 deletions packages/next/build/webpack/loaders/next-swc-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,90 +27,7 @@ DEALINGS IN THE SOFTWARE.
*/

import { transform } from '../../swc'

const nextDistPath =
/(next[\\/]dist[\\/]shared[\\/]lib)|(next[\\/]dist[\\/]client)|(next[\\/]dist[\\/]pages)/

function getSWCOptions({
filename,
isServer,
development,
isPageFile,
pagesDir,
isNextDist,
hasReactRefresh,
}) {
const isTSFile = filename.endsWith('.ts')
const isTypeScript = isTSFile || filename.endsWith('.tsx')

const jsc = {
parser: {
syntax: isTypeScript ? 'typescript' : 'ecmascript',
dynamicImport: true,
// Exclude regular TypeScript files from React transformation to prevent e.g. generic parameters and angle-bracket type assertion from being interpreted as JSX tags.
[isTypeScript ? 'tsx' : 'jsx']: isTSFile ? false : true,
},

transform: {
react: {
runtime: 'automatic',
pragma: 'React.createElement',
pragmaFrag: 'React.Fragment',
throwIfNamespace: true,
development: development,
useBuiltins: true,
refresh: hasReactRefresh,
},
optimizer: {
simplify: false,
globals: {
typeofs: {
window: isServer ? 'undefined' : 'object',
},
},
},
regenerator: {
importPath: require.resolve('regenerator-runtime'),
},
},
}

if (isServer) {
return {
jsc,
// Disables getStaticProps/getServerSideProps tree shaking on the server compilation for pages
disableNextSsg: true,
disablePageConfig: true,
isDevelopment: development,
pagesDir,
isPageFile,
env: {
targets: {
// Targets the current version of Node.js
node: process.versions.node,
},
},
}
} else {
// Matches default @babel/preset-env behavior
jsc.target = 'es5'
return {
// Ensure Next.js internals are output as commonjs modules
...(isNextDist
? {
module: {
type: 'commonjs',
},
}
: {}),
disableNextSsg: !isPageFile,
isDevelopment: development,
pagesDir,
isPageFile,
jsc,
}
}
}
import { getLoaderSWCOptions } from '../../swc/options'

async function loaderTransform(parentTrace, source, inputSourceMap) {
// Make the loader async
Expand All @@ -121,15 +38,12 @@ async function loaderTransform(parentTrace, source, inputSourceMap) {
const { isServer, pagesDir, hasReactRefresh } = loaderOptions
const isPageFile = filename.startsWith(pagesDir)

const isNextDist = nextDistPath.test(filename)

const swcOptions = getSWCOptions({
const swcOptions = getLoaderSWCOptions({
pagesDir,
filename,
isServer: isServer,
isPageFile,
development: this.mode === 'development',
isNextDist,
hasReactRefresh,
})

Expand Down
1 change: 1 addition & 0 deletions packages/next/jest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/build/swc/jest')
Loading

0 comments on commit bc88831

Please sign in to comment.