@nutin/cli 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +322 -0
- package/README.md +14 -6
- package/bin/add-feature.mjs +1 -1
- package/bin/cli.mjs +1 -1
- package/bin/create-app.mjs +1 -1
- package/bin/update-app.mjs +1 -1
- package/lib/index.mjs +6 -2
- package/lib/src/add-feature/base-template-patcher.mjs +139 -0
- package/lib/src/add-feature/feature-adder.mjs +20 -9
- package/lib/src/add-feature/feature-context-builder.mjs +1 -0
- package/lib/src/add-feature/nutin-config-updater.mjs +34 -8
- package/lib/src/add-feature/package-json-updater.mjs +7 -9
- package/lib/src/common/feature-registry.mjs +4 -0
- package/lib/src/common/file-generator.mjs +25 -3
- package/lib/src/common/git.mjs +31 -0
- package/lib/src/common/meta-bootstrap-prompt.mjs +28 -2
- package/lib/src/common/package-json-helper.mjs +14 -4
- package/lib/src/common/project-meta.mjs +2 -2
- package/lib/src/common/template-compiler.mjs +2 -0
- package/lib/src/common/transpile-ts.mjs +94 -0
- package/lib/src/create-app/context-builder.mjs +1 -0
- package/lib/src/create-app/git-manager.mjs +12 -2
- package/lib/src/create-app/json-manager.mjs +21 -18
- package/lib/src/create-app/new-app-prompt.mjs +4 -2
- package/lib/src/create-app/success-message.mjs +1 -1
- package/lib/src/update-app/conflict-report-writer.mjs +33 -21
- package/lib/src/update-app/json-drift.mjs +54 -0
- package/lib/src/update-app/project-updater.mjs +123 -14
- package/lib/src/update-app/template-differ.mjs +5 -5
- package/lib/src/update-app/three-way-merge.mjs +42 -0
- package/lib/src/update-app/update-context-builder.mjs +1 -0
- package/lib/src/update-app/update-summary-printer.mjs +10 -2
- package/package.json +12 -11
- package/templates/base/.gitignore.hbs +1 -0
- package/templates/base/AGENTS.md.hbs +45 -43
- package/templates/base/GETTING_STARTED.md.hbs +32 -23
- package/templates/base/README.md.hbs +7 -4
- package/templates/base/nutin.config.js.hbs +1 -1
- package/templates/base/src/app/components/.gitkeep +0 -0
- package/templates/base/src/app/globals.d.ts.hbs +6 -3
- package/templates/base/src/app/guards.ts.hbs +1 -3
- package/templates/base/src/app/main.ts.hbs +2 -9
- package/templates/base/src/app/routes.ts.hbs +9 -3
- package/templates/base/src/app/services/.gitkeep +0 -0
- package/templates/base/src/app/views/home/home.view.html.hbs +1 -1
- package/templates/base/src/app/views/home/home.view.ts.hbs +2 -2
- package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +2 -2
- package/templates/base/src/core/base-classes/base-component/base-component.test.js.hbs +461 -1
- package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +39 -48
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.test.js.hbs +36 -1
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +47 -77
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +307 -21
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +144 -26
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +105 -4
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +136 -62
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.test.js.hbs +85 -4
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +101 -51
- package/templates/base/src/core/base-classes/base-component/helpers/html.helper.test.js.hbs +404 -0
- package/templates/base/src/core/base-classes/base-component/helpers/html.helper.ts.hbs +242 -0
- package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.test.js.hbs +70 -1
- package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +54 -15
- package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.test.js.hbs +12 -1
- package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.ts.hbs +39 -36
- package/templates/base/src/core/base-classes/base-component/helpers/preservation.helper.test.js.hbs +179 -0
- package/templates/base/src/core/base-classes/base-component/helpers/preservation.helper.ts.hbs +93 -0
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +135 -17
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +199 -81
- package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +40 -23
- package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +67 -65
- package/templates/base/src/core/base-classes/component/component.test.js.hbs +17 -17
- package/templates/base/src/core/base-classes/component/component.ts.hbs +8 -41
- package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +1 -1
- package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +37 -39
- package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs +14 -1
- package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.ts.hbs +33 -31
- package/templates/base/src/core/base-classes/service/service.test.js.hbs +32 -0
- package/templates/base/src/core/base-classes/service/service.ts.hbs +21 -13
- package/templates/base/src/core/base-classes/view/view.test.js.hbs +4 -0
- package/templates/base/src/core/base-classes/view/view.ts.hbs +11 -8
- package/templates/base/src/core/index.ts.hbs +4 -2
- package/templates/base/src/core/internals.d.ts.hbs +199 -20
- package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +18 -0
- package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +26 -25
- package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +3 -3
- package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +3 -3
- package/templates/base/src/core/services/globals/globals.ts.hbs +7 -32
- package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +133 -50
- package/templates/base/src/core/services/http-client/helpers/http-builder.helper.ts.hbs +147 -39
- package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +75 -19
- package/templates/base/src/core/services/http-client/helpers/http-manager.helper.ts.hbs +73 -47
- package/templates/base/src/core/services/http-client/http-client.test.js.hbs +339 -232
- package/templates/base/src/core/services/http-client/http-client.ts.hbs +37 -25
- package/templates/base/src/core/services/i18n/i18n.test.js.hbs +57 -16
- package/templates/base/src/core/services/i18n/i18n.ts.hbs +42 -42
- package/templates/base/src/core/services/i18n/languages.ts.hbs +0 -2
- package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +4 -6
- package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +10 -0
- package/templates/base/src/core/services/pipe-registry/pipes.ts.hbs +4 -2
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +217 -4
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +210 -65
- package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +26 -27
- package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.ts.hbs +38 -60
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +4 -4
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +30 -30
- package/templates/base/src/core/services/router/router.test.js.hbs +321 -0
- package/templates/base/src/core/services/router/router.ts.hbs +122 -54
- package/templates/base/src/index.html.hbs +1 -0
- package/templates/base/src/styles/_styles.scss.hbs +1 -113
- package/templates/base/src/styles/main.scss.hbs +1 -1
- package/templates/base/tools/builder/builder.config.js.hbs +6 -0
- package/templates/base/tools/builder/builder.js.hbs +26 -8
- package/templates/base/tools/builder/core/app/compile-ts.js.hbs +81 -6
- package/templates/base/tools/builder/core/app/copy-static.js.hbs +12 -1
- package/templates/base/tools/builder/core/app/handler-refs.js.hbs +114 -0
- package/templates/base/tools/builder/core/app/handler-refs.test.js.hbs +70 -0
- package/templates/base/tools/builder/core/app/paths.js.hbs +17 -0
- package/templates/base/tools/builder/core/app/route-keys.js.hbs +47 -0
- package/templates/base/tools/builder/core/app/route-keys.test.js.hbs +97 -0
- package/templates/base/tools/builder/core/app/ts-errors.js.hbs +58 -0
- package/templates/base/tools/builder/core/app/ts-errors.test.js.hbs +84 -0
- package/templates/base/tools/builder/core/app/validate-routes.js.hbs +21 -55
- package/templates/base/tools/builder/core/finalize-build.js.hbs +6 -0
- package/templates/base/tools/builder/core/html-index/add-tags.js.hbs +1 -4
- package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +3 -3
- package/templates/base/tools/builder/core/html-index/validate-html.js.hbs +10 -2
- package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +111 -23
- package/templates/base/tools/builder/core/html-templates/minify-templates.js.hbs +8 -0
- package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +5 -3
- package/templates/base/tools/builder/core/html-templates/template-literal.js.hbs +193 -0
- package/templates/base/tools/builder/core/html-templates/template-literal.test.js.hbs +69 -0
- package/templates/base/tools/builder/core/i18n/build-i18n.js.hbs +14 -6
- package/templates/base/tools/builder/core/prod-bundle/bundle-options.js.hbs +82 -0
- package/templates/base/tools/builder/core/prod-bundle/bundle-options.test.js.hbs +65 -0
- package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +6 -6
- package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +25 -8
- package/templates/base/tools/builder/core/prod-bundle/hash-files.js.hbs +4 -4
- package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +31 -13
- package/templates/base/tools/builder/core/seo/generate-robots-txt.test.js.hbs +48 -0
- package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +3 -3
- package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +61 -15
- package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +22 -7
- package/templates/base/tools/builder/core/seo/generate-sitemap-xml.test.js.hbs +30 -0
- package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +48 -18
- package/templates/base/tools/builder/core/seo/html-substitutions.test.js.hbs +45 -0
- package/templates/base/tools/builder/core/seo/route-output.js.hbs +26 -8
- package/templates/base/tools/builder/core/seo/route-output.test.js.hbs +31 -0
- package/templates/base/tools/builder/core/seo/seo-routes.js.hbs +29 -0
- package/templates/base/tools/builder/core/seo/seo-routes.test.js.hbs +21 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +6 -7
- package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +34 -5
- package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.test.js.hbs +48 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +26 -9
- package/templates/base/tools/builder/core/styles/sass.js.hbs +7 -5
- package/templates/base/tools/builder/core/styles/tailwind.js.hbs +19 -59
- package/templates/base/tools/dev/dev-serve.js.hbs +71 -7
- package/templates/base/tools/dev/serve.js.hbs +152 -22
- package/templates/base/tools/dev/watcher.js.hbs +75 -28
- package/templates/base/tools/generator/generator.js.hbs +23 -18
- package/templates/base/tools/generator/handle-file.js.hbs +4 -24
- package/templates/base/tools/generator/handle-file.test.js.hbs +4 -23
- package/templates/base/tools/generator/templates/component/component.template.js.hbs +3 -3
- package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +3 -3
- package/templates/base/tools/generator/templates/service/service.template.js.hbs +1 -1
- package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +1 -1
- package/templates/base/tools/generator/templates/view/view.template.js.hbs +2 -2
- package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +2 -2
- package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +1 -1
- package/templates/base/tools/testin-nutin/core/globals/assertion-lib.js.hbs +2 -2
- package/templates/base/tools/testin-nutin/core/{tests/assertions.test.js.hbs → globals/assertion-lib.test.js.hbs} +6 -0
- package/templates/base/tools/testin-nutin/core/globals/register-test-globals.js.hbs +6 -7
- package/templates/base/tools/testin-nutin/core/{tests/globals-each-all.test.js.hbs → globals/register-test-globals.test.js.hbs} +12 -0
- package/templates/base/tools/testin-nutin/core/printer.js.hbs +10 -4
- package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +8 -8
- package/templates/base/tools/testin-nutin/mocks/mock-event-bus.js.hbs +27 -25
- package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +30 -22
- package/templates/base/tools/testin-nutin/mocks/mock-http-client.js.hbs +7 -10
- package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +12 -0
- package/templates/base/tools/testin-nutin/mocks/mock-i18n.js.hbs +47 -22
- package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +47 -26
- package/templates/base/tools/testin-nutin/mocks/mock-router.js.hbs +19 -11
- package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +24 -7
- package/templates/base/tools/testin-nutin/runner.js.hbs +12 -3
- package/templates/base/tools/testin-nutin/watch-tests.js.hbs +45 -20
- package/templates/base/tools/utils/build-lock.js.hbs +97 -0
- package/templates/base/tools/utils/ensure-deps.js.hbs +72 -0
- package/templates/base/tools/utils/ensure-deps.test.js.hbs +17 -0
- package/templates/base/tools/utils/format.js.hbs +1 -1
- package/templates/base/tools/utils/index.js.hbs +3 -1
- package/templates/base/tools/utils/print.js.hbs +2 -2
- package/templates/base/tools/utils/resolve-port.js.hbs +30 -0
- package/templates/base/tools/utils/resolve-port.test.js.hbs +47 -0
- package/templates/base/tools/utils/run-command.js.hbs +30 -1
- package/templates/base/tools/utils/run-command.test.js.hbs +12 -1
- package/templates/base/tools/utils/run-script.js.hbs +2 -2
- package/templates/features/docker/tools/docker/Dockerfile.template.hbs +5 -2
- package/templates/features/docker/tools/docker/nginx.conf.template +19 -2
- package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +1 -1
- package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +2 -2
- package/templates/features/docker/tools/docker/scripts/locale-redirects.js.hbs +28 -0
- package/templates/features/docker/tools/docker/scripts/locale-redirects.test.js.hbs +34 -0
- package/templates/features/docker/tools/docker/scripts/markdown-sources-copy.js.hbs +25 -0
- package/templates/features/docker/tools/docker/scripts/markdown-sources-copy.test.js.hbs +34 -0
- package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +3 -1
- package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +12 -0
- package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +29 -0
- package/templates/features/markdown/markdown-content/getting-started.md.hbs +28 -0
- package/templates/features/markdown/markdown-content/writing-pages.md.hbs +19 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-content/markdown-content.component.scss.hbs +75 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-content/markdown-content.component.test.js.hbs +40 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-content/markdown-content.component.ts.hbs +65 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-landing/markdown-landing.component.test.js.hbs +67 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-landing/markdown-landing.component.ts.hbs +87 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-nav/markdown-nav.component.scss.hbs +42 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-nav/markdown-nav.component.test.js.hbs +59 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-nav/markdown-nav.component.ts.hbs +72 -0
- package/templates/features/markdown/src/app/markdown/locales/en.json.hbs +6 -0
- package/templates/features/markdown/src/app/markdown/markdown-guards.test.js.hbs +63 -0
- package/templates/features/markdown/src/app/markdown/markdown-guards.ts.hbs +31 -0
- package/templates/features/markdown/src/app/markdown/markdown-i18n.test.js.hbs +78 -0
- package/templates/features/markdown/src/app/markdown/markdown-i18n.ts.hbs +31 -0
- package/templates/features/markdown/src/app/markdown/markdown-manifest.test.js.hbs +98 -0
- package/templates/features/markdown/src/app/markdown/markdown-manifest.ts.hbs +157 -0
- package/templates/features/markdown/src/app/markdown/markdown-manifests.service.test.js.hbs +87 -0
- package/templates/features/markdown/src/app/markdown/markdown-manifests.service.ts.hbs +50 -0
- package/templates/features/markdown/src/app/markdown/markdown-routes.test.js.hbs +74 -0
- package/templates/features/markdown/src/app/markdown/markdown-routes.ts.hbs +26 -0
- package/templates/features/markdown/src/app/markdown/markdown-sources.test.js.hbs +22 -0
- package/templates/features/markdown/src/app/markdown/markdown-sources.ts.hbs +42 -0
- package/templates/features/markdown/src/app/markdown/view/markdown.view.html.hbs +4 -0
- package/templates/features/markdown/src/app/markdown/view/markdown.view.scss.hbs +19 -0
- package/templates/features/markdown/src/app/markdown/view/markdown.view.test.js.hbs +125 -0
- package/templates/features/markdown/src/app/markdown/view/markdown.view.ts.hbs +146 -0
- package/templates/features/markdown/tools/builder/core/markdown/generate-markdown-pages.js.hbs +71 -0
- package/templates/features/markdown/tools/builder/core/markdown/markdown-compiler.js.hbs +582 -0
- package/templates/features/markdown/tools/builder/core/markdown/markdown-compiler.test.js.hbs +390 -0
- package/templates/features/markdown/tools/builder/core/markdown/markdown-seo-routes.js.hbs +155 -0
- package/templates/features/markdown/tools/builder/core/markdown/markdown-seo-routes.test.js.hbs +112 -0
- package/templates/base/src/app/components/index.ts.hbs +0 -1
- package/templates/base/src/app/services/index.ts.hbs +0 -1
- package/templates/base/src/app/views/index.ts.hbs +0 -3
- /package/templates/base/tools/testin-nutin/core/{tests → globals}/clock.test.js.hbs +0 -0
|
@@ -19,14 +19,19 @@ nutin.config.js build/feature config — see below
|
|
|
19
19
|
|
|
20
20
|
- Favor small, composable components.
|
|
21
21
|
- One `View` = one route.
|
|
22
|
-
- Import
|
|
22
|
+
- Import framework code from the `core/index.js` barrel — do not deep-import into `core/`. App elements have no barrels: import them by file path.
|
|
23
|
+
{{#if (eq lang "ts")}}
|
|
24
|
+
- Framework types (`Nutin.ComponentConfig`, `Nutin.ComponentOptions`, `Nutin.CatalogConfig`, `Nutin.Template`, `Nutin.Routes`, `Nutin.RouteGuard`, `Nutin.RequestConfig`, …) live in the global `Nutin` namespace declared in `core/internals.d.ts` — use them without importing. App events extend it in `src/app/globals.d.ts` (`declare namespace Nutin { interface AppEventMap { … } }`).
|
|
25
|
+
{{/if}}
|
|
26
|
+
- `src/app/` layout is free; `components/`, `views/`, `services/` is the default suggestion.
|
|
23
27
|
|
|
24
28
|
## Commands
|
|
25
29
|
|
|
26
|
-
- Serve: `{{packageManager}} run serve` (build + serve), `{{packageManager}} run serve:only` (no rebuild), `{{packageManager}} run dev` (watch mode).
|
|
27
|
-
- Build: `{{packageManager}} run build` (dev) or `{{packageManager}} run build:prod` (production).
|
|
28
|
-
- Generate: `{{packageManager}} run generate ELEMENT ELEMENT_NAME` (`ELEMENT` = `component`/`view`/`service
|
|
29
|
-
- Test: `{{packageManager}} run testin-nutin` (build + run), `{{packageManager}} run testin-nutin:only` (no rebuild), `{{packageManager}} run testin-nutin:watch`.
|
|
30
|
+
- Serve: `{{packageManager}} run serve` (build + serve), `{{packageManager}} run serve:prod` (prod build + serve), `{{packageManager}} run serve:only` (no rebuild), `{{packageManager}} run dev` (watch mode, reloads the page after each rebuild). Port defaults to 9090; override with `-- --port <n>` or `PORT=<n>`.
|
|
31
|
+
- Build: `{{packageManager}} run build` (dev) or `{{packageManager}} run build:prod` (production). Opt-in dependencies (Tailwind, `markdown`) are only installed automatically in an interactive terminal; elsewhere pass `-- -y` (or set `NUTIN_ASSUME_YES`).
|
|
32
|
+
- Generate: `{{packageManager}} run generate ELEMENT ELEMENT_NAME` (`ELEMENT` = `component`/`view`/`service`; `ELEMENT_NAME` may be a nested path, generated in `src/app/<path>/`; fails if that folder already exists and isn't empty).
|
|
33
|
+
- Test: `{{packageManager}} run testin-nutin` (build + run), `{{packageManager}} run testin-nutin:only` (no rebuild), `{{packageManager}} run testin-nutin:watch`, `{{packageManager}} run testin-nutin:coverage`, `{{packageManager}} run testin-nutin:verbose`.
|
|
34
|
+
- Update Nutin: `nutin-update` refuses to run unless the project is committed (no changes, no untracked files; `--allow-dirty` skips the check). Files edited here that also changed upstream are merged three-way; overlapping edits get `diff3` conflict markers, and the project won't build, nor will `nutin-update` run again, until they're resolved (listed in `.nutin-meta.json`'s `unmergedFiles` and `NUTIN-UPDATE-REPORT.md`).
|
|
30
35
|
|
|
31
36
|
## Base classes
|
|
32
37
|
|
|
@@ -37,15 +42,16 @@ BaseComponent<T> — render/destroy engine, data-* parsing, children
|
|
|
37
42
|
└── View — one per route; mounted/unmounted by the Router
|
|
38
43
|
```
|
|
39
44
|
|
|
40
|
-
- `render()`: `onBeforeRender()` → sanitize
|
|
45
|
+
- `render()`: `onBeforeRender()` → sanitize the template and insert the resulting nodes (`SecurityHelper.sanitizeToFragment()`, never assigned to `innerHTML`) → mount children (`data-component`/`data-catalog`) → parse `data-i18n`/`data-pipe` → bind `data-event` listeners → `onAfterRender()`. Runs in full on every `render()` call — no virtual DOM/diffing; only children with `keep`/`keepBy` are kept (see "Children components").
|
|
41
46
|
- `destroy()`: unbinds events, unsubscribes bus listeners, destroys children, removes the element.
|
|
42
|
-
- `Service`: direct `new` throws — always use `MyService.getInstance()` (or the exported const). Auto-binds all methods to the instance. `registerCleanup(fn)`
|
|
43
|
-
- Overridable hooks (all no-op by default): `onBeforeRender`, `onAfterRender`, `onBeforeDestroy`, `onAfterDestroy`, `generateTemplate()`, `registerChildren()`. `View` adds router-only `onEnter()`/`onExit()`.
|
|
47
|
+
- `Service`: direct `new` throws — always use `MyService.getInstance()` (or the exported const). Auto-binds all methods to the instance. `registerCleanup(fn)` callbacks run on `dispose()`, `MyService.destroy()` and `Service.destroyAll()` (tests, SSR) — nothing is torn down on page unload.
|
|
48
|
+
- Overridable hooks (all no-op by default): `onBeforeRender`, `onAfterRender`, `onBeforeDestroy`, `onAfterDestroy`, `onKept`, `generateTemplate()`, `registerChildren()`. `View` adds router-only `onEnter()`/`onExit()`.
|
|
44
49
|
|
|
45
50
|
## Children components
|
|
46
51
|
|
|
47
|
-
- Override `registerChildren(): ComponentConfig[]` to declare children — matched against `[data-component="selector"]` elements in the rendered template. ComponentConfig: `{ selector: 'my-selector', factory: (el) => new MyComponent(el, {...} )}`.
|
|
48
|
-
-
|
|
52
|
+
- Override `registerChildren(): Nutin.ComponentConfig[]` to declare children — matched against `[data-component="selector"]` elements in the rendered template. ComponentConfig: `{ selector: 'my-selector', factory: (el) => new MyComponent(el, {...} ), keep?: string | number }`.
|
|
53
|
+
- Children are destroyed and recreated on every parent render unless they have a `keep` value: with the same value on the next render, the child is kept as-is (factory not called, not re-rendered, DOM state preserved) and only its `onKept()` hook runs. Put in `keep` whatever should force a recreate.
|
|
54
|
+
- For repeated lists, use the inherited `createCatalogComponents({ items, elementName, selector, component })` inside `registerChildren()` instead of hand-writing one entry per item — targets `[data-catalog="selector"]`. Add `keepBy: (item, index) => id` to keep items whose id is unchanged and whose item is shallow-equal; without it the catalog is rebuilt every render.
|
|
49
55
|
|
|
50
56
|
## `data-*` attributes
|
|
51
57
|
|
|
@@ -62,7 +68,7 @@ BaseComponent<T> — render/destroy engine, data-* parsing, children
|
|
|
62
68
|
|
|
63
69
|
## DOM events binding
|
|
64
70
|
|
|
65
|
-
Handlers are (re-)bound on every `render()`, not at build time. Always use private methods prefixed with underscore:
|
|
71
|
+
Handlers are (re-)bound on every `render()`, not at build time. Always use private methods prefixed with underscore{{#if (eq lang "ts")}} (`protected` for a handler a subclass's template uses){{/if}}:
|
|
66
72
|
|
|
67
73
|
```html
|
|
68
74
|
<div data-event="event:_myFunction:@value,@id">
|
|
@@ -71,17 +77,21 @@ Handlers are (re-)bound on every `render()`, not at build time. Always use priva
|
|
|
71
77
|
private _myFunction(value: string, id: string) { doStuff() }
|
|
72
78
|
```
|
|
73
79
|
|
|
74
|
-
|
|
80
|
+
{{#if (eq lang "ts")}}The build type-checks each handler name written in a template against the class: a missing one fails `tsc` at the template line, and handlers count as used for `noUnusedLocals` (on in this project). Names built with `${}` aren't checked. {{/if}}At runtime, a missing handler binds nothing and logs a `console.warn` in development.
|
|
81
|
+
|
|
82
|
+
Tokens: `@value`, `@id`, `@class`, `@textContent`, `@event`, `@target`, `@attr:name`, `@dataset:key`, plus string/number literals (quoted literals may contain commas). Token values are raw (not HTML-escaped): escaping happens when they are rendered through an `html` template.
|
|
83
|
+
|
|
84
|
+
Only defaults that leave the page are cancelled: link clicks (`<a href>`, unless modified, e.g. ctrl+click), form `submit` and submit-button clicks inside a form. Other events keep their default (typing, checking a box); take `@event` and call `preventDefault()` yourself if needed. A component only binds its own `data-event`/`data-bind`/`data-i18n` elements, not those inside nested child components.
|
|
75
85
|
|
|
76
86
|
## Core services
|
|
77
87
|
|
|
78
|
-
- `AppEventBus` — `subscribe`/`once`/`emit`/`off`. Inside a component, prefer `this.listen(event, cb)` (auto-unsubscribes on `destroy()`) or `this.listenToRenderEvents([event1, event2, ...])` (re-runs `this.render()` whenever any of the given events fire) over calling the bus directly. Typed facades — each `on*` subscriber returns a closure you call directly to unsubscribe (`const off = Navigation.onNavigate(cb); ...; off();`), no need to keep the original callback reference around for `off()`:
|
|
88
|
+
- `AppEventBus` — `subscribe`/`once`/`emit`/`off`.{{#if (eq lang "ts")}} Declare app events in `src/app/globals.d.ts`'s `Nutin.AppEventMap`: `emit()` requires the declared payload, and an event declared `undefined` takes none.{{/if}} Inside a component, prefer `this.listen(event, cb)` (auto-unsubscribes on `destroy()`) or `this.listenToRenderEvents([event1, event2, ...])` (re-runs `this.render()` whenever any of the given events fire) over calling the bus directly. Typed facades — each `on*` subscriber returns a closure you call directly to unsubscribe (`const off = Navigation.onNavigate(cb); ...; off();`), no need to keep the original callback reference around for `off()`:
|
|
79
89
|
- `Navigation` — `navigateTo(path)`/`onNavigate`, `reload()`/`onReload`.
|
|
80
90
|
- `Lifecycle` — `viewMount(viewName)`/`onViewMount`, `viewUnmount(viewName)`/`onViewUnmount`, plus generic `beforeRender`/`afterRender`/`beforeDestroy`/`afterDestroy`.
|
|
81
|
-
- `AppRouter(routes)` — call once in `main.ts`; routes support `:param`/`:param
|
|
91
|
+
- `AppRouter(routes)` — call once in `main.ts` (optional: without it, mount components into any element with `new X(el).render()`; the build then skips the `routes` and `#app` checks, unless `generateSEOFiles` is on; a mount target that isn't found logs a `console.error` in development; testin-nutin's framework tests still mount into `#app`, see "Testing"); routes support `:param`/`:param?`, per-route guards, and lazy views: a factory returning `import('./views/x/x.view.js').then((m) => new m.XView())` loads the view on first visit (import it by its own file, not through a barrel{{#if (eq lang "ts")}}; factory type: global `Nutin.ViewFactory`{{/if}}). Keep `/` and `/404` eager. After each in-app navigation (not the first load or `Navigation.reload()`), focus moves to the view's `<h1>`, else the `<h1>` in `<main>`, else the view element, with `document.title` announced in a polite live region in that last case only (a focused heading is read by screen readers). Give each view an `<h1>`. `document.title` comes from `View.documentTitle()` if overridden, else `config/seo.json`'s title (with `generateSEOFiles`), the `<viewName>.title` translation, then `viewName`.
|
|
82
92
|
- `registerGlobals({ before?, after? })` — each entry is `{ component, id }` (a bare class, constructed internally); mounts them outside `<main id="app">` (`before`/`after` control placement relative to it), stamping each root element with `id`. `hideGlobals(ids)`/`revealGlobals(ids)` (same module) toggle visibility by those ids from any component or view. Call once in `main.ts`.
|
|
83
|
-
- `AppHttpClient` — `get/post/put/patch/delete<T>(endpoint, data?, config?)`; non-OK responses throw `HttpError`.
|
|
84
|
-
- `I18nService` — `translate(key, fallback?)`, `setCurrentLanguage(lang)`; language list comes from `config/languages.json`.
|
|
93
|
+
- `AppHttpClient` — `get/post/put/patch/delete<T>(endpoint, data?, config?)`; non-OK responses throw `HttpError`. Relative endpoints resolve against the page origin; a subclass with a base URL rejects endpoints leaving it. Build paths from values with ``path`/users/${id}` `` (encoded `/` and `\` in paths are rejected unless the API is in the `trustedAPIs` option). FormData/Blob bodies are sent as-is; 204 → `undefined`.
|
|
94
|
+
- `I18nService` — `translate(key, fallback?)`, `setCurrentLanguage(lang)`; language list comes from `config/languages.json`. `initI18n()` (same module) loads the translations when `i18n` is enabled and does nothing otherwise; `main.ts` awaits it before starting the app. With `i18n: false`, prod builds drop the i18n code unless app code references `I18nService`.
|
|
85
95
|
- `AppPipeRegistry` — `register(name, fn)` / `apply(name, value, args)`; backs `data-pipe`.
|
|
86
96
|
|
|
87
97
|
## Styling
|
|
@@ -91,40 +101,27 @@ Tokens: `@value`, `@id`, `@class`, `@textContent`, `@event`, `@target`, `@attr:n
|
|
|
91
101
|
|
|
92
102
|
## Pipes
|
|
93
103
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
// number (toFixed)
|
|
102
|
-
AppPipeRegistry.register('number');
|
|
103
|
-
|
|
104
|
-
// string pipes
|
|
105
|
-
AppPipeRegistry.register('uppercase');
|
|
106
|
-
AppPipeRegistry.register('lowercase');
|
|
107
|
-
AppPipeRegistry.register('capitalize');
|
|
108
|
-
AppPipeRegistry.register('capitalizeAll');
|
|
109
|
-
AppPipeRegistry.register('truncate');
|
|
110
|
-
|
|
111
|
-
// utility pipes
|
|
112
|
-
AppPipeRegistry.register('default');
|
|
113
|
-
AppPipeRegistry.register('json');
|
|
114
|
-
```
|
|
104
|
+
Built-in pipes, registered by `registerPipes()` in `main.ts` (args in `data-pipe="name:arg1,arg2"`):
|
|
105
|
+
|
|
106
|
+
- `currency:currency,locale` (defaults `USD`, `en-US`), `date:locale,format,time` (defaults browser language, `long`), `number:decimals` (`toFixed`, default `0`)
|
|
107
|
+
- `uppercase`, `lowercase`, `capitalize`, `capitalizeAll`, `truncate:length,suffix` (defaults `50`, `...`)
|
|
108
|
+
- `default:fallback`, `json`
|
|
109
|
+
|
|
110
|
+
Add your own with `AppPipeRegistry.register(name, (value, ...args) => string)`; args arrive as strings.
|
|
115
111
|
|
|
116
112
|
## Configuration
|
|
117
113
|
|
|
118
114
|
### `nutin.config.js`
|
|
119
115
|
|
|
120
116
|
Central options/tools config (see the inline comments for details):
|
|
121
|
-
- `i18n`, `tailwind`, `
|
|
117
|
+
- `i18n`, `tailwind`, `generateSEOFiles` — top-level feature toggles.
|
|
118
|
+
- `dockerPorts`, `markdownSources` — options of the `docker`/`markdown` features, only present once added with `nutin-add <feature>`.
|
|
122
119
|
- `builder.{sass.paths, esbuild}` — build pipeline behavior.
|
|
123
120
|
- `testinNutin.{includeFramework, includeTools, includeApp, jsdomOptions}` — test discovery/environment.
|
|
124
121
|
|
|
125
122
|
### `config/seo.json`
|
|
126
123
|
|
|
127
|
-
Consumed by builder (production only) to generate static HTML per route. Unused if `nutin.config.js ->
|
|
124
|
+
Consumed by builder (production only) to generate static HTML per route. Unused if `nutin.config.js -> generateSEOFiles` is disabled. Dynamic routes (`/blog/:slug`) are written at their real URL from `mockParams` (or `outputPath`). Route guards run first, as an anonymous visitor: a route they block or redirect isn't prerendered nor listed in `sitemap.xml`.
|
|
128
125
|
|
|
129
126
|
### `config/languages.json`
|
|
130
127
|
|
|
@@ -132,17 +129,22 @@ Consumed by i18nService and builder. Unused if i18n is disabled.
|
|
|
132
129
|
|
|
133
130
|
## Security
|
|
134
131
|
|
|
135
|
-
`
|
|
132
|
+
Write every template with the `html` tag (`import { html, raw } from` core): `templateFn = (task) => html\`<li>${task.name}</li>\``. Every `${}` is HTML-escaped, whatever the data's source (input, HTTP, localStorage). Nested `html` results are inserted as-is, and arrays are inserted item by item (no `.join('')`). For lists, use `createCatalogComponents()` (see "Children components") rather than mapping items to markup in the template: each item becomes a child component with its own events and state, kept across renders with `keepBy`. Unquoted attribute values (`title=${x}`) are quoted automatically. A plain value in tag position (`<input ${x}>`) is filtered like `raw()` there: plain attributes stay, handlers, bindings and script URLs are dropped. Wrap HTML you want rendered in `raw()`: it's parsed in place at render (works inside `<svg>`/`<table>`) and loses Nutin's binding attributes (`data-event`, `data-component`, `data-catalog`, `data-bind`, `data-i18n`, `data-pipe*`). `trustedRaw()` keeps them, for markup you wrote yourself (e.g. compiled Markdown). An untagged template literal escapes nothing, and the build warns about each one containing `${}`.
|
|
133
|
+
|
|
134
|
+
Every `render()` also sanitizes the template (`SecurityHelper.sanitizeToFragment()`) and inserts the resulting nodes directly — never re-parsed from a string. `trustLevel` (`'strict' | 'normal' | 'trusted'`, default `'normal'`) controls how much is stripped (`<script>`/`<style>`/`<link>`/`<base>`/`<meta>`, inline handlers, `srcdoc`, `javascript:` URLs, `data:` frames and SVG `<animate>`/`<set>` targeting URL attributes from `normal` up; `<iframe>`/`<object>`/`<embed>` and all `data:` URLs under `strict` only). Don't interpolate data into `data-event` args or `style`: use `data-x="${v}"` + `@dataset:x`. URL attributes (`href`, `src`, `action`, …) whose interpolated value makes them a `javascript:` URL get `about:invalid#nutin-blocked` instead, at every trust level. It is a second layer for `raw()` content, not a substitute for `html`.
|
|
136
135
|
|
|
137
136
|
## Testing (testin-nutin)
|
|
138
137
|
|
|
139
|
-
Nutin's own hand-built test runner + jsdom - not Jest. `describe`/`it` only (**no `test()` alias**), `beforeAll`/`beforeEach`/`afterEach`/`afterAll` (one of each per suite, no stacking - inside `describe` statement, not top-level). All globals — no imports needed in test files.
|
|
138
|
+
Nutin's own hand-built test runner + jsdom - not Jest. Tests are plain JavaScript (`*.test.js` importing the compiled `#root/dist/...` output){{#if (eq lang "ts")}}, so they aren't type-checked{{/if}}. `describe`/`it` only (**no `test()` alias**), `beforeAll`/`beforeEach`/`afterEach`/`afterAll` (one of each per suite, no stacking - anywhere inside the `describe` statement, not top-level). `it.todo(name)` lists a pending test. All globals — no imports needed in test files.
|
|
140
139
|
|
|
141
140
|
- Matchers: `toBe`, `toEqual`, `toContain`, `toBeTruthy`/`toBeFalsy`, `toBeDefined`/`toBeUndefined`, `toBeInstanceOf`, `toHaveBeenCalled`/`toHaveBeenCalledWith`, `toBeLessThan`/`toBeGreaterThan`, `toThrow` — each has a `.not` counterpart.
|
|
142
141
|
- `spyOn(obj, 'method')` returns `.calls`/`.callCount`/`.lastCall`, `.andCallFake(fn)`/`.andReturn(v)`, `.restore()` — unlike mocks, calls pass through by default.
|
|
143
142
|
- `tools/testin-nutin/mocks/` has ready-made `MockEventBus`/`MockHttpClient`/`MockI18n`/`MockRouter` for constructor injection.
|
|
144
|
-
- `useFakeTimers()`/`useRealTimers()`/`advanceTimersByTime(ms)`/`runAllTimers()` for time-dependent code.
|
|
143
|
+
- `useFakeTimers()`/`useRealTimers()`/`advanceTimersByTime(ms)`/`runAllTimers()` for time-dependent code; `flushPromises()` for pending promises.
|
|
144
|
+
- `silenceConsole(method | methods, fn)` runs `fn` with those `console` methods muted (sync or async). Use it in any test that triggers an expected warning/error.
|
|
145
|
+
- `$(selector)`/`$$(selector)` — `document.querySelector`/`querySelectorAll` (as an array).
|
|
145
146
|
- Test discovery/scope controlled by `nutin.config.js`'s `testinNutin.includeApp`/`includeFramework`.
|
|
147
|
+
- Tests run against the built `dist/src/index.html`, and the framework tests (`includeFramework`) mount into its `#app` element. An app without the router that has no `#app` keeps an empty `<div id="app"></div>` in `src/index.html` (not `hidden`: focus tests need it focusable), or sets `includeFramework: false`.
|
|
146
148
|
|
|
147
149
|
## Nutin
|
|
148
150
|
|
|
@@ -165,4 +167,4 @@ When modifying Nutin `core/` files:
|
|
|
165
167
|
|
|
166
168
|
## Documentation
|
|
167
169
|
|
|
168
|
-
[Nutin Documentation](https://
|
|
170
|
+
[Nutin Documentation](https://www.nutin.dev/docs)
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
```
|
|
17
17
|
src/
|
|
18
|
-
app/
|
|
18
|
+
app/ -- suggested layout: organize src/app however you like
|
|
19
19
|
components/
|
|
20
20
|
services/
|
|
21
21
|
views/
|
|
@@ -27,23 +27,33 @@ src/
|
|
|
27
27
|
|
|
28
28
|
tools/ -- build, development, generator and testing
|
|
29
29
|
|
|
30
|
-
nutin.config.js --
|
|
30
|
+
nutin.config.js -- options and configuration
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
* Views **orchestrate application flow**
|
|
34
|
+
* Components **encapsulate UI behavior**
|
|
35
|
+
* Services **handle application concerns** through shared instances
|
|
36
|
+
|
|
33
37
|
### Imports
|
|
34
38
|
|
|
35
|
-
Imports use **relative paths** and end with `.js` extension.
|
|
39
|
+
- Imports use **relative paths** and end with `.js` extension.
|
|
36
40
|
|
|
37
41
|
```js
|
|
38
42
|
import { Foo } from 'relative/path/to/foo.js';
|
|
39
43
|
```
|
|
40
44
|
|
|
45
|
+
- Use Nutin's code barrel file (`src/core/index.js`) to import Nutin's modules.
|
|
46
|
+
{{#if (eq lang "ts")}}
|
|
47
|
+
- Nutin's types (`Nutin.ComponentConfig`, `Nutin.Routes`, `Nutin.Template`, …) live in the global `Nutin` namespace declared in `src/core/internals.d.ts`: no import needed.
|
|
48
|
+
{{/if}}
|
|
49
|
+
|
|
41
50
|
## How do I run my app?
|
|
42
51
|
|
|
43
52
|
```bash
|
|
44
53
|
# Development
|
|
45
54
|
{{packageManager}} run serve # build and serve
|
|
46
55
|
{{packageManager}} run dev # build and serve with live reload
|
|
56
|
+
{{packageManager}} run dev -- --port 3000 # serve on another port (default 9090; PORT=3000 also works)
|
|
47
57
|
|
|
48
58
|
# Build
|
|
49
59
|
{{packageManager}} run build # build the application
|
|
@@ -52,16 +62,14 @@ import { Foo } from 'relative/path/to/foo.js';
|
|
|
52
62
|
|
|
53
63
|
## How do I generate components, views and services?
|
|
54
64
|
|
|
55
|
-
|
|
56
|
-
`src/app/<components|views|services>/index.ts`.
|
|
57
|
-
|
|
65
|
+
Elements are generated in `src/app/<path>/`, so you choose the project structure. The generator stops if the target folder already exists and isn't empty.
|
|
58
66
|
|
|
59
67
|
```bash
|
|
60
|
-
{{packageManager}} run generate <component|view|service> ELEMENT_NAME
|
|
68
|
+
{{packageManager}} run generate <component|view|service> PATH/TO/ELEMENT_NAME
|
|
61
69
|
|
|
62
70
|
# Examples
|
|
63
|
-
{{packageManager}} run generate component
|
|
64
|
-
{{packageManager}} run generate
|
|
71
|
+
{{packageManager}} run generate component components/hello-world # generates src/app/components/hello-world/hello-world.component.ts|html|scss
|
|
72
|
+
{{packageManager}} run generate view user/dashboard # generates src/app/user/dashboard/dashboard.view.ts|html|scss
|
|
65
73
|
```
|
|
66
74
|
|
|
67
75
|
## What does a typical component look like?
|
|
@@ -75,8 +83,8 @@ components/
|
|
|
75
83
|
```
|
|
76
84
|
|
|
77
85
|
```ts
|
|
78
|
-
// The
|
|
79
|
-
const templateFn = (product: Product) => `__TEMPLATE_PLACEHOLDER__`;
|
|
86
|
+
// The component's .html template is injected here.
|
|
87
|
+
const templateFn = (product: Product) => html`__TEMPLATE_PLACEHOLDER__`;
|
|
80
88
|
|
|
81
89
|
export class ProductCardComponent extends Component {
|
|
82
90
|
private _product: Product;
|
|
@@ -87,7 +95,7 @@ export class ProductCardComponent extends Component {
|
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
// Declares children mounted into the data-component / data-catalog placeholders below.
|
|
90
|
-
public registerChildren(): ComponentConfig[] {
|
|
98
|
+
public registerChildren(): Nutin.ComponentConfig[] {
|
|
91
99
|
// Single child, matched to <div data-component="product-details">
|
|
92
100
|
const detailsComponent = {
|
|
93
101
|
selector: `product-details`,
|
|
@@ -120,7 +128,8 @@ export class ProductCardComponent extends Component {
|
|
|
120
128
|
|
|
121
129
|
```html
|
|
122
130
|
<div
|
|
123
|
-
data-
|
|
131
|
+
data-product-id="${product.id}"
|
|
132
|
+
data-event="click:_goToProductPage:@dataset:productId"
|
|
124
133
|
class="product-card"
|
|
125
134
|
>
|
|
126
135
|
<h2 class="product-card__name">${product.name}</h2>
|
|
@@ -147,12 +156,12 @@ Defining both an inline template and an external `.html` file, or neither, fails
|
|
|
147
156
|
|
|
148
157
|
### Main file
|
|
149
158
|
|
|
150
|
-
`nutin.config.js` - Main Nutin configuration and tool options. See the inline comments for details.
|
|
159
|
+
`nutin.config.js` - Main Nutin configuration and tool options. See the inline comments and [How do I configure Nutin?](https://www.nutin.dev/docs/options-and-features/configure-nutin) for details.
|
|
160
|
+
|
|
161
|
+
This is where you can:
|
|
151
162
|
|
|
152
|
-
-
|
|
153
|
-
|
|
154
|
-
- `builder.*` — controls build behavior.
|
|
155
|
-
- `testinNutin.*` — controls Nutin's built-in testing toolkit.
|
|
163
|
+
- Toggle options ([Tailwind CSS](https://www.nutin.dev/docs/options-and-features/use-tailwind-css), [i18n](https://www.nutin.dev/docs/options-and-features/use-i18n), [SEO file generation](https://www.nutin.dev/docs/options-and-features/use-seo-file-generation)).
|
|
164
|
+
- Configure the builder and the built-in testing toolkit.
|
|
156
165
|
|
|
157
166
|
### Specialized files
|
|
158
167
|
|
|
@@ -171,7 +180,7 @@ See [Options and features](https://github.com/LoisKOUNINEF/nutin/tree/main/docs/
|
|
|
171
180
|
|
|
172
181
|
You can use any testing tool you prefer for application code.
|
|
173
182
|
|
|
174
|
-
This project includes [testin-nutin](https://
|
|
183
|
+
This project includes [testin-nutin](https://www.nutin.dev/docs/testing), Nutin's small testing toolkit. Nutin itself is tested exclusively with it, and the tests are included in the generated project so you can:
|
|
175
184
|
- use them as executable documentation;
|
|
176
185
|
- run and modify Nutin's tests when modifying its code.
|
|
177
186
|
|
|
@@ -187,13 +196,13 @@ testinNutin: {
|
|
|
187
196
|
includeApp: true,
|
|
188
197
|
}
|
|
189
198
|
```
|
|
190
|
-
*Note:* TestinNutin files use root imports.
|
|
199
|
+
*Note:* TestinNutin files are plain JavaScript and use root imports{{#if (eq lang "ts")}}: they test the compiled output, so they aren't type-checked{{/if}}.
|
|
191
200
|
```js
|
|
192
201
|
import { MyModule } from '#root/dist/src/path/to/my-module.js';
|
|
193
202
|
```
|
|
194
203
|
|
|
195
204
|
## Go further
|
|
196
205
|
|
|
197
|
-
- [
|
|
198
|
-
- [First app tutorial](https://www.nutin.
|
|
199
|
-
- [Nutin website](https://www.nutin.
|
|
206
|
+
- [Documentation](https://www.nutin.dev/docs)
|
|
207
|
+
- [First app tutorial](https://www.nutin.dev/tutorial)
|
|
208
|
+
- [Nutin website](https://www.nutin.dev)
|
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Development
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
Requires Node.js 24 or later.
|
|
6
|
+
|
|
7
|
+
- `{{packageManager}} run serve`: build and serve
|
|
8
|
+
- `{{packageManager}} run dev`: build and serve with live reload
|
|
9
|
+
- Both serve on port 9090 by default. Pass `-- --port 3000` (or set `PORT=3000`) to change it
|
|
7
10
|
|
|
8
11
|
## Build
|
|
9
12
|
|
|
10
|
-
- `{{packageManager}} run build
|
|
11
|
-
- `{{packageManager}} run build:prod
|
|
13
|
+
- `{{packageManager}} run build`: build the application
|
|
14
|
+
- `{{packageManager}} run build:prod`: build the application for production
|
|
12
15
|
|
|
13
16
|
## Getting started
|
|
14
17
|
|
|
File without changes
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
declare
|
|
1
|
+
declare namespace Nutin {
|
|
2
|
+
interface AppEventMap {
|
|
2
3
|
// Add app-specific event types here
|
|
3
|
-
// '
|
|
4
|
-
|
|
4
|
+
// 'task-saved': { taskId: number }; // emit('task-saved', { taskId }) — payload required
|
|
5
|
+
// 'tasks-cleared': undefined; // emit('tasks-cleared') — no payload
|
|
6
|
+
}
|
|
7
|
+
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { RouteGuard } from "../core/index.js";
|
|
2
|
-
|
|
3
1
|
export const Guards = {
|
|
4
2
|
/**
|
|
5
3
|
* Basic example
|
|
6
4
|
*/
|
|
7
|
-
requireAuth: (redirectTo: string = '/login'): RouteGuard => {
|
|
5
|
+
requireAuth: (redirectTo: string = '/login'): Nutin.RouteGuard => {
|
|
8
6
|
return () => {
|
|
9
7
|
const isAuthenticated = !!localStorage.getItem('user_token');
|
|
10
8
|
return isAuthenticated || redirectTo;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { AppRouter, Service, I18nService, registerPipes } from '../core/index.js';
|
|
1
|
+
import { AppRouter, initI18n, registerPipes } from '../core/index.js';
|
|
3
2
|
import { appRoutes } from './routes.js';
|
|
4
3
|
|
|
5
4
|
class App {
|
|
@@ -10,12 +9,6 @@ class App {
|
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
13
|
-
|
|
14
|
-
await I18nService.initTranslations();
|
|
15
|
-
}
|
|
12
|
+
await initI18n();
|
|
16
13
|
new App();
|
|
17
14
|
});
|
|
18
|
-
|
|
19
|
-
window.addEventListener('beforeunload', async () => {
|
|
20
|
-
await Service.destroyAll();
|
|
21
|
-
});
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { HomeView } from './views/home/home.view.js';
|
|
2
|
+
import { NotFoundView } from './views/not-found/not-found.view.js';
|
|
3
|
+
{{#if markdown}}
|
|
4
|
+
import { markdownRoutes } from './markdown/markdown-routes.js';
|
|
5
|
+
{{/if}}
|
|
3
6
|
|
|
4
|
-
export const appRoutes: Routes = {
|
|
7
|
+
export const appRoutes: Nutin.Routes = {
|
|
5
8
|
'/': () => new HomeView(),
|
|
9
|
+
{{#if markdown}}
|
|
10
|
+
...markdownRoutes(),
|
|
11
|
+
{{/if}}
|
|
6
12
|
'/404': () => new NotFoundView(),
|
|
7
13
|
}
|
|
File without changes
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
<span style="font-style:italic;font-size:clamp(.6875rem,2vw,1rem);">nutin v{{version}}</span>
|
|
4
4
|
<img src="/assets/images/logo-trans.avif" width=250 height=250 alt="nutin's logo. A frame opened on the top left through which a plant grows.">
|
|
5
5
|
<div style="color:#C56648;font-size:clamp(1.125rem,4vw,1.5rem);">Happy Coding 🎉</div>
|
|
6
|
-
<div style="font-size:clamp(.75rem,2vw,1rem);font-style:italic;position:fixed;bottom:0;">
|
|
6
|
+
<div style="font-size:clamp(.75rem,2vw,1rem);font-style:italic;position:fixed;bottom:0;padding-bottom:.2rem;">it's <span style="font-weight:bold;"><a style="text-decoration:none;color:#6C8359;" href="https://nutin.dev">nutin</a></span>.</div>
|
|
7
7
|
</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Navigation, View } from '../../../core/index.js';
|
|
1
|
+
import { Navigation, View, html } from '../../../core/index.js';
|
|
2
2
|
|
|
3
|
-
const template = `__TEMPLATE_PLACEHOLDER__`;
|
|
3
|
+
const template = html`__TEMPLATE_PLACEHOLDER__`;
|
|
4
4
|
|
|
5
5
|
export class NotFoundView extends View {
|
|
6
6
|
constructor() {
|