Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add experimental next-swc jest transform #30993

Merged
merged 6 commits into from Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 3 additions & 30 deletions jest.config.js
@@ -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
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
85 changes: 85 additions & 0 deletions packages/next/build/swc/jest.js
@@ -0,0 +1,85 @@
/*
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'

/**
* 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
@@ -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,
timneutkens marked this conversation as resolved.
Show resolved Hide resolved
hasReactRefresh: false,
globalWindow: false,
})

const isNextDist = nextDistPath.test(filename)

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

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
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
@@ -0,0 +1 @@
module.exports = require('./dist/build/swc/jest')
5 changes: 0 additions & 5 deletions yarn.lock
Expand Up @@ -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"
Expand Down