Description
Using the spread syntax to convert a non-array iterable (like a Map
or Set
) to an array doesn't work within a <script lang="ts">
block within a Vue file.
Depending on the TypeScript version, it will either produce the "RangeError: Invalid array length" error in TypeScript <= 4.1 (which is the default for @vue/cli projects) or an empty array when using a more recent version.
Reproduction
I created an empty project with the @vue/cli, and enabled Vue 3, TypeScript and Jest support.
/src/example.vue:
<script lang="ts">
import { defineComponent, h } from 'vue';
export default defineComponent({
setup: () => ({
values: [...new Set([1, 2, 3])],
}),
render: () => h('div'),
})
</script>
/test/unit/example.spec.ts:
import { mount } from '@vue/test-utils';
import Example from '../../src/example.vue';
test('Using a spread iterable from a Vue file', () => {
const { vm } = mount(Example);
expect(vm.values).toEqual([1, 2, 3]);
});
Running the test fails, either on a runtime error with an older TypeScript version, or on the assertion on newer TypeScript versions.
Full reproduction in reproduction.zip
The problem is caused by the TypeScript compiler running in ES5 mode for <script lang="ts"> blocks. This produces TypeScript code that is unaware of iterables and causes the seem behavior.
The same behavior can be reproduced in the TypeScript playground by targeting ES5.
Possible workaround
Next to targeting ES5 mode in lib/utils.js, also enable the downlevelIteration property, so iterables are properly spreadable:
diff --git a/packages/vue3-jest/lib/utils.js b/packages/vue3-jest/lib/utils.js
index 8ac12ee..f16b7b4 100644
--- a/packages/vue3-jest/lib/utils.js
+++ b/packages/vue3-jest/lib/utils.js
@@ -83,7 +83,7 @@ const getTsJestConfig = function getTsJestConfig(config) {
const tsConfig = configSet.typescript || configSet.parsedTsConfig
// Force es5 to prevent const vue_1 = require('vue') from conflicting
return {
- compilerOptions: { ...tsConfig.options, target: 'es5', module: 'commonjs' }
+ compilerOptions: { ...tsConfig.options, target: 'es5', module: 'commonjs', downlevelIteration: true }
}
}
However, that still leaves the underlying issue that <script lang="ts">
blocks are compiled to ES5, which produces huge amounts of code for constructs like async / await, the above-mentioned spreads, etc. which shouldn't be needed when targeting modern node versions.