@ellx/framework
Advanced tools
Comparing version 0.0.1 to 0.0.2
{ | ||
"name": "@ellx/framework", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"description": "Ellx full stack framework", | ||
@@ -25,5 +25,5 @@ "type": "module", | ||
"md5": "^2.3.0", | ||
"mime": "^3.0.0", | ||
"node-resolve-flow": "^0.8.0", | ||
"open": "^8.4.0", | ||
"polka": "^0.5.2", | ||
"qcjs": "^0.1.0", | ||
@@ -34,3 +34,2 @@ "quarx": "^5, >=5.4.1", | ||
"rd-parse-jsexpr": "^3.2.0", | ||
"serve-static": "^1.15.0", | ||
"ws": "^8.11.0" | ||
@@ -37,0 +36,0 @@ }, |
@@ -5,2 +5,3 @@ import { transform as esm2cjs } from 'qcjs'; | ||
const scriptTags = /(<script type="ellx">|<\/script>)/g; | ||
export const assetHrefs = /\b(href|src)="(\/assets\/[^"]+)"/g; | ||
@@ -27,13 +28,11 @@ export function transformModule(id, text) { | ||
const assets = [...text.matchAll(assetHrefs)].map(([,, path]) => 'file://' + path); | ||
return { | ||
code: `<!--${id}-->` + text, | ||
imports | ||
code: text, | ||
imports, | ||
assets | ||
} | ||
} | ||
else if (id.endsWith('.css')) { | ||
return { | ||
code: { exports: text }, | ||
} | ||
} | ||
return esm2cjs(text); | ||
} |
@@ -8,11 +8,8 @@ import { createAtom } from 'quarx'; | ||
export default function reactiveBuild(mainPage, files, updateModules) { | ||
const styleSheet = new URL('./styles.css', mainPage).href; | ||
export default function reactiveBuild(mainPageId, entryPointId, rfs, updateModules) { | ||
const entryPoints = [ | ||
mainPage, | ||
styleSheet, | ||
mainPageId, | ||
entryPointId, | ||
'conclure', | ||
'node-resolve-flow', | ||
'./src/runtime/dev/dev_runtime.js' | ||
'node-resolve-flow' | ||
]; | ||
@@ -23,11 +20,14 @@ | ||
function* loadPkgJSON(url) { | ||
const pjsonBody = yield files.get(url); | ||
const pjson = pjsonBody && JSON.parse(pjsonBody); | ||
const pjsonBody = yield rfs.get(url); | ||
const pjson = pjsonBody && JSON.parse(pjsonBody.toString('utf8')); | ||
bundle[url] = pjson && { | ||
code: { exports: pjson }, | ||
imports: {} | ||
}; | ||
if (!pjson) return null; | ||
return pjson; | ||
const pruned = {}; | ||
for (let p of ['name', 'exports', 'imports', 'browser', 'main']) { | ||
if (p in pjson) pruned[p] = pjson[p]; | ||
} | ||
bundle[url] = { code: { exports: pruned } }; | ||
return pruned; | ||
} | ||
@@ -40,3 +40,3 @@ | ||
const body = yield files.get(url); | ||
const body = yield rfs.get(url); | ||
@@ -51,7 +51,8 @@ if (body === undefined) { | ||
return bundle[url] = transformModule(url, body); | ||
return bundle[url] = url.startsWith('file:///assets/') ? body : | ||
transformModule(url, body.toString('utf8')); | ||
}); | ||
const resolveId = resolver({ | ||
isFile: url => files.has(url), | ||
isFile: url => rfs.has(url), | ||
loadPkgJSON: autoMemo(loadPkgJSON) | ||
@@ -83,2 +84,3 @@ }); | ||
.map(module => loadModule(module, id, loadStack)) | ||
.concat((node.assets || []).map(load)) | ||
); | ||
@@ -85,0 +87,0 @@ |
@@ -25,3 +25,3 @@ import chokidar from 'chokidar'; | ||
const load = path => fs.readFile(path, 'utf8') | ||
const load = path => fs.readFile(path) | ||
.catch((e) => undefined); | ||
@@ -28,0 +28,0 @@ |
#!/usr/bin/env node | ||
import http from "http"; | ||
import { WebSocketServer } from "ws"; | ||
import commandLineArgs from "command-line-args"; | ||
import { start, build } from "./build_n_serve.js"; | ||
import { pathToFileURL, fileURLToPath } from 'url'; | ||
import { join, dirname } from "path"; | ||
import { readFileSync } from 'fs'; | ||
import polka from "polka"; | ||
import serve from "serve-static"; | ||
import open from "open"; | ||
import reactiveFS from './bundler/reactive_fs.js'; | ||
import { startDevPipe } from "./dev_pipe.js"; | ||
const __dirname = dirname(fileURLToPath(import.meta.url)); | ||
// 1) - parse the main command | ||
@@ -32,3 +18,3 @@ | ||
{ name: "port", alias: "p", type: Number, defaultValue: 3002 }, | ||
{ name: "open", alias: "o", type: Boolean, defaultValue: true }, | ||
{ name: "open", alias: "o", type: Boolean, defaultValue: false }, | ||
]; | ||
@@ -38,38 +24,14 @@ | ||
const devIndex = readFileSync(join(__dirname, 'dev_index.html'), 'utf8'); | ||
const server = http.createServer(); | ||
polka({ server }) | ||
.use(serve(process.cwd())) | ||
.get("*", (req, res, next) => res.end(devIndex)) | ||
.listen(config.port, (err) => { | ||
if (err) throw err; | ||
const url = `http://localhost:${config.port}`; | ||
console.log(`> Running on ${url}`); | ||
if (config.open) { | ||
open(url).catch(console.error); | ||
} | ||
}); | ||
const wss = new WebSocketServer({ server, path: "/@@dev" }); | ||
const rfs = reactiveFS(process.cwd(), { logger: console.log }); | ||
wss.on('connection', ws => startDevPipe( | ||
ws, | ||
rfs, | ||
'file:///src/index.html' | ||
).catch(console.error)); | ||
start(process.cwd(), config); | ||
} | ||
else if (mainOptions.command === 'build') { | ||
build(process.cwd()); | ||
} | ||
else { | ||
console.log(`Please specify a command: | ||
dev | ||
build | ||
login (WIP) | ||
publish (WIP) | ||
deploy (WIP) | ||
`); | ||
process.exit(1); | ||
} |
@@ -1,6 +0,4 @@ | ||
import { writeFile, mkdir } from 'fs/promises'; | ||
import { writeFile, mkdir, rm } from 'fs/promises'; | ||
import { join } from 'path'; | ||
import { fileURLToPath } from 'url'; | ||
import md5 from 'md5'; | ||
import { all } from 'conclure/combinators'; | ||
@@ -11,3 +9,3 @@ async function makeOne(filePath, contents, secondTry = false) { | ||
if (filePath.endsWith('/')) await mkdir(filePath); | ||
else await writeFile(filePath, contents, 'utf8'); | ||
else await writeFile(filePath, contents); | ||
} | ||
@@ -25,13 +23,13 @@ catch (e) { | ||
export function* copyToDist(distDir, modules) { | ||
yield rmdir(distDir, { recursive: true }); | ||
export async function copyToDist(distDir, artefacts) { | ||
try { | ||
await rm(distDir, { recursive: true }); | ||
} | ||
catch (e) { | ||
if (e.code !== 'ENOENT') throw e; | ||
} | ||
yield all(Object.entries(modules).map(([urlPath, body]) => { | ||
if (urlPath !== 'file:///index.html') { | ||
const hash = md5(body).slice(0, 8); | ||
urlPath = urlPath.replace(/\.[^.]*$/, ext => '-' + hash + ext); | ||
} | ||
return makeOne(join(distDir, fileURLToPath(urlPath)), body); | ||
})); | ||
await Promise.all(Object.entries(artefacts) | ||
.map(([url, body]) => makeOne(join(distDir, fileURLToPath(url)), body)) | ||
); | ||
} |
@@ -1,2 +0,2 @@ | ||
export function bootstrap(modules, entryPoint, ...args) { | ||
export function bootstrap(modules, { conclureId, resolverId, mainPageId, entryPoint }) { | ||
console.debug('***Initial modules***', modules); | ||
@@ -7,5 +7,5 @@ | ||
for (let url in modules) { | ||
const { src, code } = modules[url]; | ||
const src = modules[url]; | ||
const source = src ? fetch(src).then(res => res.text()) : code; | ||
const source = typeof src === 'string' ? fetch(url.slice(7) + '?h=' + src).then(res => res.text()) : src; | ||
registry.set(url, createModuleNode(url, source)); | ||
@@ -94,4 +94,4 @@ } | ||
const { conclude, isFlow } = requireNode(Object.keys(modules).find(id => id.endsWith('/node_modules/conclure/src/conclude.js'))); | ||
const { default: makeResolve } = requireNode(Object.keys(modules).find(id => id.endsWith('/node_modules/node-resolve-flow/src/resolver.js'))); | ||
const { conclude, isFlow } = requireNode(conclureId); | ||
const { default: makeResolve } = requireNode(resolverId); | ||
@@ -140,4 +140,5 @@ const toAsync = f => function* (...args) { | ||
if (error) console.error(error.stack); | ||
init.default(...args); | ||
init.default(mainPageId); | ||
}); | ||
} |
import { autorun, batch, createAtom } from 'quarx'; | ||
import { box } from 'quarx/box'; | ||
import { observableMap } from 'quarx/map'; | ||
import { hydrate, cannotResolve } from '../dom_attach.js'; | ||
import hydrateDocument from '../dom_attach.js'; | ||
function replaceDocumentHTML(id, html) { | ||
function replaceDocumentHTML(html) { | ||
document.open(); | ||
document.write(html); | ||
document.close(); | ||
const require = url => module.requireAsync(url, id); | ||
const children = [...document.head.children, ...document.body.children]; | ||
return hydrate(children, name => ({ require })[name] || cannotResolve(name)); | ||
} | ||
@@ -50,10 +45,5 @@ | ||
export default function init(devServer) { | ||
export default function init(mainPageId) { | ||
const proto = Object.getPrototypeOf(module); | ||
const mainPage = 'file:///src/index.html'; | ||
if (!proto.registry.has(mainPage)) { | ||
throw new Error(`Main page ${mainPage} not found`); | ||
} | ||
for (let [id, node] of proto.registry) { | ||
@@ -70,4 +60,4 @@ makeInstanceReactive(id, node); | ||
for (let id in modules) { | ||
if (id.endsWith('.css')) updateStyles(id); | ||
else if (modules[id] === 'deleted') proto.registry.delete(id); | ||
if (modules[id] === 'deleted') proto.registry.delete(id); | ||
else if (id.startsWith('file:///assets/')) updateAsset(id, modules[id]); | ||
else proto.registry.set(id, makeInstanceReactive(id, proto.createNode(id, modules[id].code))); | ||
@@ -79,11 +69,10 @@ } | ||
function updateStyles(id) { | ||
console.debug('***STYLESHEET update***', id); | ||
function updateAsset(id, hash) { | ||
console.debug('***ASSET update***', id); | ||
const url = id.slice(7); | ||
const styleSheet = document.querySelector(`link[rel="stylesheet"][href^=${JSON.stringify(url)}]`); | ||
if (styleSheet) { | ||
styleSheet.href = url + '?reload' + Math.random(); | ||
} | ||
else console.error('STYLESHET not loaded', id); | ||
['href', 'src'].forEach(attr => | ||
document.querySelectorAll(`[${attr}^="${url}"]`) | ||
.forEach(asset => asset[attr] = url + '?h=' + hash) | ||
); | ||
} | ||
@@ -111,2 +100,4 @@ | ||
const devServer = new WebSocket(window.location.origin.replace(/^http/, 'ws') + '/@@dev'); | ||
devServer.addEventListener('message', listen); | ||
@@ -135,3 +126,3 @@ devServer.addEventListener('close', disconnect); | ||
`<pre style="color:red">${error}</pre>` : | ||
proto.registry.get(mainPage).instance.exports.default; | ||
proto.registry.get(mainPageId)?.instance.exports.default; | ||
@@ -141,5 +132,8 @@ // Inserting a tick here ensures that everything becomes unobserved and | ||
Promise.resolve().then(() => | ||
off = replaceDocumentHTML(mainPage, html) | ||
); | ||
Promise.resolve().then(() => { | ||
if (html !== undefined) { | ||
replaceDocumentHTML(html); | ||
} | ||
off = hydrateDocument(mainPageId); | ||
}); | ||
}, { name: 'replaceDocumentHTML' })); | ||
@@ -146,0 +140,0 @@ |
@@ -5,6 +5,13 @@ import { calcNode } from '@ellx/core'; | ||
export const cannotResolve = name => { | ||
const cannotResolve = name => { | ||
throw new Error(`Cannot resolve ${name}`); | ||
} | ||
export default function hydrateDocument(mainPageId) { | ||
const require = url => module.requireAsync(url, mainPageId); | ||
const children = [...document.head.children, ...document.body.children]; | ||
return hydrate(children, name => ({ require })[name] || cannotResolve(name)); | ||
} | ||
function bindInnerHTML(element, resolve) { | ||
@@ -244,3 +251,3 @@ const { component, props } = parseIsComponent(element.dataset.is); | ||
export function hydrate(children, resolve) { | ||
function hydrate(children, resolve) { | ||
const subscriptions = []; | ||
@@ -247,0 +254,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
35865
14
1026
14
+ Addedmime@^3.0.0
+ Addedmime@3.0.0(transitive)
- Removedpolka@^0.5.2
- Removedserve-static@^1.15.0
- Removed@arr/every@1.0.1(transitive)
- Removed@polka/url@0.5.0(transitive)
- Removeddebug@2.6.9(transitive)
- Removeddepd@2.0.0(transitive)
- Removeddestroy@1.2.0(transitive)
- Removedee-first@1.1.1(transitive)
- Removedencodeurl@1.0.22.0.0(transitive)
- Removedescape-html@1.0.3(transitive)
- Removedetag@1.8.1(transitive)
- Removedfresh@0.5.2(transitive)
- Removedhttp-errors@2.0.0(transitive)
- Removedinherits@2.0.4(transitive)
- Removedmatchit@1.1.0(transitive)
- Removedmime@1.6.0(transitive)
- Removedms@2.0.02.1.3(transitive)
- Removedon-finished@2.4.1(transitive)
- Removedparseurl@1.3.3(transitive)
- Removedpolka@0.5.2(transitive)
- Removedrange-parser@1.2.1(transitive)
- Removedsend@0.19.0(transitive)
- Removedserve-static@1.16.2(transitive)
- Removedsetprototypeof@1.2.0(transitive)
- Removedstatuses@2.0.1(transitive)
- Removedtoidentifier@1.0.1(transitive)
- Removedtrouter@2.0.1(transitive)