Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

berial

Package Overview
Dependencies
Maintainers
2
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

berial - npm Package Compare versions

Comparing version 0.0.7 to 0.0.8

dist/berial.d.ts

617

dist/berial.js

@@ -37,3 +37,3 @@ (function (global, factory) {

plugins.add(plugin);
plugin(args);
plugin(...args);
}

@@ -62,98 +62,100 @@ }

function run(code, options = {}) {
function request(url, option) {
return fetch(url, Object.assign({ mode: 'cors' }, option)).then((res) => res.text());
}
function runScript(code, allow = {}) {
try {
if (checkSyntax(code)) {
const handler = {
get(obj, prop) {
return Reflect.has(obj, prop) ? obj[prop] : null;
},
set(obj, prop, value) {
Reflect.set(obj, prop, value);
return true;
},
has(obj, prop) {
return obj && Reflect.has(obj, prop);
const handler = {
get(obj, prop) {
return Reflect.has(obj, prop) ? obj[prop] : null;
},
set(obj, prop, value) {
Reflect.set(obj, prop, value);
return true;
},
has(obj, prop) {
return obj && Reflect.has(obj, prop);
}
};
const captureHandler = {
get(obj, prop) {
return Reflect.get(obj, prop);
},
set() {
return true;
},
has() {
return true;
}
};
const allowList = Object.assign({ IS_BERIAL_SANDBOX: true, __proto__: null, console,
String,
Number,
Array,
Symbol,
Math,
Object,
Promise,
RegExp,
JSON,
Date,
Function,
parseInt,
document,
location,
performance,
MessageChannel,
SVGElement,
HTMLElement,
HTMLIFrameElement,
history,
Map,
Set,
WeakMap,
WeakSet,
Error,
localStorage,
decodeURI,
encodeURI,
decodeURIComponent,
encodeURIComponent, fetch: fetch.bind(window), setTimeout: setTimeout.bind(window), clearTimeout: clearTimeout.bind(window), setInterval: setInterval.bind(window), clearInterval: clearInterval.bind(window), requestAnimationFrame: requestAnimationFrame.bind(window), cancelAnimationFrame: cancelAnimationFrame.bind(window), addEventListener: addEventListener.bind(window), removeEventListener: removeEventListener.bind(window), eval: function (code) {
return runScript('return ' + code, {});
}, alert: function () {
alert('Sandboxed alert:' + arguments[0]);
}, innerHeight,
innerWidth,
outerHeight,
outerWidth,
pageXOffset,
pageYOffset,
screen,
screenLeft,
screenTop,
screenX,
screenY,
scrollBy,
scrollTo,
scrollX,
scrollY }, allow);
if (!Object.isFrozen(String.prototype)) {
for (const k in allowList) {
const fn = allowList[k];
if (typeof fn === 'object' && fn.prototype) {
Object.freeze(fn.prototype);
}
};
const captureHandler = {
get(obj, prop) {
return Reflect.get(obj, prop);
},
set() {
return true;
},
has() {
return true;
if (typeof fn === 'function') {
Object.freeze(fn);
}
};
const allowList = Object.assign({ IS_BERIAL_SANDBOX: true, __proto__: null, console,
String,
Number,
Array,
Symbol,
Math,
Object,
Promise,
RegExp,
JSON,
Date,
Function,
parseInt,
document,
navigator,
location,
performance,
MessageChannel,
SVGElement,
HTMLElement,
HTMLIFrameElement,
history,
Map,
Set,
WeakMap,
WeakSet,
Error,
localStorage,
decodeURI,
encodeURI,
decodeURIComponent,
encodeURIComponent, fetch: fetch.bind(window), setTimeout: setTimeout.bind(window), clearTimeout: clearTimeout.bind(window), setInterval: setInterval.bind(window), clearInterval: clearInterval.bind(window), requestAnimationFrame: requestAnimationFrame.bind(window), cancelAnimationFrame: cancelAnimationFrame.bind(window), addEventListener: addEventListener.bind(window), removeEventListener: removeEventListener.bind(window), eval: function (code) {
return run('return ' + code, null);
}, alert: function () {
alert('Sandboxed alert:' + arguments[0]);
}, innerHeight,
innerWidth,
outerHeight,
outerWidth,
pageXOffset,
pageYOffset,
screen,
screenLeft,
screenTop,
screenX,
screenY,
scrollBy,
scrollTo,
scrollX,
scrollY }, (options.allowList || {}));
if (!Object.isFrozen(String.prototype)) {
for (const k in allowList) {
const fn = allowList[k];
if (typeof fn === 'object' && fn.prototype) {
Object.freeze(fn.prototype);
}
if (typeof fn === 'function') {
Object.freeze(fn);
}
}
}
const proxy = new Proxy(allowList, handler);
const capture = new Proxy({
__proto__: null,
proxy,
globalThis: new Proxy(allowList, handler),
window: new Proxy(allowList, handler),
self: new Proxy(allowList, handler)
}, captureHandler);
return Function('proxy', 'capture', `with(capture) {
}
const proxy = new Proxy(allowList, handler);
const capture = new Proxy({
__proto__: null,
proxy,
globalThis: new Proxy(allowList, handler),
window: new Proxy(allowList, handler),
self: new Proxy(allowList, handler)
}, captureHandler);
return Function('proxy', 'capture', `with(capture) {
with(proxy) {

@@ -167,3 +169,2 @@ return (function(){

}`)(proxy, capture);
}
}

@@ -174,161 +175,159 @@ catch (e) {

}
function checkSyntax(code) {
Function(code);
if (/\bimport\s*(?:[(]|\/[*]|\/\/|<!--|-->)/.test(code)) {
throw new Error('Dynamic imports are blocked');
}
return true;
}
function error(trigger, msg) {
if (typeof trigger === 'string')
msg = trigger;
if (!trigger)
return;
throw new Error(`[Berial: Error]: ${msg}`);
const ALL_SCRIPT_REGEX = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;
const SCRIPT_TAG_REGEX = /<(script)\s+((?!type=('|')text\/ng-template\3).)*?>.*?<\/\1>/is;
const SCRIPT_SRC_REGEX = /.*\ssrc=('|")?([^>'"\s]+)/;
const SCRIPT_ENTRY_REGEX = /.*\sentry\s*.*/;
const LINK_TAG_REGEX = /<(link)\s+.*?>/gi;
const LINK_IGNORE_REGEX = /.*ignore\s*.*/;
const LINK_PRELOAD_OR_PREFETCH_REGEX = /\srel=('|")?(preload|prefetch)\1/;
const LINK_HREF_REGEX = /.*\shref=('|")?([^>'"\s]+)/;
const STYLE_TAG_REGEX = /<style[^>]*>[\s\S]*?<\/style>/gi;
const STYLE_TYPE_REGEX = /\s+rel=('|")?stylesheet\1.*/;
const STYLE_HREF_REGEX = /.*\shref=('|")?([^>'"\s]+)/;
const STYLE_IGNORE_REGEX = /<style(\s+|\s+.+\s+)ignore(\s*|\s+.*)>/i;
const HTML_COMMENT_REGEX = /<!--([\s\S]*?)-->/g;
const SCRIPT_IGNORE_REGEX = /<script(\s+|\s+.+\s+)ignore(\s*|\s+.*)>/i;
function getInlineCode(match) {
const start = match.indexOf('>') + 1;
const end = match.lastIndexOf('<');
return match.substring(start, end);
}
function request(url, option) {
console.log(url);
if (!window.fetch) {
error("It looks like that your browser doesn't support fetch. Polyfill is needed before you use it.");
}
return fetch(url, Object.assign({ mode: 'cors' }, option)).then((res) => res.text());
function hasProtocol(url) {
return (url.startsWith('//') ||
url.startsWith('http://') ||
url.startsWith('https://'));
}
function lifecycleCheck(lifecycle) {
const keys = ['bootstrap', 'mount', 'unmount'];
keys.forEach((key) => {
if (!(key in lifecycle)) {
error(`It looks like that you didn't export the lifecycle hook [${key}], which would cause a mistake.`);
function getEntirePath(path, baseURI) {
return new URL(path, baseURI).toString();
}
const genLinkReplaceSymbol = (linkHref) => `<!-- link ${linkHref} replaced by import-html-entry -->`;
const genScriptReplaceSymbol = (scriptSrc) => `<!-- script ${scriptSrc} replaced by import-html-entry -->`;
const inlineScriptReplaceSymbol = `<!-- inline scripts replaced by import-html-entry -->`;
const genIgnoreAssetReplaceSymbol = (url) => `<!-- ignore asset ${url || 'file'} replaced by import-html-entry -->`;
function parse(tpl, baseURI) {
let scripts = [];
const styles = [];
let entry = null;
const template = tpl
.replace(HTML_COMMENT_REGEX, '')
.replace(LINK_TAG_REGEX, (match) => {
const styleType = !!match.match(STYLE_TYPE_REGEX);
console.log(styleType);
if (styleType) {
const styleHref = match.match(STYLE_HREF_REGEX);
const styleIgnore = match.match(LINK_IGNORE_REGEX);
if (styleHref) {
const href = styleHref && styleHref[2];
let newHref = href;
if (href && !hasProtocol(href)) {
newHref = getEntirePath(href, baseURI);
}
if (styleIgnore) {
return genIgnoreAssetReplaceSymbol(newHref);
}
styles.push(newHref);
return genLinkReplaceSymbol(newHref);
}
}
const preloadOrPrefetchType = !!match.match(LINK_PRELOAD_OR_PREFETCH_REGEX);
if (preloadOrPrefetchType) {
const linkHref = match.match(LINK_HREF_REGEX);
if (linkHref) {
const href = linkHref[2];
if (href && !hasProtocol(href)) {
const newHref = getEntirePath(href, baseURI);
return match.replace(href, newHref);
}
}
}
return match;
})
.replace(STYLE_TAG_REGEX, (match) => {
if (STYLE_IGNORE_REGEX.test(match)) {
return genIgnoreAssetReplaceSymbol('style file');
}
return match;
})
.replace(ALL_SCRIPT_REGEX, (match) => {
const scriptIgnore = match.match(SCRIPT_IGNORE_REGEX);
if (SCRIPT_TAG_REGEX.test(match) && match.match(SCRIPT_SRC_REGEX)) {
const matchedScriptEntry = match.match(SCRIPT_ENTRY_REGEX);
const matchedScriptSrcMatch = match.match(SCRIPT_SRC_REGEX);
let matchedScriptSrc = matchedScriptSrcMatch && matchedScriptSrcMatch[2];
if (entry && matchedScriptEntry) {
throw new SyntaxError('You should not set multiply entry script!');
}
else {
if (matchedScriptSrc && !hasProtocol(matchedScriptSrc)) {
matchedScriptSrc = getEntirePath(matchedScriptSrc, baseURI);
}
entry = entry || (matchedScriptEntry && matchedScriptSrc);
}
if (scriptIgnore) {
return genIgnoreAssetReplaceSymbol(matchedScriptSrc || 'js file');
}
if (matchedScriptSrc) {
scripts.push(matchedScriptSrc);
return genScriptReplaceSymbol(matchedScriptSrc);
}
return match;
}
else {
if (scriptIgnore) {
return genIgnoreAssetReplaceSymbol('js file');
}
const code = getInlineCode(match);
const isPureCommentBlock = code
.split(/[\r\n]+/)
.every((line) => !line.trim() || line.trim().startsWith('//'));
if (!isPureCommentBlock) {
scripts.push(match);
}
return inlineScriptReplaceSymbol;
}
});
}
const MATCH_ANY_OR_NO_PROPERTY = /["'=\w\s\/]*/;
const SCRIPT_URL_RE = new RegExp('<\\s*script' +
MATCH_ANY_OR_NO_PROPERTY.source +
'(?:src="(.+?)")' +
MATCH_ANY_OR_NO_PROPERTY.source +
'(?:\\/>|>[\\s]*<\\s*\\/script>)?', 'g');
const SCRIPT_CONTENT_RE = new RegExp('<\\s*script' +
MATCH_ANY_OR_NO_PROPERTY.source +
'>([\\w\\W]+?)<\\s*\\/script>', 'g');
const MATCH_NONE_QUOTE_MARK = /[^"]/;
const CSS_URL_RE = new RegExp('<\\s*link[^>]*' +
'href="(' +
MATCH_NONE_QUOTE_MARK.source +
'+.css' +
MATCH_NONE_QUOTE_MARK.source +
'*)"' +
MATCH_ANY_OR_NO_PROPERTY.source +
'>(?:\\s*<\\s*\\/link>)?', 'g');
const STYLE_RE = /<\s*style\s*>([^<]*)<\s*\/style>/g;
const BODY_CONTENT_RE = /<\s*body[^>]*>([\w\W]*)<\s*\/body>/;
const SCRIPT_ANY_RE = /<\s*script[^>]*>[\s\S]*?(<\s*\/script[^>]*>)/g;
const TEST_URL = /^(?:https?):\/\/[-a-zA-Z0-9.]+/;
const REPLACED_BY_BERIAL = 'Script replaced by Berial.';
function importHtml(app) {
return __awaiter(this, void 0, void 0, function* () {
const template = yield request(app.url);
const styleNodes = yield loadCSS(template);
const bodyNode = loadBody(template);
const lifecycle = yield loadScript(template, app.name);
return { lifecycle, styleNodes, bodyNode };
});
}
function loadScript(template, name) {
return __awaiter(this, void 0, void 0, function* () {
const { scriptURLs, scripts } = parseScript(template);
const fetchedScripts = yield Promise.all(scriptURLs.map((url) => request(url)));
const scriptsToLoad = fetchedScripts.concat(scripts);
let bootstrap = [];
let unmount = [];
let mount = [];
scriptsToLoad.forEach((script) => {
const lifecycles = run(script, {})[name];
bootstrap = [...bootstrap, lifecycles.bootstrap];
mount = [...mount, lifecycles.mount];
unmount = [...unmount, lifecycles.unmount];
});
return { bootstrap, unmount, mount };
});
}
function parseScript(template) {
const scriptURLs = [];
const scripts = [];
SCRIPT_URL_RE.lastIndex = SCRIPT_CONTENT_RE.lastIndex = 0;
let match;
while ((match = SCRIPT_URL_RE.exec(template))) {
let captured = match[1].trim();
if (!captured)
continue;
if (!TEST_URL.test(captured)) {
captured = window.location.origin + captured;
}
scriptURLs.push(captured);
}
while ((match = SCRIPT_CONTENT_RE.exec(template))) {
const captured = match[1].trim();
if (!captured)
continue;
scripts.push(captured);
}
scripts = scripts.filter((s) => !!s);
return {
scriptURLs,
scripts
template,
scripts,
styles,
entry: entry || scripts[scripts.length - 1]
};
}
function loadCSS(template) {
function importHtml(app) {
return __awaiter(this, void 0, void 0, function* () {
const { cssURLs, styles } = parseCSS(template);
const fetchedStyles = yield Promise.all(cssURLs.map((url) => request(url)));
return toStyleNodes(fetchedStyles.concat(styles));
function toStyleNodes(styles) {
return styles.map((style) => {
const styleNode = document.createElement('style');
styleNode.appendChild(document.createTextNode(style));
return styleNode;
});
let template = '', scripts, styles;
if (app.scripts) {
scripts = app.scripts || [];
styles = app.styles || [];
}
else {
const tpl = yield request(app.url);
let res = parse(tpl, '');
scripts = res.scripts;
styles = res.styles;
template = res.template;
}
scripts = yield Promise.all(scripts.map((s) => hasProtocol(s)
? request(s)
: s.endsWith('.js')
? request(window.origin + s)
: s));
styles = styles.map((s) => hasProtocol(s) || s.endsWith('.css') ? `<link rel="stylesheet" href="${s}" ></link>` : `<style>${s}<style>`);
template = template;
let lifecycles = null;
scripts.forEach((script) => __awaiter(this, void 0, void 0, function* () {
lifecycles = runScript(script, app.allowList)[app.name];
}));
const dom = document.createDocumentFragment();
const body = document.createElement('template');
let out = '';
styles.forEach((s) => (out += s));
out += template;
body.innerHTML = out;
dom.appendChild(body.content.cloneNode(true));
return { dom, lifecycles };
});
}
function parseCSS(template) {
const cssURLs = [];
const styles = [];
CSS_URL_RE.lastIndex = STYLE_RE.lastIndex = 0;
let match;
while ((match = CSS_URL_RE.exec(template))) {
let captured = match[1].trim();
if (!captured)
continue;
if (!TEST_URL.test(captured)) {
captured = window.location.origin + captured;
}
cssURLs.push(captured);
}
while ((match = STYLE_RE.exec(template))) {
const captured = match[1].trim();
if (!captured)
continue;
styles.push(captured);
}
return {
cssURLs,
styles
};
}
function loadBody(template) {
var _a, _b;
let bodyContent = (_b = (_a = template.match(BODY_CONTENT_RE)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : '';
bodyContent = bodyContent.replace(SCRIPT_ANY_RE, scriptReplacer);
const body = document.createElement('template');
body.innerHTML = bodyContent;
return body;
function scriptReplacer(substring) {
const matchedURL = SCRIPT_URL_RE.exec(substring);
if (matchedURL) {
return `<!-- ${REPLACED_BY_BERIAL} Original script url: ${matchedURL[1]} -->`;
}
return `<!-- ${REPLACED_BY_BERIAL} Original script: inline script -->`;
}
}

@@ -348,14 +347,7 @@ var Status;

})(Status || (Status = {}));
let started = false;
const apps = new Set();
function register(name, url, match) {
apps.add({
name,
url,
match,
status: Status.NOT_LOADED
});
}
function start() {
started = true;
let apps = [];
function register(appArray) {
appArray.forEach((app) => (app.status = Status.NOT_LOADED));
apps = appArray;
hack();
reroute();

@@ -365,8 +357,3 @@ }

const { loads, mounts, unmounts } = getAppChanges();
return started ? perform() : init();
function init() {
return __awaiter(this, void 0, void 0, function* () {
yield Promise.all(loads.map(runLoad));
});
}
perform();
function perform() {

@@ -392,3 +379,3 @@ return __awaiter(this, void 0, void 0, function* () {

apps.forEach((app) => {
const isActive = app.match(window.location);
const isActive = app.path(window.location);
switch (app.status) {

@@ -406,2 +393,3 @@ case Status.NOT_LOADED:

!isActive && unmounts.push(app);
break;
}

@@ -423,12 +411,9 @@ });

let mixinLife = mapMixin();
app.host = (yield loadShadowDOM(app));
const { lifecycle: selfLife, bodyNode, styleNodes } = yield importHtml(app);
lifecycleCheck(selfLife);
(_a = app.host.shadowRoot) === null || _a === void 0 ? void 0 : _a.appendChild(bodyNode.content.cloneNode(true));
for (const k of styleNodes)
app.host.shadowRoot.insertBefore(k, app.host.shadowRoot.firstChild);
app.host = yield loadShadowDOM(app);
const { dom, lifecycles } = yield importHtml(app);
(_a = app.host) === null || _a === void 0 ? void 0 : _a.appendChild(dom);
app.status = Status.NOT_BOOTSTRAPPED;
app.bootstrap = compose(mixinLife.bootstrap.concat(selfLife.bootstrap));
app.mount = compose(mixinLife.mount.concat(selfLife.mount));
app.unmount = compose(mixinLife.unmount.concat(selfLife.unmount));
app.bootstrap = compose(mixinLife.bootstrap.concat(lifecycles.bootstrap));
app.mount = compose(mixinLife.mount.concat(lifecycles.mount));
app.unmount = compose(mixinLife.unmount.concat(lifecycles.unmount));
delete app.loaded;

@@ -441,21 +426,16 @@ return app;

function loadShadowDOM(app) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve) => {
class Berial extends HTMLElement {
static get tag() {
return app.name;
}
connectedCallback() {
resolve(this);
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
return new Promise((resolve, reject) => {
class Berial extends HTMLElement {
static get tag() {
return app.name;
}
const hasDef = window.customElements.get(app.name);
if (!hasDef) {
customElements.define(app.name, Berial);
constructor() {
super();
resolve(this.attachShadow({ mode: 'open' }));
}
});
}
const hasDef = window.customElements.get(app.name);
if (!hasDef) {
customElements.define(app.name, Berial);
}
});

@@ -496,2 +476,10 @@ }

}
function hack() {
window.addEventListener = hackEventListener(window.addEventListener);
window.removeEventListener = hackEventListener(window.removeEventListener);
window.history.pushState = hackHistory(window.history.pushState);
window.history.replaceState = hackHistory(window.history.replaceState);
window.addEventListener('hashchange', reroute);
window.addEventListener('popstate', reroute);
}
const captured = {

@@ -501,50 +489,33 @@ hashchange: [],

};
window.addEventListener('hashchange', reroute);
window.addEventListener('popstate', reroute);
const oldAEL = window.addEventListener;
const oldREL = window.removeEventListener;
window.addEventListener = function (name, fn) {
if ((name === 'hashchange' || name === 'popstate') &&
!captured[name].some((l) => l == fn)) {
captured[name].push(fn);
return;
}
return oldAEL.apply(this, arguments);
};
window.removeEventListener = function (name, fn) {
if (name === 'hashchange' || name === 'popstate') {
captured[name] = captured[name].filter((l) => l !== fn);
return;
}
return oldREL.apply(this, arguments);
};
function polyfillHistory(fn) {
function hackEventListener(func) {
return function (name, fn) {
if (name === 'hashchange' || name === 'popstate') {
if (!captured[name].some((l) => l == fn)) {
captured[name].push(fn);
return;
}
else {
captured[name] = captured[name].filter((l) => l !== fn);
return;
}
}
return func.apply(this, arguments);
};
}
function hackHistory(fn) {
return function () {
const before = window.location.href;
fn.apply(this, arguments);
fn.apply(window.history, arguments);
const after = window.location.href;
if (before !== after) {
reroute(new PopStateEvent('popstate'));
new PopStateEvent('popstate');
reroute();
}
};
}
window.history.pushState = polyfillHistory(window.history.pushState);
window.history.replaceState = polyfillHistory(window.history.replaceState);
const Berial = {
start,
register,
importHtml,
run,
mixin,
use
};
exports.Berial = Berial;
exports.default = Berial;
exports.importHtml = importHtml;
exports.mixin = mixin;
exports.register = register;
exports.run = run;
exports.start = start;
exports.runScript = runScript;
exports.use = use;

@@ -551,0 +522,0 @@

{
"name": "berial",
"version": "0.0.7",
"version": "0.0.8",
"description": "micro frontend",

@@ -10,4 +10,4 @@ "main": "dist/berial.js",

"build": "rollup -c",
"dev": "rollup -c --watch",
"check": "run-p fmt-check lint",
"dev": "rollup -c --watch",
"fix": "run-s \"lint -- --fix\"",

@@ -17,5 +17,6 @@ "fmt": "run-s \"fmt-check -- --write\"",

"lint": "eslint **/*.ts",
"serve": "serve ./demo/",
"type": "tsc --project tsconfig.json --skipLibCheck --noEmit",
"test": "karma start karma.conf.js"
"demo:init": "cd example/parent && npm run install:all && cd ../..",
"demo:serve": "npm run build && cd example/parent && npm start && cd ../..",
"demo:build": "npm run build && cd example/parent && npm run build:all"
},

@@ -34,10 +35,3 @@ "repository": {

"devDependencies": {
"@rollup/plugin-replace": "^2.3.3",
"@types/chai": "^4.2.12",
"@types/mocha": "^8.0.3",
"@types/node": "^10.12.24",
"@types/sinon": "^9.0.5",
"@typescript-eslint/eslint-plugin": "^3.7.0",
"@typescript-eslint/parser": "^3.7.0",
"chai": "^4.2.0",
"cross-env": "^7.0.2",
"eslint": "^7.5.0",

@@ -47,24 +41,20 @@ "eslint-config-prettier": "^6.11.0",

"eslint-plugin-react": "^7.20.5",
"http-server": "^0.12.3",
"husky": "^4.2.5",
"karma": "^5.1.1",
"karma-chrome-launcher": "^3.1.0",
"karma-mocha": "^2.0.1",
"karma-spec-reporter": "0.0.32",
"karma-typescript": "^5.1.0",
"mocha": "^8.1.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.5",
"puppeteer": "^5.2.1",
"rollup": "^2.23.0",
"rollup-plugin-dts": "^1.4.11",
"rollup-plugin-typescript2": "^0.27.1",
"serve": "^11.3.2",
"sinon": "^9.0.3",
"tslib": "^2.0.0",
"typescript": "^3.9.7"
"typescript": "^3.9.7",
"@typescript-eslint/eslint-plugin": "^3.7.0",
"@typescript-eslint/parser": "^3.7.0"
},
"husky": {
"hooks": {
"pre-commit": "run-p fmt fix;"
"pre-commit": "npm run fmt && npm run fix;"
}
}
}

@@ -8,3 +8,3 @@ import { mixin } from 'berial'

async function boostrap(app): Promise<void> {
const shadowRoot = app.host.shadowRoot
const shadowRoot = app.host
const define = Object.defineProperty

@@ -17,3 +17,2 @@ const fromNode = shadowRoot,

const Event = fromEvent.constructor
// @ts-ignore
const toEvent = new Event(eventName, {

@@ -20,0 +19,0 @@ ...fromEvent,

@@ -10,16 +10,10 @@ <p align="center"><img src="https://avatars0.githubusercontent.com/u/68577605?s=200&v=4" alt="berial logo" width="150"></p>

### Feature
### Why Berial
- lifecycle loop
Berial is a new approach to a popular idea: build a javascript framework for front-end microservices.
- shadow dom
There are any wonderful features of it, such as Asynchronous rendering pipeline, Web components (shadow DOM + scoped css), JavaScript sandbox (Proxy).
- scoped css
Note: diffence form fre, Berial will pay attention to business value.
- proxy sandbox
- html loader
- mixins
### Use

@@ -29,38 +23,19 @@

<one-app></one-app>
<two-app></two-app>
```
```js
import { register, start } from 'berial'
register(
'one-app',
'http://localhost:3000/one.html',
(location) => location.hash === '#/app1'
)
register(
'two-app',
'http://localhost:3000/two.html',
(location) => location.hash === '#/app2'
)
start()
<script type="module">
import { register } from 'berial'
register([{
name: 'one-app'
url: '1.html'
allowList: {} // 沙箱白名单
},{
name: 'two-app'
scripts: ['2.js'] // 可选
}])
</script>
```
### mixins
```js
import { mixin } from 'berial'
mixin({
bootstrap: () => {},
mount: () => {},
unmount: () => {}
})
```
mixins will apply all apps
### License
MIT ©yisar ©h-a-n-a
import typescript from 'rollup-plugin-typescript2'
import replace from '@rollup/plugin-replace'

@@ -7,9 +6,9 @@ export default {

output: [
// {
// file: 'dist/es/berial.esm.js',
// format: 'esm',
// sourcemap: true,
// name: 'berial'
// },
{ file: 'dist/berial.d.ts', format: 'esm', exports: 'named' },
{
file: 'dist/berial.esm.js',
format: 'esm',
sourcemap: true
},
{
file: 'dist/berial.js',

@@ -22,11 +21,8 @@ format: 'umd',

plugins: [
replace({
__DEV__: process.env.NODE_ENV !== 'production'
}),
typescript({
tsconfig: 'tsconfig.json',
removeComments: true,
useTsconfigDeclarationDir: true
})
useTsconfigDeclarationDir: true,
}),
]
}

@@ -1,164 +0,192 @@

import type { App, PromiseFn, Lifecycles, ProxyType } from './types'
import { run } from './sandbox'
import { request } from './util'
import { runScript } from './sandbox'
const MATCH_ANY_OR_NO_PROPERTY = /["'=\w\s\/]*/
const SCRIPT_URL_RE = new RegExp(
'<\\s*script' +
MATCH_ANY_OR_NO_PROPERTY.source +
'(?:src="(.+?)")' +
MATCH_ANY_OR_NO_PROPERTY.source +
'(?:\\/>|>[\\s]*<\\s*\\/script>)?',
'g'
)
const SCRIPT_CONTENT_RE = new RegExp(
'<\\s*script' +
MATCH_ANY_OR_NO_PROPERTY.source +
'>([\\w\\W]+?)<\\s*\\/script>',
'g'
)
const MATCH_NONE_QUOTE_MARK = /[^"]/
const CSS_URL_RE = new RegExp(
'<\\s*link[^>]*' +
'href="(' +
MATCH_NONE_QUOTE_MARK.source +
'+.css' +
MATCH_NONE_QUOTE_MARK.source +
'*)"' +
MATCH_ANY_OR_NO_PROPERTY.source +
'>(?:\\s*<\\s*\\/link>)?',
'g'
)
const STYLE_RE = /<\s*style\s*>([^<]*)<\s*\/style>/g
const BODY_CONTENT_RE = /<\s*body[^>]*>([\w\W]*)<\s*\/body>/
const SCRIPT_ANY_RE = /<\s*script[^>]*>[\s\S]*?(<\s*\/script[^>]*>)/g
const TEST_URL = /^(?:https?):\/\/[-a-zA-Z0-9.]+/
const ALL_SCRIPT_REGEX = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi
const SCRIPT_TAG_REGEX = /<(script)\s+((?!type=('|')text\/ng-template\3).)*?>.*?<\/\1>/is
const SCRIPT_SRC_REGEX = /.*\ssrc=('|")?([^>'"\s]+)/
const SCRIPT_ENTRY_REGEX = /.*\sentry\s*.*/
const LINK_TAG_REGEX = /<(link)\s+.*?>/gi
const LINK_IGNORE_REGEX = /.*ignore\s*.*/
const LINK_PRELOAD_OR_PREFETCH_REGEX = /\srel=('|")?(preload|prefetch)\1/
const LINK_HREF_REGEX = /.*\shref=('|")?([^>'"\s]+)/
const STYLE_TAG_REGEX = /<style[^>]*>[\s\S]*?<\/style>/gi
const STYLE_TYPE_REGEX = /\s+rel=('|")?stylesheet\1.*/
const STYLE_HREF_REGEX = /.*\shref=('|")?([^>'"\s]+)/
const STYLE_IGNORE_REGEX = /<style(\s+|\s+.+\s+)ignore(\s*|\s+.*)>/i
const HTML_COMMENT_REGEX = /<!--([\s\S]*?)-->/g
const SCRIPT_IGNORE_REGEX = /<script(\s+|\s+.+\s+)ignore(\s*|\s+.*)>/i
const REPLACED_BY_BERIAL = 'Script replaced by Berial.'
export async function importHtml(
app: App
): Promise<{
lifecycle: Lifecycles
styleNodes: HTMLStyleElement[]
bodyNode: HTMLTemplateElement
}> {
const template = await request(app.url as string)
const styleNodes = await loadCSS(template)
const bodyNode = loadBody(template)
const lifecycle = await loadScript(template, app.name)
return { lifecycle, styleNodes, bodyNode }
export function getInlineCode(match: any): string {
const start = match.indexOf('>') + 1
const end = match.lastIndexOf('<')
return match.substring(start, end)
}
export async function loadScript(
template: string,
name: string
): Promise<Lifecycles> {
const { scriptURLs, scripts } = parseScript(template)
const fetchedScripts = await Promise.all(
scriptURLs.map((url) => request(url))
function hasProtocol(url: string): any {
return (
url.startsWith('//') ||
url.startsWith('http://') ||
url.startsWith('https://')
)
const scriptsToLoad = fetchedScripts.concat(scripts)
let bootstrap: PromiseFn[] = []
let unmount: PromiseFn[] = []
let mount: PromiseFn[] = []
scriptsToLoad.forEach((script) => {
const lifecycles = run(script, {})[name]
bootstrap = [...bootstrap, lifecycles.bootstrap]
mount = [...mount, lifecycles.mount]
unmount = [...unmount, lifecycles.unmount]
})
return { bootstrap, unmount, mount }
}
function parseScript(
template: string
): {
scriptURLs: string[]
scripts: string[]
} {
const scriptURLs: string[] = []
const scripts: string[] = []
SCRIPT_URL_RE.lastIndex = SCRIPT_CONTENT_RE.lastIndex = 0
let match
while ((match = SCRIPT_URL_RE.exec(template))) {
let captured = match[1].trim()
if (!captured) continue
if (!TEST_URL.test(captured)) {
captured = window.location.origin + captured
}
scriptURLs.push(captured)
}
while ((match = SCRIPT_CONTENT_RE.exec(template))) {
const captured = match[1].trim()
if (!captured) continue
scripts.push(captured)
}
return {
scriptURLs,
scripts
}
function getEntirePath(path: string, baseURI: string): string {
return new URL(path, baseURI).toString()
}
async function loadCSS(template: string): Promise<HTMLStyleElement[]> {
const { cssURLs, styles } = parseCSS(template)
const fetchedStyles = await Promise.all(cssURLs.map((url) => request(url)))
return toStyleNodes(fetchedStyles.concat(styles))
export const genLinkReplaceSymbol = (linkHref: any): string =>
`<!-- link ${linkHref} replaced by import-html-entry -->`
export const genScriptReplaceSymbol = (scriptSrc: any): string =>
`<!-- script ${scriptSrc} replaced by import-html-entry -->`
export const inlineScriptReplaceSymbol = `<!-- inline scripts replaced by import-html-entry -->`
export const genIgnoreAssetReplaceSymbol = (url: any): string =>
`<!-- ignore asset ${url || 'file'} replaced by import-html-entry -->`
function toStyleNodes(styles: string[]): HTMLStyleElement[] {
return styles.map((style) => {
const styleNode = document.createElement('style')
styleNode.appendChild(document.createTextNode(style))
return styleNode
export function parse(tpl: string, baseURI: string): any {
let scripts: string[] = []
const styles: string[] = []
let entry: any = null
const template = tpl
.replace(HTML_COMMENT_REGEX, '')
.replace(LINK_TAG_REGEX, (match) => {
const styleType = !!match.match(STYLE_TYPE_REGEX)
console.log(styleType)
if (styleType) {
const styleHref = match.match(STYLE_HREF_REGEX)
const styleIgnore = match.match(LINK_IGNORE_REGEX)
if (styleHref) {
const href = styleHref && styleHref[2]
let newHref = href
if (href && !hasProtocol(href)) {
newHref = getEntirePath(href, baseURI)
}
if (styleIgnore) {
return genIgnoreAssetReplaceSymbol(newHref)
}
styles.push(newHref)
return genLinkReplaceSymbol(newHref)
}
}
const preloadOrPrefetchType = !!match.match(
LINK_PRELOAD_OR_PREFETCH_REGEX
)
if (preloadOrPrefetchType) {
const linkHref = match.match(LINK_HREF_REGEX)
if (linkHref) {
const href = linkHref[2]
if (href && !hasProtocol(href)) {
const newHref = getEntirePath(href, baseURI)
return match.replace(href, newHref)
}
}
}
return match
})
.replace(STYLE_TAG_REGEX, (match) => {
if (STYLE_IGNORE_REGEX.test(match)) {
return genIgnoreAssetReplaceSymbol('style file')
}
return match
})
.replace(ALL_SCRIPT_REGEX, (match) => {
const scriptIgnore = match.match(SCRIPT_IGNORE_REGEX)
if (SCRIPT_TAG_REGEX.test(match) && match.match(SCRIPT_SRC_REGEX)) {
const matchedScriptEntry = match.match(SCRIPT_ENTRY_REGEX)
const matchedScriptSrcMatch = match.match(SCRIPT_SRC_REGEX)
let matchedScriptSrc = matchedScriptSrcMatch && matchedScriptSrcMatch[2]
if (entry && matchedScriptEntry) {
throw new SyntaxError('You should not set multiply entry script!')
} else {
if (matchedScriptSrc && !hasProtocol(matchedScriptSrc)) {
matchedScriptSrc = getEntirePath(matchedScriptSrc, baseURI)
}
entry = entry || (matchedScriptEntry && matchedScriptSrc)
}
if (scriptIgnore) {
return genIgnoreAssetReplaceSymbol(matchedScriptSrc || 'js file')
}
if (matchedScriptSrc) {
scripts.push(matchedScriptSrc)
return genScriptReplaceSymbol(matchedScriptSrc)
}
return match
} else {
if (scriptIgnore) {
return genIgnoreAssetReplaceSymbol('js file')
}
const code = getInlineCode(match)
const isPureCommentBlock = code
.split(/[\r\n]+/)
.every((line) => !line.trim() || line.trim().startsWith('//'))
if (!isPureCommentBlock) {
scripts.push(match)
}
return inlineScriptReplaceSymbol
}
})
scripts = scripts.filter((s: string) => !!s)
return {
template,
scripts,
styles,
entry: entry || scripts[scripts.length - 1]
}
}
function parseCSS(
template: string
): {
cssURLs: string[]
styles: string[]
} {
const cssURLs: string[] = []
const styles: string[] = []
CSS_URL_RE.lastIndex = STYLE_RE.lastIndex = 0
let match
while ((match = CSS_URL_RE.exec(template))) {
let captured = match[1].trim()
if (!captured) continue
if (!TEST_URL.test(captured)) {
captured = window.location.origin + captured
}
cssURLs.push(captured)
}
while ((match = STYLE_RE.exec(template))) {
const captured = match[1].trim()
if (!captured) continue
styles.push(captured)
}
return {
cssURLs,
export async function importHtml(app: any): Promise<any> {
let template = '',
scripts,
styles
if (app.scripts) {
scripts = app.scripts || []
styles = app.styles || []
} else {
const tpl = await request(app.url as string)
let res = parse(tpl, '')
scripts = res.scripts
styles = res.styles
template = res.template
}
}
function loadBody(template: string): HTMLTemplateElement {
let bodyContent = template.match(BODY_CONTENT_RE)?.[1] ?? ''
bodyContent = bodyContent.replace(SCRIPT_ANY_RE, scriptReplacer)
scripts = await Promise.all(
scripts.map((s: string) =>
hasProtocol(s)
? request(s)
: s.endsWith('.js')
? request(window.origin + s)
: s
)
)
styles = styles.map((s: string) =>
hasProtocol(s) || s.endsWith('.css') ? `<link rel="stylesheet" href="${s}" ></link>` : `<style>${s}<style>`
)
template = template
let lifecycles = null
scripts.forEach(async (script: any) => {
lifecycles = runScript(script, app.allowList)[app.name]
})
const dom = document.createDocumentFragment()
const body = document.createElement('template')
body.innerHTML = bodyContent
return body
function scriptReplacer(substring: string): string {
const matchedURL = SCRIPT_URL_RE.exec(substring)
if (matchedURL) {
return `<!-- ${REPLACED_BY_BERIAL} Original script url: ${matchedURL[1]} -->`
}
return `<!-- ${REPLACED_BY_BERIAL} Original script: inline script -->`
}
let out = ''
styles.forEach((s: string) => (out += s))
out += template
body.innerHTML = out
dom.appendChild(body.content.cloneNode(true))
return { dom, lifecycles }
}

@@ -1,17 +0,6 @@

import { start, register } from './app'
import { register } from './entity'
import { mixin, use } from './mixin'
import { importHtml } from './html-loader'
import { run } from './sandbox'
import { runScript } from './sandbox'
export const Berial = {
start,
register,
importHtml,
run,
mixin,
use
}
export default Berial
export { start, register, importHtml, run, mixin, use }
export { register, importHtml, runScript, mixin, use }

@@ -1,14 +0,14 @@

import type { Lifecycles } from './types'
import type { Lifecycles, Mixin, Plugin } from './types'
const mixins: any = new Set()
const plugins: any = new Set()
const mixins: Set<Mixin> = new Set()
const plugins: Set<Plugin> = new Set()
export function use(plugin: (args: any) => any, ...args: any): void {
export function use(plugin: Plugin, ...args: any[]): void {
if (!plugins.has(plugin)) {
plugins.add(plugin)
plugin(args)
plugin(...args)
}
}
export function mixin(mix: any): void {
export function mixin(mix: Mixin): void {
if (!mixins.has(mix)) {

@@ -20,3 +20,3 @@ mixins.add(mix)

export function mapMixin(): Lifecycles {
const out: any = {
const out: Lifecycles = {
load: [],

@@ -27,4 +27,4 @@ bootstrap: [],

}
mixins.forEach((item: any) => {
item.load && out.load.push(item.load)
mixins.forEach((item: Mixin) => {
item.load && out.load!.push(item.load)
item.bootstrap && out.bootstrap.push(item.bootstrap)

@@ -31,0 +31,0 @@ item.mount && out.mount.push(item.mount)

@@ -1,125 +0,123 @@

export function run(code: string, options: any = {}): any {
export function runScript(code: string, allow: any = {}): any {
try {
if (checkSyntax(code)) {
const handler = {
get(obj: any, prop: string): any {
return Reflect.has(obj, prop) ? obj[prop] : null
},
set(obj: any, prop: string, value: any): boolean {
Reflect.set(obj, prop, value)
return true
},
has(obj: any, prop: string): boolean {
return obj && Reflect.has(obj, prop)
}
const handler = {
get(obj: any, prop: string): any {
return Reflect.has(obj, prop) ? obj[prop] : null
},
set(obj: any, prop: string, value: any): boolean {
Reflect.set(obj, prop, value)
return true
},
has(obj: any, prop: string): boolean {
return obj && Reflect.has(obj, prop)
}
const captureHandler = {
get(obj: any, prop: string): any {
return Reflect.get(obj, prop)
},
set(): boolean {
return true
},
has(): boolean {
return true
}
}
const captureHandler = {
get(obj: any, prop: string): any {
return Reflect.get(obj, prop)
},
set(): boolean {
return true
},
has(): boolean {
return true
}
}
const allowList = {
IS_BERIAL_SANDBOX: true,
__proto__: null,
console,
String,
Number,
Array,
Symbol,
Math,
Object,
Promise,
RegExp,
JSON,
Date,
Function,
parseInt,
document,
navigator,
location,
performance,
MessageChannel,
SVGElement,
HTMLElement,
HTMLIFrameElement,
history,
Map,
Set,
WeakMap,
WeakSet,
Error,
localStorage,
decodeURI,
encodeURI,
decodeURIComponent,
encodeURIComponent,
fetch: fetch.bind(window),
setTimeout: setTimeout.bind(window),
clearTimeout: clearTimeout.bind(window),
setInterval: setInterval.bind(window),
clearInterval: clearInterval.bind(window),
requestAnimationFrame: requestAnimationFrame.bind(window),
cancelAnimationFrame: cancelAnimationFrame.bind(window),
addEventListener: addEventListener.bind(window),
removeEventListener: removeEventListener.bind(window),
// eslint-disable-next-line no-shadow
eval: function (code: string): any {
return run('return ' + code, null)
},
alert: function (): void {
alert('Sandboxed alert:' + arguments[0])
},
// position related properties
innerHeight,
innerWidth,
outerHeight,
outerWidth,
pageXOffset,
pageYOffset,
screen,
screenLeft,
screenTop,
screenX,
screenY,
scrollBy,
scrollTo,
scrollX,
scrollY,
// custom allow list
...(options.allowList || {})
}
const allowList = {
IS_BERIAL_SANDBOX: true,
__proto__: null,
console,
String,
Number,
Array,
Symbol,
Math,
Object,
Promise,
RegExp,
JSON,
Date,
Function,
parseInt,
document,
location,
performance,
MessageChannel,
SVGElement,
HTMLElement,
HTMLIFrameElement,
history,
Map,
Set,
WeakMap,
WeakSet,
Error,
localStorage,
decodeURI,
encodeURI,
decodeURIComponent,
encodeURIComponent,
fetch: fetch.bind(window),
setTimeout: setTimeout.bind(window),
clearTimeout: clearTimeout.bind(window),
setInterval: setInterval.bind(window),
clearInterval: clearInterval.bind(window),
requestAnimationFrame: requestAnimationFrame.bind(window),
cancelAnimationFrame: cancelAnimationFrame.bind(window),
addEventListener: addEventListener.bind(window),
removeEventListener: removeEventListener.bind(window),
// eslint-disable-next-line no-shadow
eval: function (code: string): any {
return runScript('return ' + code, {})
},
alert: function (): void {
alert('Sandboxed alert:' + arguments[0])
},
// position related properties
innerHeight,
innerWidth,
outerHeight,
outerWidth,
pageXOffset,
pageYOffset,
screen,
screenLeft,
screenTop,
screenX,
screenY,
scrollBy,
scrollTo,
scrollX,
scrollY,
// custom allow list
...allow
}
if (!Object.isFrozen(String.prototype)) {
for (const k in allowList) {
const fn = allowList[k]
if (typeof fn === 'object' && fn.prototype) {
Object.freeze(fn.prototype)
}
if (typeof fn === 'function') {
Object.freeze(fn)
}
if (!Object.isFrozen(String.prototype)) {
for (const k in allowList) {
const fn = allowList[k]
if (typeof fn === 'object' && fn.prototype) {
Object.freeze(fn.prototype)
}
if (typeof fn === 'function') {
Object.freeze(fn)
}
}
const proxy = new Proxy(allowList, handler)
const capture = new Proxy(
{
__proto__: null,
proxy,
globalThis: new Proxy(allowList, handler),
window: new Proxy(allowList, handler),
self: new Proxy(allowList, handler)
},
captureHandler
)
return Function(
'proxy',
'capture',
`with(capture) {
}
const proxy = new Proxy(allowList, handler)
const capture = new Proxy(
{
__proto__: null,
proxy,
globalThis: new Proxy(allowList, handler),
window: new Proxy(allowList, handler),
self: new Proxy(allowList, handler)
},
captureHandler
)
return Function(
'proxy',
'capture',
`with(capture) {
with(proxy) {

@@ -133,4 +131,3 @@ return (function(){

}`
)(proxy, capture)
}
)(proxy, capture)
} catch (e) {

@@ -140,8 +137,1 @@ throw e

}
function checkSyntax(code: string): boolean {
Function(code)
if (/\bimport\s*(?:[(]|\/[*]|\/\/|<!--|-->)/.test(code)) {
throw new Error('Dynamic imports are blocked')
}
return true
}

@@ -1,2 +0,2 @@

import type { Status } from './app'
import type { Status } from './entity'

@@ -12,7 +12,17 @@ export type Lifecycles = ToArray<Lifecycle>

export type Mixin = {
load?: PromiseFn
mount?: PromiseFn
unmount?: PromiseFn
bootstrap?: PromiseFn
}
export type Plugin = (...args: any[]) => any
export type App = {
name: string
node: HTMLElement
url: ((props: App['props']) => Lifecycle) | string
match: (location: Location) => boolean
host: HTMLElement
host: DocumentFragment
props: Record<string, unknown>

@@ -19,0 +29,0 @@ status: Status

@@ -20,9 +20,2 @@ import type { Lifecycle, Lifecycles } from './types'

export function request(url: string, option?: RequestInit): Promise<string> {
console.log(url)
if (!window.fetch) {
error(
"It looks like that your browser doesn't support fetch. Polyfill is needed before you use it."
)
}
return fetch(url, {

@@ -34,15 +27,4 @@ mode: 'cors',

export function lifecycleCheck(lifecycle: Lifecycle | Lifecycles): void {
const keys = ['bootstrap', 'mount', 'unmount']
keys.forEach((key) => {
if (!(key in lifecycle)) {
error(
`It looks like that you didn't export the lifecycle hook [${key}], which would cause a mistake.`
)
}
})
}
export function reverse<T>(arr: T[]): T[] {
return Array.from(arr).reverse()
}

@@ -5,6 +5,4 @@ {

"sourceMap": true,
"declaration": true,
"declarationDir": "dist/types/",
"noImplicitAny": true,
"removeComments":true,
"removeComments": true,
"target": "es6",

@@ -16,6 +14,10 @@ "module": "es6",

"downlevelIteration": true,
"noImplicitThis": false,
"jsx": "react",
"lib": ["es6", "dom"],
"baseUrl": ".",
"outDir": "./dist"
"outDir": "./dist",
"paths": {
"@/*": ["./src/*"]
}
},

@@ -22,0 +24,0 @@ "include": ["src/**/*"],

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc