als-render
Advanced tools
Comparing version 0.2.31 to 0.3.0
@@ -1,19 +0,19 @@ | ||
const { readdirSync, readFileSync, writeFileSync } = require('fs') | ||
const { readdirSync, readFileSync, writeFileSync,statSync } = require('fs') | ||
const {join} = require('path') | ||
const UglifyJS = require("uglify-js"); | ||
const root = join(__dirname,'..') | ||
const libDir =join(root,'lib') | ||
const libDir = join(root,'lib') | ||
const nodeModulesDir = join(root,'node_modules') | ||
const fileNames = readdirSync(libDir) | ||
const ReplaceBetween = readFileSync(`${nodeModulesDir}/als-replace-between/replace-between.js`) | ||
const fileNames = readdirSync(libDir,{recursive:true}) | ||
const Require = readFileSync(`${nodeModulesDir}/als-require/require.js`) | ||
const renderConent = readFileSync(join(__dirname,'render.js'),'utf-8').replace(/.*require\(.*\).*?|module.exports.*/g,'') | ||
const renderConent = readFileSync(join(__dirname,'render.js'),'utf-8') | ||
.replace(/.*require\(.*\).*?|module.exports.*/g,'') | ||
const excludes = ['index.js','bundle.js'] | ||
const contents = fileNames.map(filename => { | ||
if(filename === 'index.js') return '' | ||
let content = readFileSync(`${libDir}/${filename}`,'utf-8') | ||
const path = `${libDir}/${filename}` | ||
if(excludes.includes(filename) || statSync(path).isDirectory()) return '' | ||
let content = readFileSync(path,'utf-8') | ||
return content.replace(/.*require\(.*\).*?|module.exports.*/g,'') | ||
}).filter(Boolean); | ||
contents.unshift(ReplaceBetween) | ||
contents.unshift(Require) | ||
contents.unshift(renderConent) | ||
contents.unshift(Require,renderConent) | ||
let ready = `const render = (function() { | ||
@@ -26,5 +26,4 @@ ${contents.join('\n')} | ||
const minified = UglifyJS.minify(ready).code | ||
writeFileSync(join(root,'render.min.js'),minified,'utf-8') | ||
writeFileSync(join(root,'render.js'),ready,'utf-8') |
const renderJsx = require('../lib/render-jsx') | ||
const newContext = require('../lib/new-context') | ||
const bind = require('../lib/bind') | ||
const { parse } = require('als-object-serializer') | ||
const Simple = require('als-simple-css') | ||
async function render(path,selector='body') { | ||
let styles = [] | ||
const context = newContext() | ||
async function render(path, selector = 'body', data = {}) { | ||
const context = { actions: [], components: {}, counter: 0 } | ||
const app = new Require(path) | ||
await app.getContent() | ||
for(const path in app.contents) { | ||
app.contents[path] = renderJsx(app.contents[path],styles) | ||
for (const path in app.contents) { | ||
app.contents[path] = renderJsx(app.contents[path],path) | ||
} | ||
if(styles.length) { | ||
const simple = new Simple(styles.map((style,i) => ({[`[style${i}]`]:parse(style)}))) | ||
styles = `<style>${simple.stylesheet()}</style>` | ||
} else styles = '' | ||
const result = app.build({},context,'context') | ||
const rawHtml = result()+styles | ||
bind(selector,rawHtml,context) | ||
const resultFn = app.build({}, context, 'context') | ||
const rawHtml = resultFn(data).trim() | ||
bind(selector, rawHtml, context) | ||
} | ||
module.exports = render |
function bind(selector,rawHtml,context,update = true) { | ||
function getElement(selector) { | ||
function actionToElement(selector,action) { | ||
const element = document.querySelector(selector) | ||
if(!element) throw `The element with selector "${selector}" not exists` | ||
return element | ||
if(!element) return | ||
action(element) | ||
} | ||
if(update) getElement(selector).innerHTML = rawHtml | ||
context.actions.forEach(({ fn, id, event }) => { | ||
getElement('#'+id).addEventListener(event, fn); | ||
if(update) actionToElement(selector,element => element.innerHTML = rawHtml) | ||
context.actions.forEach(({key,selector,event,fn}) => { | ||
actionToElement(selector,element => element.addEventListener(event,fn)) | ||
}); | ||
@@ -11,0 +11,0 @@ context.actions = [] |
const renderJsx = require('./render-jsx') | ||
const newContext = require('./new-context') | ||
const bind = require('./bind') | ||
const Require = require('als-require') | ||
const Simple = require('als-simple-css') | ||
const { parse } = require('als-object-serializer') | ||
Require.requireClassMinified | ||
const UglifyJS = require("uglify-js"); | ||
const bindSrginfified = UglifyJS.minify(bind.toString()).code + '\n' | ||
const bundleFn = require('./bundle') | ||
function render(path) { | ||
let styles = [] | ||
const context = newContext() | ||
function render(path,selector = 'body',data={}) { | ||
const app = new Require(path) | ||
app.getContent() | ||
for (const path in app.contents) { | ||
app.contents[path] = renderJsx(app.contents[path],styles) | ||
app.contents[path] = renderJsx(app.contents[path],path) | ||
} | ||
if(styles.length) { | ||
const simple = new Simple(styles.map((style,i) => ({[`[style${i}]`]:parse(style)}))) | ||
styles = `<style>${simple.stylesheet()}</style>` | ||
} else styles = '' | ||
const result = app.build({}, context, 'context') | ||
const rawHtml = result()+styles | ||
function bundle(selector = 'body', update = false, minified = true) { | ||
return app.bundle({ | ||
script: bindSrginfified + `\nbind('${selector}',result(),context,${update.toString()})`, | ||
context: newContext(), | ||
minified | ||
}) | ||
} | ||
return { rawHtml, bundle } | ||
const context = {actions:[],components:{},counter:0} | ||
const resultFn = app.build({}, context, 'context') | ||
const rawHtml = resultFn(data).trim() | ||
return { rawHtml, bundle:bundleFn(app,selector,data) } | ||
} | ||
module.exports = render |
@@ -1,22 +0,19 @@ | ||
const ReplaceBetween = require('als-replace-between') | ||
const replaceComponents = require('./replace-components') | ||
const replaceActions = require('./replace-actions') | ||
const removeComments = str => str.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//gm, '') | ||
const buildTree = require('./tree/build-tree') | ||
const findJsx = require('./jsx/find-jsx') | ||
const buildComponent = require('./component') | ||
function renderJsx(content,path) { | ||
const componentName = path.split('/').pop().replace('.js','') | ||
const isComponent = /[A-z]/.test(componentName[0]) | ||
const r = new RegExp('^.*'+componentName+'\\s*?\\(([\\s\\S]*?)\\)\\s*?\\{([\\s\\S]*)\\}','m') | ||
if(!isComponent) return content | ||
content = content.replace(r,(m,params,componentBody) => { | ||
const nodes = findJsx(componentBody) | ||
const tree = buildTree(nodes) | ||
let newBody = tree[0] | ||
return buildComponent(newBody,componentName,params) | ||
}) | ||
// console.log(content) | ||
return content | ||
} | ||
function renderJsx(content,styles) { | ||
const transformer = new ReplaceBetween(content, new RegExp(/\(\s*?</), new RegExp(/\>\s*?\)/)) | ||
if (transformer.children.length === 0) return content | ||
transformer.before = removeComments(transformer.before) | ||
transformer.after = removeComments(transformer.after) | ||
const componentNames = [] | ||
transformer.walkNodes([node => replaceComponents(node, componentNames)]) | ||
transformer.children.forEach(node => replaceActions(node,styles)); | ||
const fns = componentNames.map(cName => `context.buildComponent(${cName},'${cName}')`).join('\n') | ||
content = transformer.outer | ||
.replace(/className\=/g, 'class=') | ||
.replace(/^\s*$(?:\r\n?|\n)/gm, '') | ||
return content + '\n' + fns | ||
}; | ||
module.exports = renderJsx |
{ | ||
"name": "als-render", | ||
"version": "0.2.31", | ||
"version": "0.3.0", | ||
"main": "index.js", | ||
@@ -25,7 +25,5 @@ "scripts": { | ||
"als-object-serializer": "^1.0.0", | ||
"als-replace-between": "3.3.1", | ||
"als-require": "^0.9.0", | ||
"als-simple-css": "^9.1.0", | ||
"als-require": "1.0.1", | ||
"uglify-js": "^3.18.0" | ||
} | ||
} |
# ALS-Render | ||
**Alpha testing** | ||
**Beta testing** | ||
**Not for production use** | ||
@@ -10,3 +10,3 @@ | ||
**Important Note**: ALS-Render does not use React nor is it a complete replacement for React. This library focuses specifically on the transformation of code. The operational behavior of applications using ALS-Render differs significantly from React applications. For example, objects in style attribute use another interface, `innerHTML` within components, or various React hooks like `useEffect` (instead useEffect, used `component.update` method). Additionally, the context handling in ALS-Render operates differently. | ||
**Important Note**: ALS-Render does not use React nor is it a complete replacement for React. This library focuses specifically on the transformation of code. The operational behavior of applications using ALS-Render differs significantly from React applications. For example, objects in style attribute use another interface, or various React hooks like `useEffect` (instead useEffect, used `component.update` method). Additionally, the context handling in ALS-Render operates differently. | ||
@@ -34,3 +34,3 @@ ## Installation | ||
const { rawHtml, bundle } = render('./path/to/your/JSXComponent'); | ||
const { rawHtml, bundle } = render('./path/to/your/JSXComponent','body'); | ||
@@ -48,3 +48,3 @@ const page = `<!DOCTYPE html> | ||
</body> | ||
<script>${bundle('body', false, true)}</script> | ||
<script>${bundle(false, true)}</script> | ||
</html>`; | ||
@@ -94,3 +94,3 @@ app.get('/',(req,res) => res.end(page)) | ||
* **Styles** - The `style` attribute in ALS-Render can be a string or object for `als-simple-css`. | ||
* **Styles** - The `style` attribute in ALS-Render can be only a string | ||
@@ -100,2 +100,3 @@ * **Class Attribute** - Unlike React's `className` attribute for specifying CSS classes, ALS-Render uses the standard HTML `class` attribute. This simplifies integration with conventional HTML and CSS practices. | ||
* **Context** - ALS-Render provides a global `context` variable accessible by default in each component. This context can be used to share data and functions across components, simplifying the management of global states or configurations. | ||
* The context includes properties: components,actions,counter,update and buildComponent which should stay without modification | ||
@@ -102,0 +103,0 @@ * **Event Naming** - Events in ALS-Render follow the standard HTML naming conventions (e.g., `onclick` instead of `onClick`). This adherence simplifies the learning curve for those familiar with traditional HTML and JavaScript. |
479
render.js
const render = (function() { | ||
async function render(path,selector='body') { let styles = [] const context = newContext() const app = new Require(path) await app.getContent() for(const path in app.contents) { app.contents[path] = renderJsx(app.contents[path],styles) } if(styles.length) { const simple = new Simple(styles.map((style,i) => ({[`[style${i}]`]:parse(style)}))) styles = `<style>${simple.stylesheet()}</style>` } else styles = '' const result = app.build({},context,'context') const rawHtml = result()+styles bind(selector,rawHtml,context) } class Require { static contents = {} static async fetch(path, type = 'text') { let response = await fetch(path) if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); return await response[type]() } static isCyclyc(fullPath, path) { if (Require.contents[fullPath] && Require.contents[fullPath].children.includes(path)) { throw `cyclic dependency between ${path} and ${fullPath}` } } static async getModule(path, context, contextName, modules) { const mod = new Require(path) await mod.getContent() return mod.build(modules, context, contextName) } static getFullPath(path, relative) { const pathParts = path.split('/'); const relativeParts = relative.split('/').slice(0, -1); const fullPathParts = []; for (let part of [...relativeParts, ...pathParts]) { if (part === '..') { if (fullPathParts.length > 0 && fullPathParts[fullPathParts.length - 1] !== '..') fullPathParts.pop(); else fullPathParts.push(part); } else if (part !== '.') fullPathParts.push(part); } let fullPath = fullPathParts.join('/'); return fullPath.endsWith('.js') ? fullPath : fullPath + '.js' } static async getNodeModules(nodeModules, children, content) { if (nodeModules.length === 0) return content for (const { match, modulePath } of nodeModules) { const pkgJsonPath = `/node_modules/${modulePath}/package.json` const exists = await fetch(pkgJsonPath, { method: 'HEAD' }) if (!exists.ok) { console.warn(`The module "${modulePath}" can't be imported and will be replaced with null`) continue } const { main: filename = 'index.js' } = await Require.fetch(pkgJsonPath, 'json') const fullPath = `/node_modules/${modulePath}/${filename}` Require.isCyclyc(fullPath, modulePath) children.push(fullPath); content = content.replace(match, match.replace(modulePath, fullPath)) } return content } static relativePath = location.pathname constructor(path) { this.contents = {} this.path = path this.fullPath = Require.getFullPath(path, Require.relativePath) this.contentReady = false } async getContent() { if (this.contentReady) return this const getContent = async (path) => { if (this.contents[path] !== undefined) return // allready fetched if (!Require.contents[path]) { let content = await Require.fetch(path) const children = [], nodeModules = []; content = content.replace(/^(?!\/\/|\/\*.*\*\/).*require\(["'`](.*)["'`]\)/gm, (match, modulePath) => { if (!modulePath.startsWith('.')) { nodeModules.push({ match, modulePath }) return match } const fullPath = Require.getFullPath(modulePath, path) Require.isCyclyc(fullPath, path) children.push(fullPath); return match.replace(modulePath, fullPath) }); content = await Require.getNodeModules(nodeModules, children, content) Require.contents[path] = { content, children } } const { content, children } = Require.contents[path] this.contents[path] = content for (let childPath of children) { await getContent(childPath) } } await getContent(this.fullPath) this.contentReady = true this.prepareContents() return this } prepareContents() { const newKeys = new Set() const addKeys = (keys) => { keys.forEach(key => { if (!this.contents[key]) return addKeys(Require.contents[key].children) newKeys.add(key) }) } addKeys(Object.keys(this.contents).reverse()) this.keys = Array.from(newKeys) } build(modules = {}, context = {}, contextName = 'context') { function require(path) { return modules[path] || null } this.keys.map(path => { const module = { exports: {} } const params = { module, require, exports: module.exports, [contextName]: context } try { new Function(...Object.keys(params), this.contents[path])(...Object.values(params)) } catch (error) { this.error(error, path) } modules[path] = module.exports }) return modules[this.keys[this.keys.length - 1]] } error(error, path) { const stack = [], contents = Require.contents; const pathes = Object.keys(contents).reverse() function addToStack(curPath) { stack.push(curPath) for (const modPath of pathes) { if (contents[modPath].children.includes(curPath)) { addToStack(` at ${modPath}`) break } } } addToStack(path) const errorsStack = error.stack.split('\n') errorsStack.splice(1, 1, ...stack) error.stack = errorsStack.join('\n') throw error } }; const require = Require.getModule; | ||
const ReplaceBetween = (function() { | ||
class BaseNode { constructor() {this.children = []} get outer() { return this.before + this.inner + this.after } get inner() {return this.children.map(child => child.outer).join('')} set outer(value) {this.inner = value; this.before = ''; this.after = ''} set inner(value) {this.children = []; this.children.push(new TextNode(value,this))} walkNodes(modifiers=[],parent = this) { parent.children.forEach(child => { if(child instanceof BaseNode === false) return this.walkNodes(modifiers,child) this.runModifiers(modifiers,child,true) }); } walkTextNodes(modifiers=[],parent = this) { parent.children.forEach(child => { if(child instanceof BaseNode) this.walkTextNodes(modifiers,child) else this.runModifiers(modifiers,child,false) }); } walk(modifiers=[],parent = this) { parent.children.forEach(child => { const isNode = child instanceof BaseNode if(isNode) this.walk(modifiers,child) this.runModifiers(modifiers,child,isNode) }); } runModifiers(modifiers,child,isNode) { for(const modifier of modifiers) { const result = modifier(child,isNode) if(result === false) return false } } } function buildTree(starts, ends) { if(starts[0].n > ends[0].n) ends.shift() if(starts[starts.length-1].n > ends[ends.length-1].n) starts.pop() if (starts.length !== ends.length) throw new Error(`Parsing error: unmatched tags detected. (${starts.length},${ends.length})`); let pairs = starts.map((start, i) => ({ start, end: ends[i], children: [] })); pairs.forEach((curPair, i) => { for (let k = i; k < pairs.length; k++) { if (curPair.end.n > pairs[k].start.n) { const end = pairs[k].end const curEnd = curPair.end curPair.end = end pairs[k].end = curEnd } } }) pairs.sort((a, b) => a.end.n - b.end.n); pairs.forEach((curPair, i) => { for (let k = i; k < pairs.length; k++) { if (curPair.start.n > pairs[k].start.n && curPair.end.n < pairs[k].end.n) { if (!curPair.level) curPair.level = 0 curPair.level++ pairs[k].children.push(curPair) } } }); function filterPairs(pairs, curLevel = 0) { pairs = pairs.filter(({ level }) => (!level || level <= curLevel)) pairs.forEach(pair => { if (pair.children.length === 0) return pair.children = filterPairs(pair.children, curLevel + 1) }); return pairs } pairs = filterPairs(pairs) return pairs } function find(string, r, add, offset = 0) { const arr = [] while (true) { const match = string.match(r) if (!match) break offset += match.index + 1 const n = add < 0 ? add : add * (match[0].length - 1) arr.push({n:offset + n,length:match[0].length}) string = string.slice(match.index + 1) } return arr } class ReplaceBetween extends BaseNode { constructor(content, startR, endR) { super() if (typeof content !== 'string') throw `Expected 'content' to be a string, but got '${typeof content}'` if (startR instanceof RegExp == false) throw '"startR" should be an instance of RegExp.' if (endR instanceof RegExp == false) throw '"endR" should be an instance of RegExp.' this.content = content this.startR = startR this.endR = endR this.build() } build() { const { content, startR, endR } = this const starts = find(content, startR, -1) const ends = find(content, endR, 1) if(starts.length === 0 || ends.length === 0) return const children = buildTree(starts, ends) if(children.length === 0) return const start = children[0].start.n const end = children[children.length - 1].end.n this.before = start > 0 ? content.slice(0, start) : '' this.after = end < content.length ? content.slice(end, content.length) : '' children.forEach((child,i) => { this.children.push(new Node(this, child, this)) if(i === children.length-1) return const nextStart = children[i+1].start.n const curendEnd = child.end.n if(nextStart !== curendEnd) { this.children.push(new TextNode(content.slice(curendEnd,nextStart),this)) } }); } } class Node extends BaseNode { constructor(root, obj, parent = null) { super() this.parent = parent if(parent) this.index = parent.children.length this.root = root this.addChildren(obj) } addChildren(obj) { const { start, end, children } = obj const root = this.root let curStart = start.n + start.length, curEnd = end.n - end.length this.open = root.content.slice(start.n, curStart) children.forEach((child,i) => { if(child.start.n > curStart) this.children.push(new TextNode(root.content.slice(curStart, child.start.n),this)) this.children.push(new Node(root, child, this)) curStart = child.end.n }); if(curEnd > curStart) this.children.push(new TextNode(root.content.slice(curStart,curEnd),this)) this.close = root.content.slice(curEnd, end.n) return this } get outer() {return this.open + this.inner + this.close} set outer(value) {this.inner = value; this.open = ''; this.close = ''} get prev() {return this.parent ? this.parent.children[this.index - 1] : null} get next() {return this.parent ? this.parent.children[this.index + 1] : null} } class TextNode { constructor(content,parent) { this.outer = content this.parent = parent if(parent) this.index = parent.children.length this.open = '' this.close = '' } get prev() {return this.parent.children[this.index - 1] || null} get next() {return this.parent.children[this.index + 1] || null} } return ReplaceBetween | ||
const Require = (function(){ | ||
function isCyclyc(fullPath, path) { | ||
if (Require.contents[fullPath] && Require.contents[fullPath].children.includes(path)) { | ||
throw `cyclic dependency between ${path} and ${fullPath}` | ||
} | ||
} | ||
function getFullPath(path, relative) { | ||
const pathParts = path.split('/'); | ||
const relativeParts = relative.split('/').slice(0, -1); | ||
const fullPathParts = []; | ||
for (let part of [...relativeParts, ...pathParts]) { | ||
if (part === '..') { | ||
if (fullPathParts.length > 0 && fullPathParts[fullPathParts.length - 1] !== '..') fullPathParts.pop(); | ||
else fullPathParts.push(part); | ||
} else if (part !== '.') fullPathParts.push(part); | ||
} | ||
let fullPath = fullPathParts.join('/'); | ||
return fullPath.endsWith('.js') ? fullPath : fullPath + '.js' | ||
} | ||
async function $fetch(path, type = 'text') { | ||
let response = await fetch(path) | ||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); | ||
return await response[type]() | ||
} | ||
async function getNodeModules(nodeModules, children, content) { | ||
if (nodeModules.length === 0) return content | ||
for (const { match, modulePath } of nodeModules) { | ||
const pkgJsonPath = `/node_modules/${modulePath}/package.json` | ||
const exists = await fetch(pkgJsonPath, { method: 'HEAD' }) | ||
if (!exists.ok) { | ||
console.warn(`The module "${modulePath}" can't be imported and will be replaced with null`) | ||
continue | ||
} | ||
const { main: filename = 'index.js' } = await $fetch(pkgJsonPath, 'json') | ||
const fullPath = `/node_modules/${modulePath}/${filename}` | ||
isCyclyc(fullPath, modulePath) | ||
children.push(fullPath); | ||
content = content.replace(match, match.replace(modulePath, fullPath)) | ||
} | ||
return content | ||
} | ||
async function getContents({contents, fullPath},Require) { | ||
const getContent = async (path) => { | ||
if (contents[path] !== undefined) return // allready fetched | ||
if (!Require.contents[path]) { | ||
let content = await $fetch(path) | ||
const children = [], nodeModules = []; | ||
content = content.replace(/^(?!\/\/|\/\*.*\*\/).*require\(["'`](.*)["'`]\)/gm, (match, modulePath) => { | ||
if (!modulePath.startsWith('.')) { | ||
nodeModules.push({ match, modulePath }) | ||
return match | ||
} | ||
const fullPath = getFullPath(modulePath, path) | ||
isCyclyc(fullPath, path) | ||
children.push(fullPath); | ||
return match.replace(modulePath, fullPath) | ||
}); | ||
content = await getNodeModules(nodeModules, children, content) | ||
Require.contents[path] = { content, children } | ||
} | ||
const { content, children } = Require.contents[path] | ||
contents[path] = content | ||
for (let childPath of children) { | ||
await getContent(childPath) | ||
} | ||
} | ||
await getContent(fullPath) | ||
} | ||
function getKeys(contents, Require) { | ||
const newKeys = new Set() | ||
const addKeys = (keys) => { | ||
keys.forEach(key => { | ||
if (!contents[key]) return | ||
addKeys(Require.contents[key].children) | ||
newKeys.add(key) | ||
}) | ||
} | ||
addKeys(Object.keys(contents).reverse()) | ||
return Array.from(newKeys) | ||
} | ||
function getFns(contextName,obj) { | ||
const modulesLines = {} | ||
let curLastLine = 3 | ||
const fns = obj.keys.map((path, i) => { | ||
const fnBody = obj.contents[path]; | ||
let code = `modules['${path}'] = (function (){ | ||
const module = { exports: {} } | ||
const exports = module.exports | ||
${fnBody} | ||
return module.exports; | ||
})();` | ||
if (i === obj.keys.length - 1) code += `\nreturn modules['${path}']` | ||
modulesLines[path] = { from: curLastLine + 1 } | ||
curLastLine += code.split('\n').length | ||
modulesLines[path].to = curLastLine | ||
return code | ||
}).join('\n') | ||
const fn = new Function('modules', contextName, `function require(path) { return modules[path] || null }; | ||
${fns}`) | ||
return { fn, modulesLines, curLastLine } | ||
} | ||
function parseError(error, modulesLines, curLastLine) { | ||
let [message, ...stack] = error.stack.split('\n') | ||
stack = stack.map(string => { | ||
const m = string.match(/<anonymous>:(\d*):(\d*)\)$/) | ||
if (!m) return | ||
const line = Number(m[1]) | ||
if (line + 1 === curLastLine) return | ||
const char = Number(m[2]) | ||
const [path, { from, to }] = Object.entries(modulesLines).filter(([path, { from, to }]) => line >= from && line <= to)[0] | ||
const at = string.match(/at\s(.*?)\s/)[1] | ||
return ` at ${at} ${path} (${line - from - 2}:${char})` | ||
}).filter(Boolean) | ||
error.stack = message + '\n' + stack.join('\n') | ||
throw error | ||
} | ||
class Require { | ||
static contents = {} | ||
static async getModule(path, context, contextName, modules) { | ||
const mod = new Require(path) | ||
await mod.getContent() | ||
return mod.build(modules, context, contextName) | ||
} | ||
constructor(path) { | ||
this.contents = {} | ||
this.path = path | ||
this.fullPath = getFullPath(path, location.pathname) | ||
this.contentReady = false | ||
} | ||
async getContent() { | ||
if (this.contentReady) return this | ||
await getContents(this, Require) | ||
this.keys = getKeys(this.contents,Require) | ||
this.contentReady = true | ||
return this | ||
} | ||
build(modules = {}, context = {}, contextName = 'context') { | ||
const { fn, modulesLines, curLastLine } = getFns(contextName,this) | ||
try {return fn(modules, context)} | ||
catch (error) {parseError(error, modulesLines, curLastLine)} | ||
} | ||
} | ||
return Require; | ||
})() | ||
function bind(selector,rawHtml,context,update = true) { function getElement(selector) { const element = document.querySelector(selector) if(!element) throw `The element with selector "${selector}" not exists` return element } if(update) getElement(selector).innerHTML = rawHtml context.actions.forEach(({ fn, id, event }) => { getElement('#'+id).addEventListener(event, fn); }); context.actions = [] } function newContext() { const context = { components: {}, actions: [], counter: 0, update: (componentName,newParams, key) => { const component = context.components[componentName] const selector = `[component=${ componentName}]`; if (key) selector += `[key=${key}]`; const element = document.querySelector(selector) element.outerHTML = component(newParams); context.actions = context.actions.map(({ fn, id, event }) => { const element = document.getElementById(id) if(element) element.addEventListener(event, fn); return false; }).filter(Boolean); } } context.buildComponent = function(componentFn,componentName) { if(context.components[componentName]) return componentFn.update = function(newParams,key) {context.update(componentName,newParams,key)} context.components[componentName] = function(props) { let result = componentFn(props).trim() if(result.startsWith('<>')) return result.slice(2,-3) // remove <> and </> result = result.replace(/^<\w*/,(tag) => { key = props.key ? ` key="${props.key}"` : '' return tag + ` component="${componentName}"${key}` }) return result } context.components[componentName].update = function(newParams,key) {context.update(componentName,newParams,key)} } return context } const removeComments = str => str.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//gm, '') function renderJsx(content,styles) { const transformer = new ReplaceBetween(content, new RegExp(/\(\s*?</), new RegExp(/\>\s*?\)/)) if (transformer.children.length === 0) return content transformer.before = removeComments(transformer.before) transformer.after = removeComments(transformer.after) const componentNames = [] transformer.walkNodes([node => replaceComponents(node, componentNames)]) transformer.children.forEach(node => replaceActions(node,styles)); const fns = componentNames.map(cName => `context.buildComponent(${cName},'${cName}')`).join('\n') content = transformer.outer .replace(/className\=/g, 'class=') .replace(/^\s*$(?:\r\n?|\n)/gm, '') return content + '\n' + fns }; function buildAction(event, value) { const obj = `{event:'${event}',fn:${value},id:'action'+(context.counter++).toString()}` return '"${' + `context.actions.push(${obj})` + ` ? '' : ''}" id="\${'action'+(context.counter-1).toString()}" ` } function getBefore(node) { if (node.prev) return { beforeNode: node.prev, before: node.prev.outer.trim() } if (node.parent.before) return { beforeNode: node.parent, before: node.parent.before.trim() } return getBefore(node.parent) } function replaceActions(node, styles) { if (!node.inner) return const varTransformer = new ReplaceBetween(node.inner, /\{/, /\}/) if (varTransformer.children.length === 0) return varTransformer.walkNodes([node => { const outer = node.outer if (node.inner.startsWith('...')) return // Spreaded variable if (outer.startsWith('{/*') && outer.endsWith('*/}')) { // comment node.outer = ''; return; } const { before, beforeNode } = getBefore(node) const lastChar = before[before.length - 1]; if (lastChar === '$') return if (before[before.length - 2] + lastChar === '=>') return // Arrow function body if (lastChar === '(' || lastChar === ',') return // variable is part of object if (lastChar === '=') { let propName = '', i = before.length - 2 while (i >= 0) { propName = before[i] + propName i-- if (before[i] === ' ') break } if (propName === 'style') { if(node.parent.open === '{' && node.parent.close === '}') { styles.push(outer) if(beforeNode.before) beforeNode.before = beforeNode.before.slice(0,-1)+String(styles.length-1) node.parent.outer = '' return } } if (propName.startsWith('on')) { node.outer = buildAction(propName.split('on')[1].toLowerCase(), node.inner) } else node.outer = '"${' + `${node.inner}}"` } else if(node.open === '{') { // if((node.prev ? node.prev.outer : node.parent.before).slice(-1) === '$') return node.open = '${' } }]) node.inner = varTransformer.outer } function replaceComponents(node,componentNames) { const inner = '<'+node.inner.trim()+'>' node.open = '`'; node.close = '`'; node.inner = inner.replace(/\<([A-Z]\w*)[\s\S]*?\/\>/g,(m,cName,index) => { componentNames.push(cName) let props = [], restParams = ''; m.replace(/(\w*)\s*?\=\"([\s\S]*?)\"/g, (m1, name, val) => {props.push(`${name}:"${val}"`)}) m.replace(/(\w*)\s*?\=\s*?\{\{?(.*?)\}?\}/g, (m1, name, val) => {props.push(`${name}:${val}`)}) m.replace(/\{(\.\.\..*?)\}/, (m1, varName) => {props.push(varName)}) const code = `context.components.${cName}({${props.join()}${restParams}})` if(index === 0 && m.length === inner.length) { node.open = ''; node.close = ''; return code } return '${'+code+'}' }) .replace(/(checked|disabled)\=\{([\s\S]*?)\}/g,(m,prop,condition) => '${'+`${condition} ? '${prop}' : '' `+'}') .replace(/\/>/g, '>') } return render | ||
async function render(path, selector = 'body', data = {}) { | ||
const context = { actions: [], components: {}, counter: 0 } | ||
const app = new Require(path) | ||
await app.getContent() | ||
for (const path in app.contents) { | ||
app.contents[path] = renderJsx(app.contents[path],path) | ||
} | ||
const resultFn = app.build({}, context, 'context') | ||
const rawHtml = resultFn(data).trim() | ||
bind(selector, rawHtml, context) | ||
} | ||
function bind(selector,rawHtml,context,update = true) { | ||
function actionToElement(selector,action) { | ||
const element = document.querySelector(selector) | ||
if(!element) return | ||
action(element) | ||
} | ||
if(update) actionToElement(selector,element => element.innerHTML = rawHtml) | ||
context.actions.forEach(({key,selector,event,fn}) => { | ||
actionToElement(selector,element => element.addEventListener(event,fn)) | ||
}); | ||
context.actions = [] | ||
} | ||
function buildComponent(newBody,componentName,params) { | ||
return `function ${componentName}(props={},atts=[],inner='') { | ||
const originalFn = (${params}) => { | ||
${newBody} | ||
} | ||
let result = originalFn(props,inner) | ||
if(!result.startsWith('<>')) { | ||
result = result.replace(/<([\\s\\S]*?)\\>/,(m) => { // get first tag. | ||
atts.push('component="${componentName}"') | ||
if(props.key !== undefined) atts.push('key="'+props.key+'"') | ||
return m.slice(0,-1) + ' '+atts.join(' ')+'>' | ||
}) | ||
} | ||
result = result.replace(/\\<\\/?\\>/g,'') | ||
return result | ||
} | ||
${componentName}.update = function(props={},atts=[],inner) { | ||
let selector = '[component="${componentName}"]' | ||
if(props.key !== undefined) selector = [key="\${key}"]+selector | ||
document.querySelector(selector).outerHTML = ${componentName}(props,atts,inner) | ||
context.actions.forEach(({ selector, event, fn }) => { | ||
const element = document.querySelector(selector) | ||
if(element) element.addEventListener(event, fn); | ||
}); | ||
context.actions = [] | ||
} | ||
context.components.${componentName} = ${componentName} | ||
` | ||
} | ||
function renderJsx(content,path) { | ||
const componentName = path.split('/').pop().replace('.js','') | ||
const isComponent = /[A-z]/.test(componentName[0]) | ||
const r = new RegExp('^.*'+componentName+'\\s*?\\(([\\s\\S]*?)\\)\\s*?\\{([\\s\\S]*)\\}','m') | ||
if(!isComponent) return content | ||
content = content.replace(r,(m,params,componentBody) => { | ||
const nodes = findJsx(componentBody) | ||
const tree = buildTree(nodes) | ||
let newBody = tree[0] | ||
return buildComponent(newBody,componentName,params) | ||
}) | ||
// console.log(content) | ||
return content | ||
} | ||
function findJsx(content) { | ||
const found = [] | ||
const openTagGroup = content.match(/\(\s*?\<([\s\S]*?)\>/) | ||
if (!openTagGroup) return [content] | ||
const openIndex = openTagGroup.index | ||
if (openIndex > 0) found.push(content.slice(0, openIndex)) | ||
if (openTagGroup[1].endsWith('/')) { | ||
const nodes = parser(openTagGroup[0]) | ||
nodes.forEach(node => found.push(node)); | ||
const leftNodes = findJsx(content.slice(openIndex + openTagGroup[0].length)) | ||
leftNodes.forEach(node => found.push(node)); | ||
return found | ||
} | ||
const closeIndex = getClose(content.slice(openIndex), openIndex, openTagGroup, content) | ||
if (closeIndex) { | ||
let nodes = parser(content.slice(openIndex, closeIndex)) | ||
nodes = nodes.map(node => { | ||
const { text, type } = node | ||
if (type === undefined && text.startsWith('{') && text.endsWith('}')) { | ||
const newNodes = findJsx(text) | ||
if (newNodes[0] === text) return node | ||
return newNodes | ||
} | ||
return node | ||
}); | ||
found.push(nodes) | ||
if (closeIndex < content.length) { | ||
const newNodes = findJsx(content.slice(closeIndex)) | ||
if (newNodes.length === 1) found.push(newNodes[0]) | ||
else found.push(newNodes) | ||
} | ||
} | ||
return found | ||
} | ||
function getClose(temp,add,openTagGroup,content) { | ||
const r = new RegExp('<(\\/?)'+openTagGroup[1].split(' ')[0]+'(\\s|\>)') | ||
let opens = 0 | ||
function findClose(temp,add=0) { | ||
const match = temp.match(r) | ||
if(match === null) return null | ||
if(match[1] === '') opens++ | ||
if(match[1] === '/') opens-- | ||
let from = match.index+match[0].length | ||
if(opens === 0) { | ||
let closeIndex = from+add | ||
for(let i = closeIndex; i < content.length; i++) { | ||
if(content[i] !== ')') continue | ||
closeIndex = i+1 | ||
break | ||
} | ||
return closeIndex | ||
} else { | ||
return findClose(temp.slice(from),add+from) | ||
} | ||
} | ||
return findClose(temp,add) | ||
} | ||
function parser(jsx) { | ||
const nodes = [] | ||
let curNode = '', type, lastNode | ||
function addNode(text, curNodeText) { | ||
text.replace(/\s*/,(space) => { | ||
if(space.length > 0 && nodes[nodes.length-1].text !== space) { | ||
nodes.push({text:space,type:undefined}) | ||
} | ||
// return '' | ||
}) | ||
text = text.trim() | ||
lastNode = { text, type } | ||
type = undefined | ||
if (text.length === 0) return | ||
nodes.push(lastNode) | ||
curNode = curNodeText | ||
} | ||
let skipBraces = 0 | ||
for (let i = 0; i < jsx.length; i++) { | ||
const char = jsx[i], prevChar = jsx[i - 1], nextChar = jsx[i + 1] | ||
let lastCurNode = curNode | ||
curNode = curNode + char | ||
if (char === '{') { | ||
if(nextChar === '/' && jsx[i+2] === '*') { // remove comments | ||
for (let k = i+2; k < jsx.length; k++) { | ||
if(jsx[k] !== '*') continue | ||
if(jsx[k+1] !== '/') continue | ||
if(jsx[k+2] !== '}') continue | ||
i = k+2; | ||
curNode = curNode.slice(0,-1) | ||
break | ||
} | ||
} else skipBraces++ | ||
} else if (skipBraces) { | ||
if (char === '}') skipBraces-- | ||
} else if (char === '<') { | ||
addNode(lastCurNode, '<') | ||
if (nextChar === '/') type = 'close' | ||
else type = 'open' | ||
} else if (char === '>') { | ||
if (type === 'open') addNode(curNode,'') | ||
else { | ||
if (prevChar === '/') { | ||
if(jsx[i-2] === '<') type = 'close' | ||
else type = 'single' | ||
} | ||
addNode(curNode, '') | ||
} | ||
} | ||
if (i === jsx.length - 1) addNode(curNode, '') | ||
} | ||
return nodes | ||
} | ||
function buildAction(propname,value,attributes) { | ||
const event = propname.split('on')[1].toLowerCase() | ||
propname = event | ||
const fn = value | ||
value = '${context.counter++}' | ||
const selector = `[${propname}="\${context.counter-1}"]` | ||
const addToActions = '$'+`{context.actions.push({selector:\`${selector}\`,event:'${event}',fn:${fn}}) ? '' : ''}` | ||
value = value + addToActions | ||
attributes.push(`${propname}="${value}"`) | ||
} | ||
function buildElement(isComponent, attributes, props, tagName, children) { | ||
if (!children) { | ||
const atts = `[${attributes.map(att => '`' + att + '`').join(',')}]` | ||
if (isComponent) return '$' + `{${tagName}(${props},${atts})}` | ||
else { | ||
const atts = attributes.length ? ' ' + attributes.join(' ') : '' | ||
return '<' + tagName + atts + '>' | ||
} | ||
} | ||
if (isComponent) { | ||
const atts = `[${attributes.map(att => '`' + att + '`').join(',')}]` | ||
let inner = children.join('') | ||
if (inner.startsWith('${') && inner.endsWith('}')) inner = inner.slice(2, -1) | ||
else inner = '`' + inner + '`' | ||
return '$' + `{${tagName}(${props},${atts},${inner})}` | ||
} else { | ||
const atts = attributes.length ? ' ' + attributes.join(' ') : '' | ||
const openTag = '<' + tagName + atts + '>' | ||
const closeTag = '</' + tagName + '>' | ||
if (children.length === 0) return openTag + closeTag | ||
return openTag + children.join('') + closeTag | ||
} | ||
} | ||
function buildString(node) { | ||
if (typeof node === 'string') { | ||
const trimed = node.trim() | ||
if (node.startsWith('{')) return '$' + node | ||
else if (trimed.startsWith('(')) return node.replace('(', '`') | ||
else if (trimed.startsWith(')')) { | ||
if(trimed.startsWith(')}')) { | ||
if(trimed.slice(2).trim().startsWith('{')) return node.replace(')','`').replace('{','${') | ||
if(node === ')}') return node | ||
} | ||
if(node.startsWith(').')) return node | ||
return node.replace(')', '`') | ||
} | ||
node = node.replace(/\}[\s\S]*?(\$?\{)/g,(m,brace) => { | ||
if(brace === '{') { | ||
if(m.match(/const|let|var|\)|\(|,\s*?\{$/)) return m | ||
return m.slice(0,-1)+'${' | ||
} | ||
return m | ||
}) | ||
return node | ||
} | ||
if (node.type === undefined) { | ||
if(/\{.*\}/.test(node.text)) return node.text.replace('{','${') | ||
if (node.text === '(' || node.text === ')') return '`' | ||
return node.text | ||
} | ||
} | ||
function buildTree(nodes) { | ||
let maxIndex = 0; | ||
function getChildren(tagName,i) { | ||
const closeTagIndex = findCloseTag(nodes, i, tagName) | ||
if (closeTagIndex > maxIndex) maxIndex = closeTagIndex | ||
return buildTree(nodes.slice(i + 1, closeTagIndex)) | ||
} | ||
nodes = nodes.map((node, i) => { | ||
if (i < maxIndex) return | ||
if (Array.isArray(node)) return buildTree(node) | ||
if (typeof node === 'string' || node.type === undefined) return buildString(node) | ||
if (node.text === '<>') return '<>'+getChildren('>',i)+'</>' | ||
const tagGroup = node.text.match(/^\<(\w.*?)(\s|\>)/) | ||
if (tagGroup === null || node.type !== 'open') return | ||
const { isComponent, tagName, attributes, props } = getElement(node) | ||
if (node.type === 'single') return buildElement(isComponent, attributes, props, tagName) | ||
return buildElement(isComponent, attributes, props, tagName, getChildren(tagName,i)) | ||
}).filter(Boolean); | ||
if (nodes.length >= 3 && nodes[0] === '`' && nodes[nodes.length - 1] === '`') { | ||
const code = nodes.flat()[1].trim() | ||
if (code.startsWith('${')) return [code.slice(2, -1)] | ||
} | ||
if (nodes.flat().every(child => typeof child === 'string')) return [nodes.join('')] | ||
return nodes | ||
} | ||
function findCloseTag(nodes,i,tagName) { | ||
let opens = 0, closeTagIndex; | ||
for (let k = i + 1; k < nodes.length; k++) { | ||
if (Array.isArray(nodes[k])) continue | ||
if (nodes[k].type === 'open' && nodes[k].text.startsWith('<' + tagName)) opens++ | ||
if (nodes[k].type === 'close' && nodes[k].text.startsWith('</' + tagName)) { | ||
if (opens > 0) opens-- | ||
else { | ||
closeTagIndex = k | ||
break | ||
} | ||
} | ||
} | ||
return closeTagIndex | ||
} | ||
function getAttributes(segments, isComponent) { | ||
const attributes = [], props = []; | ||
let rest; | ||
for (let i = 0; i < segments.length; i++) { | ||
let hasBraces = false | ||
let [propname, value] = segments[i].split('=') | ||
if (propname === '/') continue | ||
if (value && value.startsWith('{')) { | ||
hasBraces = true | ||
while (!value.endsWith('}')) { | ||
i++ | ||
value = value + ' ' +segments[i] | ||
} | ||
} else if(value) { | ||
const quote = value[0] | ||
while (!value.endsWith(quote)) { | ||
i++ | ||
value = value + ' ' + segments[i] | ||
} | ||
} | ||
if (propname.startsWith('{...')) { | ||
rest = propname.slice(1, -1) | ||
continue | ||
} | ||
if (value) value = value.slice(1, -1) | ||
if (propname === 'checked' || propname === 'disabled') { | ||
attributes.push('${' + value + ` ? '${propname}' : ''}`) | ||
continue | ||
} | ||
if (propname === 'className') propname = 'class' | ||
else if (propname.startsWith('on')) { | ||
buildAction(propname,value,attributes) | ||
continue | ||
} | ||
if (!value) attributes.push(propname) | ||
else if (!hasBraces) attributes.push(`${propname}="${value}"`) | ||
else if (isComponent) props.push(`${propname}:${value}`) | ||
else attributes.push(`${propname}="\${${value}}"`) | ||
} | ||
if (rest) props.push(rest) | ||
return { attributes, props:`{${props.join(',')}}` } | ||
} | ||
function getElement(node) { | ||
const segments = node.text.trim().slice(1, -1).split(/\s/).filter(Boolean) | ||
if (segments.length === 1 && segments[0].endsWith('/')) { | ||
node.type = 'single'; | ||
segments[0] = segments[0].slice(0, -1) | ||
} else if (segments[segments.length - 1] === '/') node.type = 'single' | ||
if (node.type === 'single' && segments.length > 1) segments.pop() | ||
const tagName = segments.shift() | ||
const isComponent = tagName.match(/[A-Z]/) !== null | ||
let { attributes, props } = getAttributes(segments, isComponent) | ||
return { tagName, isComponent, attributes, props } | ||
} | ||
return render | ||
})() |
@@ -1,1 +0,34 @@ | ||
let render=function(){class h{static contents={};static async fetch(e,t="text"){e=await fetch(e);if(e.ok)return e[t]();throw new Error("HTTP error! status: "+e.status)}static isCyclyc(e,t){if(h.contents[e]&&h.contents[e].children.includes(t))throw`cyclic dependency between ${t} and `+e}static async getModule(e,t,n,r){e=new h(e);return await e.getContent(),e.build(r,t,n)}static getFullPath(e,t){var n,e=e.split("/"),r=[];for(n of[...t.split("/").slice(0,-1),...e])".."===n?0<r.length&&".."!==r[r.length-1]?r.pop():r.push(n):"."!==n&&r.push(n);t=r.join("/");return t.endsWith(".js")?t:t+".js"}static async getNodeModules(e,t,n){if(0!==e.length)for(var{match:r,modulePath:s}of e){var i=`/node_modules/${s}/package.json`;(await fetch(i,{method:"HEAD"})).ok?({main:i="index.js"}=await h.fetch(i,"json"),i=`/node_modules/${s}/`+i,h.isCyclyc(i,s),t.push(i),n=n.replace(r,r.replace(s,i))):console.warn(`The module "${s}" can't be imported and will be replaced with null`)}return n}static relativePath=location.pathname;constructor(e){this.contents={},this.path=e,this.fullPath=h.getFullPath(e,h.relativePath),this.contentReady=!1}async getContent(){if(!this.contentReady){let r=async i=>{if(void 0===this.contents[i]){if(!h.contents[i]){let e=await h.fetch(i),r=[],s=[];e=e.replace(/^(?!\/\/|\/\*.*\*\/).*require\(["'`](.*)["'`]\)/gm,(e,t)=>{var n;return t.startsWith(".")?(n=h.getFullPath(t,i),h.isCyclyc(n,i),r.push(n),e.replace(t,n)):(s.push({match:e,modulePath:t}),e)}),e=await h.getNodeModules(s,r,e),h.contents[i]={content:e,children:r}}let{content:e,children:t}=h.contents[i];this.contents[i]=e;for(var n of t)await r(n)}};await r(this.fullPath),this.contentReady=!0,this.prepareContents()}return this}prepareContents(){let t=new Set,n=e=>{e.forEach(e=>{this.contents[e]&&(n(h.contents[e].children),t.add(e))})};n(Object.keys(this.contents).reverse()),this.keys=Array.from(t)}build(r={},s={},i="context"){function o(e){return r[e]||null}return this.keys.map(t=>{var e={exports:{}},n={module:e,require:o,exports:e.exports,[i]:s};try{new Function(...Object.keys(n),this.contents[t])(...Object.values(n))}catch(e){this.error(e,t)}r[t]=e.exports}),r[this.keys[this.keys.length-1]]}error(e,t){let r=[],s=h.contents,i=Object.keys(s).reverse();!function e(t){r.push(t);for(var n of i)if(s[n].children.includes(t)){e(" at "+n);break}}(t);t=e.stack.split("\n");throw t.splice(1,1,...r),e.stack=t.join("\n"),e}}h;let c=function(){class r{constructor(){this.children=[]}get outer(){return this.before+this.inner+this.after}get inner(){return this.children.map(e=>e.outer).join("")}set outer(e){this.inner=e,this.before="",this.after=""}set inner(e){this.children=[],this.children.push(new c(e,this))}walkNodes(t=[],e=this){e.children.forEach(e=>{e instanceof r!=!1&&(this.walkNodes(t,e),this.runModifiers(t,e,!0))})}walkTextNodes(t=[],e=this){e.children.forEach(e=>{e instanceof r?this.walkTextNodes(t,e):this.runModifiers(t,e,!1)})}walk(n=[],e=this){e.children.forEach(e=>{var t=e instanceof r;t&&this.walk(n,e),this.runModifiers(n,e,t)})}runModifiers(e,t,n){for(var r of e)if(!1===r(t,n))return!1}}function o(e,n){if(e[0].n>n[0].n&&n.shift(),e[e.length-1].n>n[n.length-1].n&&e.pop(),e.length!==n.length)throw new Error(`Parsing error: unmatched tags detected. (${e.length},${n.length})`);let i=e.map((e,t)=>({start:e,end:n[t],children:[]}));return i.forEach((t,n)=>{for(let e=n;e<i.length;e++){var r,s;t.end.n>i[e].start.n&&(r=i[e].end,s=t.end,t.end=r,i[e].end=s)}}),i.sort((e,t)=>e.end.n-t.end.n),i.forEach((t,n)=>{for(let e=n;e<i.length;e++)t.start.n>i[e].start.n&&t.end.n<i[e].end.n&&(t.level||(t.level=0),t.level++,i[e].children.push(t))}),i=function t(e,n=0){return(e=e.filter(({level:e})=>!e||e<=n)).forEach(e=>{0!==e.children.length&&(e.children=t(e.children,n+1))}),e}(i)}function n(e,t,n,r=0){for(var s=[];;){var i=e.match(t);if(!i)break;r+=i.index+1;var o=n<0?n:n*(i[0].length-1);s.push({n:r+o,length:i[0].length}),e=e.slice(i.index+1)}return s}r;class h extends r{constructor(e,t,n=null){super(),(this.parent=n)&&(this.index=n.children.length),this.root=e,this.addChildren(t)}addChildren(e){var{start:e,end:t,children:n}=e;let r=this.root,s=e.n+e.length,i=t.n-t.length;return this.open=r.content.slice(e.n,s),n.forEach((e,t)=>{e.start.n>s&&this.children.push(new c(r.content.slice(s,e.start.n),this)),this.children.push(new h(r,e,this)),s=e.end.n}),i>s&&this.children.push(new c(r.content.slice(s,i),this)),this.close=r.content.slice(i,t.n),this}get outer(){return this.open+this.inner+this.close}set outer(e){this.inner=e,this.open="",this.close=""}get prev(){return this.parent?this.parent.children[this.index-1]:null}get next(){return this.parent?this.parent.children[this.index+1]:null}}class c{constructor(e,t){this.outer=e,(this.parent=t)&&(this.index=t.children.length),this.open="",this.close=""}get prev(){return this.parent.children[this.index-1]||null}get next(){return this.parent.children[this.index+1]||null}}return class extends r{constructor(e,t,n){if(super(),"string"!=typeof e)throw`Expected 'content' to be a string, but got '${typeof e}'`;if(t instanceof RegExp==0)throw'"startR" should be an instance of RegExp.';if(n instanceof RegExp==0)throw'"endR" should be an instance of RegExp.';this.content=e,this.startR=t,this.endR=n,this.build()}build(){let{content:r,startR:e,endR:t}=this;var s=n(r,e,-1),i=n(r,t,1);if(0!==s.length&&0!==i.length){let n=o(s,i);0!==n.length&&(s=n[0].start.n,i=n[n.length-1].end.n,this.before=0<s?r.slice(0,s):"",this.after=i<r.length?r.slice(i,r.length):"",n.forEach((e,t)=>{this.children.push(new h(this,e,this)),t!==n.length-1&&(t=n[t+1].start.n)!==(e=e.end.n)&&this.children.push(new c(r.slice(e,t),this))}))}}}}();let l=e=>e.replace(/\/\/.*$/gm,"").replace(/\/\*[\s\S]*?\*\//gm,"");return async function(e,t="body"){let n=[];var r=function(){let i={components:{},actions:[],counter:0,update:(e,t,n)=>{var r=i.components[e];const s=`[component=${e}]`;n&&(s+=`[key=${n}]`),document.querySelector(s).outerHTML=r(t),i.actions=i.actions.map(({fn:e,id:t,event:n})=>{t=document.getElementById(t);return t&&t.addEventListener(n,e),!1}).filter(Boolean)},buildComponent:function(n,r){i.components[r]||(n.update=function(e,t){i.update(r,e,t)},i.components[r]=function(t){let e=n(t).trim();return e.startsWith("<>")?e.slice(2,-3):e=e.replace(/^<\w*/,e=>(key=t.key?` key="${t.key}"`:"",e+(` component="${r}"`+key)))},i.components[r].update=function(e,t){i.update(r,e,t)})}};return i}(),s=new h(e);await s.getContent();for(let e in s.contents)s.contents[e]=function(e,n){var t=new c(e,new RegExp(/\(\s*?</),new RegExp(/\>\s*?\)/));if(0===t.children.length)return e;t.before=l(t.before),t.after=l(t.after);let r=[],s=(t.walkNodes([e=>{{var i=e,o=r;let s="<"+i.inner.trim()+">";i.open="`",i.close="`",i.inner=s.replace(/\<([A-Z]\w*)[\s\S]*?\/\>/g,(e,t,n)=>{o.push(t);let r=[];e.replace(/(\w*)\s*?\=\"([\s\S]*?)\"/g,(e,t,n)=>{r.push(t+`:"${n}"`)}),e.replace(/(\w*)\s*?\=\s*?\{\{?(.*?)\}?\}/g,(e,t,n)=>{r.push(t+":"+n)}),e.replace(/\{(\.\.\..*?)\}/,(e,t)=>{r.push(t)});t=`context.components.${t}({${r.join()}${""}})`;return 0===n&&e.length===s.length?(i.open="",i.close="",t):"${"+t+"}"}).replace(/(checked|disabled)\=\{([\s\S]*?)\}/g,(e,t,n)=>"${"+n+` ? '${t}' : '' `+"}").replace(/\/>/g,">")}}]),t.children.forEach(e=>{var o,t;o=n,(e=e).inner&&0!==(t=new c(e.inner,/\{/,/\}/)).children.length&&(t.walkNodes([n=>{var r=n.outer;if(!n.inner.startsWith("..."))if(r.startsWith("{/*")&&r.endsWith("*/}"))n.outer="";else{var{before:s,beforeNode:i}=function e(t){if(t.prev)return{beforeNode:t.prev,before:t.prev.outer.trim()};if(t.parent.before)return{beforeNode:t.parent,before:t.parent.before.trim()};return e(t.parent)}(n),e=s[s.length-1];if("$"!==e&&s[s.length-2]+e!=="=>"&&"("!==e&&","!==e)if("="===e){let e="",t=s.length-2;for(;0<=t&&(e=s[t]+e," "!==s[--t]););"style"===e&&"{"===n.parent.open&&"}"===n.parent.close?(o.push(r),i.before&&(i.before=i.before.slice(0,-1)+String(o.length-1)),n.parent.outer=""):e.startsWith("on")?n.outer=function(e,t){e=`{event:'${e}',fn:${t},id:'action'+(context.counter++).toString()}`;return'"${'+`context.actions.push(${e})`+` ? '' : ''}" id="\${'action'+(context.counter-1).toString()}" `}(e.split("on")[1].toLowerCase(),n.inner):n.outer='"${'+n.inner+'}"'}else"{"===n.open&&(n.open="${")}}]),e.inner=t.outer)}),r.map(e=>`context.buildComponent(${e},'${e}')`).join("\n"));return(e=t.outer.replace(/className\=/g,"class=").replace(/^\s*$(?:\r\n?|\n)/gm,""))+"\n"+s}(s.contents[e],n);n=n.length?`<style>${new Simple(n.map((e,t)=>({[`[style${t}]`]:parse(e)}))).stylesheet()}</style>`:"";var[e,t,r,i=!0]=[t,s.build({},r,"context")()+n,r];function o(e){var t=document.querySelector(e);if(t)return t;throw`The element with selector "${e}" not exists`}i&&(o(e).innerHTML=t),r.actions.forEach(({fn:e,id:t,event:n})=>{o("#"+t).addEventListener(n,e)}),r.actions=[]}}(); | ||
let render=function(){let l=function(){function l(t,e){if(s.contents[t]&&s.contents[t].children.includes(e))throw`cyclic dependency between ${e} and `+t}function c(t,e){var n,t=t.split("/"),r=[];for(n of[...e.split("/").slice(0,-1),...t])".."===n?0<r.length&&".."!==r[r.length-1]?r.pop():r.push(n):"."!==n&&r.push(n);e=r.join("/");return e.endsWith(".js")?e:e+".js"}async function a(t,e="text"){t=await fetch(t);if(t.ok)return t[e]();throw new Error("HTTP error! status: "+t.status)}async function t({contents:r,fullPath:t},o){let s=async i=>{if(void 0===r[i]){if(!o.contents[i]){let t=await a(i),r=[],s=[];t=t.replace(/^(?!\/\/|\/\*.*\*\/).*require\(["'`](.*)["'`]\)/gm,(t,e)=>{var n;return e.startsWith(".")?(l(n=c(e,i),i),r.push(n),t.replace(e,n)):(s.push({match:t,modulePath:e}),t)}),t=await async function(t,e,n){if(0!==t.length)for(var{match:r,modulePath:s}of t){var i=`/node_modules/${s}/package.json`;(await fetch(i,{method:"HEAD"})).ok?({main:i="index.js"}=await a(i,"json"),l(i=`/node_modules/${s}/`+i,s),e.push(i),n=n.replace(r,r.replace(s,i))):console.warn(`The module "${s}" can't be imported and will be replaced with null`)}return n}(s,r,t),o.contents[i]={content:t,children:r}}let{content:t,children:e}=o.contents[i];r[i]=t;for(var n of e)await s(n)}};await s(t)}class s{static contents={};static async getModule(t,e,n,r){t=new s(t);return await t.getContent(),t.build(r,e,n)}constructor(t){this.contents={},this.path=t,this.fullPath=c(t,location.pathname),this.contentReady=!1}async getContent(){return this.contentReady||(await t(this,s),this.keys=function(e,n){let r=new Set,s=t=>{t.forEach(t=>{e[t]&&(s(n.contents[t].children),r.add(t))})};return s(Object.keys(e).reverse()),Array.from(r)}(this.contents,s),this.contentReady=!0),this}build(t={},e={},r="context"){var{fn:r,modulesLines:s,curLastLine:i}=function(t,r){let s={},i=3;var e=r.keys.map((t,e)=>{let n=`modules['${t}'] = (function (){ | ||
const module = { exports: {} } | ||
const exports = module.exports | ||
${r.contents[t]} | ||
return module.exports; | ||
})();`;return e===r.keys.length-1&&(n+=` | ||
return modules['${t}']`),s[t]={from:i+1},i+=n.split("\n").length,s[t].to=i,n}).join("\n");return{fn:new Function("modules",t,`function require(path) { return modules[path] || null }; | ||
`+e),modulesLines:s,curLastLine:i}}(r,this);try{return r(t,e)}catch(n){{r=n;var o=s;var l=i;let[t,...e]=r.stack.split("\n");throw e=e.map(t=>{var e,r,s,i=t.match(/<anonymous>:(\d*):(\d*)\)$/);if(i){let n=Number(i[1]);if(n+1!==l)return i=Number(i[2]),[e,{from:r,to:s}]=Object.entries(o).filter(([,{from:t,to:e}])=>n>=t&&n<=e)[0],` at ${t.match(/at\s(.*?)\s/)[1]} ${e} (${n-r-2}:${i})`}}).filter(Boolean),r.stack=t+"\n"+e.join("\n"),r;return}}}}return s}();l.getModule;function c(t,e,n,s){let i=new RegExp("<(\\/?)"+n[1].split(" ")[0]+"(\\s|>)"),o=0;return function t(e,n=0){var r=e.match(i);if(null===r)return null;""===r[1]&&o++,"/"===r[1]&&o--;r=r.index+r[0].length;if(0!==o)return t(e.slice(r),n+r);{let e=r+n;for(let t=e;t<s.length;t++)if(")"===s[t]){e=t+1;break}return e}}(t,e)}function a(n){let r=[],s="",i,o;function t(t,e){t.replace(/\s*/,t=>{0<t.length&&r[r.length-1].text!==t&&r.push({text:t,type:void 0})}),t=t.trim(),o={text:t,type:i},i=void 0,0!==t.length&&(r.push(o),s=e)}let l=0;for(let e=0;e<n.length;e++){var c=n[e],a=n[e-1],u=n[e+1],f=s;if(s+=c,"{"===c)if("/"===u&&"*"===n[e+2]){for(let t=e+2;t<n.length;t++)if("*"===n[t]&&"/"===n[t+1]&&"}"===n[t+2]){e=t+2,s=s.slice(0,-1);break}}else l++;else l?"}"===c&&l--:"<"===c?(t(f,"<"),i="/"===u?"close":"open"):">"===c&&("open"!==i&&"/"===a&&(i="<"===n[e-2]?"close":"single"),t(s,""));e===n.length-1&&t(s,"")}return r}function u(t,e,n,r,s){if(!s)return i=`[${e.map(t=>"`"+t+"`").join(",")}]`,t?"$"+`{${r}(${n},${i})}`:"<"+r+(e.length?" "+e.join(" "):"")+">";if(t){var i=`[${e.map(t=>"`"+t+"`").join(",")}]`;let t=s.join("");return"$"+`{${r}(${n},${i},${t=t.startsWith("${")&&t.endsWith("}")?t.slice(2,-1):"`"+t+"`"})}`}return t="<"+r+(e.length?" "+e.join(" "):"")+">",n="</"+r+">",0===s.length?t+n:t+s.join("")+n}function f(t){if("string"==typeof t){var e=t.trim();if(t.startsWith("{"))return"$"+t;if(e.startsWith("("))return t.replace("(","`");if(e.startsWith(")")){if(e.startsWith(")}")){if(e.slice(2).trim().startsWith("{"))return t.replace(")","`").replace("{","${");if(")}"===t)return t}return t.startsWith(").")?t:t.replace(")","`")}return t=t.replace(/\}[\s\S]*?(\$?\{)/g,(t,e)=>"{"!==e||t.match(/const|let|var|\)|\(|,\s*?\{$/)?t:t.slice(0,-1)+"${")}if(void 0===t.type)return/\{.*\}/.test(t.text)?t.text.replace("{","${"):"("===t.text||")"===t.text?"`":t.text}function p(e,n,r){let s=0,i;for(let t=n+1;t<e.length;t++)if(!Array.isArray(e[t])&&("open"===e[t].type&&e[t].text.startsWith("<"+r)&&s++,"close"===e[t].type)&&e[t].text.startsWith("</"+r)){if(!(0<s)){i=t;break}s--}return i}function h(t){var e=t.text.trim().slice(1,-1).split(/\s/).filter(Boolean),t=(1===e.length&&e[0].endsWith("/")?(t.type="single",e[0]=e[0].slice(0,-1)):"/"===e[e.length-1]&&(t.type="single"),"single"===t.type&&1<e.length&&e.pop(),e.shift()),n=null!==t.match(/[A-Z]/),{attributes:e,props:r}=function(s,i){var o,l,c,a,u,f,p=[],h=[];let d;for(let r=0;r<s.length;r++){let t=!1,[e,n]=s[r].split("=");if("/"!==e){if(n&&n.startsWith("{"))for(t=!0;!n.endsWith("}");)r++,n=n+" "+s[r];else if(n)for(var m=n[0];!n.endsWith(m);)r++,n=n+" "+s[r];if(e.startsWith("{..."))d=e.slice(1,-1);else if(n=n&&n.slice(1,-1),"checked"===e||"disabled"===e)p.push("${"+n+` ? '${e}' : ''}`);else{if("className"===e)e="class";else if(e.startsWith("on")){o=e,l=n,c=p,f=u=a=void 0,a=o.split("on")[1].toLowerCase(),u=l,l="${context.counter++}",f=`[${o=a}="\${context.counter-1}"]`,c.push(o+`="${l+="$"+`{context.actions.push({selector:\`${f}\`,event:'${a}',fn:${u}}) ? '' : ''}`}"`);continue}n?t?i?h.push(e+":"+n):p.push(`${e}="\${${n}}"`):p.push(`${e}="${n}"`):p.push(e)}}}return d&&h.push(d),{attributes:p,props:`{${h.join(",")}}`}}(e,n);return{tagName:t,isComponent:n,attributes:e,props:r}}return async function(t,e="body",n={}){var r={actions:[],components:{},counter:0},s=new l(t);await s.getContent();for(let t in s.contents)s.contents[t]=function(t,e){let s=e.split("/").pop().replace(".js",""),n=/[A-z]/.test(s[0]),r=new RegExp("^.*"+s+"\\s*?\\(([\\s\\S]*?)\\)\\s*?\\{([\\s\\S]*)\\}","m");return t=n?t.replace(r,(t,e,n)=>{var r,n=function r(s){let o=0;function l(t,e){let n=p(s,e,t);return n>o&&(o=n),r(s.slice(e+1,n))}s=s.map((s,i)=>{if(!(i<o)){if(Array.isArray(s))return r(s);if("string"==typeof s||void 0===s.type)return f(s);if("<>"===s.text)return"<>"+l(">",i)+"</>";let t=s.text.match(/^\<(\w.*?)(\s|\>)/);if(null!==t&&"open"===s.type){let{isComponent:t,tagName:e,attributes:n,props:r}=h(s);return"single"===s.type?u(t,n,r,e):u(t,n,r,e,l(e,i))}}}).filter(Boolean);if(3<=s.length&&"`"===s[0]&&"`"===s[s.length-1]){let t=s.flat()[1].trim();if(t.startsWith("${"))return[t.slice(2,-1)]}if(s.flat().every(t=>"string"==typeof t))return[s.join("")];return s}(function r(n){let s=[];let i=n.match(/\(\s*?\<([\s\S]*?)\>/);if(!i)return[n];let o=i.index;0<o&&s.push(n.slice(0,o));if(i[1].endsWith("/")){let t=a(i[0]),e=(t.forEach(t=>s.push(t)),r(n.slice(o+i[0].length)));return e.forEach(t=>s.push(t)),s}let e=c(n.slice(o),o,i,n);if(e){let t=a(n.slice(o,e));if(t=t.map(e=>{let{text:n,type:t}=e;if(void 0===t&&n.startsWith("{")&&n.endsWith("}")){let t=r(n);return t[0]===n?e:t}return e}),s.push(t),e<n.length){let t=r(n.slice(e));1===t.length?s.push(t[0]):s.push(t)}}return s}(n))[0];return n=n,`function ${r=s}(props={},atts=[],inner='') { | ||
const originalFn = (${e}) => { | ||
${n} | ||
} | ||
let result = originalFn(props,inner) | ||
if(!result.startsWith('<>')) { | ||
result = result.replace(/<([\\s\\S]*?)\\>/,(m) => { // get first tag. | ||
atts.push('component="${r}"') | ||
if(props.key !== undefined) atts.push('key="'+props.key+'"') | ||
return m.slice(0,-1) + ' '+atts.join(' ')+'>' | ||
}) | ||
} | ||
result = result.replace(/\\<\\/?\\>/g,'') | ||
return result | ||
} | ||
${r}.update = function(props={},atts=[],inner) { | ||
let selector = '[component="${r}"]' | ||
if(props.key !== undefined) selector = [key="\${key}"]+selector | ||
document.querySelector(selector).outerHTML = ${r}(props,atts,inner) | ||
context.actions.forEach(({ selector, event, fn }) => { | ||
const element = document.querySelector(selector) | ||
if(element) element.addEventListener(event, fn); | ||
}); | ||
context.actions = [] | ||
} | ||
context.components.${r} = ${r} | ||
`}):t}(s.contents[t],t);var[t,i,e,n=!0]=[e,s.build({},r,"context")(n).trim(),r];function o(t,e){t=document.querySelector(t);t&&e(t)}n&&o(t,t=>t.innerHTML=i),e.actions.forEach(({selector:t,event:e,fn:n})=>{o(t,t=>t.addEventListener(e,n))}),e.actions=[]}}(); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
3
22
940
105
2
5
49612
+ Addedals-require@1.0.1(transitive)
- Removedals-replace-between@3.3.1
- Removedals-simple-css@^9.1.0
- Removedals-css-parse@1.3.0(transitive)
- Removedals-replace-between@3.3.13.5.0(transitive)
- Removedals-require@0.9.0(transitive)
- Removedals-simple-css@9.3.0(transitive)
Updatedals-require@1.0.1