2.1.1

Changes

  • src/app no longer has an imposed structure.

The generator now creates elements in src/app/<path>/ instead of src/app/<type>s/<path>/ (e.g. generate view user/user-view → src/app/user/user-view/).

New projects still ship components/, services/ (empty) and views/ as the suggested layout, so pass the folder explicitly to keep it (generate component components/my-component).

  • Removed the src/app/{components,views,services}/index.ts barrel files from new projects, and the generator no longer appends exports to them.

Import app elements by their file path.

Import Nutin elements by the framework's barrel (src/core/index.ts).

  • Sass now compiles every .scss file under src/app instead of only src/app/components and src/app/views.

  • The generator now exits before writing anything if the target folder already exists and isn't empty, since folder names carry no type suffix.

  • Dev builds (dev/serve/build) now bundle with esbuild too, unminified with a sourcemap and console/debugger kept, so npm runtime dependencies work in dev without import maps or vendoring. tsc's per-file output is still emitted for testin-nutin.

  • Sass now resolves @use "pkg:<package>/<path>" imports through node_modules.

  • The default builder.esbuild.target in nutin.config.js is now es2020 (was es2015), which libraries relying on native async functions (e.g. Alpine) require. Existing projects keep their own nutin.config.js value.

  • Generated SEO HTML now includes hreflang alternates (one per language plus x-default) when i18n is enabled, og:type and twitter:card (summary_large_image) meta tags, and an absolute og:image/twitter:image URL (a relative ogImage in config/seo.json is now prefixed with baseUrl).

  • The router now supports #hash fragments: navigating to path#id keeps the hash in the URL and scrolls to the matching element, falling back to the top of the page. The hash is also kept on reload and on language change.

  • New NavigationManager.replaceState(path) (exported from core/index.ts) rewrites the current URL without adding a history entry. The router now updates history before rendering the new view, so a view's onEnter() can call it without the change being overwritten.

  • Docker feature: tools/docker/Dockerfile.template now defaults to Node 24.21.0 (was 22.23.2) Alpine 3.24 (was 3.20, end-of-life) for the nginx runtime image (nginx 1.26 → 1.30).

  • Docker feature: in i18n projects, nginx now redirects a bare / to the default language (generated into nginx.conf through a new __ROOT_REDIRECT_PLACEHOLDER__ token).

Fixes

  • generate with a missing argument now prints its usage message instead of crashing.

  • Attributes set on a data-component placeholder (class, id, aria-*, data-*, …) are now kept on the mounted component's element instead of being dropped. Classes are merged with the component's own.

  • Builds now take a lock (.build-lock/, gitignored) before touching dist-build, so concurrent builds (a manual build during dev, or stray watcher processes) no longer corrupt each other's output.

  • The dev watcher no longer drops a file change made while a rebuild is running. It now rebuilds again once the current build finishes.

  • Stopping dev now kills the whole process group of live-server and the watcher (including an in-flight build), so no orphaned watcher processes are left behind. On Windows, the process tree is killed with taskkill instead.

  • SSR now renders views into a real <main id="app">, so unset data-optional fields are removed from prerendered HTML instead of leaking the literal text undefined.

  • Ctrl/Cmd/Shift/Alt-clicks and middle-clicks on <a href> elements with a data-event click handler now fall through to the browser's native behaviour (e.g. open in a new tab), instead of being intercepted.

  • Docker feature: nginx now issues relative redirects (absolute_redirect off), so the trailing-slash redirect on prerendered routes no longer leaks the container's internal port or scheme behind a proxy. A route directory without its own index.html now serves the SPA shell instead of a 403.