Home

Awesome

JS in JSON

A server side agnostic way to stream JavaScript, ideal for:

The Session utility is currently available for:


An "islands friendly" approach to Server Side Rendering able to produce stream-able JS on demand, via any programming language, through a single JSON bundle file instrumented to flush() any script, after optional transpilation and/or minification.

The produced output can be also pre-generated and served as static content, with the advantages that js-in-json bundles require zero network activity: forget round-trips, thousand ESM requests per library or project, and simply provide all it's needed right on the page.

// a basic serving example
const {JSinJSON} = require('js-in-json');

// see ## Options
const {options} = require('./js-in-json-options.js');

const islands = JSinJSON(options);
// islands.save(); when needed to create the JSON bundle

http.createServer((req, res) => {
  // see ## Session
  const js = islands.session();
  js.add('Main');
  res.writeHead(200, {'content-type': 'text/html'});
  res.write(`
    <!doctype html>
    <script src="//external.cdn.js"></script>
    <script>${js.flush()}</script>
    <body>
      <div class="component"></div>
      <script>${js.add('UI/component').flush()}</script>
    </body>
  `.trim());
  js.add('Footer');
  if (global.condition) {
    js.add('SpecialCondition');
    res.write(`<script>${js.flush()}</script>`);
  }
  res.end();
});

Session

A js-in-json session can be initialized right away via js-in-json/session exported Session class, or through the main JSinJSON(options).session() utility.

A session created via JSinJSON optionally accepts a JSON bundle, retrieved from options, if not provided, and it exposes 2 methods:

In order to have a session, a JSON bundle must be created.

Options

Following the object literal with all its defaults that can be passed to the JSinJSON(options) export.

const {save, session} = JSinJSON({

  // TOP LEVEL CONFIG ONLY

  // MANDATORY
  // the root folder from which each `input` is retrieved
  // used to resolve the optional output, if relative to this folder
  root: '/full/project/root/folder'

  // OPTIONAL
  // where to store the resulting JSON cache usable via JSinJSON.session(cache)
  // if omitted, the cache is still processed and returned
  output: './bundle.json',
  // the global context used to attach the `require` like function
  global: 'self',
  // the `require` like unique function name, automatically generated,
  // and it's different per each saved JSON (hint: don't specify it)
  prefix: '_uid',


  // OPTIONAL EXTRAS: CAN BE OVERWRITTEN PER EACH MODULE
  // use Babel transformer to target @babel/preset-env
  babel: true,
  // use terser to minify produced code
  minify: true,
  // transform specific bare imports into other imports, it's {} by default
  // see: rollup-plugin-includepaths
  replace: {
    // example: replace CE polyfill with an empty file
    '@ungap/custom-elements': './empty.js'
  },
  // executed each time a JSinJSON.session.flush() happens
  // no matter which module has been added to the stack
  // it's missing/no-op by default and it has no access
  // to the outer scope of this file (it's serialized as function)
  code(require) {
    // each code receives the `require` like function
    const {upgradeAll} = require('Bootstrap');
    upgradeAll();
  },
  // an object literal to define all modules flushed in the page
  // whenever any of these is needed
  modules: {
    // the module name available via the `require` like function
    Bootstrap: {
      // MANDATORY
      // the ESM entry point for this module
      input: './bootstrap.js',

      // OPTIONAL: overwrite top level options per each module
      // don't transform and/or don't minify
      babel: false,
      minify: false,
      // will be merged with the top level
      replace: {'other': './file.js'},
      // don't flush anything when injected
      code: null
    },

    // other module example
    Login: {
      input: './login.js',
      code() {
        document.documentElement.classList.add('wait');
        fetch('/login/challenge').then(b => b.json).then(result => {
          self.challenge = result;
          document.documentElement.classList.remove('wait');
        });
      }
    }
  }
});

Options Rules / Limitations