diff --git a/packages/sucrase/README.md b/packages/sucrase/README.md index 680e5a7b2..b639c11c8 100644 --- a/packages/sucrase/README.md +++ b/packages/sucrase/README.md @@ -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` diff --git a/packages/sucrase/src/index.js b/packages/sucrase/src/index.js index dfeb9a765..cefe1cf4c 100644 --- a/packages/sucrase/src/index.js +++ b/packages/sucrase/src/index.js @@ -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`, @@ -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, enableLegacyTypeScriptModuleInterop: opts.enableLegacyTypeScriptModuleInterop, enableLegacyBabel5ModuleInterop: opts.enableLegacyBabel5ModuleInterop, production: opts.production, diff --git a/packages/sucrase/test/fixtures/inject-create-require/main.ts b/packages/sucrase/test/fixtures/inject-create-require/main.ts new file mode 100644 index 000000000..6dc8b4b3c --- /dev/null +++ b/packages/sucrase/test/fixtures/inject-create-require/main.ts @@ -0,0 +1,3 @@ +import foo = require('foo'); + +export default foo; diff --git a/packages/sucrase/test/fixtures/jsx-import-source/example.jsx b/packages/sucrase/test/fixtures/jsx-import-source/example.jsx new file mode 100644 index 000000000..87153ec31 --- /dev/null +++ b/packages/sucrase/test/fixtures/jsx-import-source/example.jsx @@ -0,0 +1 @@ +export default () =>
hello world
diff --git a/packages/sucrase/test/fixtures/jsx-import-source/main.js b/packages/sucrase/test/fixtures/jsx-import-source/main.js new file mode 100644 index 000000000..9d03081dd --- /dev/null +++ b/packages/sucrase/test/fixtures/jsx-import-source/main.js @@ -0,0 +1,4 @@ +/* eslint-disable import/extensions */ +import example from './example.jsx'; + +t.snapshot(example.toString()); diff --git a/packages/sucrase/test/fixtures/jsx-runtime/example.jsx b/packages/sucrase/test/fixtures/jsx-runtime/example.jsx new file mode 100644 index 000000000..87153ec31 --- /dev/null +++ b/packages/sucrase/test/fixtures/jsx-runtime/example.jsx @@ -0,0 +1 @@ +export default () =>
hello world
diff --git a/packages/sucrase/test/fixtures/jsx-runtime/main.js b/packages/sucrase/test/fixtures/jsx-runtime/main.js new file mode 100644 index 000000000..9d03081dd --- /dev/null +++ b/packages/sucrase/test/fixtures/jsx-runtime/main.js @@ -0,0 +1,4 @@ +/* eslint-disable import/extensions */ +import example from './example.jsx'; + +t.snapshot(example.toString()); diff --git a/packages/sucrase/test/fixtures/preserve-dynamic-import/main.js b/packages/sucrase/test/fixtures/preserve-dynamic-import/main.js new file mode 100644 index 000000000..edb2ee72e --- /dev/null +++ b/packages/sucrase/test/fixtures/preserve-dynamic-import/main.js @@ -0,0 +1,3 @@ +export default function loadModule(name) { + return import(name); +} diff --git a/packages/sucrase/test/snapshots/test.js.md b/packages/sucrase/test/snapshots/test.js.md index cabbf6a3d..7ca1a8db1 100644 --- a/packages/sucrase/test/snapshots/test.js.md +++ b/packages/sucrase/test/snapshots/test.js.md @@ -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 @@ -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" )' diff --git a/packages/sucrase/test/snapshots/test.js.snap b/packages/sucrase/test/snapshots/test.js.snap index 4ab40afdd..d3be49ecc 100644 Binary files a/packages/sucrase/test/snapshots/test.js.snap and b/packages/sucrase/test/snapshots/test.js.snap differ diff --git a/packages/sucrase/test/test.js b/packages/sucrase/test/test.js index 3d3e77ef0..39fa962f9 100644 --- a/packages/sucrase/test/test.js +++ b/packages/sucrase/test/test.js @@ -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); }); @@ -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: [ { @@ -80,13 +96,8 @@ 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); @@ -94,15 +105,46 @@ test('resolves typescript directory imports', async (t) => { }); 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/); +}); diff --git a/packages/sucrase/test/types.ts b/packages/sucrase/test/types.ts index e4e7f2c66..336a141b0 100644 --- a/packages/sucrase/test/types.ts +++ b/packages/sucrase/test/types.ts @@ -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'] diff --git a/packages/sucrase/types/index.d.ts b/packages/sucrase/types/index.d.ts index 3b2147b1a..8f7143cd7 100644 --- a/packages/sucrase/types/index.d.ts +++ b/packages/sucrase/types/index.d.ts @@ -6,8 +6,12 @@ interface RollupSucraseOptions extends Pick< SucraseOptions, | 'transforms' + | 'jsxRuntime' + | 'jsxImportSource' | 'jsxPragma' | 'jsxFragmentPragma' + | 'preserveDynamicImport' + | 'injectCreateRequireForImportRequire' | 'enableLegacyTypeScriptModuleInterop' | 'enableLegacyBabel5ModuleInterop' | 'production'