Comparing version 0.1.8 to 0.2.0
{ | ||
"name": "nuekit", | ||
"version": "0.1.8", | ||
"description": "The Nue framework for building websites and webapps", | ||
"version": "0.2.0", | ||
"description": "The closer-to-standards web framework. Build sites and apps with less effort.", | ||
"homepage": "https://nuejs.org", | ||
@@ -22,8 +22,5 @@ "license": "MIT", | ||
"js-yaml": "^4.1.0", | ||
"marked": "^9.1.6", | ||
"nuejs-core": "^0.3.0" | ||
"nuejs-core": "^0.3.0", | ||
"nuemark": "^0.1.0" | ||
}, | ||
"devDependencies": { | ||
"esbuild": "^0.19.11" | ||
}, | ||
"engines": { | ||
@@ -30,0 +27,0 @@ "bun": ">= 1", |
@@ -17,5 +17,5 @@ | ||
- [Improved development experience](https://nuejs.org/docs/why-nue/developer-experience.html) | ||
- [Standards-based development model](https://nuejs.org/docs/why-nue/closer-to-standards.html) | ||
- [Improved website performance](https://nuejs.org/docs/why-nue/website-performance.html) | ||
- [Content first](https://nuejs.org/docs/why-nue/content-first.html) | ||
- [Extreme performance](https://nuejs.org/docs/why-nue/extreme-performance.html) | ||
- [Closer to standards](https://nuejs.org/docs/why-nue/closer-to-standards.html) | ||
@@ -33,5 +33,5 @@ #### Notable features | ||
### Roadmap | ||
The ultimate goal of Nue is to create a simpler and more powerful alternative to **Vercel**, **Gatsby**, and **Netlify**. | ||
The ultimate goal of Nue is to build a content first alternative to **Vercel** and **Netlify**, which is extremely fast and ridiculously easy to use. | ||
![Nue Roadmap](https://nuejs.org/img/roadmap3-big.png) | ||
![Nue Roadmap](https://nuejs.org/img/roadmap4-big.png) | ||
@@ -38,0 +38,0 @@ |
/* Builders for CSS, JS, and TS */ | ||
import { join, extname } from 'node:path' | ||
@@ -72,2 +74,75 @@ | ||
const not_found = {} | ||
export async function findModule(name, path='') { | ||
if (not_found[name]) return | ||
const attempts = [ | ||
join(process.cwd(), 'node_modules', name, path), | ||
join(process.cwd(), '..', 'node_modules', name, path), | ||
name, | ||
] | ||
for (const path of attempts) { | ||
try { | ||
return await import(path) | ||
} catch {} | ||
} | ||
not_found[name] = true | ||
} | ||
export async function buildCSS({ css, ext, minify }) { | ||
// stylus | ||
if (ext == '.styl') { | ||
const stylus = await findModule('stylus') | ||
if (!stylus) throw 'Module not found: "stylus"' | ||
try { | ||
process.stdout.write('🖌️') | ||
return stylus.render(css, { compress: minify }) | ||
} catch({ message, stack }) { | ||
const [l, col] = message.slice(7, message.indexOf('\n')).split(':') | ||
throw { | ||
title: 'Stylus syntax error', | ||
text: stack.split('\n')[0], | ||
lineText: css.split('\n')[l -1], | ||
column: 1 * col, | ||
line: 1 * l, | ||
} | ||
} | ||
} | ||
// Nue CSS (not public yet) | ||
if (ext == '.style') { | ||
const nuecss = await import('nuecss') | ||
return nuecss.default(css, { minify }) | ||
} | ||
const mod = await findModule('lightningcss', 'node/index.mjs') | ||
// Standard CSS | ||
if (!mod) return minify ? minifyCSS(css) : css | ||
// Lightning CSS | ||
process.stdout.write('⚡️') | ||
try { | ||
const include = mod.Features.Colors | mod.Features.Nesting | ||
return mod.transform({ code: Buffer.from(css), include, minify }).code?.toString() | ||
} catch({ source, loc, data}) { | ||
throw { | ||
title: 'CSS syntax error', | ||
lineText: source.split('\n')[loc.line -1], | ||
text: data.type, | ||
...loc | ||
} | ||
} | ||
} | ||
// temporary hack, until Bun does this natively | ||
@@ -74,0 +149,0 @@ export function minifyCSS(code) { |
@@ -27,3 +27,3 @@ #!/usr/bin/env bun | ||
// skip | ||
if (arg.endsWith('/cli.js') || arg == '--') { | ||
if (arg.endsWith('/cli.js') || arg.endsWith('/nue') || arg == '--') { | ||
@@ -69,3 +69,4 @@ // test suite | ||
const { promises } = await import('fs') | ||
const path = new URL('../package.json', import.meta.url).pathname | ||
const pathname = new URL('../package.json', import.meta.url).pathname | ||
const path = process.platform === "win32" && pathname.startsWith('/') ? pathname.slice(1) : pathname | ||
const json = await promises.readFile(path, 'utf-8') | ||
@@ -72,0 +73,0 @@ return JSON.parse(json).version |
@@ -13,3 +13,4 @@ | ||
const cwd = process.cwd() | ||
const srcdir = new URL('.', import.meta.url).pathname | ||
const pathname = new URL('.', import.meta.url).pathname | ||
const srcdir = process.platform === "win32" && pathname.startsWith('/') ? pathname.slice(1) : pathname | ||
const fromdir = join(srcdir, 'browser') | ||
@@ -21,3 +22,3 @@ const outdir = join(cwd, dist, '@nue') | ||
// has all latest? | ||
const latest = join(outdir, '.016') | ||
const latest = join(outdir, '.020') | ||
try { | ||
@@ -27,3 +28,3 @@ return await fs.stat(latest) | ||
} catch { | ||
await fs.rmdir(outdir, { recursive: true }) | ||
await fs.rm(outdir, { recursive: true, force: true }) | ||
await fs.mkdir(outdir, { recursive: true }) | ||
@@ -70,5 +71,4 @@ await fs.writeFile(latest, '') | ||
await buildPackage('nuemark', 'nuemark.js') | ||
await buildPackage('nuejs-core', 'nue.js') | ||
await buildPackage('diff-dom', 'diffdom.js') | ||
await buildFile('page-router') | ||
@@ -80,2 +80,3 @@ await buildFile('app-router') | ||
if (is_dev) { | ||
await buildPackage('diff-dom', 'diffdom.js') | ||
await buildFile('hotreload') | ||
@@ -82,0 +83,0 @@ await copy('error.css', outdir) |
@@ -61,3 +61,3 @@ import { extname } from 'node:path' | ||
// inline style | ||
inline_css.forEach(css => head.push(`<style href="${base}${css.path}">${ css.content }</style>`)) | ||
inline_css.forEach(el => head.push(`<style href="${base}${el.path}">${ el.css }</style>`)) | ||
@@ -64,0 +64,0 @@ // stylesheets |
@@ -38,3 +38,3 @@ | ||
if (e.errno == -2) await onremove(path) | ||
else console.info(e) | ||
else console.error(e) | ||
} | ||
@@ -48,3 +48,3 @@ }) | ||
for (const f of files) { | ||
if (!ignore(f.name[0])) { | ||
if (!ignore(f.name)) { | ||
const path = join(_dir, f.name) | ||
@@ -59,4 +59,6 @@ if (isDir(f)) await fswalk(root, path, _ret) | ||
const IGNORE = ['node_modules', 'package.json', 'bun.lockb', 'pnpm-lock.yaml'] | ||
function ignore(name) { | ||
return '._'.includes(name[0]) | ||
return '._'.includes(name[0]) || IGNORE.includes(name) | ||
} | ||
@@ -63,0 +65,0 @@ |
import { parse as parseNue, compile as compileNue } from 'nuejs-core/index.js' | ||
import { log, colors, parseMarkdown, getAppDir, getParts } from './util.js' | ||
import { join, parse as parsePath, extname, basename } from 'node:path' | ||
import { renderHead, getDefaultHTML, getDefaultSPA } from './layout.js' | ||
import { join, parse as parsePath, extname } from 'node:path' | ||
import { readStats, printTable, categorize } from './stats.js' | ||
import { log, colors, getAppDir, getParts } from './util.js' | ||
import { parsePage, renderPage } from 'nuemark/index.js' | ||
import { createServer, send } from './nueserver.js' | ||
import { minifyCSS, buildJS } from './builder.js' | ||
import { buildCSS, buildJS } from './builder.js' | ||
import { promises as fs } from 'node:fs' | ||
@@ -37,4 +38,7 @@ import { createSite } from './site.js' | ||
// alphabtical order | ||
paths.sort() | ||
if (data.inline_css) { | ||
data.inline_css = await readAllCSS(paths) | ||
data.inline_css = await buildAllCSS(paths) | ||
@@ -49,2 +53,11 @@ // prefetch global CSS | ||
async function buildAllCSS(paths) { | ||
const arr = [] | ||
for (const path of paths) { | ||
const { css } = await processStyle({ path, ...parsePath(path)}) | ||
arr.push({ path, css }) | ||
} | ||
return arr | ||
} | ||
async function setupScripts(dir, data) { | ||
@@ -66,2 +79,3 @@ | ||
if (is_dev && !data.no_hotreload) push('hotreload') | ||
if (data.page?.isomorphic) push('nuemark') | ||
if (data.components?.length) push('mount') | ||
@@ -73,11 +87,14 @@ } | ||
const { dir } = parsePath(path) | ||
const data = await site.getData(dir) | ||
// markdown | ||
const raw = await read(path) | ||
const { meta, content } = parseMarkdown(raw) | ||
const file = parsePath(path) | ||
const dir = meta.appdir || file.dir || '.' | ||
// { meta, sections, headings, links } | ||
const page = parsePage(raw, data) | ||
const { meta } = page | ||
// YAML data | ||
const data = { ...await site.getData(dir), content, ...getParts(path), ...meta } | ||
Object.assign(data, getParts(path), meta, { page }) | ||
@@ -92,4 +109,5 @@ // content collection | ||
// scripts & styling | ||
await setupScripts(dir, data) | ||
await setupStyles(dir, data) | ||
const asset_dir = meta.appdir || dir | ||
await setupScripts(asset_dir, data) | ||
await setupStyles(asset_dir, data) | ||
@@ -103,6 +121,6 @@ return data | ||
// data | ||
const file = parsePath(index_path) | ||
const dir = file.dir | ||
const appdir = getAppDir(index_path) | ||
const data = { ...await site.getData(appdir), ...getParts(index_path), is_spa: true } | ||
const file = parsePath(index_path) | ||
const dir = file.dir || '.' | ||
@@ -129,8 +147,12 @@ // scripts & styling | ||
// Markdown- based multi-page application page | ||
async function renderPage(path, data) { | ||
const appdir = data.appdir || getAppDir(path) | ||
const lib = await site.getLayoutComponents(appdir) | ||
async function renderMPA(path, data) { | ||
const file = parsePath(path) | ||
const dir = data.appdir || file.dir | ||
const lib = await site.getLayoutComponents(dir) | ||
data.content = renderPage(data.page, { data, lib }).html | ||
function render(name, def) { | ||
const layout = lib.find(el => el.tagName == name) || def && parseNue(def)[0] | ||
try { | ||
@@ -155,6 +177,7 @@ return layout ? layout.render(data, lib) : '' | ||
async function writePage(file) { | ||
// processor methods | ||
async function processPage(file) { | ||
const { dir, name, path } = file | ||
const data = await getPageData(path) | ||
const html = await renderPage(path, data) | ||
const html = await renderMPA(path, data) | ||
await write(html, dir, `${name}.html`) | ||
@@ -164,18 +187,3 @@ return html | ||
async function renderNueCSS(path) { | ||
const nuecss = await import('nuecss') | ||
const raw = await read(path) | ||
return nuecss.default(raw, { minify: is_prod }) | ||
} | ||
async function readAllCSS(paths) { | ||
const arr = [] | ||
for (const path of paths) { | ||
const content = extname(path) == '.css' ? await read(path) : await renderNueCSS(path) | ||
arr.push({ path, content }) | ||
} | ||
return arr | ||
} | ||
async function processScript(file) { | ||
@@ -188,4 +196,4 @@ const { path } = file | ||
await buildJS({ | ||
outdir: join(dist, file.dir), | ||
path: join('.', root, path), | ||
outdir: join(process.cwd(), dist, file.dir), | ||
path: join(process.cwd(), root, path), | ||
minify: is_prod, | ||
@@ -198,3 +206,10 @@ bundle | ||
async function processStyle({ path, ext, name, dir}) { | ||
const raw = await read(path) | ||
const css = await buildCSS({ css: raw, ext, minify: is_prod }) | ||
await write(css, dir, `${name}.css`) | ||
return { css } | ||
} | ||
// the page user is currently working on | ||
@@ -216,3 +231,3 @@ let active_page | ||
if (file.is_md) { | ||
const html = await writePage(file) | ||
const html = await processPage(file) | ||
active_page = file | ||
@@ -233,21 +248,6 @@ return { html } | ||
// Nue CSS (not official yet) | ||
if (file.is_style) { | ||
const css = await renderNueCSS(path) | ||
await write(css, dir, `${name}.css`) | ||
return { css } | ||
} | ||
// style | ||
const is_style = ['.css', '.styl', '.style'].includes(ext) | ||
if (is_style) return await processStyle(file) | ||
// Plain CSS | ||
if (file.is_css) { | ||
const css = await read(path) | ||
// production -> minify | ||
if (is_prod) return await write(minifyCSS(css), dir, base) | ||
// development -> return for hot-reload | ||
await copy(file) | ||
return { css } | ||
} | ||
// reactive component | ||
@@ -281,3 +281,4 @@ if (file.is_nue) { | ||
async function gen(path, is_bulk) { | ||
await processFile({ path, ...parsePath(path) }, is_bulk) | ||
const page = await processFile({ path, ...parsePath(path) }, is_bulk) | ||
return page?.html | ||
} | ||
@@ -344,4 +345,4 @@ | ||
} catch (e) { | ||
send({ error: e, ...file }) | ||
console.error(e) | ||
send({ error: e, ...file }) | ||
} | ||
@@ -357,6 +358,11 @@ | ||
// server.once('error', e => { if (e.code == 'EADDRINUSE') {} }) | ||
try { | ||
server.listen(port) | ||
log(`http://localhost:${port}/`) | ||
server.listen(port) | ||
log(`http://localhost:${port}/`) | ||
} catch (e) { | ||
if (e.code != 'EADDRINUSE') console.error(e) | ||
log.error(e.message, '\n') | ||
process.exit() | ||
} | ||
} | ||
@@ -373,3 +379,3 @@ | ||
// for testing only | ||
gen, getPageData, renderPage, renderSPA, | ||
gen, getPageData, renderMPA, renderSPA, | ||
@@ -376,0 +382,0 @@ // public API |
import { log, parseMarkdown, getParts, getAppDir, getDirs, colors } from './util.js' | ||
import { join, extname, basename, sep, parse as parsePath } from 'node:path' | ||
import { log, getParts, getAppDir, getDirs, colors } from './util.js' | ||
import { parse as parseNue } from 'nuejs-core/index.js' | ||
import { nuemark } from 'nuemark/index.js' | ||
import { promises as fs } from 'node:fs' | ||
@@ -12,2 +13,4 @@ import { fswalk } from './nuefs.js' | ||
const NOT_FOUND = -2 | ||
// file not found error code in windows | ||
const ENOENT = -4058 | ||
@@ -42,3 +45,3 @@ export async function createSite(args) { | ||
} catch (e) { | ||
if (e.errno != NOT_FOUND) { | ||
if (e.errno != NOT_FOUND && e.errno != ENOENT) { | ||
throw `YAML parse error in ${path}` | ||
@@ -78,3 +81,2 @@ } else if (path == env) throw e | ||
async function write(content, dir, filename) { | ||
@@ -90,3 +92,3 @@ const todir = join(dist, dir) | ||
} catch (e) { | ||
if (e.errno != NOT_FOUND) throw e | ||
if (e.errno != NOT_FOUND && e.errno != ENOENT) throw e | ||
await fs.mkdir(todir, { recursive: true }) | ||
@@ -106,3 +108,3 @@ return await write(content, dir, filename) | ||
} catch (e) { | ||
if (e.errno != NOT_FOUND) throw e | ||
if (e.errno != NOT_FOUND && e.errno != ENOENT) throw e | ||
await fs.mkdir(join(dist, dir), { recursive: true }) | ||
@@ -115,3 +117,3 @@ await copy(file) | ||
async function getAssets(dir, exts, to_ext) { | ||
const dirs = [...self.globals, ...getDirs(dir)] | ||
const dirs = [...self.globals, ...getDirs(dir || '.')] | ||
const key = ':' + dir + exts | ||
@@ -136,3 +138,3 @@ const arr = [] | ||
} catch (e) { | ||
if (e.errno != NOT_FOUND) return console.error(e) | ||
if (e.errno != NOT_FOUND && e.errno != ENOENT) return console.error(e) | ||
} | ||
@@ -155,7 +157,8 @@ } | ||
self.getData = async function (appdir) { | ||
if (!appdir || appdir == '.') return site_data | ||
const app_data = await readData(`${appdir}/app.yaml`) | ||
return { ...site_data, ...app_data } | ||
self.getData = async function (pagedir) { | ||
const data = { ...site_data } | ||
for (const dir of getDirs(pagedir)) { | ||
Object.assign(data, await readData(`${dir}/app.yaml`)) | ||
} | ||
return data | ||
} | ||
@@ -173,14 +176,13 @@ | ||
// get fromt matter data from all .md files on a directory | ||
self.getContentCollection = async function (appdir) { | ||
const key = 'coll:' + appdir | ||
self.getContentCollection = async function (dir) { | ||
const key = 'coll:' + dir | ||
if (cache[key]) return cache[key] | ||
const paths = await fswalk(join(root, dir)) | ||
const mds = paths.filter(el => el.endsWith('.md')).map(el => join(dir, el)) | ||
const paths = await fswalk(join(root, appdir)) | ||
const mds = paths.filter(el => el.endsWith('.md')).map(el => join(appdir, el)) | ||
const arr = [] | ||
for (const path of mds) { | ||
const raw = await read(path) | ||
const meta = parseMarkdown(raw, true) | ||
const { meta } = nuemark(raw) | ||
arr.push({ ...meta, ...getParts(path) }) | ||
@@ -199,14 +201,12 @@ } | ||
self.getLayoutComponents = async function (appdir) { | ||
const dirs = ['.'] | ||
if (appdir && appdir != '.') dirs.unshift(appdir) | ||
self.getLayoutComponents = async function (pagedir) { | ||
const lib = [] | ||
for (const dir of dirs) { | ||
for (const dir of ['.', ...getDirs(pagedir)]) { | ||
const path = join(dir, `layout.html`) | ||
try { | ||
const html = await read(path) | ||
lib.push(...parseNue(html)) | ||
lib.unshift(...parseNue(html)) | ||
} catch (e) { | ||
if (e.errno != NOT_FOUND) { | ||
if (e.errno != NOT_FOUND && e.errno != ENOENT) { | ||
log.error('parse error', path) | ||
@@ -251,2 +251,1 @@ console.error(e) | ||
} | ||
/* misc stuff. think shame.css */ | ||
import { sep, parse } from 'node:path' | ||
import { marked } from 'marked' | ||
import yaml from 'js-yaml' | ||
// .md stuff will be lot cooler with Nuemark | ||
export function parseMarkdown(raw, meta_only) { | ||
const [_, front, md] = raw.split(/---+\n/) | ||
const matter = front?.endsWith('---') ? front.slice(0, -3) : front | ||
const meta = front && yaml.load(matter) || {} | ||
if (!meta.title) { | ||
marked.use({ walkTokens: function({ type, text }) { | ||
if (!meta.title && type == 'heading') meta.title = text | ||
}}) | ||
} | ||
return meta_only ? meta : { meta, content: marked(md || _) } | ||
} | ||
export function log(msg, extra='') { | ||
@@ -58,5 +41,6 @@ console.log(colors.green('✓'), msg, extra) | ||
const [ appdir ] = path.split(sep) | ||
return appdir == path ? '.' : appdir | ||
return appdir == path ? '' : appdir | ||
} | ||
// getDirs('a/b/c') --> ['a', 'a/b', 'a/b/c'] | ||
export function getDirs(dir) { | ||
@@ -63,0 +47,0 @@ if (!dir) return [] |
import { buildCSS, findModule } from '../src/builder.js' | ||
import { parseMarkdown, getParts } from '../src/util.js' | ||
@@ -8,4 +9,57 @@ import { match } from '../src/browser/app-router.js' | ||
const lcss = await findModule('lightningcss') | ||
const stylus = await findModule('stylus') | ||
test('Lightning CSS errors', async () => { | ||
if (!lcss) return | ||
try { | ||
await buildCSS({ css: 'body margin: 0 }', minify: true }) | ||
} catch (e) { | ||
expect(e.lineText).toBe('body margin: 0 }') | ||
expect(e.line).toBe(1) | ||
} | ||
}) | ||
test('Lightning CSS', async () => { | ||
if (lcss) { | ||
const css = await buildCSS({ css: 'body { margin: 0 }', minify: true }) | ||
expect(css).toBe('body{margin:0}') | ||
} | ||
}) | ||
test('Stylus error', async () => { | ||
if (!stylus) return | ||
try { | ||
const css = await buildCSS({ css: 'foo { mb: 0', ext: '.styl' }) | ||
} catch (e) { | ||
expect(e.line).toBe(1) | ||
expect(e.column).toBe(12) | ||
expect(e.lineText).toBe('foo { mb: 0') | ||
} | ||
}) | ||
test('Stylus', async () => { | ||
if (!stylus) return | ||
const css = await buildCSS({ css: 'body\n margin: 0', ext: '.styl', minify: true }) | ||
expect(css).toBe('body{margin:0}') | ||
}) | ||
test('Lightning CSS errors', async () => { | ||
if (!lcss) return | ||
try { | ||
await buildCSS({ css: 'body margin: 0 }', minify: true }) | ||
} catch (e) { | ||
expect(e.lineText).toBe('body margin: 0 }') | ||
expect(e.line).toBe(1) | ||
} | ||
}) | ||
test('Lightning CSS', async () => { | ||
if (!lcss) return | ||
const css = await buildCSS({ css: 'body { margin: 0 }', minify: true }) | ||
expect(css).toBe('body{margin:0}') | ||
}) | ||
test('CLI args', () => { | ||
@@ -12,0 +66,0 @@ const args = getArgs(['nue', 'build', '--verbose', '-pnve', 'joku.yaml']) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
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
77430
0
1839
+ Addednuemark@^0.1.0
+ Addednuemark@0.1.1(transitive)
- Removedmarked@^9.1.6