svelte-preval Svelte Themes

Svelte Preval

compile time eval for svelte components

svelte-preval

compile time eval for svelte components

performant:
eval static expressions at compile time
allow shorter load times for client

readable:
avoid "magic numbers"
colocate "all the code" in one place

powerful:
use any node package on compile time

license = public domain = CC0-1.0
warranty = none

install

npm i -D https://github.com/milahu/svelte-preval

install dependencies

TODO better?

npm i -D tosource terser sync-rpc acorn

other peerDependencies are provided by svelte

config

merge this with your rollup.config.js

import sveltePreval from 'svelte-preval';

export default {
  plugins: [
    svelte({
      preprocess: [
        sveltePreval({
          //defaultOn: true,
          //funcName: 'preval',
          //scriptAttr: 'preval',
          //baseDir: __dirname, // directory of rollup.config.js = project root
          // here you can add more values
          // the whole options object is passed to the eval-ed function
        }),
      ],
    }),
  ],
};

example

in your App.svelte add to your <script>

// preval object
let testPrevalObj = preval(() => ({
  a: Math.PI * 0.5,
  b: Math.sqrt(2),
}));

// result
let testPrevalObj = {
  a: 1.5707963267948966,
  b: 1.4142135623730951
};

config

defaultOn

set to false
to make preval "normally off"
and only enable in

<script preval>
or
<script preval="custom_preval_funcName">

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({
    funcName: 'custom_preval_name',
  }),
],

scriptAttr

change the <script> attribute
to activate preval like <script preval>
or to change the funcName like <script preval="myPreval">

default is "preval", so <script preval>

using modules

yes you can use modules inside the preval code
but modules must be in commonJS format

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();
});

to use ES6 modules
you must transpile to commonJS format

npm i -D @babel/core @babel/cli \
  @babel/plugin-transform-modules-commonjs

./node_modules/@babel/cli/bin/babel.js \
  --plugins @babel/plugin-transform-modules-commonjs \
  src/module.js > src/module.cjs

and then use with

let testPreval = preval(() => {
  const moduleName = require('/absolute/path/to/module.cjs');
  return moduleName.moduleFunction();
});

using modules with relative paths

require works relative to the directory of sveltePreval.js, so

let res = preval(() => require('./src/script.js').someProp);

will try to require node_modules/svelte-preval/src/src/script.js

you could fix that with a relative path like ../../../src/script.js
but this is not portable
for example this breaks with pnpm package manager
cos symlinks are unidirectional and ../../../ is the pnpm global store

better solution: use absolute paths

in your rollup.config.js set

        sveltePreval({
          baseDir: __dirname, // directory of rollup.config.js
        }),

and in your App.svelte write

let res = preval(({baseDir}) => require(baseDir+'/src/script.js').someProp);

more examples

unpack array

let [a, b] = preval(() => ([
  Math.PI * 0.5,
  Math.sqrt(2),
]));

// result
let [a, b] = [1.5707963267948966, 1.4142135623730951];

unpack properties to global scope

Object.assign(window, preval(() => ({
  a: Math.PI * 0.5,
  b: Math.sqrt(2),
})));

// result
Object.assign(window, {
  a: 1.5707963267948966,
  b: 1.4142135623730951
});

preval functions

let testPrevalFuncs = preval(()=>([
  ()=>'hello',
  ()=>('world'),
  ()=>{return 'foo'},
  function(){return 'bar'},
]));

// result
let testPrevalFuncs = [
  () => "hello",
  () => "world",
  () => "foo",
  function () {
    return "bar";
  }
];

get file list

const ace_assets = preval(() => {

  const glob = require('glob');

  function bn(val){
    return val.match(/\/?([^/]+)\.js/)[1];
  }

  return {
    modes: glob.sync('node_modules/brace/mode/*.js').map(bn),
    themes: glob.sync('node_modules/brace/theme/*.js').map(bn),
  };

});

