diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7f8c559c..d959c143 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,25 +5,30 @@ The draft looks good and covers the key aspects, but let's enhance it to better First and foremost: Thank you for helping make web development simpler and more standards-focused! ❤️❤️ ## Writing and Sharing + The most valuable contribution right now is spreading awareness about standards-first development. Write blog posts, create demos, or share examples that showcase the power of modern web standards. Show how native browser capabilities can replace complex framework abstractions. Focus especially on: + - How modern CSS enables sophisticated design systems - The untapped power of semantic HTML - Progressive enhancement through vanilla JavaScript - Real-world examples of framework complexity vs web standards simplicity ## Community Support + Help fellow developers discover the elegant simplicity of standards-first development. While I (Tero Piirainen, product lead) focus intensely on the core engineering, the community's guidance is essential. Answer questions, suggest solutions, and share your experiences. ## Bug Fixes + When fixing bugs, always include a test case that demonstrates both the issue and the solution. This helps maintain the codebase's minimalist nature while ensuring reliability. ## Feature Proposals -Nue has a clear vision: take modern web standards to their absolute peak. Before implementing any new feature, no matter how small, let's discuss how it aligns with this goal. The framework's power comes from ruthless simplicity - every addition must justify its existence. +Nue has a clear vision: take modern web standards to their absolute peak. Before implementing any new feature, no matter how small, let's discuss how it aligns with this goal. The framework's power comes from ruthless simplicity - every addition must justify its existence. ## Development Philosophy + Nue's development style might surprise those coming from traditional JavaScript projects. For engineers steeped in TypeScript, dependency injection, and "enterprise patterns", the codebase might even look stupid or feel like a toy project at first glance. This reaction reveals a fundamental divide in how we think about web development. While most codebases optimize for type safety, abstraction layers, and "proper engineering practices", Nue pursues radical minimalism. We strive to make each line of code meaningful through its functionality and clarity. The goal is to figure out what's truly needed (and only that) and find out the cleanest way to implement it. @@ -34,9 +39,8 @@ Or take view transitions: Nue's entire implementation fits in about 250 lines of By working directly with web standards rather than building layers of abstractions, we strive to create systems that are both more powerful and easier to maintain. - - ### Code Style + Maintain the existing minimalist aesthetic: 1. No semicolons - they add visual noise without value @@ -47,6 +51,7 @@ Maintain the existing minimalist aesthetic: Nue avoids Prettier/ESLint as they would add 40MB of complexity. The `.prettierrc.yaml` provides sufficient consistency. ### Testing + ```sh # Bun (recommended) bun install @@ -60,23 +65,39 @@ npm test ``` ### Local Development + +#### Using Bun (recommended) + ```sh -# Bun (recommended) +# Clone the Nue repository +cd your-projects-dir +git clone git@github.com:nuejs/nue.git +cd nue +# Install dependencies bun install +# Link the nuekit package cd packages/nuekit bun link -cd my/nue/project +# Link the nuekit package in your project +cd your-projects-dir/your-nue-project-dir +bun link nuekit +# Serve the project using the local nuekit package nue -nue build --production +``` -# Node +#### Using Node + +```sh +cd your-projects-dir +git clone git@github.com:nuejs/nue.git +cd nue npm install cd packages/nuekit npm link -cd my/nue/project +cd your-projects-dir/your-nue-project-dir +npm link nuekit npm install --save-dev esbuild node $(which nue) -node $(which nue) build --production ``` -Let's maintain this clear vision of simplicity and standards as we build something extraordinary together. \ No newline at end of file +Let's maintain this clear vision of simplicity and standards as we build something extraordinary together. diff --git a/packages/nuekit/src/layout/components.js b/packages/nuekit/src/layout/components.js index 15d9d1d0..1d375f94 100644 --- a/packages/nuekit/src/layout/components.js +++ b/packages/nuekit/src/layout/components.js @@ -1,4 +1,3 @@ - import { elem, parseSize, renderInline, renderIcon } from 'nuemark' import { readFileSync } from 'node:fs' @@ -17,16 +16,17 @@ export function renderPageList(data) { return elem('ul', this.attr, pages.join('\n')) } - // the "main" method called by the tag export function renderNavi(data) { const { items, icon_dir } = data - return Array.isArray(items) ? renderNav({ items, icon_dir }) : - typeof items == 'object' ? renderMultiNav(items, data) : '' + return Array.isArray(items) + ? renderNav({ items, icon_dir }) + : typeof items == 'object' + ? renderMultiNav(items, data) + : '' } - function renderTOC(data) { const { document, attr } = data return document.renderTOC(attr) @@ -38,7 +38,6 @@ function renderPrettyDate(date) { return elem('time', { datetime: date.toISOString() }, prettyDate(date)) } - // in Nue JS component format export function getLayoutComponents() { return [ @@ -47,7 +46,7 @@ export function getLayoutComponents() { { name: 'toc', create: renderTOC }, { name: 'markdown', create: ({ content }) => renderInline(content) }, { name: 'pretty-date', create: ({ date }) => renderPrettyDate(date) }, - { name: 'icon', create: (data) => renderIcon(data.src, data.symbol, data.icon_dir) }, + { name: 'icon', create: data => renderIcon(data.src, data.symbol, data.icon_dir) }, ] } @@ -55,7 +54,7 @@ export function getLayoutComponents() { export function renderPage(page) { const { title, url } = page - const desc = page.desc || page.description + const desc = page.desc || page.description const thumb = toAbsolute(page.thumb, page.dir) // date @@ -66,18 +65,22 @@ export function renderPage(page) { const h2 = title ? elem('h2', renderInline(title)) : '' const p = desc ? elem('p', renderInline(desc)) : '' - const body = !thumb ? time + elem('a', { href: url }, h2 + p) : - - // figure - elem('a', { href: url }, elem('figure', - elem('img', { src: thumb, loading: 'lazy' }) + elem('figcaption', time + h2 + p)) - ) + const body = !thumb + ? time + elem('a', { href: url }, h2 + p) + : // figure + elem( + 'a', + { href: url }, + elem( + 'figure', + elem('img', { src: thumb, loading: 'lazy' }) + elem('figcaption', time + h2 + p) + ) + ) return elem('li', { class: isNew(date) && 'is-new' }, body) } - -function isNew(date, offset=4) { +function isNew(date, offset = 4) { const diff = new Date() - date return diff < offset * 24 * 3600 * 1000 } @@ -86,12 +89,12 @@ function prettyDate(date) { return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', - day: 'numeric' + day: 'numeric', }) } export function toAbsolute(path, dir) { - return path && path[0] != '/' ? `/${dir}/${path}`: path + return path && path[0] != '/' ? `/${dir}/${path}` : path } export function parseLink(item) { @@ -100,11 +103,10 @@ export function parseLink(item) { if (typeof item == 'string') { return item.startsWith('---') ? { separator: item } : { label: item, url: '' } } - const [ label, url ] = Object.entries(item)[0] + const [label, url] = Object.entries(item)[0] return { label, ...parseClass(url) } } - export function renderLink(item, icon_dir) { const img = item.image ? renderImage(item) : '' const icon = renderIcon(item.icon, item.symbol, icon_dir) @@ -113,7 +115,20 @@ export function renderLink(item, icon_dir) { const link = parseLink(item) - const attr = { href: link.url, class: link.class, role: item.role } + // Attributes supported by in addition to "class" and "role" + // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attributes + const attr = { + class: link.class, + download: item.download, + href: link.url, + hreflang: item.hreflang, + ping: item.ping, + referrerpolicy: item.referrerpolicy, + rel: item.rel, + role: item.role, + target: item.target, + type: item.type, + } return elem('a', attr, icon + img + renderInline(link.label) + icon_right + kbd) } @@ -132,14 +147,12 @@ export function parseClass(url) { return data } - -export function renderNav({ items, icon_dir, heading='' }) { +export function renderNav({ items, icon_dir, heading = '' }) { const html = items.map(item => renderLink(item, icon_dir)) return elem('nav', heading + html.join('\n')) } - -export function renderMultiNav(cats, data={}) { +export function renderMultiNav(cats, data = {}) { const { icon_dir } = data const html = [] @@ -152,4 +165,3 @@ export function renderMultiNav(cats, data={}) { return elem('div', { class: data.class }, html.join('\n')) } - diff --git a/packages/nuekit/test/component.test.js b/packages/nuekit/test/component.test.js index f3643e27..99457909 100644 --- a/packages/nuekit/test/component.test.js +++ b/packages/nuekit/test/component.test.js @@ -1,4 +1,3 @@ - // tests for helper/core components import { renderPage, @@ -6,14 +5,14 @@ import { parseLink, renderNav, renderLink, - renderMultiNav } from '../src/layout/components.js' - + renderMultiNav, +} from '../src/layout/components.js' test('render page', () => { const html = renderPage({ desc: 'Wassup *bro*', title: 'Yo', - url: '/bruh/' + url: '/bruh/', }) expect(html).toStartWith('
  • ') }) - test('parse class', () => { - expect(parseClass('/foo "bar"')).toEqual({ url: "/foo", class: "bar" }) + expect(parseClass('/foo "bar"')).toEqual({ url: '/foo', class: 'bar' }) }) test('parse link', () => { - expect(parseLink({ FAQ: '/faq' })).toEqual({ label: "FAQ", url: "/faq" }) - expect(parseLink({ Hey: '/ "baz"' })).toEqual({ label: "Hey", url: '/', class: 'baz' }) + expect(parseLink({ FAQ: '/faq' })).toEqual({ label: 'FAQ', url: '/faq' }) + expect(parseLink({ Hey: '/ "baz"' })).toEqual({ label: 'Hey', url: '/', class: 'baz' }) }) - test('parse link / plain string', () => { - expect(parseLink('FAQ')).toEqual({ label: "FAQ", url: "" }) + expect(parseLink('FAQ')).toEqual({ label: 'FAQ', url: '' }) expect(parseLink('---')).toEqual({ separator: '---' }) }) test('render link', () => { - expect(renderLink({ 'Hey': '/' })).toBe('Hey') - expect(renderLink({ url: '/', label: 'Hey'})).toBe('Hey') + expect(renderLink({ Hey: '/' })).toBe('Hey') + expect(renderLink({ url: '/', label: 'Hey' })).toBe('Hey') +}) + +test('render link with attributes', () => { + expect(renderLink({ Hey: '/', target: '_blank' })).toBe('Hey') + expect(renderLink({ url: '/', label: 'Hey', target: '_blank' })).toBe( + 'Hey' + ) }) test('render image link', () => { @@ -58,24 +61,25 @@ test('render image link', () => { url: '/', }) - expect(html).toStartWith('') }) test('render categorized nav', () => { - const html = renderMultiNav({ - Hey: [{ Foo: '/'}], - Foo: [{ Bar: '/'}], - - }, { class: 'epic' }) + const html = renderMultiNav( + { + Hey: [{ Foo: '/' }], + Foo: [{ Bar: '/' }], + }, + { class: 'epic' } + ) expect(html).toStartWith('
    ') expect(html).toEndWith('
    ') }) -