├── .npmrc
├── cjs
├── package.json
├── rollup.js
├── graph.js
├── index.js
├── utils.js
├── handlers.js
└── bundler.js
├── test
├── package.json
├── project
│ ├── exports.js
│ ├── test.js
│ ├── imports.js
│ ├── module.js
│ └── package.json
├── index.js
└── test.json
├── .gitignore
├── logo
├── JSinJSON.png
├── JSinJSON.24.png
└── JSinJSON.svg
├── .npmignore
├── examples
└── builtin-elements
│ ├── package.json
│ ├── js
│ └── main.js
│ ├── README.md
│ ├── ff.html
│ ├── js-in-json.js
│ ├── server.js
│ └── index.html
├── LICENSE
├── package.json
├── esm
├── rollup.js
├── graph.js
├── index.js
├── utils.js
├── handlers.js
└── bundler.js
└── README.md
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/cjs/package.json:
--------------------------------------------------------------------------------
1 | {"type":"commonjs"}
--------------------------------------------------------------------------------
/test/package.json:
--------------------------------------------------------------------------------
1 | {"type":"commonjs"}
--------------------------------------------------------------------------------
/test/project/exports.js:
--------------------------------------------------------------------------------
1 | export * from 'uarray';
2 |
--------------------------------------------------------------------------------
/test/project/test.js:
--------------------------------------------------------------------------------
1 | export const anotherOne = 'another one';
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .nyc_output
3 | coverage/
4 | node_modules/
5 | js-in.json
--------------------------------------------------------------------------------
/logo/JSinJSON.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WebReflection/js-in-json/HEAD/logo/JSinJSON.png
--------------------------------------------------------------------------------
/logo/JSinJSON.24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WebReflection/js-in-json/HEAD/logo/JSinJSON.24.png
--------------------------------------------------------------------------------
/test/project/imports.js:
--------------------------------------------------------------------------------
1 | import * as test from 'uarray';
2 | import {ever} from './exports.js';
3 |
4 | export {test, ever};
5 |
--------------------------------------------------------------------------------
/test/project/module.js:
--------------------------------------------------------------------------------
1 | // export {render, html} from 'uhtml';
2 |
3 | const what = 1, ever = 2;
4 |
5 | export {what, ever};
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .nyc_output
3 | .eslintrc.json
4 | .travis.yml
5 | coverage/
6 | examples/
7 | node_modules/
8 | rollup/
9 | test/
10 |
--------------------------------------------------------------------------------
/test/project/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "uarray": "^1.0.0",
4 | "uhtml": "^2.7.2",
5 | "umap": "^1.0.2",
6 | "umeta": "^0.2.4"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/builtin-elements/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "module",
3 | "dependencies": {
4 | "builtin-elements": "^0.2.1",
5 | "uhtml-ssr": "^0.6.1",
6 | "umeta": "^0.2.4"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/builtin-elements/js/main.js:
--------------------------------------------------------------------------------
1 | import {HTML} from 'builtin-elements';
2 |
3 | export class Main extends HTML.Element {
4 | upgradedCallback() {
5 | this.textContent = 'Main Content';
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/examples/builtin-elements/README.md:
--------------------------------------------------------------------------------
1 | # Builtin Elements
2 |
3 | [Live result](https://webreflection.github.io/js-in-json/examples/builtin-elements/)
4 |
5 | ```sh
6 | cd examples/builtin-elements
7 | npm i
8 | PORT=8080 node server.js
9 | ```
10 |
--------------------------------------------------------------------------------
/examples/builtin-elements/ff.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Firefox Epic Fail
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2021, Andrea Giammarchi, @WebReflection
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 | PERFORMANCE OF THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/examples/builtin-elements/js-in-json.js:
--------------------------------------------------------------------------------
1 | import {existsSync} from 'fs';
2 | import {join} from 'path';
3 |
4 | import info from 'umeta';
5 |
6 | import {JSinJSON} from '../../esm/index.js';
7 |
8 | const {dirName: root} = info(import.meta);
9 | const output = join(root, 'js-in.json');
10 |
11 | const {save, session} = JSinJSON({
12 | root,
13 | output,
14 | babel: false,
15 | modules: {
16 | '@main': {
17 | input: './js/main.js',
18 | code(require) {
19 | const {upgrade} = require('builtin-elements');
20 | const {Main} = require('@main');
21 | document.querySelectorAll('main').forEach(node => {
22 | upgrade(node, Main);
23 | });
24 | }
25 | }
26 | }
27 | });
28 |
29 | export {session};
30 |
31 | export const ready = existsSync(output) ? Promise.resolve() : save();
32 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | const {join} = require('path');
2 |
3 | const {JSinJSON} = require('../cjs');
4 |
5 |
6 | const {save, session} = JSinJSON({
7 | root: join(__dirname, 'project'),
8 | output: join(__dirname, 'test.json'),
9 | // babel: false,
10 | // minify: false,
11 | modules: {
12 | imports: {
13 | input: './imports.js',
14 | code: 'function($){console.log($.length)}'
15 | },
16 | exports: {
17 | input: './exports.js',
18 | code() {
19 | console.log(456);
20 | }
21 | }
22 | }
23 | });
24 |
25 | save().then(() => {
26 | const ssr = session();
27 | console.log(ssr.add('imports').flush());
28 | console.log('');
29 | console.log(ssr.add('imports').flush());
30 | console.log('');
31 | console.log(ssr.add('exports').flush());
32 | console.log('');
33 | console.log(ssr.add('exports').flush());
34 | });
35 |
--------------------------------------------------------------------------------
/test/test.json:
--------------------------------------------------------------------------------
1 | {
2 | "_": {
3 | "module": "self._mj0=function _($){return _[$]};",
4 | "code": "",
5 | "dependencies": []
6 | },
7 | "uarray": {
8 | "module": "_mj0.uarray=function(r){\"use strict\";var i={},e=Array.isArray,s=[],a=s.indexOf,n=s.slice,c=i.isArray=e,f=i.indexOf=a,t=i.slice=n;return r.default=i,r.indexOf=f,r.isArray=c,r.slice=t,r}({});",
9 | "etag": "\"bd-cT9a1mqHEKdMPsLpJmy/3ObWePA\"",
10 | "code": "",
11 | "dependencies": []
12 | },
13 | "exports": {
14 | "module": "_mj0.exports=function(r){\"use strict\";var a=_mj0.uarray;return r.default=a,r}({});",
15 | "etag": "\"52-Z35wJL73TI9d+YEGviJo3HLSU6U\"",
16 | "code": "_mj0,console.log(456);",
17 | "dependencies": [
18 | "uarray"
19 | ]
20 | },
21 | "imports": {
22 | "module": "_mj0.imports=function(r){\"use strict\";var e=_mj0.uarray,t=_mj0.exports.ever;return r.ever=t,r.test=e,r}({});",
23 | "etag": "\"6c-671l0DbkOBEPX+nieYM9WlUhPww\"",
24 | "code": "!function($){console.log($.length)}(_mj0);",
25 | "dependencies": [
26 | "uarray",
27 | "exports"
28 | ]
29 | }
30 | }
--------------------------------------------------------------------------------
/examples/builtin-elements/server.js:
--------------------------------------------------------------------------------
1 | import http from 'http';
2 | import {env} from 'process';
3 |
4 | import {render, html, Hole} from 'uhtml-ssr';
5 | function JS(name) { return new Hole(this.add(name).flush()); }
6 |
7 | import {ready, session} from './js-in-json.js';
8 |
9 | const handler = (req, res) => {
10 | if (req.url === '/favicon.ico') {
11 | res.writeHead(404)
12 | res.write('Not Found');
13 | }
14 | else {
15 | const js = JS.bind(session());
16 | res.writeHead(200, {'content-type': 'text/html;charset=utf-8'});
17 | render(res, html`
18 |
19 |
20 |
21 |
22 | JS in JSON
23 |
24 |
25 |
26 |
27 |
28 | `);
29 | }
30 | res.end();
31 | };
32 |
33 | ready.then(() => {
34 | const server = http.createServer(handler).listen(
35 | env.PORT || 0,
36 | () => {
37 | const {port} = server.address();
38 | console.log(`http://localhost:${port}/`);
39 | }
40 | );
41 | });
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-in-json",
3 | "version": "0.2.2",
4 | "description": "A server side agnostic way to stream JavaScript.",
5 | "main": "./cjs/index.js",
6 | "scripts": {
7 | "build": "npm run cjs && npm run test",
8 | "cjs": "ascjs --no-default esm cjs",
9 | "coveralls": "c8 report --reporter=text-lcov | coveralls",
10 | "test": "c8 node test/index.js"
11 | },
12 | "keywords": [
13 | "stream",
14 | "JS",
15 | "SSR",
16 | "JSON",
17 | "islands"
18 | ],
19 | "author": "Andrea Giammarchi",
20 | "license": "ISC",
21 | "devDependencies": {
22 | "ascjs": "^5.0.1",
23 | "c8": "^7.12.0",
24 | "coveralls": "^3.1.1"
25 | },
26 | "module": "./esm/index.js",
27 | "type": "module",
28 | "exports": {
29 | ".": {
30 | "import": "./esm/index.js",
31 | "default": "./cjs/index.js"
32 | },
33 | "./package.json": "./package.json",
34 | "./session": {
35 | "import": "./esm/session.js",
36 | "default": "./cjs/session.js"
37 | }
38 | },
39 | "dependencies": {
40 | "@babel/core": "^7.20.12",
41 | "@babel/parser": "^7.20.15",
42 | "@babel/plugin-transform-runtime": "^7.19.6",
43 | "@babel/preset-env": "^7.20.2",
44 | "@rollup/plugin-babel": "^6.0.3",
45 | "@rollup/plugin-commonjs": "^24.0.1",
46 | "@rollup/plugin-node-resolve": "^15.0.1",
47 | "@rollup/plugin-terser": "^0.4.0",
48 | "etag": "^1.8.1",
49 | "js-in-json-session": "^0.1.6",
50 | "rollup": "^3.14.0",
51 | "rollup-plugin-includepaths": "^0.2.4",
52 | "terser": "^5.16.3"
53 | },
54 | "repository": {
55 | "type": "git",
56 | "url": "git+https://github.com/WebReflection/js-in-json.git"
57 | },
58 | "bugs": {
59 | "url": "https://github.com/WebReflection/js-in-json/issues"
60 | },
61 | "homepage": "https://github.com/WebReflection/js-in-json#readme"
62 | }
63 |
--------------------------------------------------------------------------------
/esm/rollup.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * ISC License
3 | *
4 | * Copyright (c) 2021, Andrea Giammarchi, @WebReflection
5 | *
6 | * Permission to use, copy, modify, and/or distribute this software for any
7 | * purpose with or without fee is hereby granted, provided that the above
8 | * copyright notice and this permission notice appear in all copies.
9 | *
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 | */
18 |
19 | import {rollup} from 'rollup';
20 | import includePaths from 'rollup-plugin-includepaths';
21 | import terser from '@rollup/plugin-terser';
22 | import {babel} from '@rollup/plugin-babel';
23 | import {nodeResolve} from '@rollup/plugin-node-resolve';
24 | import commonjs from '@rollup/plugin-commonjs';
25 |
26 | import {babelOptions, getGlobal} from './utils.js';
27 |
28 | export const iife = async (input, name, esModule, {
29 | babel: transpile,
30 | minify,
31 | global,
32 | namespace,
33 | replace,
34 | require
35 | }) => {
36 | const bundle = await rollup({
37 | input,
38 | plugins: [].concat(
39 | replace ? [includePaths({include: replace})] : [],
40 | [nodeResolve()],
41 | [commonjs()],
42 | transpile ? [babel({...babelOptions, babelHelpers: 'runtime'})] : [],
43 | minify ? [terser()] : []
44 | )
45 | });
46 | const ignore = namespace === require ? global : require;
47 | const {output} = await bundle.generate({
48 | esModule: false,
49 | name: '__',
50 | format: 'iife',
51 | exports: 'named',
52 | globals: esModule ? {} : {[ignore]: ignore}
53 | });
54 | const module = output.map(({code}) => code).join('\n').trim();
55 | return module.replace(
56 | /^(?:var|const|let)\s+__\s*=/,
57 | `${getGlobal(require, name)}=`
58 | );
59 | };
60 |
--------------------------------------------------------------------------------
/cjs/rollup.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /*!
3 | * ISC License
4 | *
5 | * Copyright (c) 2021, Andrea Giammarchi, @WebReflection
6 | *
7 | * Permission to use, copy, modify, and/or distribute this software for any
8 | * purpose with or without fee is hereby granted, provided that the above
9 | * copyright notice and this permission notice appear in all copies.
10 | *
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
14 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
16 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
17 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 | */
19 |
20 | const {rollup} = require('rollup');
21 | const includePaths = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('rollup-plugin-includepaths'));
22 | const terser = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('@rollup/plugin-terser'));
23 | const {babel} = require('@rollup/plugin-babel');
24 | const {nodeResolve} = require('@rollup/plugin-node-resolve');
25 | const commonjs = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('@rollup/plugin-commonjs'));
26 |
27 | const {babelOptions, getGlobal} = require('./utils.js');
28 |
29 | const iife = async (input, name, esModule, {
30 | babel: transpile,
31 | minify,
32 | global,
33 | namespace,
34 | replace,
35 | require
36 | }) => {
37 | const bundle = await rollup({
38 | input,
39 | plugins: [].concat(
40 | replace ? [includePaths({include: replace})] : [],
41 | [nodeResolve()],
42 | [commonjs()],
43 | transpile ? [babel({...babelOptions, babelHelpers: 'runtime'})] : [],
44 | minify ? [terser()] : []
45 | )
46 | });
47 | const ignore = namespace === require ? global : require;
48 | const {output} = await bundle.generate({
49 | esModule: false,
50 | name: '__',
51 | format: 'iife',
52 | exports: 'named',
53 | globals: esModule ? {} : {[ignore]: ignore}
54 | });
55 | const module = output.map(({code}) => code).join('\n').trim();
56 | return module.replace(
57 | /^(?:var|const|let)\s+__\s*=/,
58 | `${getGlobal(require, name)}=`
59 | );
60 | };
61 | exports.iife = iife;
62 |
--------------------------------------------------------------------------------
/esm/graph.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * ISC License
3 | *
4 | * Copyright (c) 2021, Andrea Giammarchi, @WebReflection
5 | *
6 | * Permission to use, copy, modify, and/or distribute this software for any
7 | * purpose with or without fee is hereby granted, provided that the above
8 | * copyright notice and this permission notice appear in all copies.
9 | *
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 | */
18 |
19 | import {readFile} from 'fs/promises';
20 | import {dirname, resolve} from 'path';
21 |
22 | import {
23 | getBody, getInclude,
24 | hasOwnProperty, isJS, isLocal, slice
25 | } from './utils.js';
26 |
27 | const {keys} = Object;
28 |
29 | const parse = async (file, entry, map, include, index, key) => {
30 | if (!map.has(file))
31 | map.set(file, {
32 | name: '_' + (index.i++).toString(36),
33 | code: (await readFile(file)).toString(),
34 | count: 0,
35 | key
36 | });
37 |
38 | const info = map.get(file);
39 |
40 | if (entry)
41 | info.name = entry;
42 |
43 | if (info.count++)
44 | return;
45 |
46 | // TODO: the AST for crawled files could be stored too
47 | for (const {type, source} of getBody(info.code)) {
48 | switch (type) {
49 | case 'ExportNamedDeclaration':
50 | if (!source)
51 | break;
52 | case 'ExportAllDeclaration':
53 | case 'ImportDeclaration':
54 | let name = slice(info.code, source).slice(1, -1);
55 | if (hasOwnProperty.call(include, name))
56 | name = include[name];
57 | if (isJS(name) && isLocal(name))
58 | await parse(
59 | resolve(dirname(file), name), '', map, include, index, key
60 | );
61 | break;
62 | }
63 | }
64 | };
65 |
66 | export const crawl = async (options) => {
67 | const map = new Map;
68 | const index = {i: 0};
69 | const {modules, root} = options;
70 | for (const entry of keys(modules)) {
71 | const {input, replace} = modules[entry];
72 | const include = getInclude(root, replace || options.replace || {});
73 | await parse(resolve(root, input), entry, map, include, index, entry);
74 | }
75 | return new Map([...map.entries()].sort(
76 | ([a, {count: A}], [z, {count: Z}]) => (Z - A)
77 | ));
78 | };
79 |
--------------------------------------------------------------------------------
/cjs/graph.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /*!
3 | * ISC License
4 | *
5 | * Copyright (c) 2021, Andrea Giammarchi, @WebReflection
6 | *
7 | * Permission to use, copy, modify, and/or distribute this software for any
8 | * purpose with or without fee is hereby granted, provided that the above
9 | * copyright notice and this permission notice appear in all copies.
10 | *
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
14 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
16 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
17 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 | */
19 |
20 | const {readFile} = require('fs/promises');
21 | const {dirname, resolve} = require('path');
22 |
23 | const {
24 | getBody, getInclude, hasOwnProperty, isJS, isLocal, slice
25 | } = require('./utils.js');
26 |
27 | const {keys} = Object;
28 |
29 | const parse = async (file, entry, map, include, index, key) => {
30 | if (!map.has(file))
31 | map.set(file, {
32 | name: '_' + (index.i++).toString(36),
33 | code: (await readFile(file)).toString(),
34 | count: 0,
35 | key
36 | });
37 |
38 | const info = map.get(file);
39 |
40 | if (entry)
41 | info.name = entry;
42 |
43 | if (info.count++)
44 | return;
45 |
46 | // TODO: the AST for crawled files could be stored too
47 | for (const {type, source} of getBody(info.code)) {
48 | switch (type) {
49 | case 'ExportNamedDeclaration':
50 | if (!source)
51 | break;
52 | case 'ExportAllDeclaration':
53 | case 'ImportDeclaration':
54 | let name = slice(info.code, source).slice(1, -1);
55 | if (hasOwnProperty.call(include, name))
56 | name = include[name];
57 | if (isJS(name) && isLocal(name))
58 | await parse(
59 | resolve(dirname(file), name), '', map, include, index, key
60 | );
61 | break;
62 | }
63 | }
64 | };
65 |
66 | const crawl = async (options) => {
67 | const map = new Map;
68 | const index = {i: 0};
69 | const {modules, root} = options;
70 | for (const entry of keys(modules)) {
71 | const {input, replace} = modules[entry];
72 | const include = getInclude(root, replace || options.replace || {});
73 | await parse(resolve(root, input), entry, map, include, index, entry);
74 | }
75 | return new Map([...map.entries()].sort(
76 | ([a, {count: A}], [z, {count: Z}]) => (Z - A)
77 | ));
78 | };
79 | exports.crawl = crawl;
80 |
--------------------------------------------------------------------------------
/examples/builtin-elements/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JS in JSON
6 |
11 |
12 |
13 |
15 |
--------------------------------------------------------------------------------
/logo/JSinJSON.svg:
--------------------------------------------------------------------------------
1 |
2 |
57 |
--------------------------------------------------------------------------------
/esm/index.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * ISC License
3 | *
4 | * Copyright (c) 2021, Andrea Giammarchi, @WebReflection
5 | *
6 | * Permission to use, copy, modify, and/or distribute this software for any
7 | * purpose with or without fee is hereby granted, provided that the above
8 | * copyright notice and this permission notice appear in all copies.
9 | *
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 | */
18 |
19 | import {createRequire} from 'module';
20 | import {join, resolve} from 'path';
21 | import {writeFile} from 'fs/promises';
22 |
23 | import Session from 'js-in-json-session';
24 |
25 | import {parse} from './bundler.js';
26 | import {crawl} from './graph.js';
27 | import {getGlobal, getInclude, isSimple, keys, stringify} from './utils.js';
28 |
29 | const defaults = {
30 | babel: true,
31 | minify: true,
32 | global: 'self',
33 | prefix: `_${Date.now().toString(36).slice(-2)}`,
34 | code: null,
35 | replace: null
36 | };
37 |
38 | const createModuleEntry = (module, options) => ({
39 | input: resolve(options.root, module.input),
40 | babel: !!getEntryValue('babel', module, options),
41 | minify: !!getEntryValue('minify', module, options),
42 | code: getEntryValue('code', module, options),
43 | replace: getEntryValue('replace', module, options),
44 | });
45 |
46 | const getEntryValue = (name, module, options) => {
47 | let value = module[name];
48 | if (value == null) {
49 | value = options[name];
50 | if (value == null)
51 | value = defaults[name];
52 | }
53 | return value;
54 | };
55 |
56 | let instances = 0;
57 | export const JSinJSON = options => {
58 | const {output, root} = options;
59 | const id = (instances++).toString(36);
60 | const CommonJS = createRequire(join(root, 'node_modules'));
61 | let json = null;
62 | return {
63 | session(cache = json) {
64 | return new Session(cache || (json = CommonJS(resolve(root, output))));
65 | },
66 | async save() {
67 | const graph = await crawl(options);
68 | const main = `${getEntryValue('prefix', {}, options)}${id}`;
69 | const global = getEntryValue('global', {}, options);
70 | const namespace = getGlobal(global, main);
71 | const require = isSimple(main) ? main : namespace;
72 | const cache = {
73 | _: {
74 | module: `${namespace}=function _($){return _[$]};`,
75 | code: '',
76 | dependencies: []
77 | }
78 | };
79 | const modules = {};
80 | for (const key of keys(options.modules)) {
81 | const entry = createModuleEntry(options.modules[key], options);
82 | if (entry.replace)
83 | entry.replace = getInclude(root, entry.replace);
84 | modules[key] = {
85 | ...entry,
86 | global,
87 | namespace,
88 | require,
89 | cache,
90 | graph
91 | };
92 | }
93 | await parse(CommonJS, graph, modules);
94 | if (output)
95 | await writeFile(
96 | resolve(root, output),
97 | stringify(cache, null, 2)
98 | );
99 | return cache;
100 | }
101 | };
102 | };
103 |
104 | export {Session};
105 |
--------------------------------------------------------------------------------
/cjs/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /*!
3 | * ISC License
4 | *
5 | * Copyright (c) 2021, Andrea Giammarchi, @WebReflection
6 | *
7 | * Permission to use, copy, modify, and/or distribute this software for any
8 | * purpose with or without fee is hereby granted, provided that the above
9 | * copyright notice and this permission notice appear in all copies.
10 | *
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
14 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
16 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
17 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 | */
19 |
20 | const {createRequire} = require('module');
21 | const {join, resolve} = require('path');
22 | const {writeFile} = require('fs/promises');
23 |
24 | const Session = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('js-in-json-session'));
25 |
26 | const {parse} = require('./bundler.js');
27 | const {crawl} = require('./graph.js');
28 | const {getGlobal, getInclude, isSimple, keys, stringify} = require('./utils.js');
29 |
30 | const defaults = {
31 | babel: true,
32 | minify: true,
33 | global: 'self',
34 | prefix: `_${Date.now().toString(36).slice(-2)}`,
35 | code: null,
36 | replace: null
37 | };
38 |
39 | const createModuleEntry = (module, options) => ({
40 | input: resolve(options.root, module.input),
41 | babel: !!getEntryValue('babel', module, options),
42 | minify: !!getEntryValue('minify', module, options),
43 | code: getEntryValue('code', module, options),
44 | replace: getEntryValue('replace', module, options),
45 | });
46 |
47 | const getEntryValue = (name, module, options) => {
48 | let value = module[name];
49 | if (value == null) {
50 | value = options[name];
51 | if (value == null)
52 | value = defaults[name];
53 | }
54 | return value;
55 | };
56 |
57 | let instances = 0;
58 | const JSinJSON = options => {
59 | const {output, root} = options;
60 | const id = (instances++).toString(36);
61 | const CommonJS = createRequire(join(root, 'node_modules'));
62 | let json = null;
63 | return {
64 | session(cache = json) {
65 | return new Session(cache || (json = CommonJS(resolve(root, output))));
66 | },
67 | async save() {
68 | const graph = await crawl(options);
69 | const main = `${getEntryValue('prefix', {}, options)}${id}`;
70 | const global = getEntryValue('global', {}, options);
71 | const namespace = getGlobal(global, main);
72 | const require = isSimple(main) ? main : namespace;
73 | const cache = {
74 | _: {
75 | module: `${namespace}=function _($){return _[$]};`,
76 | code: '',
77 | dependencies: []
78 | }
79 | };
80 | const modules = {};
81 | for (const key of keys(options.modules)) {
82 | const entry = createModuleEntry(options.modules[key], options);
83 | if (entry.replace)
84 | entry.replace = getInclude(root, entry.replace);
85 | modules[key] = {
86 | ...entry,
87 | global,
88 | namespace,
89 | require,
90 | cache,
91 | graph
92 | };
93 | }
94 | await parse(CommonJS, graph, modules);
95 | if (output)
96 | await writeFile(
97 | resolve(root, output),
98 | stringify(cache, null, 2)
99 | );
100 | return cache;
101 | }
102 | };
103 | };
104 | exports.JSinJSON = JSinJSON;
105 |
106 | exports.Session = Session;
107 |
--------------------------------------------------------------------------------
/esm/utils.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * ISC License
3 | *
4 | * Copyright (c) 2021, Andrea Giammarchi, @WebReflection
5 | *
6 | * Permission to use, copy, modify, and/or distribute this software for any
7 | * purpose with or without fee is hereby granted, provided that the above
8 | * copyright notice and this permission notice appear in all copies.
9 | *
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 | */
18 |
19 | import {extname, resolve} from 'path';
20 |
21 | import * as parser from '@babel/parser';
22 |
23 | const {keys} = Object;
24 | const {stringify} = JSON;
25 | const {hasOwnProperty} = {};
26 |
27 | export {hasOwnProperty, keys, stringify};
28 |
29 | // TODO: find out in which case this is actually needed as rollup is forced to export names
30 | export const asModule = name => `(m=>m.__esModule&&m.default||m)(${name})`;
31 |
32 | export const getBody = code => parser.parse(code, parserOptions).program.body;
33 |
34 | export const getCallback = callback => ''.replace.call(
35 | callback,
36 | /^\s*([^(]+)/,
37 | (_, $1) => {
38 | return /^(?:function|)$/.test($1.trim()) ? $1 : 'function';
39 | }
40 | );
41 |
42 | export const getGlobal = (namespace, name) =>
43 | `${namespace}${isSimple(name) ? `.${name}` : `[${stringify(name)}]`}`;
44 |
45 | export const getInclude = (root, hash) => {
46 | const include = {};
47 | for (const key of keys(hash))
48 | include[key] = resolve(root, hash[key]);
49 | return include;
50 | };
51 |
52 | export const getModule = (require, name) => name[0] === '_' ?
53 | getGlobal(require, name) :
54 | asModule(getGlobal(require, name));
55 |
56 | export const getName = name => {
57 | const ext = extname(name);
58 | return `${name.slice(0, -ext.length)}@JSinJSON${ext}`;
59 | };
60 |
61 | export const getNames = specifiers => {
62 | const imports = [];
63 | const exports = [];
64 | for (const {local, exported} of specifiers) {
65 | const {name} = exported;
66 | imports.push(local.name == name ? name : `${local.name}: ${name}`);
67 | exports.push(name);
68 | }
69 | return {imports, exports};
70 | };
71 |
72 | export const getRealName = (code, source, replace) => {
73 | let name = slice(code, source).slice(1, -1);
74 | if (replace && hasOwnProperty.call(replace, name))
75 | name = replace[name];
76 | return name;
77 | };
78 |
79 | export const isJS = name => /^\.[mc]?js$/i.test(extname(name));
80 | export const isLocal = name => /^[./]/.test(name);
81 | export const isSimple = name => /^[$_a-z]+[$_0-9a-z]*$/i.test(name);
82 |
83 | export const slice = (code, info) => code.slice(info.start, info.end);
84 |
85 | export const warn = (...args) => {
86 | console.warn('⚠ \x1b[1mWarning\x1b[0m', ...args);
87 | };
88 |
89 | export const defaults = {
90 | babel: true,
91 | minify: true,
92 | global: 'self',
93 | prefix: `_${Date.now().toString(36).slice(-2)}`
94 | };
95 |
96 | export const babelOptions = {
97 | presets: ['@babel/preset-env'],
98 | plugins: [['@babel/plugin-transform-runtime', {useESModules: true}]]
99 | };
100 |
101 | export const parserOptions = {
102 | allowAwaitOutsideFunction: true,
103 | sourceType: 'module',
104 | plugins: [
105 | // 'estree',
106 | 'jsx',
107 | 'typescript',
108 | 'exportExtensions',
109 | 'exportDefaultFrom',
110 | 'exportNamespaceFrom',
111 | 'dynamicImport',
112 | 'importMeta',
113 | 'asyncGenerators',
114 | 'bigInt',
115 | 'classProperties',
116 | 'classPrivateProperties',
117 | 'classPrivateMethods',
118 | ['decorators', {decoratorsBeforeExport: true}],
119 | 'doExpressions',
120 | 'functionBind',
121 | 'functionSent',
122 | 'logicalAssignment',
123 | 'nullishCoalescingOperator',
124 | 'numericSeparator',
125 | 'objectRestSpread',
126 | 'optionalCatchBinding',
127 | 'optionalChaining',
128 | 'partialApplication',
129 | ['pipelineOperator', {proposal: 'minimal'}],
130 | 'throwExpressions',
131 | 'topLevelAwait'
132 | ]
133 | };
134 |
--------------------------------------------------------------------------------
/cjs/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /*!
3 | * ISC License
4 | *
5 | * Copyright (c) 2021, Andrea Giammarchi, @WebReflection
6 | *
7 | * Permission to use, copy, modify, and/or distribute this software for any
8 | * purpose with or without fee is hereby granted, provided that the above
9 | * copyright notice and this permission notice appear in all copies.
10 | *
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
14 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
16 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
17 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 | */
19 |
20 | const {extname, resolve} = require('path');
21 |
22 | const parser = require('@babel/parser');
23 |
24 | const {keys} = Object;
25 | const {stringify} = JSON;
26 | const {hasOwnProperty} = {};
27 |
28 | exports.hasOwnProperty = hasOwnProperty;
29 | exports.keys = keys;
30 | exports.stringify = stringify;
31 |
32 | // TODO: find out in which case this is actually needed as rollup is forced to export names
33 | const asModule = name => `(m=>m.__esModule&&m.default||m)(${name})`;
34 | exports.asModule = asModule;
35 |
36 | const getBody = code => parser.parse(code, parserOptions).program.body;
37 | exports.getBody = getBody;
38 |
39 | const getCallback = callback => ''.replace.call(
40 | callback,
41 | /^\s*([^(]+)/,
42 | (_, $1) => {
43 | return /^(?:function|)$/.test($1.trim()) ? $1 : 'function';
44 | }
45 | );
46 | exports.getCallback = getCallback;
47 |
48 | const getGlobal = (namespace, name) =>
49 | `${namespace}${isSimple(name) ? `.${name}` : `[${stringify(name)}]`}`;
50 | exports.getGlobal = getGlobal;
51 |
52 | const getInclude = (root, hash) => {
53 | const include = {};
54 | for (const key of keys(hash))
55 | include[key] = resolve(root, hash[key]);
56 | return include;
57 | };
58 | exports.getInclude = getInclude;
59 |
60 | const getModule = (require, name) => name[0] === '_' ?
61 | getGlobal(require, name) :
62 | asModule(getGlobal(require, name));
63 | exports.getModule = getModule;
64 |
65 | const getName = name => {
66 | const ext = extname(name);
67 | return `${name.slice(0, -ext.length)}@JSinJSON${ext}`;
68 | };
69 | exports.getName = getName;
70 |
71 | const getNames = specifiers => {
72 | const imports = [];
73 | const exports = [];
74 | for (const {local, exported} of specifiers) {
75 | const {name} = exported;
76 | imports.push(local.name == name ? name : `${local.name}: ${name}`);
77 | exports.push(name);
78 | }
79 | return {imports, exports};
80 | };
81 | exports.getNames = getNames;
82 |
83 | const getRealName = (code, source, replace) => {
84 | let name = slice(code, source).slice(1, -1);
85 | if (replace && hasOwnProperty.call(replace, name))
86 | name = replace[name];
87 | return name;
88 | };
89 | exports.getRealName = getRealName;
90 |
91 | const isJS = name => /^\.[mc]?js$/i.test(extname(name));
92 | exports.isJS = isJS;
93 | const isLocal = name => /^[./]/.test(name);
94 | exports.isLocal = isLocal;
95 | const isSimple = name => /^[$_a-z]+[$_0-9a-z]*$/i.test(name);
96 | exports.isSimple = isSimple;
97 |
98 | const slice = (code, info) => code.slice(info.start, info.end);
99 | exports.slice = slice;
100 |
101 | const warn = (...args) => {
102 | console.warn('⚠ \x1b[1mWarning\x1b[0m', ...args);
103 | };
104 | exports.warn = warn;
105 |
106 | const defaults = {
107 | babel: true,
108 | minify: true,
109 | global: 'self',
110 | prefix: `_${Date.now().toString(36).slice(-2)}`
111 | };
112 | exports.defaults = defaults;
113 |
114 | const babelOptions = {
115 | presets: ['@babel/preset-env'],
116 | plugins: [['@babel/plugin-transform-runtime', {useESModules: true}]]
117 | };
118 | exports.babelOptions = babelOptions;
119 |
120 | const parserOptions = {
121 | allowAwaitOutsideFunction: true,
122 | sourceType: 'module',
123 | plugins: [
124 | // 'estree',
125 | 'jsx',
126 | 'typescript',
127 | 'exportExtensions',
128 | 'exportDefaultFrom',
129 | 'exportNamespaceFrom',
130 | 'dynamicImport',
131 | 'importMeta',
132 | 'asyncGenerators',
133 | 'bigInt',
134 | 'classProperties',
135 | 'classPrivateProperties',
136 | 'classPrivateMethods',
137 | ['decorators', {decoratorsBeforeExport: true}],
138 | 'doExpressions',
139 | 'functionBind',
140 | 'functionSent',
141 | 'logicalAssignment',
142 | 'nullishCoalescingOperator',
143 | 'numericSeparator',
144 | 'objectRestSpread',
145 | 'optionalCatchBinding',
146 | 'optionalChaining',
147 | 'partialApplication',
148 | ['pipelineOperator', {proposal: 'minimal'}],
149 | 'throwExpressions',
150 | 'topLevelAwait'
151 | ]
152 | };
153 | exports.parserOptions = parserOptions;
154 |
--------------------------------------------------------------------------------
/esm/handlers.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * ISC License
3 | *
4 | * Copyright (c) 2021, Andrea Giammarchi, @WebReflection
5 | *
6 | * Permission to use, copy, modify, and/or distribute this software for any
7 | * purpose with or without fee is hereby granted, provided that the above
8 | * copyright notice and this permission notice appear in all copies.
9 | *
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 | */
18 |
19 | import etag from 'etag';
20 | import {dirname, resolve} from 'path';
21 | import {iife} from './rollup.js';
22 | import {
23 | getGlobal, getModule, getName,
24 | hasOwnProperty, isJS, isLocal
25 | } from './utils.js';
26 |
27 | const createCacheEntry = async (
28 | CommonJS, module, dependencies, innerName
29 | ) => {
30 | const {cache} = module;
31 | if (!dependencies.has(innerName))
32 | dependencies.add(innerName);
33 | if (!hasOwnProperty.call(cache, innerName)) {
34 | const body = await iife(
35 | CommonJS.resolve(innerName),
36 | innerName,
37 | true,
38 | module
39 | );
40 | cache[innerName] = {
41 | module: body,
42 | etag: etag(body),
43 | code: '',
44 | dependencies: []
45 | };
46 | }
47 | };
48 |
49 | const createLocalEntry = async (
50 | CommonJS, dependencies, module, parsed, remove,
51 | input,
52 | moduleTransformer,
53 | ifLocal
54 | ) => {
55 | await moduleTransformer(
56 | CommonJS, '',
57 | {...module, input},
58 | dependencies, parsed, remove
59 | );
60 | const newName = getName(input);
61 | await ifLocal(remove.has(newName) ? newName : input);
62 | };
63 |
64 | export const exportHandler = async (
65 | CommonJS, input, dependencies, module, parsed, remove, innerName,
66 | {
67 | addCacheEntry,
68 | moduleTransformer,
69 | ifModule,
70 | ifLocal
71 | }
72 | ) => {
73 | const {graph} = module;
74 | if (isLocal(innerName)) {
75 | if (!isJS(innerName))
76 | return;
77 | innerName = resolve(dirname(input), innerName);
78 | const {count, name} = graph.get(innerName);
79 | if (count > 1) {
80 | innerName = name;
81 | if (!dependencies.has(name))
82 | dependencies.add(name);
83 | await addCacheEntry(CommonJS, innerName, module, parsed, remove);
84 | await ifModule(innerName);
85 | }
86 | else {
87 | await createLocalEntry(
88 | CommonJS, dependencies, module, parsed, remove,
89 | innerName,
90 | moduleTransformer,
91 | ifLocal
92 | );
93 | }
94 | }
95 | else {
96 | await createCacheEntry(CommonJS, module, dependencies, innerName);
97 | await ifModule(innerName);
98 | }
99 | };
100 |
101 | export const importHandler = async (
102 | CommonJS, input, dependencies, module, parsed, remove, innerName,
103 | {
104 | addCacheEntry,
105 | moduleTransformer,
106 | ifModule,
107 | ifLocal,
108 | specifiers
109 | }
110 | ) => {
111 | const {graph, require} = module;
112 | if (isLocal(innerName)) {
113 | if (!isJS(innerName))
114 | return;
115 | innerName = resolve(dirname(input), innerName);
116 | const {count, name} = graph.get(innerName);
117 | if (count > 1) {
118 | innerName = name;
119 | if (!dependencies.has(name))
120 | dependencies.add(name);
121 | await addCacheEntry(CommonJS, innerName, module, parsed, remove);
122 | }
123 | else {
124 | await createLocalEntry(
125 | CommonJS, dependencies, module, parsed, remove,
126 | innerName,
127 | moduleTransformer,
128 | ifLocal
129 | );
130 | return;
131 | }
132 | }
133 | else {
134 | await createCacheEntry(CommonJS, module, dependencies, innerName);
135 | }
136 | const imports = [];
137 | const names = [];
138 | for (const {type, imported, local} of specifiers) {
139 | switch(type) {
140 | case 'ImportDefaultSpecifier':
141 | imports.push(
142 | `const ${local.name} = ${getModule(require, innerName)};`
143 | );
144 | break;
145 | case 'ImportNamespaceSpecifier':
146 | imports.push(
147 | `const ${local.name} = ${getGlobal(require, innerName)};`
148 | );
149 | break;
150 | case 'ImportSpecifier':
151 | names.push(
152 | local.name === imported.name ?
153 | local.name :
154 | `${imported.name}: ${local.name}`
155 | );
156 | break;
157 | }
158 | }
159 |
160 | if (names.length) {
161 | const rep = getGlobal(require, innerName);
162 | imports.push(`const {${names.join(', ')}} = ${rep};`);
163 | }
164 |
165 | ifModule(imports.join('\n'));
166 | };
167 |
--------------------------------------------------------------------------------
/cjs/handlers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /*!
3 | * ISC License
4 | *
5 | * Copyright (c) 2021, Andrea Giammarchi, @WebReflection
6 | *
7 | * Permission to use, copy, modify, and/or distribute this software for any
8 | * purpose with or without fee is hereby granted, provided that the above
9 | * copyright notice and this permission notice appear in all copies.
10 | *
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
14 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
16 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
17 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 | */
19 |
20 | const etag = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('etag'));
21 | const {dirname, resolve} = require('path');
22 | const {iife} = require('./rollup.js');
23 | const {
24 | getGlobal, getModule, getName, hasOwnProperty, isJS, isLocal
25 | } = require('./utils.js');
26 |
27 | const createCacheEntry = async (
28 | CommonJS, module, dependencies, innerName
29 | ) => {
30 | const {cache} = module;
31 | if (!dependencies.has(innerName))
32 | dependencies.add(innerName);
33 | if (!hasOwnProperty.call(cache, innerName)) {
34 | const body = await iife(
35 | CommonJS.resolve(innerName),
36 | innerName,
37 | true,
38 | module
39 | );
40 | cache[innerName] = {
41 | module: body,
42 | etag: etag(body),
43 | code: '',
44 | dependencies: []
45 | };
46 | }
47 | };
48 |
49 | const createLocalEntry = async (
50 | CommonJS, dependencies, module, parsed, remove,
51 | input,
52 | moduleTransformer,
53 | ifLocal
54 | ) => {
55 | await moduleTransformer(
56 | CommonJS, '',
57 | {...module, input},
58 | dependencies, parsed, remove
59 | );
60 | const newName = getName(input);
61 | await ifLocal(remove.has(newName) ? newName : input);
62 | };
63 |
64 | const exportHandler = async (
65 | CommonJS, input, dependencies, module, parsed, remove, innerName,
66 | {
67 | addCacheEntry,
68 | moduleTransformer,
69 | ifModule,
70 | ifLocal
71 | }
72 | ) => {
73 | const {graph} = module;
74 | if (isLocal(innerName)) {
75 | if (!isJS(innerName))
76 | return;
77 | innerName = resolve(dirname(input), innerName);
78 | const {count, name} = graph.get(innerName);
79 | if (count > 1) {
80 | innerName = name;
81 | if (!dependencies.has(name))
82 | dependencies.add(name);
83 | await addCacheEntry(CommonJS, innerName, module, parsed, remove);
84 | await ifModule(innerName);
85 | }
86 | else {
87 | await createLocalEntry(
88 | CommonJS, dependencies, module, parsed, remove,
89 | innerName,
90 | moduleTransformer,
91 | ifLocal
92 | );
93 | }
94 | }
95 | else {
96 | await createCacheEntry(CommonJS, module, dependencies, innerName);
97 | await ifModule(innerName);
98 | }
99 | };
100 | exports.exportHandler = exportHandler;
101 |
102 | const importHandler = async (
103 | CommonJS, input, dependencies, module, parsed, remove, innerName,
104 | {
105 | addCacheEntry,
106 | moduleTransformer,
107 | ifModule,
108 | ifLocal,
109 | specifiers
110 | }
111 | ) => {
112 | const {graph, require} = module;
113 | if (isLocal(innerName)) {
114 | if (!isJS(innerName))
115 | return;
116 | innerName = resolve(dirname(input), innerName);
117 | const {count, name} = graph.get(innerName);
118 | if (count > 1) {
119 | innerName = name;
120 | if (!dependencies.has(name))
121 | dependencies.add(name);
122 | await addCacheEntry(CommonJS, innerName, module, parsed, remove);
123 | }
124 | else {
125 | await createLocalEntry(
126 | CommonJS, dependencies, module, parsed, remove,
127 | innerName,
128 | moduleTransformer,
129 | ifLocal
130 | );
131 | return;
132 | }
133 | }
134 | else {
135 | await createCacheEntry(CommonJS, module, dependencies, innerName);
136 | }
137 | const imports = [];
138 | const names = [];
139 | for (const {type, imported, local} of specifiers) {
140 | switch(type) {
141 | case 'ImportDefaultSpecifier':
142 | imports.push(
143 | `const ${local.name} = ${getModule(require, innerName)};`
144 | );
145 | break;
146 | case 'ImportNamespaceSpecifier':
147 | imports.push(
148 | `const ${local.name} = ${getGlobal(require, innerName)};`
149 | );
150 | break;
151 | case 'ImportSpecifier':
152 | names.push(
153 | local.name === imported.name ?
154 | local.name :
155 | `${imported.name}: ${local.name}`
156 | );
157 | break;
158 | }
159 | }
160 |
161 | if (names.length) {
162 | const rep = getGlobal(require, innerName);
163 | imports.push(`const {${names.join(', ')}} = ${rep};`);
164 | }
165 |
166 | ifModule(imports.join('\n'));
167 | };
168 | exports.importHandler = importHandler;
169 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JS in JSN
2 |
3 | A server side agnostic way to stream JavaScript, ideal for:
4 |
5 | * inline hydration
6 | * network free ad-hoc dependencies
7 | * bootstrap on demand
8 | * libraries on demand
9 | * one JSON file to rule all SSR cases
10 |
11 | The **Session** utility is currently available for:
12 |
13 | * [JS](https://github.com/WebReflection/js-in-json-session#readme)
14 | * [PHP](https://github.com/WebReflection/js-in-json-session/blob/main/php/session.php)
15 | * [Python](https://github.com/WebReflection/js-in-json-session/blob/main/python/session.py)
16 |
17 | - - -
18 |
19 | 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.
20 |
21 | 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.
22 |
23 | ```js
24 | // a basic serving example
25 | const {JSinJSON} = require('js-in-json');
26 |
27 | // see ## Options
28 | const {options} = require('./js-in-json-options.js');
29 |
30 | const islands = JSinJSON(options);
31 | // islands.save(); when needed to create the JSON bundle
32 |
33 | http.createServer((req, res) => {
34 | // see ## Session
35 | const js = islands.session();
36 | js.add('Main');
37 | res.writeHead(200, {'content-type': 'text/html'});
38 | res.write(`
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | `.trim());
47 | js.add('Footer');
48 | if (global.condition) {
49 | js.add('SpecialCondition');
50 | res.write(``);
51 | }
52 | res.end();
53 | });
54 | ```
55 |
56 | ## Session
57 |
58 | 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.
59 |
60 | A session created via `JSinJSON` optionally accepts a JSON bundle, retrieved from *options*, if not provided, and it exposes 2 methods:
61 |
62 | * `add("ModuleName")` to *flush* its content *only once* and, if repeatedly added, and available, bootstrap its *code*
63 | * `flush()` to return all modules and their dependencies previously added and, if available, their code to bootstrap
64 |
65 | In order to have a *session*, a JSON bundle must be created.
66 |
67 |
68 | ## Options
69 |
70 | Following the object literal with all its defaults that can be passed to the `JSinJSON(options)` export.
71 |
72 | ```js
73 | const {save, session} = JSinJSON({
74 |
75 | // TOP LEVEL CONFIG ONLY
76 |
77 | // MANDATORY
78 | // the root folder from which each `input` is retrieved
79 | // used to resolve the optional output, if relative to this folder
80 | root: '/full/project/root/folder'
81 |
82 | // OPTIONAL
83 | // where to store the resulting JSON cache usable via JSinJSON.session(cache)
84 | // if omitted, the cache is still processed and returned
85 | output: './bundle.json',
86 | // the global context used to attach the `require` like function
87 | global: 'self',
88 | // the `require` like unique function name, automatically generated,
89 | // and it's different per each saved JSON (hint: don't specify it)
90 | prefix: '_uid',
91 |
92 |
93 | // OPTIONAL EXTRAS: CAN BE OVERWRITTEN PER EACH MODULE
94 | // use Babel transformer to target @babel/preset-env
95 | babel: true,
96 | // use terser to minify produced code
97 | minify: true,
98 | // transform specific bare imports into other imports, it's {} by default
99 | // see: rollup-plugin-includepaths
100 | replace: {
101 | // example: replace CE polyfill with an empty file
102 | '@ungap/custom-elements': './empty.js'
103 | },
104 | // executed each time a JSinJSON.session.flush() happens
105 | // no matter which module has been added to the stack
106 | // it's missing/no-op by default and it has no access
107 | // to the outer scope of this file (it's serialized as function)
108 | code(require) {
109 | // each code receives the `require` like function
110 | const {upgradeAll} = require('Bootstrap');
111 | upgradeAll();
112 | },
113 | // an object literal to define all modules flushed in the page
114 | // whenever any of these is needed
115 | modules: {
116 | // the module name available via the `require` like function
117 | Bootstrap: {
118 | // MANDATORY
119 | // the ESM entry point for this module
120 | input: './bootstrap.js',
121 |
122 | // OPTIONAL: overwrite top level options per each module
123 | // don't transform and/or don't minify
124 | babel: false,
125 | minify: false,
126 | // will be merged with the top level
127 | replace: {'other': './file.js'},
128 | // don't flush anything when injected
129 | code: null
130 | },
131 |
132 | // other module example
133 | Login: {
134 | input: './login.js',
135 | code() {
136 | document.documentElement.classList.add('wait');
137 | fetch('/login/challenge').then(b => b.json).then(result => {
138 | self.challenge = result;
139 | document.documentElement.classList.remove('wait');
140 | });
141 | }
142 | }
143 | }
144 | });
145 | ```
146 |
147 | ### Options Rules / Limitations
148 |
149 | * the `root` should better be a fully qualified path, instead of relative
150 | * the `code` is always transformed with `@babel/preset-env` target
151 | * the `code` **cannot be asynchronous**
152 | * modules *cannot* have `_` as name prefix, that's reserved for internal resolutions
153 | * modules *should* have *non-npm* modules names, to avoid conflicts/clashing with imports
154 | * modules *can* be capitalized
155 |
--------------------------------------------------------------------------------
/esm/bundler.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * ISC License
3 | *
4 | * Copyright (c) 2021, Andrea Giammarchi, @WebReflection
5 | *
6 | * Permission to use, copy, modify, and/or distribute this software for any
7 | * purpose with or without fee is hereby granted, provided that the above
8 | * copyright notice and this permission notice appear in all copies.
9 | *
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 | */
18 |
19 | import {writeFile, unlink} from 'fs/promises';
20 | import {env} from 'process';
21 |
22 | import {transformSync} from "@babel/core";
23 | import {minify as terser} from 'terser';
24 |
25 | import {iife} from './rollup.js';
26 |
27 | import {
28 | babelOptions,
29 | getBody, getCallback, getGlobal, getModule, getName, getNames, getRealName,
30 | hasOwnProperty, stringify,
31 | slice, warn
32 | } from './utils.js';
33 |
34 | import {exportHandler, importHandler} from './handlers.js';
35 | import etag from 'etag';
36 |
37 | const addCacheEntry = async (CommonJS, name, module, parsed, remove) => {
38 | const {cache, code} = module;
39 | if (hasOwnProperty.call(cache, name))
40 | return;
41 | const dependencies = new Set;
42 | const body = await moduleTransformer(
43 | CommonJS,
44 | name,
45 | module,
46 | dependencies,
47 | parsed,
48 | remove
49 | );
50 | cache[name] = {
51 | module: body,
52 | etag: etag(body),
53 | code: code ? (
54 | typeof code === 'string' ?
55 | `!${code}(${module.require});` :
56 | (await codeTransformer(module))
57 | ) : '',
58 | dependencies: [...dependencies]
59 | };
60 | };
61 |
62 | const chunk = (info, esm, cjs) => ({
63 | start: info.start,
64 | end: info.end,
65 | esm, cjs
66 | });
67 |
68 | const codeTransformer = async ({babel, code, minify, require}) => {
69 | let output = `(${getCallback(code)})(${require});`;
70 | if (babel)
71 | output = transformSync(
72 | output, babelOptions).code.replace(/^"use strict";/, ''
73 | );
74 | if (minify)
75 | output = (await terser(output)).code;
76 | return output;
77 | };
78 |
79 | const getOutput = (code, chunks) => {
80 | const output = [];
81 | const {length} = chunks;
82 | let c = 0;
83 | for (let i = 0; i < length; i++) {
84 | output.push(
85 | code.slice(c, chunks[i].start),
86 | chunks[i].cjs
87 | );
88 | c = chunks[i].end;
89 | }
90 | output.push(length ? code.slice(c) : code);
91 | return output.join('').trim();
92 | };
93 |
94 | const moduleTransformer = async (
95 | CommonJS, name, module, dependencies, parsed, remove
96 | ) => {
97 | let {graph, input, replace, require} = module;
98 | if (parsed.has(input))
99 | return;
100 | parsed.add(input);
101 | const {code} = graph.get(input);
102 | const chunks = [];
103 | for (const item of getBody(code)) {
104 | const {source} = item;
105 | const esm = slice(code, item);
106 | switch (item.type) {
107 | case 'ExportAllDeclaration': {
108 | await exportHandler(
109 | CommonJS, input, dependencies, module, parsed, remove,
110 | getRealName(code, source, replace),
111 | {
112 | ...exportUtils,
113 | // TODO: find a way to resolve modules via their entry point
114 | // only if these modules are ESM ... otherwise think about
115 | // warning here and but the bundler include the whole library?
116 | async ifModule(name) {
117 | warn(
118 | 'export * from',
119 | `\x1b[1m${name[0] === '_' ? input : name}\x1b[0m`,
120 | 'is being exported instead as default'
121 | );
122 | const rep = `export default ${getGlobal(require, name)};`;
123 | chunks.push(chunk(item, esm, rep));
124 | },
125 | ifLocal(name) {
126 | const rep = esm.replace(slice(code, source), stringify(name));
127 | chunks.push(chunk(item, esm, rep));
128 | }
129 | }
130 | );
131 | break;
132 | }
133 | case 'ExportNamedDeclaration': {
134 | if (source) {
135 | const {specifiers} = item;
136 | await exportHandler(
137 | CommonJS, input, dependencies, module, parsed, remove,
138 | getRealName(code, source, replace),
139 | {
140 | ...exportUtils,
141 | ifModule(name) {
142 | const {imports, exports} = getNames(specifiers);
143 | const rep = imports.join(', ');
144 | chunks.push(chunk(item, esm, [
145 | `const {${rep}} = ${getModule(require, name)};`,
146 | `export {${exports.join(', ')}};`
147 | ].join('\n')));
148 | },
149 | ifLocal(name) {
150 | const {imports, exports} = getNames(specifiers);
151 | const rep = imports.map(n => n.replace(':', ' as')).join(', ');
152 | chunks.push(chunk(item, esm, [
153 | `import {${rep}} from ${stringify(name)};`,
154 | `export {${exports.join(', ')}};`
155 | ].join('\n')));
156 | }
157 | }
158 | );
159 | }
160 | break;
161 | }
162 | case 'ImportDeclaration': {
163 | await importHandler(
164 | CommonJS, input, dependencies, module, parsed, remove,
165 | getRealName(code, source, replace),
166 | {
167 | ...exportUtils,
168 | specifiers: item.specifiers,
169 | ifModule(names) {
170 | chunks.push(chunk(item, esm, names));
171 | },
172 | ifLocal(name) {
173 | const rep = esm.replace(slice(code, source), stringify(name));
174 | chunks.push(chunk(item, esm, rep));
175 | }
176 | }
177 | );
178 | break;
179 | }
180 | }
181 | }
182 | if (chunks.length) {
183 | input = getName(input);
184 | await saveFile(input, code, chunks, remove);
185 | }
186 | return name ? (await iife(input, name, false, module)) : '';
187 | };
188 |
189 | const saveFile = (file, code, chunks, remove) => {
190 | if (remove.has(file)) {
191 | warn(`possible circular dependency for ${file}`);
192 | return;
193 | }
194 | remove.add(file);
195 | return writeFile(file, getOutput(code, chunks));
196 | };
197 |
198 | const exportUtils = {addCacheEntry, moduleTransformer};
199 |
200 | export const parse = async (CommonJS, graph, modules) => {
201 | const parsed = new Set;
202 | const remove = new Set;
203 | for (const [input, {name, count, key}] of graph.entries()) {
204 | if (count > 1 || name === key) {
205 | const module = modules[name] || modules[key];
206 | await addCacheEntry(
207 | CommonJS, name, {...module, input}, parsed, remove
208 | );
209 | }
210 | }
211 | if (!/^(?:1|true|y|yes)$/i.test(env.JS_IN_JSON_DEBUG))
212 | await Promise.all([...remove].map(file => unlink(file)));
213 | };
214 |
--------------------------------------------------------------------------------
/cjs/bundler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /*!
3 | * ISC License
4 | *
5 | * Copyright (c) 2021, Andrea Giammarchi, @WebReflection
6 | *
7 | * Permission to use, copy, modify, and/or distribute this software for any
8 | * purpose with or without fee is hereby granted, provided that the above
9 | * copyright notice and this permission notice appear in all copies.
10 | *
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
14 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
16 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
17 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 | */
19 |
20 | const {writeFile, unlink} = require('fs/promises');
21 | const {env} = require('process');
22 |
23 | const {transformSync} = require("@babel/core");
24 | const {minify: terser} = require('terser');
25 |
26 | const {iife} = require('./rollup.js');
27 |
28 | const {
29 | babelOptions,
30 | getBody,
31 | getCallback,
32 | getGlobal,
33 | getModule,
34 | getName,
35 | getNames,
36 | getRealName,
37 | hasOwnProperty,
38 | stringify,
39 | slice,
40 | warn
41 | } = require('./utils.js');
42 |
43 | const {exportHandler, importHandler} = require('./handlers.js');
44 | const etag = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('etag'));
45 |
46 | const addCacheEntry = async (CommonJS, name, module, parsed, remove) => {
47 | const {cache, code} = module;
48 | if (hasOwnProperty.call(cache, name))
49 | return;
50 | const dependencies = new Set;
51 | const body = await moduleTransformer(
52 | CommonJS,
53 | name,
54 | module,
55 | dependencies,
56 | parsed,
57 | remove
58 | );
59 | cache[name] = {
60 | module: body,
61 | etag: etag(body),
62 | code: code ? (
63 | typeof code === 'string' ?
64 | `!${code}(${module.require});` :
65 | (await codeTransformer(module))
66 | ) : '',
67 | dependencies: [...dependencies]
68 | };
69 | };
70 |
71 | const chunk = (info, esm, cjs) => ({
72 | start: info.start,
73 | end: info.end,
74 | esm, cjs
75 | });
76 |
77 | const codeTransformer = async ({babel, code, minify, require}) => {
78 | let output = `(${getCallback(code)})(${require});`;
79 | if (babel)
80 | output = transformSync(
81 | output, babelOptions).code.replace(/^"use strict";/, ''
82 | );
83 | if (minify)
84 | output = (await terser(output)).code;
85 | return output;
86 | };
87 |
88 | const getOutput = (code, chunks) => {
89 | const output = [];
90 | const {length} = chunks;
91 | let c = 0;
92 | for (let i = 0; i < length; i++) {
93 | output.push(
94 | code.slice(c, chunks[i].start),
95 | chunks[i].cjs
96 | );
97 | c = chunks[i].end;
98 | }
99 | output.push(length ? code.slice(c) : code);
100 | return output.join('').trim();
101 | };
102 |
103 | const moduleTransformer = async (
104 | CommonJS, name, module, dependencies, parsed, remove
105 | ) => {
106 | let {graph, input, replace, require} = module;
107 | if (parsed.has(input))
108 | return;
109 | parsed.add(input);
110 | const {code} = graph.get(input);
111 | const chunks = [];
112 | for (const item of getBody(code)) {
113 | const {source} = item;
114 | const esm = slice(code, item);
115 | switch (item.type) {
116 | case 'ExportAllDeclaration': {
117 | await exportHandler(
118 | CommonJS, input, dependencies, module, parsed, remove,
119 | getRealName(code, source, replace),
120 | {
121 | ...exportUtils,
122 | // TODO: find a way to resolve modules via their entry point
123 | // only if these modules are ESM ... otherwise think about
124 | // warning here and but the bundler include the whole library?
125 | async ifModule(name) {
126 | warn(
127 | 'export * from',
128 | `\x1b[1m${name[0] === '_' ? input : name}\x1b[0m`,
129 | 'is being exported instead as default'
130 | );
131 | const rep = `export default ${getGlobal(require, name)};`;
132 | chunks.push(chunk(item, esm, rep));
133 | },
134 | ifLocal(name) {
135 | const rep = esm.replace(slice(code, source), stringify(name));
136 | chunks.push(chunk(item, esm, rep));
137 | }
138 | }
139 | );
140 | break;
141 | }
142 | case 'ExportNamedDeclaration': {
143 | if (source) {
144 | const {specifiers} = item;
145 | await exportHandler(
146 | CommonJS, input, dependencies, module, parsed, remove,
147 | getRealName(code, source, replace),
148 | {
149 | ...exportUtils,
150 | ifModule(name) {
151 | const {imports, exports} = getNames(specifiers);
152 | const rep = imports.join(', ');
153 | chunks.push(chunk(item, esm, [
154 | `const {${rep}} = ${getModule(require, name)};`,
155 | `export {${exports.join(', ')}};`
156 | ].join('\n')));
157 | },
158 | ifLocal(name) {
159 | const {imports, exports} = getNames(specifiers);
160 | const rep = imports.map(n => n.replace(':', ' as')).join(', ');
161 | chunks.push(chunk(item, esm, [
162 | `import {${rep}} from ${stringify(name)};`,
163 | `export {${exports.join(', ')}};`
164 | ].join('\n')));
165 | }
166 | }
167 | );
168 | }
169 | break;
170 | }
171 | case 'ImportDeclaration': {
172 | await importHandler(
173 | CommonJS, input, dependencies, module, parsed, remove,
174 | getRealName(code, source, replace),
175 | {
176 | ...exportUtils,
177 | specifiers: item.specifiers,
178 | ifModule(names) {
179 | chunks.push(chunk(item, esm, names));
180 | },
181 | ifLocal(name) {
182 | const rep = esm.replace(slice(code, source), stringify(name));
183 | chunks.push(chunk(item, esm, rep));
184 | }
185 | }
186 | );
187 | break;
188 | }
189 | }
190 | }
191 | if (chunks.length) {
192 | input = getName(input);
193 | await saveFile(input, code, chunks, remove);
194 | }
195 | return name ? (await iife(input, name, false, module)) : '';
196 | };
197 |
198 | const saveFile = (file, code, chunks, remove) => {
199 | if (remove.has(file)) {
200 | warn(`possible circular dependency for ${file}`);
201 | return;
202 | }
203 | remove.add(file);
204 | return writeFile(file, getOutput(code, chunks));
205 | };
206 |
207 | const exportUtils = {addCacheEntry, moduleTransformer};
208 |
209 | const parse = async (CommonJS, graph, modules) => {
210 | const parsed = new Set;
211 | const remove = new Set;
212 | for (const [input, {name, count, key}] of graph.entries()) {
213 | if (count > 1 || name === key) {
214 | const module = modules[name] || modules[key];
215 | await addCacheEntry(
216 | CommonJS, name, {...module, input}, parsed, remove
217 | );
218 | }
219 | }
220 | if (!/^(?:1|true|y|yes)$/i.test(env.JS_IN_JSON_DEBUG))
221 | await Promise.all([...remove].map(file => unlink(file)));
222 | };
223 | exports.parse = parse;
224 |
--------------------------------------------------------------------------------