Comparing version 0.5.3 to 1.0.0-beta
{ | ||
"name": "nuekit", | ||
"version": "0.5.3", | ||
"description": "The closer-to-standards web framework. Build sites and apps with less effort.", | ||
"homepage": "https://nuejs.org", | ||
"license": "MIT", | ||
"type": "module", | ||
"repository": { | ||
"url": "https://github.com/nuejs/nue", | ||
"directory": "packages/nuekit", | ||
"type": "git" | ||
}, | ||
"bin": { | ||
"nue": "./src/cli.js" | ||
}, | ||
"name": "nuekit", | ||
"version": "1.0.0-beta", | ||
"description": "Web Framework For UX Developers. Build the slickest websites in the world and wonder why you ever did them any other way", | ||
"homepage": "https://nuejs.org", | ||
"license": "MIT", | ||
"type": "module", | ||
"repository": { | ||
"url": "https://github.com/nuejs/nue", | ||
"directory": "packages/nuekit", | ||
"type": "git" | ||
}, | ||
"bin": { | ||
"nue": "./src/cli.js" | ||
}, | ||
"engines": { | ||
"bun": ">= 1", | ||
"node": ">= 18" | ||
}, | ||
"scripts": { | ||
"test": "cd test && bun test" | ||
"test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --runInBand" | ||
}, | ||
"dependencies": { | ||
"diff-dom": "^5.0.6", | ||
"es-main": "^1.3.0", | ||
"import-meta-resolve": "^4.0.0", | ||
"js-yaml": "^4.1.0", | ||
"lightningcss": "^1.24.0", | ||
"nue-glow": "latest", | ||
"nuejs-core": "latest", | ||
"nuemark": "latest" | ||
}, | ||
"engines": { | ||
"bun": ">= 1", | ||
"node": ">= 18" | ||
} | ||
"dependencies": { | ||
"diff-dom": "^5.1.4", | ||
"es-main": "^1.3.0", | ||
"import-meta-resolve": "^4.1.0", | ||
"js-yaml": "^4.1.0", | ||
"lightningcss": "^1.26.0", | ||
"nue-glow": "*", | ||
"nuejs-core": "*", | ||
"nuemark": "*" | ||
}, | ||
"devDependencies": { | ||
"@happy-dom/global-registrator": "^14.12.3" | ||
}, | ||
"jest": { | ||
"setupFilesAfterEnv": [ | ||
"jest-extended/all", | ||
"<rootDir>/../../setup-jest.js" | ||
] | ||
} | ||
} |
<a href="https://nuejs.org"> | ||
<img src="https://nuejs.org/img/nue-banner-big.png?1"> | ||
# Nue [![test](https://github.com/nuejs/nue/actions/workflows/test.yaml/badge.svg?branch=master)](https://github.com/nuejs/nue/actions/workflows/test.yaml) | ||
<a href="https://nuejs.org/"> | ||
<img src="https://www2.nuejs.org/img/og-blue-big.png" width="900"> | ||
</a> | ||
# Nue [![test](https://github.com/nuejs/nue/actions/workflows/test.yaml/badge.svg?branch=master)](https://github.com/nuejs/nue/actions/workflows/test.yaml) | ||
Nue(kit) is a static website generator and web application builder. It's an amazingly simple and powerful alternative to **Next.js** and **Astro**: | ||
### What is Nue? | ||
Nue is a web framework for UX developers. What used to take a React specialist, and an absurd amount of JavaScript can now be done by a UX developer and a small amount of CSS. | ||
[Learn how it works](https://nuejs.org/docs/) | ||
### Getting Started | ||
Please see https://nuejs.org/docs/ | ||
### Who is it for? | ||
Nue is designed for the following people: | ||
1. **UX developers**: who natively jump between **Figma** and **CSS** without confusing [designer-developer handoff](https://medium.com/design-warp/5-most-common-designer-developer-handoff-mishaps-ba96012be8a7) processes in the way. | ||
### Why Nue? | ||
2. **Beginner web developers**: who want to skip the [redundant frontend layers](https://roadmap.sh/frontend) and start building websites quickly with HTML, CSS, and JavaScript. | ||
- [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) | ||
3. **Experienced JS developers**: frustrated with the absurd amount of layers in the [React stack](https://roadmap.sh/react) and look for simpler ways to develop professional websites. | ||
#### Notable features | ||
4. **Designers**: aiming to learn web development, but find the React/JavaScript ecosystem impossible to grasp | ||
- [Universal hot-reloading](https://nuejs.org/docs/concepts/universal-hot-reloading.html) | ||
- [Hybrid app routing and page routing](https://nuejs.org/docs/concepts/client-side-navigation.html) | ||
- [layout components](https://nuejs.org/docs/concepts/layout-components.html) | ||
- [JS/TypeScript modules](https://nuejs.org/docs/concepts/js-modules.html) | ||
- [Content collections](https://nuejs.org/docs/concepts/content-collections.html) | ||
- [Reactive islands](https://nuejs.org/docs/concepts/reactive-islands.html) | ||
5. **Parents & Teachers**: who wants to educate people [how the web works](https://www.websitearchitecture.co.uk/resources/examples/web-standards-model/) | ||
### The Goal | ||
Eventually, Nue is a content-first alternative to **Vercel** and **Netlify**, which is extremely fast and ridiculously easy to use. The goal is to be a so-called [perfect framework](https://nuejs.org/blog/perfect-web-framework/): | ||
### Installation | ||
<a href="https://nuejs.org/blog/perfect-web-framework/"> | ||
<img src="https://nuejs.org/img/perfect-banner-big.jpg" width="500"></a> | ||
Check out [installation docs](https://nuejs.org/docs/installation.html) | ||
### Roadmap | ||
Here's the high-level roadmap. Click for a [more detailed](https://nuejs.org/blog/perfect-web-framework/#product-roadmap) version. | ||
### Ultimate goal | ||
Ultimately Nue will be a ridiculously simpler alternative to **Next.js**, **Gatsby**, and **Astro** | ||
<a href="https://nuejs.org/blog/perfect-web-framework/#product-roadmap"> | ||
<img src="https://nuejs.org/img/roadmap6-big.png" width="800"></a> | ||
[Learn more about the vision](https://nuejs.org/blog/perfect-web-framework/) | ||
### Contributing | ||
Please see [contributing.md](/CONTRIBUTING.md) | ||
Please see [CONTRIBUTING.md](/CONTRIBUTING.md) | ||
@@ -48,0 +43,0 @@ |
// Router for single-page applications | ||
import { onclick, loadPage, setActive } from './page-router.js' | ||
import { onclick, loadPage, setActive } from './view-transitions.js' | ||
@@ -5,0 +5,0 @@ const is_browser = typeof window == 'object' |
@@ -10,3 +10,3 @@ | ||
sse.onmessage = function(e) { | ||
sse.onmessage = async function(e) { | ||
const data = e.data ? JSON.parse(e.data) : {} | ||
@@ -30,11 +30,15 @@ const { error, html, css, dir, url, path } = data | ||
if (data.is_md && location.pathname != uri) location.href = uri | ||
else patch(html) | ||
else { | ||
await patch(html) | ||
dispatchEvent(new Event('reload')) | ||
} | ||
} | ||
// web components cannot be re-defined :( | ||
// web components cannot be re-mounnted :( | ||
// if (data.is_js) import('/' + path + '?' + Math.random()) | ||
// reactive component | ||
if (data.is_nue) remount('/' + data.path.replace('.nue', '.js')) | ||
if (data.is_nue || data.is_htm) remount('/' + data.path.replace(data.ext, '.js')) | ||
// styling (inline && stylesheets) | ||
@@ -41,0 +45,0 @@ if (css) { |
@@ -10,3 +10,5 @@ | ||
async function importAll(reload_path) { | ||
// hmr_path argument only used by hotreload.js | ||
async function importAll(hmr_path) { | ||
const comps = document.querySelector('[name="nue:components"]')?.getAttribute('content') | ||
@@ -18,3 +20,3 @@ if (!comps) return [] | ||
for (let path of comps.split(' ')) { | ||
if (path == reload_path) path += `?${++remounts}` | ||
if (path == hmr_path) path += `?${++remounts}` | ||
const { lib } = await import(path) | ||
@@ -27,7 +29,8 @@ if (lib) arr.push(...lib) | ||
export async function mountAll(reload_path) { | ||
export async function mountAll() { | ||
const els = document.querySelectorAll('[is]') | ||
const lib = els[0] ? await importAll(reload_path) : [] | ||
const lib = els[0] ? await importAll() : [] | ||
if (!lib[0]) return | ||
const { createApp } = await import('./nue.js') | ||
@@ -49,3 +52,3 @@ | ||
} else { | ||
console.error(`Web or Nue component not fouind: "${name}"`) | ||
// console.error(`Component not found: "${name}"`) | ||
} | ||
@@ -64,6 +67,10 @@ } | ||
// mount all after route | ||
addEventListener('route', mountAll) | ||
addEventListener('route', () => | ||
// must give empty argument | ||
mountAll() | ||
) | ||
// initial page load | ||
addEventListener('DOMContentLoaded', mountAll) | ||
addEventListener('DOMContentLoaded', () => dispatchEvent(new Event('route'))) |
@@ -10,4 +10,5 @@ | ||
export async function getBuilder(is_esbuild) { | ||
const actual_cwd = process.env.ACTUAL_CWD || process.cwd() | ||
try { | ||
return is_esbuild ? await import(await resolve('esbuild', `file://${process.cwd()}/`)) : Bun | ||
return is_esbuild ? await import(resolve('esbuild', `file://${actual_cwd}/`)) : Bun | ||
} catch { | ||
@@ -80,6 +81,5 @@ throw 'Bundler not found. Please use Bun or install esbuild' | ||
let include = Features.Colors | ||
if (!opts.css_2023) include |= Features.Nesting | ||
if (opts.native_css_nesting) include |= Features.Nesting | ||
try { | ||
process.stdout.write('⚡️') | ||
return transform({ code: Buffer.from(css), include, minify }).code?.toString() | ||
@@ -90,3 +90,3 @@ | ||
title: 'CSS syntax error', | ||
lineText: source.split('\n')[loc.line -1], | ||
lineText: source.split(/\r\n|\r|\n/)[loc.line -1], | ||
text: data.type, | ||
@@ -97,3 +97,1 @@ ...loc | ||
} | ||
@@ -0,4 +1,3 @@ | ||
import { colors, openUrl, getVersion } from './util.js' | ||
import { colors } from './util.js' | ||
const HELP = ` | ||
@@ -14,2 +13,3 @@ Usage | ||
stats Show site statistics | ||
create Use a project starter template | ||
@@ -20,2 +20,6 @@ Options | ||
-e or --environment Read extra options to override defaults in site.yaml | ||
-s or --stats Show site statistics after current command | ||
-I or --init Force clear and initialize output directory | ||
-n or --dry-run Show what would be built. Does not create outputs | ||
-b or --esbuild Use esbuild as bundler. Please install it manually | ||
@@ -40,3 +44,3 @@ File matches | ||
# more examples | ||
open https://nuejs.org/docs/cli | ||
${openUrl} https://nuejs.org/docs/command-line-interface.html | ||
@@ -46,9 +50,8 @@ Less is more | ||
┏━┓┏┓┏┳━━┓ | ||
┃┏┓┫┃┃┃┃━┫ | ||
┃┏┓┫┃┃┃┃━┫ kit ${await getVersion()} | ||
┃┃┃┃┗┛┃┃━┫ nuejs.org | ||
┗┛┗┻━━┻━━┛ | ||
` | ||
const commands = ['serve', 'build', 'stats'] | ||
const commands = ['serve', 'build', 'stats', 'create'] | ||
@@ -75,4 +78,1 @@ function formatLine(line) { | ||
} | ||
#!/usr/bin/env bun | ||
import { log, colors } from './util.js' | ||
import { log, colors, getVersion, getEngine } from './util.js' | ||
import esMain from 'es-main' | ||
import { sep } from 'node:path' | ||
@@ -21,10 +22,10 @@ // [-npe] --> [-n, -p, -e] | ||
export function getArgs(argv) { | ||
const commands = ['serve', 'build', 'stats'] | ||
const args = { paths: [], root: '.' } | ||
const commands = ['serve', 'build', 'stats', 'create'] | ||
const args = { paths: [], root: null } | ||
const checkExecutable = /[\\\/]nue(\.(cmd|ps1|bunx|exe))?$/ | ||
let opt | ||
expandArgs(argv.slice(1)).forEach((arg, i) => { | ||
// skip | ||
if (arg.endsWith('/cli.js') || arg.endsWith('/nue') || arg == '--') { | ||
if (arg.endsWith(sep + 'cli.js') || checkExecutable.test(arg) || arg == '--') { | ||
@@ -40,3 +41,3 @@ // test suite | ||
// options | ||
} else if (arg[0] == '-') { | ||
} else if (!opt && arg[0] == '-') { | ||
@@ -51,3 +52,3 @@ // booleans | ||
else if (['-b', '--esbuild'].includes(arg)) args.esbuild = true | ||
else if (['-P', '--push'].includes(arg)) args.push = true | ||
else if (['-P', '--push'].includes(arg)) args.push = args.is_prod = true | ||
else if (['-I', '--init'].includes(arg)) args.init = true | ||
@@ -60,28 +61,15 @@ | ||
// bad options | ||
else if (opt) throw `"${opt}" option is not set` | ||
else throw `Unknown option: "${arg}"` | ||
} else if (arg) { | ||
} else if (arg && arg[0] != '-') { | ||
if (opt) { args[opt] = arg; opt = null } | ||
else args.paths.push(arg) | ||
} | ||
} else if (opt) throw `"${opt}" option is not set` | ||
}) | ||
if (opt) throw `"${opt}" option is not set` | ||
return args | ||
} | ||
// read from package.json | ||
async function getVersion() { | ||
const { promises } = await import('fs') | ||
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') | ||
return JSON.parse(json).version | ||
} | ||
function getEngine() { | ||
const v = process.versions | ||
return process.isBun ? 'Bun ' + v.bun : 'Node ' + v.node | ||
} | ||
async function printHelp() { | ||
@@ -100,8 +88,16 @@ const { getHelp } = await import('./cli-help.js') | ||
const { createKit } = await import('./nuekit.js') | ||
const { cmd='serve', dryrun, push, root=null } = args | ||
if (!root) args.root = '.' | ||
console.info('') | ||
// create nue | ||
if (cmd == 'create') { | ||
const { create } = await import('./create.js') | ||
return await create({ root, name: args.paths[0] }) | ||
} | ||
const nue = await createKit(args) | ||
args.nuekit_version = await printVersion() | ||
const nue = await createKit(args) | ||
const { cmd='serve', dryrun, push } = args | ||
@@ -112,3 +108,3 @@ // stats | ||
// build | ||
else if (push || args.paths[0] || cmd == 'build') { | ||
if (dryrun || push || args.paths[0] || cmd == 'build') { | ||
const paths = await nue.build(args.paths, dryrun) | ||
@@ -140,6 +136,2 @@ | ||
// root is required | ||
} else if (!args.root) { | ||
console.info('Project root not specified') | ||
// command | ||
@@ -146,0 +138,0 @@ } else if (!args.test) { |
import { compileFile as nueCompile} from 'nuejs-core' | ||
import { join, basename } from 'node:path' | ||
import { dirname, join } from 'node:path' | ||
import { fileURLToPath } from 'node:url' | ||
import { promises as fs } from 'node:fs' | ||
import { resolve } from 'import-meta-resolve' | ||
import { buildJS } from './builder.js' | ||
import { colors } from './util.js' | ||
import { colors, srcdir } from './util.js' | ||
@@ -14,10 +15,6 @@ | ||
const cwd = process.cwd() | ||
const srcdir = getSourceDir() | ||
const fromdir = join(srcdir, 'browser') | ||
const outdir = join(cwd, dist, '@nue') | ||
const minify = !is_dev | ||
// has all latest? | ||
const latest = join(outdir, '.043') | ||
const latest = join(outdir, '.05') | ||
try { | ||
@@ -34,5 +31,10 @@ if (force) doError() | ||
// chdir hack (Bun does not support absWorkingDir) | ||
process.chdir(srcdir) | ||
await initDir({ dist, is_dev, esbuild, cwd, srcdir, outdir }) | ||
} | ||
async function initDir({ dist, is_dev, esbuild, cwd, srcdir, outdir }) { | ||
const fromdir = join(srcdir, 'browser') | ||
const minify = !is_dev | ||
// simple function to wtite dot | ||
@@ -49,3 +51,3 @@ function dot() { process.stdout.write('•') } | ||
async function copyAsset(npm_path, toname) { | ||
const path = await resolvePath(npm_path) | ||
const path = resolvePath(npm_path) | ||
await fs.copyFile(path, join(outdir, toname)) | ||
@@ -65,3 +67,3 @@ dot() | ||
bundle: true, esbuild, minify, outdir, toname, | ||
path: await resolvePath(npm_path), | ||
path: resolvePath(npm_path), | ||
}) | ||
@@ -77,3 +79,3 @@ dot() | ||
await buildPackage('nuejs-core/src/browser/nue.js', 'nue.js') | ||
await buildFile('page-router') | ||
await buildFile('view-transitions') | ||
await buildFile('app-router') | ||
@@ -83,3 +85,3 @@ await buildFile('mount') | ||
// glow.css | ||
await copyAsset('nue-glow/minified/glow.css', 'glow.css') | ||
await copyAsset('nue-glow/minified/syntax.css', 'syntax.css') | ||
@@ -97,4 +99,2 @@ // dev only | ||
process.chdir(cwd) | ||
// new line | ||
@@ -105,13 +105,6 @@ console.log('') | ||
async function resolvePath(npm_path) { | ||
function resolvePath(npm_path) { | ||
const [ npm_name, ...parts ] = npm_path.split('/') | ||
let main = await resolve(npm_name, `file://${process.cwd()}/`) | ||
main = main.replace(/^file:\/\//, '') | ||
main = process.platform === 'win32' && main.startsWith('/') ? main.slice(1) : main | ||
return main.replace('index.js', parts.join('/')) | ||
const module_path = dirname(fileURLToPath(resolve(npm_name, import.meta.url))) | ||
return join(module_path, ...parts) | ||
} | ||
function getSourceDir() { | ||
const path = new URL('.', import.meta.url).pathname | ||
return process.platform === "win32" && path.startsWith('/') ? path.slice(1) : path | ||
} |
@@ -19,4 +19,4 @@ | ||
export async function fswatch(dir, onfile, onremove) { | ||
watch(dir, { recursive: true }, async function(e, path) { | ||
export function fswatch(dir, onfile, onremove) { | ||
return watch(dir, { recursive: true }, async function(e, path) { | ||
try { | ||
@@ -70,3 +70,3 @@ const file = parse(path) | ||
const IGNORE = ['node_modules', 'functions', 'package.json', 'bun.lockb', 'pnpm-lock.yaml'] | ||
const IGNORE = ['node_modules', 'functions', 'package.json', 'bun.lockb', 'pnpm-lock.yaml', 'README.md'] | ||
@@ -78,3 +78,3 @@ function ignore(name='') { | ||
function isLegit(file) { | ||
return !ignore(file.name) && !ignore(file.dir) | ||
return !ignore(file.name) && !ignore(file.base) && !ignore(file.dir) | ||
} | ||
@@ -81,0 +81,0 @@ |
import { log, colors, getAppDir, parsePathParts, extendData } from './util.js' | ||
import { join, parse as parsePath, extname, basename } from 'node:path' | ||
import { renderHead, getDefaultHTML, getDefaultSPA } from './layout.js' | ||
import { renderPage, renderSinglePage } from './layout/page-layout.js' | ||
import { parse as parseNue, compile as compileNue } from 'nuejs-core' | ||
import { log, colors, getAppDir, getParts } from './util.js' | ||
import { parsePage, renderPage } from 'nuemark/index.js' | ||
import { lightningCSS, buildJS } from './builder.js' | ||
import { createServer, send } from './nueserver.js' | ||
import { lightningCSS, buildJS } from './builder.js' | ||
import { printStats, categorize } from './stats.js' | ||
@@ -13,2 +13,3 @@ import { promises as fs } from 'node:fs' | ||
import { fswatch } from './nuefs.js' | ||
import { parsePage } from 'nuemark' | ||
import { init } from './init.js' | ||
@@ -25,2 +26,3 @@ | ||
// site: various file based functions | ||
@@ -37,7 +39,10 @@ const site = await createSite(args) | ||
async function setupStyles(dir, data) { | ||
const paths = data.styles = await site.getStyles(dir, data) | ||
const paths = await site.getStyles(dir, data) | ||
if (data.inline_css) { | ||
data.inline_css = await buildAllCSS(paths) | ||
delete data.styles | ||
data.styles = paths.filter(path => path.includes('@nue')) | ||
} else { | ||
data.styles = paths | ||
} | ||
@@ -49,4 +54,6 @@ } | ||
for (const path of paths) { | ||
const { css } = await processCSS({ path, ...parsePath(path)}) | ||
arr.push({ path, css }) | ||
if (!path.startsWith('/@nue')) { | ||
const { css } = await processCSS({ path, ...parsePath(path)}) | ||
arr.push({ path, css }) | ||
} | ||
} | ||
@@ -59,6 +66,6 @@ return arr | ||
// scripts | ||
const scripts = data.scripts = await site.getScripts(dir, data.main) | ||
const scripts = data.scripts = await site.getScripts(dir, data) | ||
// components | ||
if (data.automount !== false) data.components = await site.getComponents(dir) | ||
data.components = await site.getClientComponents(dir, data) | ||
@@ -74,3 +81,3 @@ // system scripts | ||
if (data.page?.isomorphic) push('nuemark') | ||
if (data.router) push('page-router') | ||
if (data.view_transitions || data.router) push('view-transitions') | ||
} | ||
@@ -81,14 +88,13 @@ | ||
const { dir } = parsePath(path) | ||
const data = await site.getData(dir) | ||
// markdown | ||
// markdown data: meta, sections, headings, links | ||
const raw = await read(path) | ||
// { meta, sections, headings, links } | ||
const page = parsePage(raw, data) | ||
const page = parsePage(raw) | ||
const { meta } = page | ||
const { dir } = parsePath(path) | ||
const data = await site.getData(meta.appdir || dir) | ||
// YAML data | ||
Object.assign(data, getParts(path), meta, { page }) | ||
Object.assign(data, parsePathParts(path), { page }) | ||
extendData(data, meta) | ||
@@ -110,2 +116,13 @@ // content collection | ||
// Markdown page | ||
async function renderMPA(path) { | ||
const data = await getPageData(path) | ||
const file = parsePath(path) | ||
const lib = await site.getServerComponents(data.appdir || file.dir, data) | ||
return DOCTYPE + renderPage(data, lib) | ||
} | ||
// index.html for single-page application | ||
@@ -118,3 +135,3 @@ async function renderSPA(index_path) { | ||
const appdir = getAppDir(index_path) | ||
const data = { ...await site.getData(appdir), ...getParts(index_path) } | ||
const data = { ...await site.getData(appdir), ...parsePathParts(index_path) } | ||
@@ -125,5 +142,2 @@ // scripts & styling | ||
// head / meta tags | ||
data.head = renderHead(data, is_prod) | ||
// SPA components and layout | ||
@@ -133,50 +147,11 @@ const html = await read(index_path) | ||
if (html.includes('<html')) { | ||
const lib = await site.getLayoutComponents(appdir) | ||
const lib = await site.getServerComponents(appdir, data) | ||
const [ spa, ...spa_lib ] = parseNue(html) | ||
return DOCTYPE + spa.render(data, lib.concat(spa_lib)) | ||
return DOCTYPE + spa.render(data, [...lib, ...spa_lib]) | ||
} | ||
const [ spa ] = parseNue(getDefaultSPA(html, data)) | ||
const [ spa ] = parseNue(renderSinglePage(html, data)) | ||
return DOCTYPE + spa.render(data) | ||
} | ||
// Markdown- based multi-page application page | ||
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 { | ||
return layout ? layout.render(data, lib) : '' | ||
} catch (e) { | ||
delete data.inline_css | ||
log.error(`Error in ${path}, on <${name}> component`) | ||
throw { component: name, ...e } | ||
} | ||
} | ||
data.header = render('header') | ||
data.footer = render('footer') | ||
data.main = render('main', '<slot for="content"/>') | ||
data.custom_head = render('head').slice(6, -7) | ||
data.head = renderHead(data, is_prod) | ||
return DOCTYPE + render('html', getDefaultHTML(data)) | ||
} | ||
// processor methods | ||
async function processPage(file) { | ||
const { dir, name, path } = file | ||
const data = await getPageData(path) | ||
const html = await renderMPA(path, data) | ||
await write(html, dir, `${name}.html`) | ||
return html | ||
} | ||
async function processScript(file) { | ||
@@ -226,3 +201,5 @@ const { path } = file | ||
if (file.is_md) { | ||
const html = await processPage(file) | ||
const { dir, name, path } = file | ||
const html = await renderMPA(path) | ||
await write(html, dir, `${name}.html`) | ||
active_page = file | ||
@@ -246,4 +223,4 @@ return { html } | ||
// reactive component | ||
if (file.is_nue) { | ||
// reactive component (.nue, .htm) | ||
if (file.is_nue || file.is_htm) { | ||
const raw = await read(path) | ||
@@ -266,3 +243,3 @@ const js = await compileNue(raw) | ||
function isAssetFor(page, asset) { | ||
if (['layout.html', 'site.yaml', 'app.yaml'].includes(asset.base)) { | ||
if (['.html', '.yaml'].includes(asset.ext)) { | ||
const appdir = getAppDir(asset.dir) | ||
@@ -348,6 +325,7 @@ return ['', '.', ...site.globals].includes(appdir) || getAppDir(page.dir) == appdir | ||
// dev mode -> watch for changes | ||
is_dev && fswatch(root, async file => { | ||
let watcher | ||
if (is_dev) watcher = fswatch(root, async file => { | ||
try { | ||
const ret = await processFile(file) | ||
if (ret) send({ ...file, ...getParts(file.path), ...ret }) | ||
if (ret) send({ ...file, ...parsePathParts(file.path), ...ret }) | ||
} catch (e) { | ||
@@ -368,5 +346,14 @@ send({ error: e, ...file }) | ||
const cleanup = () => { | ||
if (watcher) watcher.close() | ||
} | ||
const terminate = () => { | ||
cleanup() | ||
server.close() | ||
} | ||
try { | ||
server.listen(port) | ||
log(`http://localhost:${port}/`) | ||
return terminate | ||
@@ -386,3 +373,3 @@ } catch (e) { | ||
// public API | ||
build, serve, stats, dist, | ||
build, serve, stats, dist, port, | ||
} | ||
@@ -389,0 +376,0 @@ |
120
src/site.js
import { log, getParts, getAppDir, getDirs, colors, toPosix, sortCSS } from './util.js' | ||
import { | ||
traverseDirsUp, | ||
parsePathParts, | ||
extendData, | ||
getAppDir, | ||
log, | ||
colors, | ||
toPosix, | ||
sortCSS, | ||
joinRootPath } from './util.js' | ||
import { join, extname, basename, sep, parse as parsePath } from 'node:path' | ||
@@ -36,3 +46,3 @@ import { parse as parseNue } from 'nuejs-core' | ||
async function read(path) { | ||
return await fs.readFile(join(root, path), 'utf-8') | ||
return (await fs.readFile(join(root, path), 'utf-8')).replace(/\r\n|\r/g, '\n') | ||
} | ||
@@ -51,2 +61,3 @@ | ||
async function readOpts() { | ||
@@ -73,7 +84,8 @@ const data = await readData('site.yaml') || {} | ||
const { | ||
dist = `${root}/.dist/${is_prod ? 'prod' : 'dev'}`, | ||
dist: rawDist, | ||
port = is_prod ? 8081 : 8080 | ||
} = site_data | ||
const dist = joinRootPath(root, rawDist || join('.dist', is_prod ? 'prod' : 'dev')) | ||
// flag if .dist is empty | ||
@@ -117,3 +129,3 @@ try { | ||
async function getPageFiles(page_dir) { | ||
async function getPageAssets(page_dir) { | ||
const key = ':' + page_dir | ||
@@ -123,3 +135,3 @@ if (cache[key]) return cache[key] | ||
for (const dir of getDirs(page_dir || '.')) { | ||
for (const dir of traverseDirsUp(page_dir || '.')) { | ||
try { | ||
@@ -139,8 +151,8 @@ const paths = await fs.readdir(join(root, dir)) | ||
async function getAllFiles(from_dirs) { | ||
const key = '@' + from_dirs | ||
async function walkDirs(dirs) { | ||
const key = '@' + dirs | ||
if (cache[key]) return cache[key] | ||
const arr = [] | ||
for (const dir of from_dirs) { | ||
for (const dir of dirs) { | ||
try { | ||
@@ -161,3 +173,11 @@ const paths = await fswalk(join(root, dir)) | ||
const { include=[], exclude=[] } = data | ||
let paths = [...await getAllFiles(self.globals), ...await getPageFiles(dir)] | ||
const subdirs = !dir ? [] : self.globals.map(el => join(dir, el)) | ||
let paths = [ | ||
...await walkDirs(self.globals), | ||
...await walkDirs(subdirs), | ||
...await getPageAssets(dir) | ||
] | ||
const ret = [] | ||
@@ -167,4 +187,3 @@ | ||
if (include[0]) { | ||
for (const path of await getAllFiles(self.libs)) { | ||
for (const path of await walkDirs(self.libs)) { | ||
// included only | ||
@@ -205,6 +224,10 @@ if (include.find(match => toPosix(path).includes(match))) paths.push(path) | ||
self.getData = async function (pagedir) { | ||
const data = { nuekit_version, ...site_data } | ||
for (const dir of getDirs(pagedir)) { | ||
Object.assign(data, await readData(`${dir}/app.yaml`)) | ||
async function readDirData(dir) { | ||
const paths = await getPageAssets(dir) | ||
const data = {} | ||
for (const path of paths) { | ||
if (path.endsWith('.yaml')) { | ||
Object.assign(data, await readData(path)) | ||
} | ||
} | ||
@@ -214,13 +237,13 @@ return data | ||
self.walk = async function() { | ||
return await fswalk(root) | ||
} | ||
self.getData = async function(pagedir) { | ||
const data = { nuekit_version, ...site_data, is_prod } | ||
self.getScripts = async function (dir, main=['main.js']) { | ||
const arr = await getAssets({ dir, exts: ['js', 'ts'], to_ext: 'js' }) | ||
return arr.filter(path => main.includes(basename(path))) | ||
for (const dir of traverseDirsUp(pagedir)) { | ||
extendData(data, await readDirData(dir)) | ||
} | ||
return data | ||
} | ||
self.getComponents = async function(dir) { | ||
return await getAssets({ dir, exts: ['nue'], to_ext: 'js' }) | ||
self.walk = async function() { | ||
return await fswalk(root) | ||
} | ||
@@ -230,31 +253,39 @@ | ||
// get fromt matter data from all .md files on a directory | ||
self.getContentCollection = async function (dir) { | ||
self.getContentCollection = async function(dir) { | ||
const key = 'coll:' + dir | ||
if (cache[key]) return cache[key] | ||
const arr = [] | ||
// make sure dir exists | ||
try { | ||
await fs.stat(join(root, dir)) | ||
} catch (e) { | ||
console.error(`content collection: "${dir}" does not exist`) | ||
return arr | ||
} | ||
const paths = await fswalk(join(root, dir)) | ||
const mds = paths.filter(el => el.endsWith('.md')).map(el => join(dir, el)) | ||
const arr = [] | ||
for (const path of mds) { | ||
const raw = await read(path) | ||
const { meta } = nuemark(raw) | ||
arr.push({ ...meta, ...getParts(path) }) | ||
arr.push({ ...meta, ...parsePathParts(path) }) | ||
} | ||
arr.sort((a, b) => { | ||
const [d1, d2] = [a, b].map(v => v.pubDate || Infinity) | ||
const [d1, d2] = [a, b].map(v => v.pubDate || v.date || Infinity) | ||
return d2 - d1 | ||
}) | ||
if (is_bulk) cache[key] = arr | ||
return arr | ||
} | ||
self.getStyles = async function (dir, data={}) { | ||
self.getStyles = async function(dir, data={}) { | ||
let paths = await getAssets({ dir, exts: ['css'], data }) | ||
// syntax highlighting | ||
if (data.page?.has_code_blocks && data.glow_css !== false) paths.push(`/@nue/glow.css`) | ||
if (data.page?.has_code_blocks && data.syntax_highlight !== false) paths.push(`/@nue/syntax.css`) | ||
@@ -267,8 +298,24 @@ // cascading order: globals -> area -> page | ||
self.getScripts = async function(dir, data) { | ||
return await getAssets({ dir, exts: ['js', 'ts'], to_ext: 'js', data }) | ||
} | ||
self.getLayoutComponents = async function (pagedir) { | ||
self.getClientComponents = async function(dir, data) { | ||
return await getAssets({ dir, exts: ['nue', 'htm'], to_ext: 'js', data }) | ||
} | ||
self.getServerComponents = async function(dir, data) { | ||
const paths = await getAssets({ dir, exts: ['html'], data }) | ||
if (dir && dir != '.') { | ||
const more = await fs.readdir(join(root, dir)) | ||
more.forEach(p => { | ||
if (p.endsWith('.html')) paths.unshift(p) | ||
}) | ||
} | ||
const lib = [] | ||
for (const dir of ['.', ...getDirs(pagedir)]) { | ||
const path = join(dir, `layout.html`) | ||
for (const path of paths) { | ||
try { | ||
@@ -284,3 +331,2 @@ const html = await read(path) | ||
} | ||
return lib | ||
@@ -290,3 +336,3 @@ } | ||
// @returns { src, path, code: 200 } | ||
self.getRequestPaths = async function (url) { | ||
self.getRequestPaths = async function(url) { | ||
let { dir, name, base, ext } = parsePath(url.slice(1)) | ||
@@ -293,0 +339,0 @@ if (!name) name = 'index' |
@@ -13,2 +13,4 @@ | ||
export async function printStats(site, args) { | ||
if (args.dryrun) return | ||
const { dist, globals } = site | ||
@@ -54,5 +56,5 @@ let paths = await fswalk(dist) | ||
['js', 'ts'].includes(ext) ? cats.scripts : | ||
ext == 'yaml' || base == 'layout.html' ? misc : | ||
ext == 'yaml' || ext == 'html' ? misc : | ||
base == 'index.html' ? cats.spa : | ||
ext == 'nue' ? cats.islands : | ||
ext == 'nue' || ext == 'htm' ? cats.islands : | ||
ext == 'md' ? cats.pages : | ||
@@ -59,0 +61,0 @@ cats.media |
/* misc stuff. think shame.css */ | ||
import { sep, parse, normalize } from 'node:path' | ||
import { sep, parse, normalize, join, isAbsolute, dirname } from 'node:path' | ||
import { fileURLToPath } from 'node:url' | ||
import { promises as fs } from 'node:fs' | ||
export const srcdir = dirname(fileURLToPath(import.meta.url)) | ||
export const openUrl = process.platform == 'darwin' ? 'open' : process.platform == 'win32' ? 'start' : 'xdg-open' | ||
// read from package.json | ||
export async function getVersion() { | ||
const path = join(srcdir, '../package.json') | ||
const json = await fs.readFile(path, 'utf-8') | ||
return JSON.parse(json).version | ||
} | ||
export function getEngine() { | ||
const v = process.versions | ||
return process.isBun ? 'Bun ' + v.bun : 'Node ' + v.node | ||
} | ||
export function log(msg, extra='') { | ||
@@ -28,13 +45,14 @@ console.log(colors.green('✓'), msg, extra) | ||
/* path parts */ | ||
export function getParts(path) { | ||
// returns { url, dir, slug, appdir } | ||
export function parsePathParts(path) { | ||
path = normalize(path) | ||
const { dir, name, base } = parse(path) | ||
const appdir = getAppDir(path) | ||
const basedir = getAppDir(path) | ||
const url = getUrl(dir, name) | ||
return { url, dir, slug: name + '.html', appdir } | ||
return { url, dir, slug: name + '.html', basedir } | ||
} | ||
export function joinRootPath(root, path, abs = false) { | ||
return join(abs ? process.cwd() : '', isAbsolute(path) ? '' : root, path) | ||
} | ||
@@ -47,4 +65,4 @@ export function getAppDir(path) { | ||
// getDirs('a/b/c') --> ['a', 'a/b', 'a/b/c'] | ||
export function getDirs(dir) { | ||
// traverseDirsUp('a/b/c') --> ['a', 'a/b', 'a/b/c'] | ||
export function traverseDirsUp(dir) { | ||
if (!dir) return [] | ||
@@ -59,4 +77,3 @@ dir = normalize(dir) | ||
if (url[0] != '/') url = '/' + url | ||
// if (name != 'index') | ||
url += name + '.html' | ||
if (name != 'index') url += name + '.html' | ||
return url | ||
@@ -69,3 +86,12 @@ } | ||
export function extendData(to, from={}) { | ||
const { include = [], exclude = [] } = to | ||
if (from.include) include.push(...from.include) | ||
if (from.exclude) exclude.push(...from.exclude) | ||
Object.assign(to, from) | ||
to.include = include | ||
to.exclude = exclude | ||
} | ||
export function sortCSS({ paths, globals, dir }) { | ||
@@ -87,2 +113,1 @@ function score(path) { | ||
@@ -17,3 +17,3 @@ import { promises as fs } from 'node:fs' | ||
test('bun init', async () => { | ||
await init({ dist, is_dev: true }) | ||
await init({ dist, is_dev: true }) | ||
const names = await fs.readdir(join(dist, '@nue')) | ||
@@ -24,5 +24,5 @@ expect(names.length).toBe(11) | ||
test('esbuild init', async () => { | ||
await init({ dist, is_dev: true, esbuild: true }) | ||
await init({ dist, is_dev: true, esbuild: true }) | ||
const names = await fs.readdir(join(dist, '@nue')) | ||
expect(names.length).toBe(11) | ||
}) |
@@ -0,7 +1,6 @@ | ||
import { match } from '../src/browser/app-router.js' | ||
import { match } from '../src/browser/app-router.js' | ||
import { getParts, sortCSS } from '../src/util.js' | ||
import { parsePathParts } from '../src/util.js' | ||
import { lightningCSS } from '../src/builder.js' | ||
import { renderHead } from '../src/layout.js' | ||
import { create } from '../src/create.js' | ||
import { getArgs } from '../src/cli.js' | ||
@@ -11,5 +10,17 @@ | ||
import { promises as fs } from 'node:fs' | ||
expect.extend({ toMatchPath }) | ||
// temporary directory | ||
const root = '_test' | ||
// setup and teardown | ||
beforeAll(async () => { | ||
await fs.rm(root, { recursive: true, force: true }) | ||
await fs.mkdir(root, { recursive: true }) | ||
}) | ||
afterAll(async () => await fs.rm(root, { recursive: true, force: true })) | ||
test('Lightning CSS errors', async () => { | ||
@@ -36,9 +47,2 @@ try { | ||
test('head', () => { | ||
const head = renderHead({ charset: 'foo', title: 'Hey', preload_image: 'hey.png' }) | ||
expect(head).toInclude('meta charset="foo"') | ||
expect(head).toInclude('<title>Hey</title>') | ||
expect(head).toInclude('<link rel="preload" as="image" href="hey.png">') | ||
}) | ||
test('app router', async () => { | ||
@@ -52,8 +56,16 @@ expect(match('/fail/:id', '/users/20')).toBeNull() | ||
test('path parts', () => { | ||
const parts = getParts('docs/glossary/semantic-css.md') | ||
const parts = parsePathParts('docs/glossary/semantic-css.md') | ||
expect(parts.url).toBe('/docs/glossary/semantic-css.html') | ||
expect(parts.dir).toMatchPath('docs/glossary') | ||
expect(parts.appdir).toMatchPath('docs') | ||
expect(parts.basedir).toMatchPath('docs') | ||
expect(parts.slug).toBe('semantic-css.html') | ||
}) | ||
test('create', async () => { | ||
const terminate = await create({ root, name: 'test' }) | ||
terminate() | ||
const contents = await fs.readdir(root) | ||
expect(contents).toContain('index.md') // should be unpacked to correct dir | ||
expect(contents).toContain('.dist') // should be built | ||
}) |
@@ -52,6 +52,6 @@ | ||
test('defaults', async () => { | ||
test('site defaults', async () => { | ||
const site = await getSite() | ||
expect(site.is_empty).toBe(true) | ||
expect(site.dist).toBe('_test/.dist/dev') | ||
expect(site.dist).toMatchPath('_test/.dist/dev') | ||
expect(site.port).toBe(8080) | ||
@@ -75,3 +75,3 @@ expect(site.globals).toEqual([]) | ||
expect(site.globals).toEqual(['global']) | ||
expect(site.dist).toBe('.mydist') | ||
expect(site.dist).toMatchPath('_test/.mydist') | ||
expect(site.port).toBe(1500) | ||
@@ -88,3 +88,3 @@ | ||
expect(site.dist).toBe('.alt') | ||
expect(site.dist).toMatchPath('_test/.alt') | ||
expect(site.port).toBe(8080) | ||
@@ -120,6 +120,18 @@ }) | ||
test('include / exclude', async () => { | ||
test('include/exclude data', async () => { | ||
await write('site.yaml', 'include: [a]\nexclude: [a]') | ||
await write('blog/app.yaml', 'include: [b]\nexclude: [b]') | ||
await write('blog/index.md', '---\ninclude: [c]\n---\n') | ||
const kit = await getKit() | ||
const data = await kit.getPageData('blog/index.md') | ||
expect(data.include).toEqual([ "a", "b", "c" ]) | ||
expect(data.exclude).toEqual([ "a", "b" ]) | ||
}) | ||
test('asset include/exclude', async () => { | ||
await write('site.yaml', 'globals: [global]\nlibs: [lib, ext]\n') | ||
await write('global/global.css') | ||
await write('global/kama.css') | ||
await write('global/kama.nue') | ||
await write('lib/zoo.css') | ||
@@ -133,2 +145,3 @@ await write('blog/index.md') | ||
expect(data.styles).toEqual([ "/global/global.css", "/lib/zoo.css" ]) | ||
// expect(data.components).toEqual([ "/global/kama.js", "/lib/zoo.css" ]) | ||
}) | ||
@@ -145,3 +158,3 @@ | ||
expect(data).toEqual({ foo: 1, bar: 1, baz: 1 }) | ||
expect(data).toMatchObject({ foo: 1, bar: 1, baz: 1 }) | ||
}) | ||
@@ -196,3 +209,3 @@ | ||
// root layout | ||
const comps = await site.getLayoutComponents() | ||
const comps = await site.getServerComponents() | ||
expect(comps.length).toBe(1) | ||
@@ -202,7 +215,7 @@ expect(comps[0].tagName).toBe('header') | ||
// app layout | ||
const comps2 = await site.getLayoutComponents('blog') | ||
expect(comps2.length).toBe(2) | ||
const comps2 = await site.getServerComponents('blog') | ||
// expect(comps2.length).toBe(2) | ||
// page layout | ||
const comps3 = await site.getLayoutComponents('blog/entry') | ||
const comps3 = await site.getServerComponents('blog/entry') | ||
expect(comps3.length).toBe(3) | ||
@@ -213,2 +226,20 @@ expect(comps3[0].name).toBe('c') | ||
test('page layout', async () => { | ||
await write('site.yaml', 'header: { navi: [{ image: foo }, bar] }\nfooter: { navi: [bar] }') | ||
await write('layout.html', '<aside>Sidebar</aside><aside @name="complementary">Aside</aside>') | ||
await write('index.md', '# Hey') | ||
const kit = await getKit() | ||
const html = await kit.gen('index.md') | ||
expect(html).toInclude('<header>') | ||
expect(html).toInclude('<footer>') | ||
expect(html).toInclude('<a>bar</a></nav>') | ||
expect(html).toInclude('<aside>Sidebar</aside>') | ||
expect(html).toInclude('<aside>Aside</aside>') | ||
// console.info(html) | ||
}) | ||
test('getRequestPaths', async () => { | ||
@@ -240,3 +271,3 @@ await write('index.md') | ||
expect(data.inline_css[0].path).toEqual('/inline/style.css') | ||
const html = await kit.renderMPA('inline/index.md', data) | ||
const html = await kit.gen('inline/index.md') | ||
expect(html).toInclude('<style href="/inline/style.css">') | ||
@@ -254,21 +285,35 @@ expect(html).toInclude('margin:') | ||
test('line endings', async () => { | ||
const kit = await getKit() | ||
await write('index.md', '---\ntitle: Page title\rhero: img/image.png\r\n---\r\n\r# Hello\r\n\rWorld') | ||
const data = await kit.getPageData('index.md') | ||
expect(data.title).toBe('Page title') | ||
expect(data.hero).toBe('img/image.png') | ||
const html = await kit.gen('index.md') | ||
expect(html).toInclude('<h1>Hello</h1>') | ||
expect(html).toInclude('<p>World</p>') | ||
}) | ||
test('page assets', async() => { | ||
await write('site.yaml', 'libs: [lib]') | ||
await write('blog/app.yaml', 'include: [video]') | ||
await write('lib/video.nue') | ||
await write('blog/index.md', '# Hey') | ||
await write('blog/comp.htm', '<div/>') | ||
await write('blog/hello.ts', 'var a') | ||
await write('blog/main.js', 'var a') | ||
const kit = await getKit() | ||
await write('scripts/app.yaml', 'main: [hello.js]\nhotreload: false') | ||
await write('scripts/index.md', '# Hey') | ||
await write('scripts/hello.nue', '<div/>') | ||
await write('scripts/hello.ts', 'var a') | ||
await write('scripts/main.js', 'var a') | ||
const data = await kit.getPageData('scripts/index.md') | ||
const data = await kit.getPageData('blog/index.md') | ||
expect(data.components).toEqual([ "/scripts/hello.js" ]) | ||
expect(data.scripts).toEqual([ "/scripts/hello.js", "/@nue/mount.js" ]) | ||
expect(data.components).toEqual([ "/blog/comp.js", "/lib/video.js" ]) | ||
expect(data.scripts.length).toEqual(4) | ||
}) | ||
test('index.html', async() => { | ||
test('single-page app index', async() => { | ||
await write('index.html', '<test/>') | ||
const kit = await getKit() | ||
await kit.gen('index.html') | ||
const html = await readDist(kit.dist, 'index.html') | ||
const html = await kit.renderSPA('index.html') | ||
// const html = await readDist(kit.dist, 'index.html') | ||
@@ -280,3 +325,3 @@ expect(html).toInclude('hotreload.js') | ||
test('index.md', async() => { | ||
await write('index.md', '# Hey') | ||
await write('index.md', '# Hey { .yo }\n\n## Foo { .foo#bar.baz }') | ||
const kit = await getKit() | ||
@@ -287,3 +332,4 @@ await kit.gen('index.md') | ||
expect(html).toInclude('<title>Hey</title>') | ||
expect(html).toInclude('<h1 id="hey">') | ||
expect(html).toInclude('<h1 class="yo">Hey</h1>') | ||
expect(html).toInclude('<h2 class="foo baz" id="bar"><a href="#bar" title="Foo"></a>Foo</h2>') | ||
}) | ||
@@ -326,8 +372,5 @@ | ||
test.skip('random unit test', async() => { | ||
const kit = await createKit({ root: '../nextjs-blog', dryrun: true }) | ||
}) | ||
test('the project was started for the first time', async () => { | ||
const kit = await getKit() | ||
await write('site.yaml', 'port: 9090') | ||
await write('globals/bar.css') | ||
@@ -337,6 +380,10 @@ await write('home.css') | ||
await kit.serve() | ||
const html = await readDist(kit.dist, 'index.html') | ||
expect(html).toInclude('hotreload.js') | ||
process.exit() | ||
}) | ||
const kit = await getKit() | ||
const terminate = await kit.serve() | ||
try { | ||
const html = await readDist(kit.dist, 'index.html') | ||
expect(html).toInclude('hotreload.js') | ||
} finally { | ||
terminate() | ||
} | ||
}) |
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
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 3 instances in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
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
108402
38
2638
1
4
48
2
2
Updateddiff-dom@^5.1.4
Updatedimport-meta-resolve@^4.1.0
Updatedlightningcss@^1.26.0
Updatednue-glow@*
Updatednuejs-core@*
Updatednuemark@*