Describe the bug
The content of the the svelte file is a large HTML chunk, withouy any script logics or styling being involved. Max stack size exceeded error while compiling.
To Reproduce
https://svelte.dev/repl/a9dfcc17551c4aeb95e8fe748a97061d?version=3.20.1
Expected behavior
Compiling should not break
Information about your Svelte project:
Svelte 3.20.1, Rollup, Windows
Severity
Its probably an uncommon case for most people. I ran into it, when using a generated data privacy HTML template. I would not see this as a priority. Workaround for me: copy the HTML into a JS template literal variable and inject it via @html: https://svelte.dev/repl/1ab32c5a3c2c426e973512cfc8da023c?version=3.20.1
the compiling process involves push spreading all nodes into another array at some point and the v8 engine does not support spreading that many items
works fine on firefox
Interesting. I've run into this before in V8 and ended up using an array spread and a reassignment, rather than an argument spread in .push(). https://github.com/Conduitry/do-not-zip/commit/861a929f747f6722dc949d52f146d47c5b7caa87 It's not very helpful that V8 gives a stack size error rather than an argument count error.
I'm not sure how many places we're doing .push(...foo) in the codebase, but this might be a way forward.
Same issue when using Tailwind UI.
In a regular svelte + tailwindcss project if I add in the tailwind.config.js the tailwind UI package plugins: [ require('@tailwindcss/ui'),] then the building breaks with the same issue.
Could we update the issue name as well to include tailwindUI word? @siebeneicher
same here
im building a large single page app / single file app, with many many html nodes
error is
[!] (plugin svelte) RangeError: Maximum call stack size exceeded
src/Main.svelte
RangeError: Maximum call stack size exceeded
at /tmp/x/node_modules/svelte/node_modules/code-red/dist/code-red.mjs:581:10
at /tmp/x/node_modules/svelte/node_modules/code-red/dist/code-red.mjs:226:10
at handle (/tmp/x/node_modules/svelte/node_modules/code-red/dist/code-red.mjs:70:17)
at /tmp/x/node_modules/svelte/node_modules/code-red/dist/code-red.mjs:248:18
at Array.map (<anonymous>)
at handle_body (/tmp/x/node_modules/svelte/node_modules/code-red/dist/code-red.mjs:247:21)
at Program (/tmp/x/node_modules/svelte/node_modules/code-red/dist/code-red.mjs:318:10)
at handle (/tmp/x/node_modules/svelte/node_modules/code-red/dist/code-red.mjs:70:17)
at print (/tmp/x/node_modules/svelte/node_modules/code-red/dist/code-red.mjs:1413:17)
at Component.generate (/tmp/x/node_modules/svelte/src/compiler/compile/Component.ts:316:9)
with npm install code-red
node_modules/code-red/dist/code-red.mjs line 581
chunks.push(
c(') '),
...handle(node.body, state)
);
.... as expected, using push instead of reassign
compiles to node_modules/svelte/compiler.js line 5911
const chunks = [c('for (')];
//
chunks.push(
c(') '),
...handle(node.body, state)
);
todo: push ----> reassign
like
let chunks = [c('for (')];
//
chunks = [
...chunks,
c(') '),
...handle(node.body, state)
];
my svelte compiler is fixed
todo: push ----> reassign
here is a quick-and-dirty patch script
/*
src/patch-svelte-compiler.js
replace push with reassign
cp src/patch-svelte-compiler.js node_modules/svelte/
cd node_modules/svelte
node patch-svelte-compiler.js
*/
const base_file = "compiler.js";
const input_file = base_file + ".orig";
const output_file = base_file + ".new";
// replaceMethod
// origin: a.push(...a1, ...a2, e, ...a3); // error: Max Stack Size Exceeded
// spread: a = [...a1, ...a2, e, ...a3];
// concat: a = a1.concat(a2, [e], a3);
// performance is equal on nodejs
const replaceMethod = "spread";
//const replaceMethod = "concat";
const acorn_parse = require("acorn").parse;
const estree_walk = require("estree-walker").walk;
const node_tosource = require("tosource");
const magicString = require("magic-string");
const fs = require("fs");
if (fs.existsSync(input_file) || fs.existsSync(output_file)) {
console.log('error: input or output file exists. run this script only once');
process.exit(1);
}
console.log(`move file: ${base_file} --> ${input_file}`)
fs.renameSync(base_file, input_file);
// input
const content = fs.readFileSync(input_file, 'utf8');
// output
let code = new magicString(content);
const ast = acorn_parse(
content, {
// ecmaVersion: 10, // default in year 2019
sourceType: 'module',
});
const funcName = "push";
let arrayNameList = [];
estree_walk( ast, {
enter: function ( node, parent, prop, index ) {
// node must be array.push()
if (
node.type !== 'CallExpression' ||
node.callee === undefined ||
node.callee.property === undefined ||
node.callee.property.name !== funcName
) { return; }
// argument list must include spread operators
if (node.arguments.find(
a => (a.type == 'SpreadElement')) === undefined)
{ return; }
const nodeSrc = content.substring(node.start, node.end);
const pushObj = node.callee.object;
const arrayName = content.substring(pushObj.start, pushObj.end);
const pushProp = node.callee.property;
arrayNameList.push(arrayName);
// patch .push(
if (replaceMethod == "spread") {
// push --> assign array
// find "(" bracket after .push
const pushPropLen = content.substring(pushProp.start, node.end).indexOf("(");
code.overwrite(
(pushProp.start - 1),
(pushProp.start + pushPropLen + 1),
" /* PATCHED */ = [..."+arrayName+", "
);
// patch closing bracket
const closeIdx = node.start + nodeSrc.lastIndexOf(")");
code.overwrite(closeIdx, (closeIdx + 1), "]");
}
if (replaceMethod == "concat") {
// push --> assign concat
// ".push" --> " = array.concat"
code.overwrite(
(pushProp.start - 1),
pushProp.end,
" /* PATCHED */ = "+arrayName+".concat");
// patch arguments of .concat()
node.arguments.forEach(a => {
if (a.type == 'SpreadElement') {
// unspread: ...array --> array
const spreadArgSrc = content.substring(a.argument.start, a.argument.end);
//console.log('spread argument: '+spreadArgSrc);
code.overwrite(a.start, a.end, spreadArgSrc);
} else {
// enlist: element --> [element]
const argSrc = content.substring(a.start, a.end);
//console.log('non spread argument: '+argSrc);
code.overwrite(a.start, a.end, "["+argSrc+"]");
}
});
}
}});
code = code.toString();
function filterUnique(value, index, array) {
return array.indexOf(value) === index;
}
// replace const with let
arrayNameList.filter(filterUnique).forEach(arrayName => {
console.log(`arrayName = ${arrayName}`)
code = code.replace(
new RegExp("const "+arrayName+" = ", 'g'), // global = replace all
"/* PATCHED const "+arrayName+" */ let "+arrayName+" = "
);
})
fs.writeFileSync(output_file, code);
console.log(`move file: ${output_file} --> ${base_file}`)
fs.renameSync(output_file, base_file);
edit: included patch by rhythm-section
edit: move input/output files in javascript
edit: added replaceMethod = "concat", but performance is equal
@milahu Thank you for the compiler patch. It works great. With version 3.23.0 of svelte I still had one issue with the patch that resulted in an error because of line 6442 inside comiler.js:
const chunks = node.kind !== 'init'
? [c(`${node.kind} `)]
: [];
Because of the tertiary operator the const declaration does not get replaced with let which leads to an assignment error.
Not sure if it is a good solution or if it leads to other issues with other svelte versions, but the following worked for me. I changed the regex from:
new RegExp("const "+arrayName+" = \\[", 'g'), // global = replace all
"/* PATCHED const "+arrayName+" */ let "+arrayName+" = ["
to:
new RegExp("const "+arrayName+" = ", 'g'), // global = replace all
"let "+arrayName+" = "
Hope this helps someone still having troubles.
Is there any CI-able solution?
you mean patching the typescript source files?
here is a start:
https://github.com/milahu/random/blob/master/svelte/patch-svelte-compiler-sources.js
todo:
svelte/compiler.js via sourcemapscosmetic:
node.parent to find declaration in local scopethe problem with concat is: concat is slower than push
= overhead of immutable data
this should be fastest:
Array.prototype._concat_inplace = function(other) { // aka _push_array
for (let i = 0; i < other.length; i++) {
this.push(other[i]);
}
return this; // chainable
};
array1._concat_inplace(array2)._concat_inplace(array3);
its even faster than array1.push(...array2)
and it is not limited by max call stack size (node ~100K, firefox ~500K)
This issue is affecting working with .svg aswell. I have a bunch of huge SVGs that I want to animate with Svelte (in Sapper). Unfortunately I run into
✗ client
Maximum call stack size exceeded
My workaround is to downsize the svg, by reducing the complexity of paths used in it.
Most helpful comment
@milahu Thank you for the compiler patch. It works great. With version
3.23.0of svelte I still had one issue with the patch that resulted in an error because of line 6442 inside comiler.js:Because of the tertiary operator the
constdeclaration does not get replaced withletwhich leads to an assignment error.Not sure if it is a good solution or if it leads to other issues with other svelte versions, but the following worked for me. I changed the regex from:
to:
Hope this helps someone still having troubles.