nuxt-ignis 0.1.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/.gitattributes ADDED
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
@@ -0,0 +1,16 @@
1
+ # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
2
+ # https://github.blog/2023-08-24-a-faster-way-to-manage-version-updates-with-dependabot/
3
+
4
+ version: 2
5
+ updates:
6
+ - package-ecosystem: "npm"
7
+ directory: "/"
8
+ schedule:
9
+ interval: "monthly"
10
+ time: "05:10"
11
+ commit-message:
12
+ prefix: "npm"
13
+ groups:
14
+ all:
15
+ patterns:
16
+ - "*"
@@ -0,0 +1,9 @@
1
+ {
2
+ // enable auto-linting
3
+ "editor.codeActionsOnSave": {
4
+ "source.fixAll": "explicit",
5
+ "source.fixAll.eslint": "explicit"
6
+ },
7
+ // ESlint flat config
8
+ "eslint.useFlatConfig": true
9
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Alois Sečkár
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # Nuxt Ignis
2
+
3
+ ![Nuxt Ignis](https://github.com/AloisSeckar/nuxt-ignis/blob/main/public/nuxt-ignis.png)
4
+
5
+ This is a template starter for Nuxt web applicatons. It is being build as the setup I'd currently use to start with a new "real world" [Nuxt](https://nuxt.com/) webapp. It will improve and grow together with my skills. I also try to include **WHAT** and **WHY** comments based on my knowledge about the framework and used libraries.
6
+
7
+ ## How to use
8
+
9
+ ### As standalone template
10
+ 1. Do a `git checkout` from https://github.com/AloisSeckar/nuxt-ignis.git
11
+ 2. Open in IDE and run `pnpm install` in terminal
12
+ 3. Configure modules via `.env` properties
13
+ 4. Start dev server with `pnpm dev` command
14
+ 5. Visit `localhost:3000` in browser
15
+
16
+ You are ready to build your next awesome project in Nuxt.
17
+
18
+ ### As a layer
19
+ Aside from being "forked", `nuxt-ignis` is also available as a NPM package that can be referenced as a single-import dependency with all the features incomming.
20
+
21
+ More info comming soon...
22
+
23
+ ## Overview
24
+
25
+ **Fundamentals**
26
+ - [`pnpm`](https://pnpm.io/) based project
27
+ - [Nuxt](https://nuxt.com/) application framework built atop [Vue.js](https://vuejs.org/)
28
+ - Available as a template or standalone package
29
+
30
+ **Built-in features**
31
+ - linting for maintaining coding standards and improving code quality via [`@nuxt/eslint`](https://nuxt.com/modules/eslint)
32
+ - zero-config OWASP security patterns for Nuxt via [`nuxt-security`](https://nuxt-security.vercel.app/)
33
+ - de-facto standard state management library for Vue apps via [`@pinia/nuxt`](https://pinia.vuejs.org/ssr/nuxt.html)
34
+ - integration with utility functions library for Vue apps via [`@vueuse/nuxt`](https://vueuse.org/nuxt/README.html)
35
+ - handful tools for working with images via [`@nuxt/image`](https://image.nuxt.com/)
36
+ - SSR-friendly component for rendering dynamic date/time via [`nuxt-time`](https://nuxt.com/modules/time)
37
+
38
+ **Configurable features**
39
+ - UI (pick 0-1)
40
+ - **Nuxt UI** - UI component and CSS library via [`@nuxt/ui`](https://ui.nuxt.com/)
41
+ - **Tailwind CSS** - CSS library (included in Nuxt UI) via [`@nuxtjs/tailwindcss`](https://tailwindcss.nuxtjs.org/)
42
+ - Database (pick 0-1)
43
+ - **Neon** - serverless PostgreSQL database via [`nuxt-neon`](https://github.com/AloisSeckar/nuxt-neon/)
44
+ - **Supabase** - serverless PostgreSQL database via [`@nuxtjs/supabase`](https://supabase.nuxtjs.org/)
45
+ - Other (opt-in)
46
+ - **I18N** - translations and internalization made easy via [`@nuxtjs/i18n`](hhttps://i18n.nuxtjs.org/)
47
+ - **FormKit** - for handling input forms via [`@formkit/nuxt`](https://nuxt.com/modules/formkit)
48
+ - **Content** - for working with website content in `.md` or `.json` via [`@nuxt/content`](https://content.nuxt.com/)
49
+ - **Open Props** - extra CSS styles via [Open Props](https://open-props.style/)
50
+
51
+ ## Configuration
52
+ It is possible to select which Nuxt modules will be activated in your project. All dependencies are being downloaded into local `node_modules`, but Nuxt build process will ensure only relevant packages will be bundled for production.
53
+
54
+ ### UI preset
55
+ It is possible to pick from three options:
56
+ - `nuxt-ui` - full https://ui.nuxt.com/ via `@nuxt/ui` connector module **[RECOMMENDED]**
57
+ - `tailwind` - only https://tailwindcss.com/ via `@nuxtjs/tailwindcss` connector module
58
+ - `off` - no UI library preset **[DEFAULT]**
59
+
60
+ Set the value via `NUXT_PUBLIC_IGNIS_PRESET_UI` env variable.
61
+
62
+ Value other than `off` will override Optional modules setting.
63
+
64
+ ### Database preset
65
+ It is possible to pick from three options:
66
+ - `neon` - https://neon.tech/ via `nuxt-neon` connector module **[RECOMMENDED]**
67
+ - `supabase` - https://supabase.com/ via `@nuxtjs/supabase` connector module
68
+ - `off` - no database module preset **[DEFAULT]**
69
+
70
+ Set the value via `NUXT_PUBLIC_IGNIS_DB_PRESET` env variable.
71
+
72
+ Value other than `off` will override Optional modules setting.
73
+
74
+ ### Optional modules
75
+ Currently, following modules are opinionated:
76
+ - `@nuxt/ui` - set `NUXT_PUBLIC_IGNIS_UI` to `true | false`
77
+ - `@nuxtjs/tailwindcss` - set `NUXT_PUBLIC_IGNIS_TAILWIND` to `true | false` (ignored if `NUXT_PUBLIC_IGNIS_UI=true`)
78
+ - `nuxt-neon` - set `NUXT_PUBLIC_IGNIS_NEON` to `true | false`
79
+ - `@nuxtjs/supabase` - set `NUXT_PUBLIC_IGNIS_SUPABASE` to `true | false`
80
+ - `@nuxtjs/i18n` - set `NUXT_PUBLIC_IGNIS_I18N` to `true | false`
81
+ - `@formkit/nuxt` - set `NUXT_PUBLIC_IGNIS_FORMKIT` to `true | false`
82
+ - `@nuxt/content` - set `NUXT_PUBLIC_IGNIS_CONTENT` to `true | false`
83
+
84
+ Default values are **false** (not included) for all optional modules.
85
+
86
+ ### Optional features
87
+ Currently, following extra features (not using separate Nuxt Modules) are opinionated:
88
+ - `Open Props CSS` - set `NUXT_PUBLIC_IGNIS_OPENPROPS` to `true | false`
89
+
90
+ Default values are **false** (not included) for all optional features.
package/app.config.ts ADDED
@@ -0,0 +1,3 @@
1
+ export default defineAppConfig({
2
+ textTitle: 'Nuxt Ignis',
3
+ })
package/app.vue ADDED
@@ -0,0 +1,52 @@
1
+ <template>
2
+ <div class="p-3">
3
+ <div class="ignis-header">
4
+ <img src="/nuxt-ignis.png" class="ignis-logo" :title :alt>
5
+ <h1 class="my-4 text-4xl text-amber-400 font-bold">
6
+ {{ useT("title") }}
7
+ </h1>
8
+ <img src="/nuxt-ignis.png" class="ignis-logo" :title :alt>
9
+ </div>
10
+ <div>{{ useT("subtitle") }}</div>
11
+ <NuxtPage />
12
+ <CurrentTime />
13
+ <div class="link text-xs">
14
+ <NuxtLink to="https://github.com/AloisSeckar/nuxt-ignis">
15
+ https://github.com/AloisSeckar/nuxt-ignis
16
+ </NuxtLink>
17
+ </div>
18
+ </div>
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ useHead({
23
+ title: useAppConfig().textTitle,
24
+ htmlAttrs: {
25
+ lang: 'en',
26
+ },
27
+ bodyAttrs: {
28
+ class: 'bg-slate-900 m-auto text-center text-white'
29
+ }
30
+ })
31
+ initConsola()
32
+ log.info('Nuxt Ignis was here!')
33
+ const title = useT('title')
34
+ const alt = title
35
+ </script>
36
+
37
+ <style scoped>
38
+ .ignis-header {
39
+ display: flex;
40
+ flex-direction: row;
41
+ justify-content: center;
42
+ margin-bottom: 10px;
43
+ }
44
+ .ignis-logo {
45
+ display: inline;
46
+ width: 64px;
47
+ height: 64px;
48
+ }
49
+ .ignis-title {
50
+ margin-top: 15px;
51
+ }
52
+ </style>
@@ -0,0 +1,20 @@
1
+ /*
2
+ This will import Open Props CSS styles
3
+ https://open-props.style/
4
+
5
+ NUXT_PUBLIC_IGNIS_OPENPROPS=true is required to use this feature
6
+ */
7
+
8
+ @import "open-props/normalize";
9
+ @import "open-props/buttons";
10
+
11
+ /* demo class - used in AppFeature component instance */
12
+ .openprops-feature {
13
+
14
+ /* https://open-props.style/#gradients */
15
+ background: var(--gradient-18);
16
+
17
+ /* https://open-props.style/#colors */
18
+ color: var(--gray-12);
19
+
20
+ }
@@ -0,0 +1,49 @@
1
+ /*
2
+ This file is auto-imported when using Tailwind CSS
3
+
4
+ Use @apply if you don't want to define style for each element separately
5
+
6
+ Read more at:
7
+ https://tailwindcss.com/docs/reusing-styles#extracting-classes-with-apply
8
+ https://tailwindcss.com/docs/functions-and-directives#apply
9
+
10
+ Use correct @layer to keep your Tailwind CSS organized:
11
+ https://bloggie.io/@kinopyo/organize-your-css-in-the-tailwind-style-with-layer-directive
12
+
13
+ And most importantly: THINK TWICE before using this approach!
14
+ */
15
+
16
+ @tailwind base;
17
+ @tailwind components;
18
+ @tailwind utilities;
19
+
20
+ @layer base {
21
+
22
+ /*
23
+ Tailwind CSS classes might be auto-applied to all respective elements like this.
24
+ However, I recommend NOT to use it like that, because this obsfucates code logic
25
+ and makes maintaing harder (even you should soon forgot styles are defined here)
26
+ */
27
+
28
+ /* NOTE: For setting "body" tag attributes, it is better to use useHead() composable - @see `app.vue`*/
29
+ /*
30
+ body {
31
+ @apply bg-slate-900;
32
+ }
33
+ */
34
+
35
+ }
36
+
37
+ @layer components {
38
+
39
+ /*
40
+ Following classes are available to use throughout the app as shorthands.
41
+ Although still discouraged by Tailwind CSS authors, this usage makes more
42
+ sense, because the class name is visible and trackable in SFC templates.
43
+ */
44
+
45
+ .link {
46
+ @apply my-2 text-amber-400 hover:text-amber-200;
47
+ }
48
+
49
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "title" : "Nuxt Ignis",
3
+ "subtitle" : "A ready-to-use setup for your next application in Nuxt",
4
+ "page2" : "This is a second page of the application",
5
+ "image" : "Image by Unsplash displayed with Nuxt Image",
6
+ "goto1" : "Go back to index",
7
+ "goto2" : "Go to 2nd page",
8
+ "features" : {
9
+ "nuxt" : "Nuxt application framework atop Vue.js",
10
+ "security" : "Nuxt Security module helping with OWASP patterns",
11
+ "image" : "NuxtImage to optimize use of images",
12
+ "pinia" : "Pinia for state management",
13
+ "vueuse" : "VueUse utils available",
14
+ "i18n" : "I18n for translations",
15
+ "consola" : "Logging with unjs/consola",
16
+ "ui" : "Nuxt UI as UI components library",
17
+ "tailwind" : "Tailwind CSS for styling",
18
+ "icon" : "Icon module for displaying icons",
19
+ "supabase" : "Supabase for auth and DB services",
20
+ "neon" : "Neon for DB services",
21
+ "formkit" : "FormKit for input forms",
22
+ "content" : "Nuxt Content for creating and editing content",
23
+ "openprops" : "Additional CSS by Open Props"
24
+ }
25
+ }
@@ -0,0 +1,62 @@
1
+ <!--
2
+ https://nuxt.com/docs/guide/directory-structure/components
3
+
4
+ Example of a reusable Nuxt component.
5
+
6
+ Any valid .vue file inside `/components` directory is auto-imported everywhere by Nuxt
7
+ => no need for explicit imports.
8
+
9
+ Modern Vue.js Composition API syntax is used for setup.
10
+ https://vuejs.org/api/sfc-script-setup.html
11
+
12
+ For usage go to `/pages/index.vue`
13
+
14
+ {{ text }}
15
+ - "double mustache" syntax is used to display JS expression inside HTML template
16
+ - notice you can call props directly in template section
17
+
18
+ Icon
19
+ - icons are provided by "nuxt-icon" module
20
+ - module is auto-imported via "@nuxt/ui"
21
+ - NUXT_PUBLIC_IGNIS_UI=nuxt-ui is required to use this feature
22
+ -->
23
+
24
+ <template>
25
+ <div class="m-1 px-2 py-1 border border-amber-300 font-bold text-lg text-feature hover:bg-slate-700">
26
+ {{ text }}
27
+ <div v-if="showIcon" style="display: inline;">
28
+ <Icon name="ic:sharp-add-reaction" style="color: yellow" />
29
+ </div>
30
+ </div>
31
+ </template>
32
+
33
+ <script setup lang="ts">
34
+ /**
35
+ * Macro `defineProps()` comes from Vue.js API
36
+ * (https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits).
37
+ * This is how to pass variables inside components in Vue.
38
+ *
39
+ * `required` - value has to be provided, otherwise an error will occur
40
+ *
41
+ * `default` - will have this value, unless overwritten by caller
42
+ */
43
+ const props = defineProps({
44
+ text: { type: String, required: true },
45
+ optionalText: { type: String, default: 'default' },
46
+ })
47
+
48
+ /**
49
+ * `Icon` component can only be used if `@nuxt/ui` module is activated.
50
+ */
51
+ const showIcon = useRuntimeConfig().public.ignis.ui === 'nuxt-ui'
52
+
53
+ // In setup section, you have to adress properties like this.
54
+ // You cannot reach them directly like in template.
55
+
56
+ // Note special inline `eslint` flag because normally you shouldn't use build-in console directly
57
+ // @see https://eslint.org/docs/latest/rules/no-console
58
+
59
+ /* eslint "no-console" : "off" */
60
+ log.debug(props.text)
61
+ log.debug(props.optionalText)
62
+ </script>
@@ -0,0 +1,30 @@
1
+ <!--
2
+ The `NuxtTime` component of `nuxt-time` module is a way how to deal with SSR in Nuxt.
3
+ Because page on the server is rendered miliseconds before being re-rendered on client,
4
+ wild "hydration error mismatch" may appear from a difference between displayed times.
5
+
6
+ This special component gracefully deals with the issue:
7
+ https://github.com/nuxt/nuxt/discussions/23278#discussioncomment-7607298
8
+ -->
9
+
10
+ <template>
11
+ <div>
12
+ Current time by
13
+ <span class="link">
14
+ <NuxtLink to="https://github.com/danielroe/nuxt-time">Nuxt Time</NuxtLink>
15
+ </span>:
16
+ <NuxtTime
17
+ :datetime="currentDate"
18
+ year="numeric"
19
+ month="2-digit"
20
+ day="2-digit"
21
+ hour="2-digit"
22
+ minute="2-digit"
23
+ second="2-digit"
24
+ />
25
+ </div>
26
+ </template>
27
+
28
+ <script setup lang="ts">
29
+ const currentDate = useNow()
30
+ </script>
@@ -0,0 +1,48 @@
1
+ import lang from '@/assets/lang/en.json'
2
+
3
+ /**
4
+ * An adapter above `t` function from `i18n` module.
5
+ *
6
+ * This function is basically a shorthand for obtaining `i18n` translation in scripts.
7
+ * In templates `$t` function is available, but in scripts we normally have to
8
+ * access the `t` function via the instance of `$i18n` living inside current Nuxt App.
9
+ *
10
+ * Since $i18n is an optional dependency in Nuxt Ignis setup, this also gracefully handles
11
+ * cases when user turns the module off while still using the translations in the code.
12
+ *
13
+ * @param key identifier of text that should be displayed
14
+ * @returns translated text from i18n sources
15
+ */
16
+ export function useT(key: string): string {
17
+ if (useRuntimeConfig().public.ignis.i18n) {
18
+ // i18n available => just use it
19
+ // @ts-ignore (in case i18n is turned off, type of $i18n cannot be infered)
20
+ return useNuxtApp().$i18n.t(key)
21
+ } else {
22
+ // backdoor for Nuxt Ignis to display values on demo index page
23
+ const backdoorValue = searchLang(key)
24
+ if (backdoorValue) {
25
+ return backdoorValue as string
26
+ }
27
+ // for other custom values a warning will be produced and a placeholder will be returned
28
+ log.warn('@nuxtjs/i18n turned off, translations are not available (set NUXT_PUBLIC_IGNIS_I18N=true)')
29
+ return "Translation not available"
30
+ }
31
+ }
32
+
33
+ /** AI-generated helper to search a value for given key in JSON lang file. */
34
+ function searchLang(key: string): unknown {
35
+ const keys = key.split(".")
36
+
37
+ let current = lang
38
+ for (const key of keys) {
39
+ if (current && key in current) {
40
+ // @ts-expect-error TODO this should be fixed
41
+ current = current[key]
42
+ } else {
43
+ return undefined
44
+ }
45
+ }
46
+
47
+ return current
48
+ }
@@ -0,0 +1,40 @@
1
+ import withNuxt from './.nuxt/eslint.config.mjs'
2
+
3
+ // config is being passed as an array of separate objects
4
+ // as suggested here: https://github.com/nuxt/eslint/discussions/413
5
+
6
+ export default withNuxt([
7
+
8
+ // files to be processed (JS/TS + Vue components)
9
+ { files: ['**/*.js', '**/*.ts', '**/*.vue'] },
10
+
11
+ // `rules` section can follow, where you can change default eslint behaviour if needed
12
+ // you can adjust or even turn off some rules if you cannot or don't want to satisfy them
13
+ {
14
+ rules: {
15
+ // the default for this rule is "1", but I find it too restrictive
16
+ // https://eslint.vuejs.org/rules/max-attributes-per-line.html
17
+ 'vue/max-attributes-per-line': ['error', {
18
+ singleline: {
19
+ max: 4,
20
+ },
21
+ multiline: {
22
+ max: 3,
23
+ },
24
+ }],
25
+ // the default rule forces newline after "else"
26
+ // I prefer using "} else {" on single row
27
+ 'vue/html-closing-bracket-newline': [
28
+ 'error',
29
+ {
30
+ multiline: 'never',
31
+ selfClosingTag: {
32
+ multiline: 'never',
33
+ },
34
+ },
35
+ ],
36
+ '@stylistic/brace-style': 'off',
37
+ },
38
+ },
39
+
40
+ ])
package/features.ts ADDED
@@ -0,0 +1,110 @@
1
+ import { defu } from 'defu'
2
+ import OpenProps from 'open-props'
3
+ import { log } from './utils/consola'
4
+
5
+ export function setFeatures() {
6
+ // list of optional extra features
7
+ const extras = [] as string[]
8
+
9
+ // object for optional config that will be merged with global Nuxt config
10
+ // declared in nuxt.config.ts
11
+ let nuxtConfig = {
12
+ modules: [] as string[],
13
+ }
14
+
15
+ // 1. default modules (mandatory)
16
+ nuxtConfig.modules.push(
17
+ 'nuxt-time',
18
+ 'nuxt-security',
19
+ '@nuxt/eslint',
20
+ '@nuxt/image',
21
+ '@pinia/nuxt',
22
+ '@vueuse/nuxt',
23
+ )
24
+
25
+ // 2. optional modules
26
+
27
+ // ui
28
+ const uiPreset = process.env.NUXT_PUBLIC_IGNIS_PRESET_UI
29
+ if (uiPreset === 'nuxt-ui' || (!uiPreset && process.env.NUXT_PUBLIC_IGNIS_UI === 'true')) {
30
+ nuxtConfig.modules.push('@nuxt/ui')
31
+ } else {
32
+ // remove @nuxt/ui-specific components from resolution if module is not used
33
+ nuxtConfig = defu({
34
+ vue: {
35
+ compilerOptions: {
36
+ isCustomElement: (tag: string) => tag === 'Icon',
37
+ },
38
+ },
39
+ }, nuxtConfig)
40
+
41
+ // evaluate separate Tailwind CSS module
42
+ if (uiPreset === 'tailwind' || (!uiPreset && process.env.NUXT_PUBLIC_IGNIS_TAILWIND === 'true')) {
43
+ nuxtConfig.modules.push('@nuxtjs/tailwindcss')
44
+ }
45
+ }
46
+
47
+ // database
48
+ const dbPreset = process.env.NUXT_PUBLIC_IGNIS_PRESET_DB
49
+ if (dbPreset === 'neon' || (!dbPreset && process.env.NUXT_PUBLIC_IGNIS_NEON === 'true')) {
50
+ // module definition
51
+ nuxtConfig.modules.push('nuxt-neon')
52
+ } else if (dbPreset === 'supabase' || (!dbPreset && process.env.NUXT_PUBLIC_IGNIS_SUPABASE === 'true')) {
53
+ // module definition
54
+ nuxtConfig.modules.push('@nuxtjs/supabase')
55
+ // module-specific config key
56
+ nuxtConfig = defu({
57
+ supabase: {
58
+ redirect: false, // https://github.com/supabase/supabase/issues/16551#issuecomment-1685300935
59
+ },
60
+ }, nuxtConfig)
61
+ }
62
+
63
+ // i18n
64
+ if (process.env.NUXT_PUBLIC_IGNIS_I18N === 'true') {
65
+ // module definition
66
+ nuxtConfig.modules.push('@nuxtjs/i18n')
67
+ // module-specific config key
68
+ nuxtConfig = defu({
69
+ i18n: {
70
+ vueI18n: './i18n.config.ts',
71
+ locales: ['en'],
72
+ defaultLocale: 'en',
73
+ },
74
+ }, nuxtConfig)
75
+ }
76
+
77
+ // formkit
78
+ if (process.env.NUXT_PUBLIC_IGNIS_FORMKIT === 'true') {
79
+ nuxtConfig.modules.push('@formkit/nuxt')
80
+ }
81
+
82
+ // content
83
+ if (process.env.NUXT_PUBLIC_IGNIS_CONTENT === 'true') {
84
+ nuxtConfig.modules.push('@nuxt/content')
85
+ }
86
+
87
+ // Open Props CSS
88
+ if (process.env.NUXT_PUBLIC_IGNIS_OPENPROPS === 'true') {
89
+ extras.push('Open Props CSS')
90
+ nuxtConfig = defu({
91
+ // import Open Prpops stylesheet
92
+ css: ['~/assets/css/open-props.css'],
93
+ // CSS processor for Open Props
94
+ postcss: {
95
+ plugins: {
96
+ 'postcss-jit-props': OpenProps,
97
+ },
98
+ },
99
+ }, nuxtConfig)
100
+ }
101
+
102
+ let overview = 'Nuxt Ignis will start using following settings:\n'
103
+ overview += 'Modules: ' + nuxtConfig.modules.join(', ') + '\n'
104
+ if (extras.length > 0) {
105
+ overview += 'Extras: ' + extras.join(', ') + '\n'
106
+ }
107
+ log.info(overview)
108
+
109
+ return nuxtConfig
110
+ }
@@ -0,0 +1,11 @@
1
+ // https://formkit.com/guides/optimizing-for-production#using-the-nuxt-module
2
+ import { en, de } from '@formkit/i18n'
3
+ import type { DefaultConfigOptions } from '@formkit/vue'
4
+
5
+ const config: DefaultConfigOptions = {
6
+ // example of importing different message translations and set the default one
7
+ locales: { en, de },
8
+ locale: 'en',
9
+ }
10
+
11
+ export default config
package/i18n.config.ts ADDED
@@ -0,0 +1,12 @@
1
+ import en from '@/assets/lang/en.json'
2
+ // create and load more language files if needed
3
+
4
+ export default defineI18nConfig(() => ({
5
+ legacy: false,
6
+ strategy: 'no_prefix',
7
+ locale: 'en',
8
+ defaultLocale: 'en',
9
+ fallbackLocale: 'en',
10
+ messages: { en }, // allowe more language if needed
11
+ warnHtmlMessage: false,
12
+ }))
package/nuxt.config.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { defu } from 'defu'
2
+ import { setFeatures } from './features'
3
+
4
+ const ignisFeatures = setFeatures()
5
+
6
+ // https://nuxt.com/docs/guide/directory-structure/nuxt-config
7
+ const nuxtConfig = defu(ignisFeatures, {
8
+
9
+ // https://nuxt.com/docs/api/nuxt-config#compatibilitydate
10
+ compatibilityDate: '2024-12-01',
11
+
12
+ // simple eslint config - see eslint.config.mjs
13
+ eslint: {
14
+ config: {
15
+ stylistic: true,
16
+ },
17
+ },
18
+
19
+ // app configuration
20
+ runtimeConfig: {
21
+ // nitro-only secret env-like variables go here
22
+ public: {
23
+ // client-exposed env-like variables go here
24
+
25
+ // features
26
+ // NOTE: due to static-like nature of nuxt.config.ts file
27
+ // actual values MUST BE provided via .env file (or production equivalent)
28
+ ignis: {
29
+ preset: {
30
+ ui: 'off', // nuxt-ui/tailwind/off
31
+ db: 'off', // neon/supabase/off
32
+ },
33
+ // individual modules
34
+ ui: false, // true/false
35
+ tailwind: false, // true/false (ignored, if ui=true)
36
+ neon: false, // true/false
37
+ supabase: false, // true/false
38
+ i18n: false, // true/false
39
+ formkit: false, // true/false
40
+ content: false, // true/false
41
+ openprops: false, // true/false
42
+ },
43
+
44
+ // logging
45
+ logLevel: 'info',
46
+ },
47
+ },
48
+ })
49
+
50
+ // https://nuxt.com/docs/getting-started/configuration#nuxt-configuration
51
+ // @ts-expect-error unknown object type
52
+ // TODO elaborate correct type for "nuxtConfig" object
53
+ export default defineNuxtConfig(nuxtConfig)
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "nuxt-ignis",
3
+ "version": "0.1.0",
4
+ "description": "Enhanced and customizable Nuxt application starter pack",
5
+ "repository": "github:AloisSeckar/nuxt-ignis",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "scripts": {
9
+ "analyze": "nuxt analyze",
10
+ "eslint": "eslint .",
11
+ "build": "nuxt build",
12
+ "dev": "nuxt dev",
13
+ "generate": "nuxt generate",
14
+ "preview": "nuxt preview",
15
+ "start": "nuxt start"
16
+ },
17
+ "dependencies": {
18
+ "@formkit/nuxt": "1.6.9",
19
+ "@nuxt/content": "2.13.4",
20
+ "@nuxt/eslint": "0.7.2",
21
+ "@nuxt/image": "1.8.1",
22
+ "@nuxt/ui": "2.19.2",
23
+ "@nuxtjs/i18n": "9.1.0",
24
+ "@nuxtjs/supabase": "1.4.3",
25
+ "@nuxtjs/tailwindcss": "6.12.2",
26
+ "@pinia/nuxt": "0.8.0",
27
+ "@vueuse/core": "12.0.0",
28
+ "@vueuse/nuxt": "12.0.0",
29
+ "consola": "3.2.3",
30
+ "defu": "6.1.4",
31
+ "nuxt": "3.14.1592",
32
+ "nuxt-neon": "0.1.2",
33
+ "nuxt-security": "2.1.5",
34
+ "nuxt-time": "1.0.3",
35
+ "open-props": "1.7.7",
36
+ "pinia": "2.2.8",
37
+ "postcss-jit-props": "1.0.14",
38
+ "typescript": "5.7.2",
39
+ "vue": "latest",
40
+ "vue-router": "latest"
41
+ },
42
+ "packageManager": "pnpm@9.14.4"
43
+ }
@@ -0,0 +1,51 @@
1
+ <!--
2
+ https://nuxt.com/docs/guide/directory-structure/pages
3
+
4
+ An example of Nuxt page.
5
+ Nuxt automaticaly provides routing and displays this page when user visits root URL or `/index`.
6
+
7
+ AppFeature
8
+ - an example usage of auto-imported Nuxt component declared in `/components` directory
9
+ - the text is (usually) being loaded localized via nuxtjs/i18n module
10
+ - features are being displayed conditionally according to current setting
11
+
12
+ NuxtLink
13
+ - special component for improved handling for HTML links (<a> tags)
14
+ -->
15
+
16
+ <template>
17
+ <div>
18
+ <div class="m-auto my-4 w-3/5 flex flex-col">
19
+ <AppFeature :text="useT('features.nuxt')" />
20
+ <AppFeature :text="useT('features.security')" />
21
+ <AppFeature :text="useT('features.image')" />
22
+ <AppFeature :text="useT('features.pinia')" />
23
+ <AppFeature :text="useT('features.vueuse')" />
24
+ <AppFeature :text="useT('features.consola')" />
25
+ <AppFeature v-if="ui === 'nuxt-ui'" :text="useT('features.ui')" />
26
+ <AppFeature v-if="ui !== 'off'" :text="useT('features.tailwind')" />
27
+ <AppFeature v-if="ui === 'nuxt-ui'" :text="useT('features.icon')" />
28
+ <AppFeature v-if="db === 'supabase'" :text="useT('features.supabase')" />
29
+ <AppFeature v-if="db === 'neon'" :text="useT('features.neon')" />
30
+ <AppFeature v-if="i18n" :text="useT('features.i18n')" />
31
+ <AppFeature v-if="formkit" :text="useT('features.formkit')" />
32
+ <AppFeature v-if="content" :text="useT('features.content')" />
33
+ <AppFeature v-if="openprops" class="openprops-feature" :text="useT('features.openprops')" />
34
+ </div>
35
+ <div class="link">
36
+ <NuxtLink to="/second">
37
+ {{ useT("goto2") }}
38
+ </NuxtLink>
39
+ </div>
40
+ </div>
41
+ </template>
42
+
43
+ <script setup lang="ts">
44
+ const setup = useRuntimeConfig().public.ignis
45
+ const ui = setup.ui
46
+ const db = setup.db
47
+ const i18n = setup.i18n
48
+ const formkit = setup.formkit
49
+ const content = setup.content
50
+ const openprops = setup.openprops
51
+ </script>
@@ -0,0 +1,27 @@
1
+ <!--
2
+ https://nuxt.com/docs/guide/directory-structure/pages
3
+
4
+ This page appears under URL `/second`
5
+
6
+ NuxtImg
7
+ - special component for displaying images with NuxtImage module
8
+ -->
9
+
10
+ <template>
11
+ <div>
12
+ <div class="m-4">
13
+ {{ useT("page2") }}
14
+ </div>
15
+ <div class="m-4 flex flex-col items-center">
16
+ <NuxtImg src="/unsplash.jpg" width="400" />
17
+ <div class="text-xs">
18
+ {{ useT("image") }}
19
+ </div>
20
+ </div>
21
+ <div class="link">
22
+ <NuxtLink to="/">
23
+ {{ useT("goto1") }}
24
+ </NuxtLink>
25
+ </div>
26
+ </div>
27
+ </template>
Binary file
Binary file
Binary file
@@ -0,0 +1,28 @@
1
+ // https://tailwindcss.com/docs/configuration
2
+ // https://tailwindcss.com/docs/plugins
3
+
4
+ import plugin from 'tailwindcss/plugin'
5
+
6
+ module.exports = {
7
+ content: [
8
+ './src/**/*.{html,js,vue}',
9
+ ],
10
+ theme: {
11
+ // example of extending Tailwind CSS with custom color
12
+ extend: {
13
+ colors: {
14
+ feature: '#3CB371',
15
+ },
16
+ },
17
+ },
18
+ plugins: [
19
+ // example how to enable custom color class inside @apply directive
20
+ plugin(function ({ addComponents, theme }) {
21
+ addComponents({
22
+ '.text-feature': {
23
+ color: theme('colors.feature'),
24
+ },
25
+ })
26
+ }),
27
+ ],
28
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ // https://nuxt.com/docs/guide/directory-structure/tsconfig
2
+ {
3
+ "extends": "./.nuxt/tsconfig.json",
4
+ "compilerOptions": {
5
+ "noUncheckedIndexedAccess": true
6
+ }
7
+ }
@@ -0,0 +1,92 @@
1
+ // centralized logging system
2
+ // https://github.com/unjs/consola
3
+
4
+ import type { LogLevel, LogObject } from 'consola/core'
5
+ import { LogLevels, createConsola } from 'consola/core'
6
+ import { consola } from 'consola'
7
+ import { useDateFormat } from '@vueuse/core'
8
+
9
+ // default instance to write into browser's console
10
+ const defaultReporter = consola
11
+
12
+ /**
13
+ * Logging object that is available across the app thanks to Nuxt auto-imports.
14
+ */
15
+ export const log = createConsola({
16
+ level: LogLevels.debug,
17
+ reporters: [
18
+ {
19
+ log: (logObj) => {
20
+ // enhancing log with more info (i.e. time + relevant stack for warn/error)
21
+ const msg = transformLog(logObj)
22
+ // logs are being written into browser's console
23
+ // `logObj.type` = log level name = corresponing method on the logger (debug, info, warn, error, etc.)
24
+ defaultReporter[logObj.type](msg)
25
+ },
26
+ },
27
+ ],
28
+ })
29
+
30
+ /**
31
+ * Initialize logger functions.
32
+ * Called in app.vue's setup.
33
+ */
34
+ export async function initConsola() {
35
+ // set default log level from config
36
+ const logLevel = useRuntimeConfig().public.logLevel
37
+ log.level = getLogLevel(logLevel)
38
+ defaultReporter.level = log.level
39
+ log.debug(`[consola] log level set to '${logLevel}'`)
40
+ }
41
+
42
+ /**
43
+ * Transform text values of logLevel
44
+ * to numeric equivalent used by consola.
45
+ */
46
+ function getLogLevel(logLevel: string): LogLevel {
47
+ const logLevelString = logLevel.toLocaleLowerCase()
48
+ switch (logLevelString) {
49
+ case 'fatal': return LogLevels.fatal
50
+ case 'error': return LogLevels.error
51
+ case 'warn': return LogLevels.warn
52
+ case 'log': return LogLevels.log
53
+ case 'info': return LogLevels.info
54
+ case 'success': return LogLevels.success
55
+ case 'debug': return LogLevels.debug
56
+ case 'trace': return LogLevels.trace
57
+ case 'silent': return LogLevels.silent
58
+ case 'verbose': return LogLevels.verbose
59
+ default: log.fatal(`Invalid log level ${logLevel}`)
60
+ throw new Error(`Invalid log level ${logLevel}`)
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Enhance received log object, which should be logged.
66
+ * Add current date+time and trim irrelevant call-stack for warn/errors.
67
+ * @param logObj Object to be logged
68
+ * @returns Enhanced object to be logged
69
+ */
70
+ function transformLog(logObj: LogObject): string {
71
+ let logBody = logObj.args[0]
72
+ if (typeof logBody !== 'string') {
73
+ logBody = JSON.stringify(logBody)
74
+ }
75
+
76
+ const timestamp = useDateFormat(new Date(), 'YYYY-MM-DD HH:mm:ss.SSS').value
77
+ logBody = timestamp + '\n' + logBody
78
+
79
+ if (logObj.level <= LogLevels.warn) {
80
+ const fullStack = new Error(logBody).stack
81
+ const filteredStack = fullStack?.split('\n at ').filter(x => !x.includes('node_modules'))
82
+ const relevantStack = filteredStack?.slice(0, filteredStack.length - 3)
83
+ if (relevantStack?.length && logObj.level <= LogLevels.warn) {
84
+ if (logObj.level === LogLevels.warn) {
85
+ relevantStack[0] = relevantStack[0]!.replace('Error:', 'Warn:')
86
+ }
87
+ logBody = timestamp + '\n' + relevantStack.join('\n\tat ')
88
+ }
89
+ }
90
+
91
+ return logBody
92
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Available variants for UI libraries.
3
+ *
4
+ * `nuxt-ui` - full https://ui.nuxt.com/ via `@nuxt/ui` connector module **[RECOMMENDED]**
5
+ *
6
+ * `tailwind` - only https://tailwindcss.com/ via `@nuxtjs/tailwindcss` connector module
7
+ *
8
+ * `off` - no UI library **[DEFAULT]**
9
+ */
10
+ export type UIOptions = 'nuxt-ui' | 'tailwind' | 'off'
11
+
12
+ /**
13
+ * Available variants for Database connector.
14
+ *
15
+ * `neon` - https://neon.tech/ via `nuxt-neon` connector module **[RECOMMENDED]**
16
+ *
17
+ * `supabase` - https://supabase.com/ via `@nuxtjs/supabase` connector module
18
+ *
19
+ * `off` - no database module **[DEFAULT]**
20
+ */
21
+ export type DBOptions = 'neon' | 'supabase' | 'off'