Files
vercel__next.js/test/unit/next-babel-loader-dev.test.ts
Benjamin Woodruff 9d596b1502 Turbopack: Fix babel-loader (allowing built-in or manual configuration) (#82676)
## Context: What's broken?

For using `styled-jsx` with `react-compiler`, we need to use `babel-loader`, but it turns out that outside of our current narrow use of `react-compiler`, our usage of `babel-loader` in Turbopack is totally broken:
* Babel configs cause us to exit with an error claiming it's not supported.
* We have code in Turbopack for enabling `babel-loader`, but we try to use the version from NPM instead of the one bundled internally with Next.js. We should use the one bundled internally with Next.js.
* We shouldn't run all the babel transforms in the `next/babel` preset because they're redundant with SWC. `react-compiler` added a "standalone" mode to the babel-loader that's close to what we want, but I need to extend it for Turbopack's use-case.

## This PR

- Remove the warnings/errors that say babel isn't supported with Turbopack.
- Automatically enable babel when a config file is present.
- Modify the `next/babel` preset in Turbopack (I'm re-using/extending `'standalone'` mode) to enable syntax plugins, but disable any transformations or down-leveling (we expect SWC to do this). This is a bit hacky because babel's presets aren't designed to support this configuration.
- Pre-bundle `plugin-syntax-typescript`. This is a stub package that's also used by the typescript preset, so this really just exposes an extra entrypoint into the babel bundle.
- Migrate one of the legacy babel `test/integration` tests (that uses flow syntax) to `test/e2e`. The turbopack `foreign` condition doesn't work correctly without test isolation.
- Enable Turbopack for all the babel-related integration and e2e tests I could find.

## Follow-ups

- [ ] https://github.com/vercel/next.js/pull/83502 React compiler can cause babel to run *twice*. Merge the logic for automatic configuration of `react-compiler` (currently in JS) and `babel` (in Rust), so that this can't happen.
- [ ] https://github.com/vercel/next.js/pull/84002 Update the docs to show that Babel is now supported.
2025-09-22 09:40:58 -07:00

116 lines
3.1 KiB
TypeScript

/* eslint-env jest */
import os from 'os'
import path from 'path'
import { Span } from 'next/dist/trace'
import loader from 'next/dist/build/babel/loader'
const dir = path.resolve(os.tmpdir())
const babel = async (code: string, queryOpts = {} as any) => {
const { isServer = false, resourcePath = `index.js` } = queryOpts
let isAsync = false
const options = {
// loader opts
cwd: dir,
isServer,
distDir: path.resolve(dir, '.next'),
pagesDir:
'pagesDir' in queryOpts ? queryOpts.pagesDir : path.resolve(dir, 'pages'),
cache: false,
development: true,
hasReactRefresh: !isServer,
transformMode: 'default',
}
return new Promise<string>((resolve, reject) => {
function callback(err, content) {
if (err) {
reject(err)
} else {
resolve(content.replace(/\n/g, ''))
}
}
const res = loader.bind({
resourcePath,
async() {
isAsync = true
return callback
},
callback,
emitWarning() {},
query: options,
getOptions: function () {
return options
},
currentTraceSpan: new Span({ name: 'test' }),
})(code, null)
if (!isAsync) {
resolve(res)
}
})
}
describe('next-babel-loader', () => {
describe('replace constants', () => {
it('should replace NODE_ENV on client (dev)', async () => {
const code = await babel(`process.env.NODE_ENV`, {
isServer: false,
})
expect(code).toMatchInlineSnapshot(`""development";"`)
})
it('should replace NODE_ENV in statement (dev)', async () => {
const code = await babel(`if (process.env.NODE_ENV === 'development') {}`)
expect(code).toMatchInlineSnapshot(`"if (true) {}"`)
})
it('should support 9.4 regression', async () => {
const pageFile = path.resolve(dir, 'pages', 'index.js')
const output = await babel(
`
import React from "react";
import queryGraphql from "../graphql/schema";
const gql = String.raw;
export default function Home({ greeting }) {
return <h1>{greeting}</h1>;
}
export async function getStaticProps() {
const greeting = await getGreeting();
return {
props: {
greeting,
},
};
}
async function getGreeting() {
const result = await queryGraphql(
gql\`
{
query {
greeting
}
}
\`
);
return result.data.greeting;
}
`,
{ resourcePath: pageFile, isServer: false }
)
expect(output).toContain(
`var __jsx = React.createElement;import React from "react";export var __N_SSG = true;export default function Home(_ref) { var greeting = _ref.greeting; return __jsx("h1", { __self: this, __source: { fileName: _jsxFileName, lineNumber: 8, columnNumber: 20 } }, greeting);}_c = Home;var _c;$RefreshReg$(_c, "Home");`
)
})
})
})