Last active
November 22, 2020 12:17
-
-
Save milahu/e1cd4aeedda84b77351cf84e10a97c07 to your computer and use it in GitHub Desktop.
preval for svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// moved to https://github.com/milahu/svelte-preval | |
/* | |
preval for svelte | |
code is at line 250 :P | |
performant: | |
eval static expressions at compile time | |
allow shorter load times for client | |
readable: | |
avoid "magic numbers" | |
colocate "all the code" in one place | |
license = public domain | |
warranty = none | |
save file | |
sveltePreval.js | |
in your svelte project folder | |
next to the rollup.config.js file | |
install deps | |
npm i -D estree-walker lave astring | |
bundle size | |
acorn 96 KB // dependency of svelte | |
magic-string 20 KB // dependency of svelte | |
estree-walker 1 KB // yay to Rich Harris :D | |
lave 8 KB | |
astring 14 KB | |
edit rollup.config.js | |
import sveltePreval from './sveltePreval.js' | |
export default { | |
plugins: [ | |
svelte({ | |
preprocess: [ | |
sveltePreval(), | |
], | |
}), | |
], | |
} | |
use in App.svelte | |
let testPreval = preval(() => ({ | |
a: Math.PI * 0.5, | |
b: Math.sqrt(2), | |
})) | |
// --> testPreval.a = 1.5707963267948966 | |
// unpack object properties to global scope | |
Object.assign(window, preval(() => ({ | |
a: Math.PI * 0.5, | |
b: Math.sqrt(2), | |
}))) | |
// --> a = 1.5707963267948966 | |
let testPrevalFuncs = preval(()=>([ | |
()=>'hello', | |
()=>('world'), | |
()=>{return 'foo'}, | |
function(){return 'bar'}, | |
])) | |
// --> testPrevalFuncs[0] = ()=>'hello' | |
run | |
npm run dev | |
see result in http://localhost:5000/build/bundle.js | |
let testPreval = { | |
"a": 1.5707963267948966, | |
"b": 1.4142135623730951 | |
}; | |
let testPrevalFuncs = [ | |
() => "hello", | |
() => "world", | |
() => { | |
return "foo"; | |
}, | |
function () { | |
return "bar"; | |
} | |
]; | |
config "defaultOn" | |
set to false | |
to make preval "normally off" | |
and only enable in | |
<script preval> | |
or | |
<script preval="custom_preval_name"> | |
config "funcName" | |
instead of "let x = preval(f)" | |
you can use "let x = custom_preval_name(f)" | |
local - by changing your App.svelte to | |
<script preval="local_preval_name"> | |
let x = local_preval_name(() => ('result')) | |
</script> | |
global - by changing rollup.config.js to | |
preprocess: [ | |
sveltePreval('custom_preval_name'), | |
], | |
import modules | |
yes you can use modules inside the preval code | |
but modules must be in commonJS format | |
that is, they must be compatible with node.js | |
so you can use | |
let testPreval = preval(() => { | |
const fs = require('fs') | |
return fs.readFileSync('input.txt').toString() | |
}) | |
but this will NOT work [ES6 module format] | |
let testPreval = preval(() => { | |
import { moduleFunction } from 'moduleName' | |
// error: [!] (plugin svelte) SyntaxError: 'import' and 'export' may only appear at the top level | |
return moduleFunction('foo') | |
}) | |
to use ES6 modules | |
you must transform them to commonJS format | |
npm i --save-dev @babel/core @babel/cli @babel/plugin-transform-modules-commonjs | |
./node_modules/@babel/cli/bin/babel.js --plugins @babel/plugin-transform-modules-commonjs src/moduleName.js > src/moduleName.cjs | |
and then use with | |
let testPreval = preval(() => { | |
const moduleName = require('./path/to/moduleName.cjs') | |
return moduleName.moduleFunction('foo') | |
}) | |
TODO | |
support ES6 module format | |
transform require-d modules on the fly | |
to commonJS format, compatible with require() | |
see section "import modules" | |
eval more safe? | |
https://rollupjs.org/guide/en/#avoiding-eval | |
"rename eval" seems to break stuff | |
use meriyah JS parser? | |
pro: faster than acorn by factor 3 | |
con: svelte is using acorn parser | |
--> + 100 KB bundle size | |
keep original line numbers after expansion? | |
source map? | |
allow to pass variables to preval | |
or: make preval aware of the previous script environment | |
assume other variables as static | |
let envVar = 'hello' | |
let prevalTest = preval(() => (envVar+' world')) | |
run before svelte js parser? | |
for now, svelte fails on parse errors | |
which per se is ok, | |
but "pre process" should run before svelte, no? | |
change <script type="x"> ? | |
expand this to use sweet.js macro system | |
https://jlongster.com/Stop-Writing-JavaScript-Compilers--Make-Macros-Instead | |
constexpr - evaluate expressions at compile time | |
https://gist.github.com/natefaubion/f4be4c8531ef45de87b4 | |
write an html/css preprocessor | |
to replace all color formats to hex rgb | |
including NCS and HSV format | |
hex rgb is fastest to parse in browser | |
yay micro optimization | |
client side: support color theme switching | |
most basic: light mode vs dark mode | |
libraries | |
JS code parser, AST tree generator | |
https://github.com/acornjs/acorn # 100 KB + fast | |
https://github.com/mishoo/UglifyJS2 # slow | |
compare parser speed | |
https://cherow.github.io/cherow/performance/ | |
http://turtlescript.github.cscott.net/benchmark/ | |
https://marijnhaverbeke.nl/acorn/test/bench/index.html | |
parsers even faster than acorn | |
https://github.com/cherow/cherow [unmaintained] | |
fork projects: | |
https://github.com/meriyah/meriyah # 100 KB + super fast | |
faster than acorn by factor 3 | |
https://meriyah.github.io/meriyah/performance/ | |
https://github.com/buntis/buntis | |
AST walker = read only | |
https://github.com/Rich-Harris/estree-walker # 1 KB | |
https://github.com/davidbonnet/astravel # 7 kB | |
https://github.com/acornjs/acorn/tree/master/acorn-walk # 7 kB | |
AST transformer = read + write | |
https://github.com/substack/node-falafel # 90 kB | |
https://github.com/alloc/nebu # 120 kB | |
https://github.com/benjamn/recast # 500 kB | |
code generator = AST printer | |
https://github.com/estools/escodegen # slow | |
https://github.com/davidbonnet/astring # 14 KB + super fast | |
*/ | |
// acorn = ast from javascript parser | |
// estree-walker = read-only AST tree walker | |
// lave = ast from live object serializer. eval in reverse | |
// astring = ast to javascript code generator | |
// magic-string = replace strings, keep index | |
import { parse as acorn_parse } from 'acorn' | |
import { walk as estree_walk } from 'estree-walker' | |
import lave from 'lave' | |
import { generate as astring_generate } from 'astring' | |
import magicString from 'magic-string' | |
const PREVAL_ATTR = 'preval' | |
const PREVAL_NAME = 'preval' | |
export default function sveltePreval(defaultOn = true, funcName = PREVAL_NAME) { | |
return { | |
script({ content, attributes /*, filename*/ }) | |
{ | |
// override global config per script | |
// <script preval="local_preval_name"> | |
if (PREVAL_ATTR in attributes) { | |
if (attributes[PREVAL_ATTR] !== true) { | |
funcName = attributes[PREVAL_ATTR] | |
} | |
// true means "empty attribute" <script preval> | |
// --> keep default funcName | |
} | |
else if (defaultOn === false) { | |
return { code: content } // no change | |
} | |
const ast = acorn_parse( | |
content, { | |
// ecmaVersion: 10, // default in year 2019 | |
sourceType: 'module', | |
}) | |
let code = new magicString(content) | |
estree_walk( ast, { | |
enter: function ( node, parent, prop, index ) { | |
if ( | |
node.type !== 'CallExpression' | |
|| node.callee.name !== funcName | |
) | |
{ return } // ignore this node | |
if (node.arguments.length !== 1) { | |
return console.error(`preval usage: ${funcName}(f)`) | |
} | |
const arg0Src = content.substring( | |
node.arguments[0].start, node.arguments[0].end) | |
const evalRes = eval('('+arg0Src+')()') | |
let evalAst = lave(evalRes).body[0].expression | |
let evalSrc = astring_generate( | |
evalAst, { | |
indent: '', | |
// sourceMap: sourceMapGenerator, | |
}) | |
// lave BUG: function(){} --> eval("(function(){})") | |
// workaround: remove all eval("(....)") | |
if (evalSrc.indexOf('eval("(') !== -1) { | |
evalAst = acorn_parse(evalSrc) // lave's AST sucks? | |
// BUG? lave seems to give wrong start/end numbers | |
evalSrc = new magicString(evalSrc) | |
estree_walk( evalAst, { | |
enter: function ( node, parent, prop, index ) { | |
if ( | |
node.type !== 'CallExpression' | |
|| node.callee.name !== 'eval' | |
|| node.arguments.length !== 1 | |
) | |
{ return } // ignore this node | |
// patch the evalSrc | |
// patch the source | |
evalSrc.overwrite( | |
node.start, node.end, | |
node.arguments[0].value | |
) | |
} | |
}) | |
evalSrc = evalSrc.toString() | |
} | |
// patch the source | |
code.overwrite( node.start, node.end, evalSrc ) | |
}}) | |
code = code.toString() | |
return { code } | |
}}} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
moved to https://github.com/milahu/svelte-preval