// result:
const ace_assets = {
  modes: [
    "abap",
    "abc",
    // ....
  ],
  themes: [
    "ambiance",
    "chaos",
    // ....
  ]
};

inline asset files

// inline asset files to javascript on compile time
// move to end of script for faster page load

// set options.baseDir in rollup.config.js:
// sveltePreval({baseDir: __dirname})
// so file paths are relative to project root

// install dependency:
// npm i -D mime-types

let fotoData = preval(function(options) {

  const imgBase = '/src/images';
  let fotoData = [
    {uri: '/homer.jpg', name: 'Homer'},
    {uri: '/lisa.webp', name: 'Lisa'},
    {uri: 'data:image/jpeg,', name: 'empty foto'},
  ];

  const fs = require('fs');
  const mime = require('mime-types');

  return fotoData.map(({uri, name}) => {

    // options.baseDir is defined in rollup.config.js
    const fileAbs = options.baseDir + imgBase + uri;

    if (!fs.existsSync(fileAbs)) {
      console.log('error in preval inline: no such file: '+fileAbs);
      return {uri, name}; // no change
    }

    const fileType = mime.lookup(fileAbs)
      || 'application/octet-stream';
    const base64data = fs.readFileSync(
      fileAbs, {encoding: 'base64'}
    );
    const dataUri = 'data:'+fileType+';base64,'+base64data;

    return {uri: dataUri, name};
  });

});

// result:
let fotoData = [
  {uri: 'data:image/jpeg,....', name: 'Homer'},
  {uri: 'data:image/webp,....', name: 'Lisa'},
  {uri: 'data:image/jpeg,', name: 'empty foto'},
];

inline source code

// set options.baseDir in rollup.config.js:
// sveltePreval({baseDir: __dirname})

window.sourceCode = preval(
  ({baseDir}) => {
    const sourceFileĹist = [
      'src/App.svelte',
      'src/main.js',
      'public/index.html',
      'rollup.config.js',
      'package.json',
      'README.md',
      'LICENSE',
    ];
    const fs = require('fs');
    return sourceFileĹist.reduce(
      (acc, sourceFile) => {
        acc[sourceFile] = fs.readFileSync(
          baseDir+'/'+sourceFile, {
          encoding: 'utf-8',
        });
        return acc;
      },
      {}
    );
  }
);

these outputs are prettified by svelte
the script output is minified to preserve line numbers
cos the svelte preprocessor accepts no sourcemaps

notes

wait for svelte PR #5015 add source map support for preprocessors
and use sourcemap instead of minify
to keep line numbers

support ES6 module format
transform require-d modules on the fly
to commonJS format, compatible with require()
see "import modules"

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 the sweet.js macro system
see: sweetjs macro constexpr - evaluate expressions at compile time

similar projects

vite-plugin-compile-time

https://github.com/egoist/vite-plugin-compile-time

the magic function is import.meta.compileTime which can produce data and code

// get-data.ts
export default async () => {
  return {
    data: "hello",
    code: "const name = 'world';",
  }
}
// index.ts
// get the data at compile time
const data = import.meta.compileTime("./get-data.ts")
console.log(data == "hello")
console.log(name == "world")

babel-plugin-compile-time-expressions

https://github.com/wereHamster/babel-plugin-compile-time-expressions

using functions for code, like svelte-preval. very flexible

// input
const x = compileTimeExpression(({t}) =>
  t.stringLiteral("Hello" + "World"))

// output
const x = "HelloWorld"

babel-plugin-preval

https://github.com/kentcdodds/babel-plugin-preval

using template-strings for code. less flexible

// input
const x = preval`module.exports = 1`

// output
const x = 1

prepack

https://github.com/facebookarchive/prepack - 15K stars - A JavaScript bundle optimizer

focus on react

Prepack is a partial evaluator for JavaScript. Prepack rewrites a JavaScript bundle, resulting in JavaScript code that executes more efficiently. For initialization-heavy code, Prepack works best in an environment where JavaScript parsing is effectively cached.

Top categories

Loading Svelte Themes