ちょっと話題の記事

あたらしいフロントエンド開発ツール「Nue」

2023.09.21

About Nue

鵺(ぬえ)。
猿の顔、タヌキの体、蛇の尻尾、虎の脚を持つ妖怪です。

日本人ならNueと聞いた場合、思い浮かべるのは↑かと思うのですが、
これは先日リリースされた、フロントエンド開発ツールセットです。
*「Nue」はドイツ語で、「新しい」という意味だそうな

*「Nue」はドイツ語の「neu」に由来しており、英語で「新しい」という意味だそうな

開発者はTero Piirainenという方で、
現在彼1人で開発しています。

ここを見ると、「Webの開発方法が変わるかも」と
かなりすごいことが書いてありますが、どんなものなのか見ていきましょう。

Nueとはなんなのか?

公式を見ると、↓のようなことを言っています。

  • React、Vue、Next.js、Vite、Astroの代わりになる
  • NueはSPAMPAの両方に対するサポートを備えたWebアプリケーションビルダーである
  • Nueは最終的に完全なフロントエンド開発ツールセットとなる。目標は2024年3月

最終的にここにあるライブラリ・ツールがすべて実装されて
Nue Toolsの完成となります。
これが完成したとき、Nueは完全なフロントエンド開発ツールセットとなり、
Vite、Next.js、Astroなどをリプレイスする手段となるとのこと。
これがNueの最終目標であり、2024年3月を目処に開発しているようです。

Nue JS

今回リリースされたライブラリはNue JSで、
これはUIを構築するためのJavaScriptライブラリです。
React、Vue、Svelteの代替として使うことができ、
HTML、CSS、JavaScriptの基本がわかれば使えます。

その他Nue

Nue Js以外の、Nue css、SPAを構築するためのNue MVC、
UI開発のための再利用可能なコンポーネントであるNue UI、
MDX、MDCの代わりとなるNuemark、
NextやNuxt、Astroの代替となるNuekitなどについては
今後順次リリースしていく予定のようです。

Bun + Nue

現状、NueはNodeかbunで動作しますが、Bunがおすすめみたいです。
今回はBunをつかってNueのサンプルをうごかしてみましょう。

Environment

今回試した環境は以下のとおりです。

  • MacBook Pro (13-inch, M1, 2020)
  • OS : MacOS 13.5.2
  • Bun : 1.0.2

Try Nue.js

まずはbunをインストールまたはupgradeしましょう。

% curl -fsSL https://bun.sh/install | bash
or
% bun upgrade

・・・

% bun --version
1.0.2

Nueを動かす方法は2つ紹介されています。
簡単なのはリポジトリをcloneする方法で、↓のようにすればすぐ動作確認できます。

# clone the repository
git clone https://github.com/nuejs/create-nue.git

# cd to your newly created app
cd create-nue

# install dependencies
npm install

# Build demo site and start a HTTP server
npm run start

# Open the demo on the browser
open "http://localhost:8080"

今回は↑のコードを参考に、最小のサンプルをつくって確認してみましょう。
適当なディレクトリをつくってpackage.jsonを作成します。

% mkdir hello-nue && cd hello-nue
% mkdir www
% npm init 
% npm install nuejs-core js-yaml --save

package.jsonの中身はこんな感じです。

{
  "name": "hello-nue",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "serve": "cd www && ../scripts/server.js",
    "compile": "./scripts/compile.js",
    "render": "./scripts/render.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "js-yaml": "^4.1.0",
    "nuejs-core": "^0.1.1"
  }
}

ES Module方式にするのと、
コンパイル・レンダリング・サーバ起動用のスクリプトを記述します。

Componentの作成

Nueコンポーネントは再利用可能なUIパーツです。

src/foo.nueファイルを作成しましょう。

<div @name="foo-div" class="{ type }">
  <img src="{ img }" height="{img_height}" width="{img_width}">
  <aside>
    <h3>{ title }</h3>
    <p>{ desc }</p>
  </aside>
</div>

コンポーネントは、@nameで指定された名前を持つHTMLのフラグメントです。
ファイルには任意の拡張子が使えますが「.nue」拡張子が推奨とのこと。

次に.nueをコンパイルするファイル(scripts/compile.js)を作成します。
このファイルはnuejs-coreモジュールを使ってさきほどのnueファイルをjsにコンパイルして
公開用ディレクトリに出力します。

#!/usr/bin/env node

import { compileFile } from 'nuejs-core'

const target_js = 'www/foo.js'

// compile nue source code for browser execution
await compileFile('src/foo.nue', target_js)
console.info('compiled', target_js)

次はレンダリング用ファイルを作成します。
サーバーコンポーネントはrenderor/renderFile関数でレンダリングし、
reactiveコンポーネントはmountをつかってレンダリングするとのことです。
※参考:https://nuejs.org/docs/nuejs/component-basics.html

#!/usr/bin/env node

import { parse, render } from 'nuejs-core'
import { promises as fs } from 'node:fs'

// read() function for reading assets
const read = async (name, dir='src') => await fs.readFile(dir + '/' + name, 'utf-8')

// define a component
const component = await read('foo.nue');

// render the component with some data
const html = '<!DOCTYPE html>\n\n' + render(component, {
  title: '鵺サンプル',
  desc: 'Hello 鵺!',
  img: '<鵺画像のパス>',
  img_width:"20%",
  img_height:"20%",
  type: 'banner',
});

// write index.html
await fs.writeFile('./www/index.html', html)

console.log('wrote', 'www/index.html')

ここまでできたらcompileとrenderをbunで実行します。

% bun compile
compiled www/foo.js

% bun render
$ ./scripts/render.js
wrote www/index.html

これでfoo.jsとindex.htmlが生成されました。
最後にscripts/serve.jsを作成して、
htmlサーバを起動します。 (create-nueほぼそのまま)

#!/usr/bin/env node

// a super minimal web server to serve files on the current working directory
import { join, extname } from 'node:path'
import http from 'node:http'
import fs from 'node:fs'

const TYPES = {
  html: 'text/html; charset=UTF-8',
  js:   'application/javascript',
  svg:  'image/svg+xml',
  ico:  'image/x-icon',
  png:  'image/png',
  jpg:  'image/jpg',
  css:  'text/css'
}

const PORT = 8080

http.createServer(async (req, res) => {
  let { url } = req

  // favicon.icoのリクエストを無効化。
  // これがないとエラーになってた
  if (url === '/favicon.ico') {
    res.writeHead(204, { 'Content-Type': 'image/x-icon' });
    res.end();
    return;
  }

  if (url.endsWith('/')) url += 'index.html'
  const path = join('.', url)
  const ext = extname(path).slice(1)
  const head = { 'Content-Type': TYPES[ext] }

  try {
    res.writeHead(200, head)
    fs.createReadStream(path).pipe(res)

  } catch(e) {
    res.writeHead(404, head)
    res.end('')
  }

}).listen(PORT)

console.log(process.isBun ? 'Bun' : 'Node', `HTTP server at http://localhost:${PORT}/`)

bun serveでHTTPサーバを起動してブラウザでアクセスしてみましょう。

% bun serve
$ cd www && ../scripts/server.js
Node HTTP server at http://localhost:8080/

nueで作成したHTMLが表示されました。

bun-nue

Summary

今回はとりあえずNue Jsでシンプルなコンポーネントを
ビルドして表示してみました。
いちいち手動で面倒だったのですが、今後このあたりも洗練されていくかとおもいます。

References