Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/sucrase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,15 @@ Then call `rollup` either via the [CLI](https://www.rollupjs.org/guide/en/#comma

The following [Sucrase options](https://github.com/alangpierce/sucrase#transforms) may be passed as options for this plugin:

- `disableESTransforms`
- `enableLegacyBabel5ModuleInterop`
- `enableLegacyTypeScriptModuleInterop`
- `injectCreateRequireForImportRequire`
- `jsxFragmentPragma`
- `jsxImportSource`
- `jsxPragma`
- `jsxRuntime`
- `preserveDynamicImport`
- `production`
- `transforms`

Expand Down
6 changes: 5 additions & 1 deletion packages/sucrase/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default function sucrase(opts = {}) {
// eslint-disable-next-line consistent-return
resolveId(importee, importer) {
if (importer && /^[./]/.test(importee)) {
const resolved = path.resolve(importer ? path.dirname(importer) : process.cwd(), importee);
const resolved = path.resolve(path.dirname(importer), importee);
// resolve in the same order that TypeScript resolves modules
const resolvedFilenames = [
`${resolved}.ts`,
Expand Down Expand Up @@ -42,8 +42,12 @@ export default function sucrase(opts = {}) {

const result = transform(code, {
transforms: opts.transforms,
jsxRuntime: opts.jsxRuntime,
jsxImportSource: opts.jsxImportSource,
jsxPragma: opts.jsxPragma,
jsxFragmentPragma: opts.jsxFragmentPragma,
preserveDynamicImport: opts.preserveDynamicImport,
injectCreateRequireForImportRequire: opts.injectCreateRequireForImportRequire,
Comment on lines 44 to +50
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Newly supported Sucrase options are now forwarded to transform (jsxImportSource, preserveDynamicImport, injectCreateRequireForImportRequire), but there are no runtime tests asserting these options actually affect output. Add focused tests/fixtures for at least jsxImportSource (and ideally the other two) to prevent regressions where the options stop being passed through.

Copilot uses AI. Check for mistakes.
enableLegacyTypeScriptModuleInterop: opts.enableLegacyTypeScriptModuleInterop,
enableLegacyBabel5ModuleInterop: opts.enableLegacyBabel5ModuleInterop,
production: opts.production,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import foo = require('foo');

export default foo;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => <div id="foo">hello world</div>
4 changes: 4 additions & 0 deletions packages/sucrase/test/fixtures/jsx-import-source/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/* eslint-disable import/extensions */
import example from './example.jsx';

t.snapshot(example.toString());
1 change: 1 addition & 0 deletions packages/sucrase/test/fixtures/jsx-runtime/example.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => <div id="foo">hello world</div>
4 changes: 4 additions & 0 deletions packages/sucrase/test/fixtures/jsx-runtime/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/* eslint-disable import/extensions */
import example from './example.jsx';

t.snapshot(example.toString());
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function loadModule(name) {
return import(name);
}
14 changes: 7 additions & 7 deletions packages/sucrase/test/snapshots/test.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ Generated by [AVA](https://avajs.dev).

> Snapshot 1
'() => React.createElement(\'div\', { id: "foo", __self: undefined, __source: {fileName: _jsxFileName, lineNumber: 1}}, "hello world" )'
'() => React.createElement(\'div\', { id: "foo", __self: this, __source: {fileName: _jsxFileName, lineNumber: 1}}, "hello world" )'

## converts jsx with custom jsxPragma

> Snapshot 1
'() => FakeReactCreateElement(\'div\', { id: "foo", __self: undefined, __source: {fileName: _jsxFileName, lineNumber: 1}}, "hello world" )'
'() => FakeReactCreateElement(\'div\', { id: "foo", __self: this, __source: {fileName: _jsxFileName, lineNumber: 1}}, "hello world" )'

## converts typescript

Expand Down Expand Up @@ -54,20 +54,20 @@ Generated by [AVA](https://avajs.dev).

> Snapshot 1
'() => React.createElement(\'div\', { id: "foo", __self: undefined, __source: {fileName: _jsxFileName$4, lineNumber: 1}}, "hello world" )'
'() => React.createElement(\'div\', { id: "foo", __self: this, __source: {fileName: _jsxFileName$4, lineNumber: 1}}, "hello world" )'

> Snapshot 2
'() => React.createElement(\'div\', { id: "example-b", __self: undefined, __source: {fileName: _jsxFileName$3, lineNumber: 1}}, "hello world (a second time)" )'
'() => React.createElement(\'div\', { id: "example-b", __self: this, __source: {fileName: _jsxFileName$3, lineNumber: 1}}, "hello world (a second time)" )'

> Snapshot 3
'() => React.createElement(\'div\', { id: "foo", __self: undefined, __source: {fileName: _jsxFileName$2, lineNumber: 1}}, "hello world" )'
'() => React.createElement(\'div\', { id: "foo", __self: this, __source: {fileName: _jsxFileName$2, lineNumber: 1}}, "hello world" )'

> Snapshot 4
'() => React.createElement(\'div\', { id: "foo", __self: undefined, __source: {fileName: _jsxFileName$1, lineNumber: 1}}, "hello world" )'
'() => React.createElement(\'div\', { id: "foo", __self: this, __source: {fileName: _jsxFileName$1, lineNumber: 1}}, "hello world" )'

> Snapshot 5
'() => React.createElement(\'div\', { id: "foo", __self: undefined, __source: {fileName: _jsxFileName, lineNumber: 1}}, "hello world" )'
'() => React.createElement(\'div\', { id: "foo", __self: this, __source: {fileName: _jsxFileName, lineNumber: 1}}, "hello world" )'
Binary file modified packages/sucrase/test/snapshots/test.js.snap
Binary file not shown.
124 changes: 83 additions & 41 deletions packages/sucrase/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,60 @@ require('source-map-support').install();

process.chdir(__dirname);

test('converts jsx', async (t) => {
const bundle = await rollup({
input: 'fixtures/jsx/main.js',
plugins: [
sucrase({
transforms: ['jsx']
})
]
function getBundle(input, sucraseOptions, rollupOptions) {
return rollup({
/**
* Explicitly set Rollup's top-level `this` context to silence build-time warnings about `this` being undefined in ES modules.
* This is a bundler-level concern only and does not affect the plugin's transform behavior, which runs before bundling.
*/
context: 'this',
input,
plugins: [sucrase(sucraseOptions)],
...rollupOptions
});
}

test('calls without options', async (t) => {
const plugin = sucrase();
t.is(plugin.name, 'sucrase');
});

test('does not transform files excluded by filter', async (t) => {
const plugin = sucrase({ exclude: '**/*.ts', transforms: ['typescript'] });
const result = plugin.transform('const x: number = 1;', 'foo.ts');
t.is(result, null);
});

test('converts jsx', async (t) => {
const bundle = await getBundle('fixtures/jsx/main.js', { transforms: ['jsx'] });
t.plan(1);
return testBundle(t, bundle);
});

test('converts jsx with custom jsxPragma', async (t) => {
const bundle = await rollup({
input: 'fixtures/jsx/main.js',
plugins: [
sucrase({
transforms: ['jsx'],
jsxPragma: 'FakeReactCreateElement'
})
]
const bundle = await getBundle('fixtures/jsx/main.js', {
transforms: ['jsx'],
jsxPragma: 'FakeReactCreateElement'
});
t.plan(1);
return testBundle(t, bundle);
});

test('converts jsx with jsxRuntime automatic', async (t) => {
const bundle = await getBundle(
'fixtures/jsx-runtime/main.js',
{ transforms: ['jsx'], jsxRuntime: 'automatic' },
{ external: ['react/jsx-dev-runtime'] }
);
const { output } = await bundle.generate({ format: 'cjs', exports: 'auto' });
const [{ code }] = output;
// Check that the code uses the automatic runtime instead of React.createElement
t.regex(code, /require\(['"]react\/jsx-dev-runtime['"]\)/);
t.notRegex(code, /React\.createElement/);
});

test('converts typescript', async (t) => {
const bundle = await rollup({
input: 'fixtures/typescript/main.js',
plugins: [
sucrase({
transforms: ['typescript']
})
]
});
const bundle = await getBundle('fixtures/typescript/main.js', { transforms: ['typescript'] });
t.plan(4);
return testBundle(t, bundle);
});
Expand All @@ -60,9 +78,7 @@ if (process.platform !== 'win32') {
const bundle = await rollup({
input: 'fixtures/typescript-with-aliases/main.js',
plugins: [
sucrase({
transforms: ['typescript']
}),
sucrase({ transforms: ['typescript'] }),
alias({
entries: [
{
Expand All @@ -80,29 +96,55 @@ if (process.platform !== 'win32') {
}

test('resolves typescript directory imports', async (t) => {
const bundle = await rollup({
input: 'fixtures/typescript-resolve-directory/main.js',
plugins: [
sucrase({
transforms: ['typescript']
})
]
const bundle = await getBundle('fixtures/typescript-resolve-directory/main.js', {
transforms: ['typescript']
});
t.plan(2);

return testBundle(t, bundle);
});

test('converts typescript jsx ("tsx")', async (t) => {
const bundle = await rollup({
input: 'fixtures/typescript-with-tsx/main.js',
plugins: [
sucrase({
transforms: ['typescript', 'jsx']
})
]
const bundle = await getBundle('fixtures/typescript-with-tsx/main.js', {
transforms: ['typescript', 'jsx']
});
t.plan(5);

return testBundle(t, bundle);
});

test('converts jsx with jsxImportSource', async (t) => {
const bundle = await getBundle(
'fixtures/jsx-import-source/main.js',
{ transforms: ['jsx'], jsxRuntime: 'automatic', jsxImportSource: 'preact' },
{ external: ['preact/jsx-dev-runtime'] }
);
const { output } = await bundle.generate({ format: 'cjs', exports: 'auto' });
const [{ code }] = output;
t.regex(code, /require\(['"]preact\/jsx-dev-runtime['"]\)/);
t.notRegex(code, /['"]react\/jsx-dev-runtime['"]/);
});

test('preserveDynamicImport keeps import() expression', async (t) => {
const bundle = await getBundle('fixtures/preserve-dynamic-import/main.js', {
transforms: ['imports'],
preserveDynamicImport: true
});
const { output } = await bundle.generate({ format: 'es' });
const [{ code }] = output;
t.regex(code, /import\(/);
});

test('injectCreateRequireForImportRequire emits createRequire', async (t) => {
const bundle = await getBundle(
'fixtures/inject-create-require/main.ts',
{
transforms: ['typescript'],
injectCreateRequireForImportRequire: true
},
{ external: ['foo', 'module'] }
);
const { output } = await bundle.generate({ format: 'es' });
const [{ code }] = output;
t.regex(code, /createRequire/);
});
4 changes: 4 additions & 0 deletions packages/sucrase/test/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ const config: RollupOptions = {
enableLegacyTypeScriptModuleInterop: true,
jsxFragmentPragma: 'React.fragment',
jsxPragma: 'React',
jsxRuntime: 'automatic',
jsxImportSource: 'preact',
preserveDynamicImport: true,
injectCreateRequireForImportRequire: true,
production: true,
disableESTransforms: true,
transforms: ['jsx']
Expand Down
4 changes: 4 additions & 0 deletions packages/sucrase/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ interface RollupSucraseOptions
extends Pick<
SucraseOptions,
| 'transforms'
| 'jsxRuntime'
| 'jsxImportSource'
| 'jsxPragma'
| 'jsxFragmentPragma'
| 'preserveDynamicImport'
| 'injectCreateRequireForImportRequire'
| 'enableLegacyTypeScriptModuleInterop'
| 'enableLegacyBabel5ModuleInterop'
| 'production'
Expand Down
Loading