|
|
|
@ -1,8 +1,49 @@ |
|
|
|
class Builder { |
|
|
|
const defaultHtml = ` |
|
|
|
|
|
|
|
<!doctype html> |
|
|
|
<html> |
|
|
|
<head> |
|
|
|
<title>macchiato.dev</title> |
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" /> |
|
|
|
<style type="text/css"> |
|
|
|
html, body { |
|
|
|
margin: 0; |
|
|
|
padding: 0; |
|
|
|
} |
|
|
|
html { |
|
|
|
box-sizing: border-box; |
|
|
|
} |
|
|
|
*, *:before, *:after { |
|
|
|
box-sizing: inherit; |
|
|
|
} |
|
|
|
</style> |
|
|
|
</head> |
|
|
|
<body> |
|
|
|
${ '<' + 'script type="module" src="/app.js"><' + '/script>' } |
|
|
|
</body> |
|
|
|
</html> |
|
|
|
|
|
|
|
`.trim()
|
|
|
|
|
|
|
|
const defaultIntro = ` |
|
|
|
|
|
|
|
window.Macchiato = { |
|
|
|
modules: {}, |
|
|
|
} |
|
|
|
|
|
|
|
`.trim()
|
|
|
|
|
|
|
|
export class Builder { |
|
|
|
constructor(files) { |
|
|
|
this.files = files |
|
|
|
} |
|
|
|
|
|
|
|
buildStyle(file) { |
|
|
|
const style = document.createElement('style') |
|
|
|
style.textContent = file.data |
|
|
|
return style.outerHTML |
|
|
|
} |
|
|
|
|
|
|
|
buildModule(file) { |
|
|
|
const script = document.createElement('script') |
|
|
|
script.setAttribute('type', 'module') |
|
|
|
@ -39,44 +80,74 @@ class Builder { |
|
|
|
return script.outerHTML |
|
|
|
} |
|
|
|
|
|
|
|
buildReplace(filesMap) { |
|
|
|
if ('_replace.js' in filesMap) { |
|
|
|
const rSrc = filesMap['_replace.js'] |
|
|
|
return new Function( |
|
|
|
rSrc.match(/\((\w+)\)/)[1], |
|
|
|
rSrc.slice( |
|
|
|
rSrc.indexOf('{') + 1, |
|
|
|
rSrc.lastIndexOf('}') |
|
|
|
) |
|
|
|
) |
|
|
|
} else { |
|
|
|
return s => s |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
build() { |
|
|
|
const modules = this.files.map(file => { |
|
|
|
return this.buildModule(file) |
|
|
|
const filesMap = Object.fromEntries( |
|
|
|
this.files.map( |
|
|
|
({name, data}) => ([name, data]) |
|
|
|
) |
|
|
|
) |
|
|
|
const intro = this.buildModule({ |
|
|
|
name: '_intro.js', |
|
|
|
data: ( |
|
|
|
'_intro.js' in filesMap ? |
|
|
|
filesMap['_intro.js'] : |
|
|
|
defaultIntro |
|
|
|
), |
|
|
|
}) |
|
|
|
return ` |
|
|
|
<!doctype html> |
|
|
|
<html> |
|
|
|
<head> |
|
|
|
<title>Editor</title> |
|
|
|
<style> |
|
|
|
html, body, iframe { |
|
|
|
margin: 0; |
|
|
|
padding: 0; |
|
|
|
width: 100%; |
|
|
|
height: 100%; |
|
|
|
} |
|
|
|
iframe { |
|
|
|
border: none; |
|
|
|
display: block; |
|
|
|
} |
|
|
|
html { |
|
|
|
box-sizing: border-box; |
|
|
|
} |
|
|
|
*, *:before, *:after { |
|
|
|
box-sizing: inherit; |
|
|
|
} |
|
|
|
</style> |
|
|
|
</head> |
|
|
|
<body> |
|
|
|
${'<'}script type="module"> |
|
|
|
window.Macchiato = {modules: {}} |
|
|
|
${'</'}script> |
|
|
|
${modules.join("\n")} |
|
|
|
${'<'}script type="module"> |
|
|
|
|
|
|
|
${'</'}script> |
|
|
|
</body> |
|
|
|
</html> |
|
|
|
`.trim()
|
|
|
|
const replace = this.buildReplace(filesMap) |
|
|
|
const modules = this.files.filter(({name}) => ( |
|
|
|
name.endsWith('.js') && |
|
|
|
!name.startsWith('_') |
|
|
|
)).map(file => ( |
|
|
|
this.buildModule({ |
|
|
|
...file, |
|
|
|
data: replace(file.data), |
|
|
|
}) |
|
|
|
)) |
|
|
|
const styles = this.files.filter(({name}) => ( |
|
|
|
name.endsWith('.css') |
|
|
|
)).map(file => ( |
|
|
|
this.buildStyle(file) |
|
|
|
)) |
|
|
|
let html = ( |
|
|
|
'index.html' in filesMap ? |
|
|
|
filesMap['index.html'] : |
|
|
|
defaultHtml |
|
|
|
) |
|
|
|
let replaced = false |
|
|
|
html = html.replace(/\s*<\/head>/, match => { |
|
|
|
replaced = true |
|
|
|
return styles.join("\n") + "\n" + match |
|
|
|
}) |
|
|
|
if (!replaced) { |
|
|
|
html = styles.join("\n") + "\n" + html |
|
|
|
} |
|
|
|
replaced = false |
|
|
|
html = html.replace( |
|
|
|
'<' + 'script type="module" src="/app.js"><' + '/script>', |
|
|
|
() => { |
|
|
|
replaced = true |
|
|
|
return intro + "\n" + modules.join("\n") |
|
|
|
} |
|
|
|
) |
|
|
|
if (!replaced) { |
|
|
|
html += "\n" + intro + "\n" + modules.join("\n") |
|
|
|
} |
|
|
|
return html |
|
|
|
} |
|
|
|
} |