nextia 6.1.1 → 7.0.2

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.
Files changed (76) hide show
  1. package/README.md +1 -1
  2. package/biome.json +21 -0
  3. package/package.json +13 -8
  4. package/src/bin.js +239 -0
  5. package/src/{lib.js → lib/fx.js} +18 -33
  6. package/src/lib/index.js +32 -0
  7. package/src/lib/ui.js +112 -0
  8. package/src/lib/utils.js +107 -0
  9. package/template/README.md +29 -0
  10. package/template/_env.dev +4 -0
  11. package/template/_env.prod +1 -0
  12. package/template/_env.test +1 -0
  13. package/template/_gitignore +10 -0
  14. package/template/biome.json +21 -0
  15. package/template/package.json +35 -0
  16. package/template/public/error.html +14 -0
  17. package/template/public/logo.svg +105 -0
  18. package/template/src/assets/i18n/index.js +26 -0
  19. package/template/src/assets/img/image.svg +6 -0
  20. package/template/src/assets/img/image.webp +0 -0
  21. package/template/src/components/Counter/index.jsx +33 -0
  22. package/template/src/components/Counter/style.css +5 -0
  23. package/template/src/components/Message/index.jsx +12 -0
  24. package/template/src/components/index.js +6 -0
  25. package/template/src/components/ui/Translate/index.jsx +20 -0
  26. package/template/src/index.html +18 -0
  27. package/template/src/index.jsx +4 -0
  28. package/template/src/pages/env/functions.js +3 -0
  29. package/template/src/pages/env/index.jsx +26 -0
  30. package/template/src/pages/env/style.css +2 -0
  31. package/template/src/pages/functions.js +37 -0
  32. package/template/src/pages/home/functions.js +43 -0
  33. package/template/src/pages/home/index.jsx +211 -0
  34. package/template/src/pages/home/style.css +51 -0
  35. package/template/src/pages/http/not-found/index.jsx +10 -0
  36. package/template/src/pages/http/not-found/style.css +2 -0
  37. package/template/src/pages/icons/functions.js +3 -0
  38. package/template/src/pages/icons/index.jsx +21 -0
  39. package/template/src/pages/icons/style.css +8 -0
  40. package/template/src/pages/images/functions.js +3 -0
  41. package/template/src/pages/images/index.jsx +25 -0
  42. package/template/src/pages/images/style.css +27 -0
  43. package/template/src/pages/index.jsx +124 -0
  44. package/template/src/pages/mockapi/functions.js +71 -0
  45. package/template/src/pages/mockapi/index.jsx +101 -0
  46. package/template/src/pages/mockapi/style.css +57 -0
  47. package/template/src/pages/my-context/functions.js +7 -0
  48. package/template/src/pages/my-context/index.jsx +32 -0
  49. package/template/src/pages/my-context/style.css +2 -0
  50. package/template/src/pages/resize/functions.js +3 -0
  51. package/template/src/pages/resize/index.jsx +15 -0
  52. package/template/src/pages/resize/style.css +2 -0
  53. package/template/src/pages/search-params/functions.js +3 -0
  54. package/template/src/pages/search-params/index.jsx +35 -0
  55. package/template/src/pages/search-params/style.css +2 -0
  56. package/template/src/pages/subpage/hello/functions.js +3 -0
  57. package/template/src/pages/subpage/hello/index.jsx +11 -0
  58. package/template/src/pages/subpage/hello/style.css +2 -0
  59. package/template/src/pages/translate/functions.js +5 -0
  60. package/template/src/pages/translate/index.jsx +31 -0
  61. package/template/src/pages/translate/style.css +12 -0
  62. package/template/src/pages/view-transition/functions.js +6 -0
  63. package/template/src/pages/view-transition/index.jsx +30 -0
  64. package/template/src/pages/view-transition/style.css +2 -0
  65. package/template/src/services/api.js +9 -0
  66. package/template/src/services/http.js +40 -0
  67. package/template/src/theme/fonts/Roboto-Regular.ttf +0 -0
  68. package/template/src/theme/fonts/index.css +7 -0
  69. package/template/src/theme/icons/icons.svg +125 -0
  70. package/template/src/theme/icons/index.css +55 -0
  71. package/template/src/theme/index.css +39 -0
  72. package/template/src/theme/utils/index.css +29 -0
  73. package/template/src/theme/utils/view-transition.css +72 -0
  74. package/template/src/utils/index.js +5 -0
  75. package/template/test/index.test.js +11 -0
  76. package/template/vite.config.js +97 -0
package/README.md CHANGED
@@ -6,7 +6,7 @@ Create fast web applications
6
6
 
7
7
  ```sh
8
8
  npm install
9
- cd test-webapp
9
+ cd template
10
10
  npm install
11
11
  node --run dev
12
12
  ```
package/biome.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "formatter": {
3
+ "indentStyle": "space",
4
+ "indentWidth": 2
5
+ },
6
+
7
+ "javascript": {
8
+ "formatter": {
9
+ "semicolons": "asNeeded",
10
+ "quoteStyle": "single",
11
+ "jsxQuoteStyle": "double",
12
+ "trailingCommas": "none"
13
+ }
14
+ },
15
+
16
+ "css": {
17
+ "parser": {
18
+ "tailwindDirectives": true
19
+ }
20
+ }
21
+ }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "nextia",
3
3
  "description": "Create fast web applications",
4
- "version": "6.1.1",
4
+ "version": "7.0.2",
5
+ "license": "MIT",
6
+ "type": "module",
5
7
  "engines": {
6
8
  "node": ">22"
7
9
  },
8
- "type": "module",
9
- "license": "MIT",
10
10
  "author": {
11
11
  "name": "Sinuhe Maceda",
12
12
  "email": "sinuhe.dev@gmail.com",
@@ -20,9 +20,14 @@
20
20
  "keywords": [
21
21
  "react"
22
22
  ],
23
- "main": "src/lib.js",
23
+ "bin": {
24
+ "nextia": "src/bin.js"
25
+ },
26
+ "exports": {
27
+ ".": "./src/lib/index.js"
28
+ },
24
29
  "scripts": {
25
- "clean": "rm -fr node_modules package-lock.json pnpm-lock.yaml .coverage target",
30
+ "clean": "rm -fr my-app node_modules package-lock.json .coverage out",
26
31
  "format": "biome format",
27
32
  "lint": "biome lint src",
28
33
  "check": "biome check --reporter=summary src",
@@ -37,8 +42,8 @@
37
42
  },
38
43
  "devDependencies": {
39
44
  "@vitejs/plugin-react": "^6.0.1",
40
- "@vitest/coverage-v8": "^4.0.18",
41
- "jsdom": "^29.0.0",
42
- "vitest": "^4.0.18"
45
+ "@vitest/coverage-v8": "^4.1.2",
46
+ "jsdom": "^29.0.1",
47
+ "vitest": "^4.1.2"
43
48
  }
44
49
  }
package/src/bin.js ADDED
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Copyright (c) 2025 Sinuhe Maceda https://sinuhe.dev
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ *
9
+ * https://github.com/sinuhedev/create-nextia
10
+ */
11
+
12
+ import {
13
+ access,
14
+ cp,
15
+ mkdir,
16
+ readFile,
17
+ rename,
18
+ writeFile
19
+ } from 'node:fs/promises'
20
+ import { dirname } from 'node:path'
21
+ import { fileURLToPath } from 'node:url'
22
+ import pkg from '../package.json' with { type: 'json' }
23
+
24
+ function toPascalCase(str) {
25
+ return str
26
+ .toLowerCase()
27
+ .replace(/[^a-zA-Z0-9 ]/g, ' ') // replace special characters
28
+ .split(/\s+/) // split by spaces
29
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
30
+ .join('')
31
+ }
32
+
33
+ async function createPage(name) {
34
+ const dirName = `./src/pages/${name}`
35
+ const pageName = `${toPascalCase(name)}Page`
36
+
37
+ try {
38
+ await mkdir(dirName)
39
+
40
+ // index.jsx
41
+ writeFile(
42
+ `${dirName}/index.jsx`,
43
+ `import { css, useFx } from 'nextia'
44
+ import { useEffect } from 'react'
45
+ import functions from './functions'
46
+ import './style.css'
47
+
48
+ export default function ${pageName} () {
49
+ const { state, fx } = useFx(functions)
50
+
51
+ return (
52
+ <section className={css('${pageName}', '')}>
53
+ ${pageName}
54
+ </section>
55
+ )
56
+ }
57
+ `
58
+ )
59
+
60
+ // style.sss
61
+ writeFile(
62
+ `${dirName}/style.css`,
63
+ `.${pageName} {
64
+ }
65
+ `
66
+ )
67
+
68
+ // function.js
69
+ writeFile(
70
+ `${dirName}/functions.js`,
71
+ `const initialState = {}
72
+
73
+ export default { initialState }
74
+ `
75
+ )
76
+ console.info(`✔ Page "${pageName}" created at ${dirName}`)
77
+ } catch (err) {
78
+ console.error(`Failed to create page: ${err.message}`)
79
+ }
80
+ }
81
+
82
+ async function createComponent(name) {
83
+ const dirName = `./src/components/${name}`
84
+
85
+ try {
86
+ await mkdir(dirName)
87
+ const componentName = toPascalCase(name)
88
+
89
+ // index.jsx
90
+ writeFile(
91
+ `${dirName}/index.jsx`,
92
+ `import { css } from 'nextia'
93
+ import { useEffect } from 'react'
94
+ import './style.css'
95
+
96
+ export default function ${componentName} ({ className, style }) {
97
+ return (
98
+ <article className={css('${componentName}', className)} style={style}>
99
+ ${componentName}
100
+ </article>
101
+ )
102
+ }
103
+ `
104
+ )
105
+
106
+ // style.css
107
+ writeFile(
108
+ `${dirName}/style.css`,
109
+ `.${componentName} {
110
+ }
111
+ `
112
+ )
113
+ console.info(`✔ Component "${name}" created at ${dirName}`)
114
+ } catch (err) {
115
+ console.error(`Failed to create component: ${err.message}`)
116
+ }
117
+ }
118
+
119
+ async function createContainer(name) {
120
+ const dirName = `./src/components/${name}`
121
+
122
+ try {
123
+ await mkdir(dirName)
124
+ const containerName = toPascalCase(name)
125
+
126
+ // index.jsx
127
+ writeFile(
128
+ `${dirName}/index.jsx`,
129
+ `import { useEffect } from 'react'
130
+ import { css, useFx } from 'nextia'
131
+ import functions from './functions'
132
+ import './style.css'
133
+
134
+ export default function ${containerName} ({ className, style }) {
135
+ const { state, fx } = useFx(functions)
136
+
137
+ return (
138
+ <article className={css('${containerName}', className, '')} style={style}>
139
+ ${containerName}
140
+ </article>
141
+ )
142
+ }
143
+ `
144
+ )
145
+
146
+ // style.css
147
+ writeFile(
148
+ `${dirName}/style.css`,
149
+ `.${containerName} {
150
+ }
151
+ `
152
+ )
153
+
154
+ // function.js
155
+ writeFile(
156
+ `${dirName}/functions.js`,
157
+ `const initialState = {}
158
+
159
+ export default { initialState }
160
+ `
161
+ )
162
+ console.info(`✔ Container "${name}" created at ${dirName}`)
163
+ } catch (err) {
164
+ console.error(`Failed to create container: ${err.message}`)
165
+ }
166
+ }
167
+
168
+ async function createProject(name) {
169
+ const projectPath = `${process.cwd()}/${name}/`
170
+
171
+ try {
172
+ await access(projectPath)
173
+ console.error(`The ${name} already exists`)
174
+ return
175
+ } catch {
176
+ /* directory doesn't exist, proceed */
177
+ }
178
+
179
+ try {
180
+ const __dirname = dirname(fileURLToPath(import.meta.url))
181
+ const templatePath = `${__dirname}/../template`
182
+
183
+ const replaceToken = async (filename, token, value) => {
184
+ const content = await readFile(projectPath + filename, 'utf8')
185
+ await writeFile(
186
+ projectPath + filename,
187
+ content.replaceAll(token, value),
188
+ 'utf8'
189
+ )
190
+ }
191
+
192
+ await cp(templatePath, projectPath, { recursive: true })
193
+
194
+ await Promise.all(
195
+ ['env.dev', 'env.prod', 'env.test', 'gitignore'].map((fileName) =>
196
+ rename(`${projectPath}_${fileName}`, `${projectPath}.${fileName}`)
197
+ )
198
+ )
199
+
200
+ await replaceToken('README.md', 'TEMPLATE', name)
201
+ await replaceToken('package.json', 'TEMPLATE', name)
202
+ await replaceToken('package.json', 'file:../', pkg.version)
203
+
204
+ console.info(`✔ Project "${name}" created successfully!`)
205
+ } catch (err) {
206
+ console.error(`Failed to create project: ${err.message}`)
207
+ }
208
+ }
209
+
210
+ async function main() {
211
+ const ARG1 = process.argv[2]
212
+ const ARG2 = process.argv[3]
213
+
214
+ switch (ARG1) {
215
+ case 'page':
216
+ if (ARG2) await createPage(ARG2)
217
+ else console.warn('node --run nextia page <page-name>')
218
+ break
219
+
220
+ case 'component':
221
+ if (ARG2) await createComponent(ARG2)
222
+ else console.warn('node --run nextia component <ComponentName>')
223
+ break
224
+
225
+ case 'container':
226
+ if (ARG2) await createContainer(ARG2)
227
+ else console.warn('node --run nextia container <ContainerName>')
228
+ break
229
+
230
+ default:
231
+ if (ARG1) await createProject(ARG1)
232
+ else console.info(`v${pkg.version}\nnextia <ProjectName>`)
233
+ break
234
+ }
235
+ }
236
+
237
+ main().catch((e) => {
238
+ console.error(e)
239
+ })
@@ -9,32 +9,13 @@
9
9
 
10
10
  import { createContext, use, useReducer } from 'react'
11
11
 
12
- const PagesContext = createContext()
13
- const isLogger = import.meta.env.DEV
12
+ const LOGGER = import.meta.env.DEV && import.meta.env.PUBLIC_LOGGER !== 'false'
13
+ const PagesFx = createContext()
14
14
 
15
15
  /**
16
16
  * util
17
17
  */
18
18
 
19
- function css(...classNames) {
20
- return classNames
21
- .reduce((accumulator, currentValue) => {
22
- if (typeof currentValue === 'string') {
23
- accumulator.push(currentValue.trim())
24
- } else if (
25
- !Array.isArray(currentValue) &&
26
- typeof currentValue === 'object'
27
- ) {
28
- for (const e in currentValue) {
29
- if (currentValue[e]) accumulator.push(e.trim())
30
- }
31
- }
32
- return accumulator
33
- }, [])
34
- .filter((e) => e)
35
- .join(' ')
36
- }
37
-
38
19
  function values(state, payload, value) {
39
20
  const paths = payload.split('.')
40
21
 
@@ -166,10 +147,10 @@ const reducerLogger = (state, action) => {
166
147
  */
167
148
 
168
149
  function useFx(functions = { initialState: {} }) {
169
- const pageContext = use(PagesContext)
150
+ const pagesFx = use(PagesFx)
170
151
  const { initialState } = functions
171
152
  const [state, dispatch] = useReducer(
172
- isLogger ? reducerLogger : reducer,
153
+ LOGGER ? reducerLogger : reducer,
173
154
  initialState
174
155
  )
175
156
 
@@ -177,7 +158,12 @@ function useFx(functions = { initialState: {} }) {
177
158
  const commonActions = ['set', 'show', 'hide', 'change', 'reset'].reduce(
178
159
  (acc, e) => {
179
160
  acc[e] = (payload) =>
180
- dispatch({ type: e, payload, initialState, isContext: !pageContext })
161
+ dispatch({
162
+ type: e,
163
+ payload,
164
+ initialState,
165
+ isContext: !pagesFx?.context
166
+ })
181
167
  return acc
182
168
  },
183
169
  {}
@@ -190,10 +176,8 @@ function useFx(functions = { initialState: {} }) {
190
176
  const actionsProps = {
191
177
  ...commonActions,
192
178
  state,
193
- payload
194
- }
195
- if (pageContext) {
196
- actionsProps.context = pageContext
179
+ payload,
180
+ context: pagesFx?.context
197
181
  }
198
182
 
199
183
  return functions[e](Object.freeze(actionsProps))
@@ -206,13 +190,14 @@ function useFx(functions = { initialState: {} }) {
206
190
  const props = {
207
191
  initialState,
208
192
  state,
209
- fx: { ...commonActions, ...actions }
210
- }
211
- if (pageContext) {
212
- props.context = pageContext
193
+ fx: { ...commonActions, ...actions },
194
+ //
195
+ context: pagesFx?.context,
196
+ icons: pagesFx?.icons,
197
+ i18n: pagesFx?.i18n
213
198
  }
214
199
 
215
200
  return Object.freeze(props)
216
201
  }
217
202
 
218
- export { css, PagesContext, useFx }
203
+ export { PagesFx, useFx }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Copyright (c) 2025 Sinuhe Maceda https://sinuhe.dev
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * https://github.com/sinuhedev/nextia
8
+ */
9
+
10
+ import { PagesFx, useFx } from './fx.js'
11
+ import { I18n, Icon, Link, Svg } from './ui.js'
12
+ import {
13
+ css,
14
+ env,
15
+ startViewTransition,
16
+ useQueryString,
17
+ useResize
18
+ } from './utils.js'
19
+
20
+ export {
21
+ css,
22
+ env,
23
+ I18n,
24
+ Icon,
25
+ Link,
26
+ PagesFx,
27
+ Svg,
28
+ startViewTransition,
29
+ useFx,
30
+ useQueryString,
31
+ useResize
32
+ }
package/src/lib/ui.js ADDED
@@ -0,0 +1,112 @@
1
+ import { createElement, useEffect, useRef } from 'react'
2
+ import { useFx } from './fx'
3
+ import { css } from './utils'
4
+
5
+ function Link({ children, href, value = {}, ...props }) {
6
+ href ??= window.location.hash.split('?')[0]
7
+ value = Object.keys(value).length
8
+ ? `?${new URLSearchParams(value).toString()}`
9
+ : ''
10
+
11
+ return createElement('a', { href: href + value, ...props }, children)
12
+ }
13
+
14
+ function I18n({ value, args = [] }) {
15
+ const { context, i18n } = useFx()
16
+
17
+ if (i18n) {
18
+ try {
19
+ let text = value.split('.').reduce((ac, el) => ac[el], i18n)
20
+ text = text[i18n.locales.indexOf(context.state.i18n.currentLocale)]
21
+
22
+ if (args) {
23
+ text = text.replace(
24
+ /([{}])\\1|[{](.*?)(?:!(.+?))?[}]/g,
25
+ (match, _literal, number) => args[number] || match
26
+ )
27
+ }
28
+
29
+ return text
30
+ } catch {
31
+ console.error(`Error in [il8n] => ${value}`)
32
+ return value
33
+ }
34
+ }
35
+ }
36
+
37
+ function Icon({
38
+ id,
39
+ className,
40
+ animate = false,
41
+ style,
42
+ width = '48',
43
+ height,
44
+ viewBox = '0 0 48 48',
45
+ fill = 'none',
46
+ color = 'currentColor',
47
+ stroke = 'currentColor',
48
+ strokeWidth = '2',
49
+ strokeLinecap = 'round',
50
+ strokeLinejoin = 'round',
51
+ ...props
52
+ }) {
53
+ const { icons } = useFx()
54
+ const ref = useRef()
55
+
56
+ useEffect(() => {
57
+ if (icons) {
58
+ const svg = new DOMParser()
59
+ .parseFromString(icons, 'image/svg+xml')
60
+ .documentElement.getElementById(id)
61
+
62
+ if (svg) {
63
+ ref.current.innerHTML = svg.innerHTML
64
+ }
65
+ }
66
+ }, [id, icons])
67
+
68
+ return createElement('svg', {
69
+ xmlns: 'http://www.w3.org/2000/svg',
70
+ ref,
71
+ id,
72
+ className: css({ 'nextia-animate-icon': animate }, className),
73
+ style,
74
+ width,
75
+ height: height ?? width,
76
+ viewBox,
77
+ fill,
78
+ color,
79
+ stroke,
80
+ strokeWidth,
81
+ strokeLinecap,
82
+ strokeLinejoin,
83
+ ...props
84
+ })
85
+ }
86
+
87
+ function Svg({ ref, src, width, height, ...props }) {
88
+ ref ??= useRef()
89
+
90
+ useEffect(() => {
91
+ const svg = new DOMParser().parseFromString(
92
+ src,
93
+ 'image/svg+xml'
94
+ ).documentElement
95
+
96
+ for (const { name, value } of svg.attributes) {
97
+ if (name !== 'width' && name !== 'height')
98
+ ref.current.setAttribute(name, value)
99
+ }
100
+
101
+ ref.current.replaceChildren(...svg.children)
102
+ }, [src, ref])
103
+
104
+ return createElement('svg', {
105
+ ref,
106
+ width,
107
+ height: height ?? width,
108
+ ...props
109
+ })
110
+ }
111
+
112
+ export { I18n, Icon, Link, Svg }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Copyright (c) 2025 Sinuhe Maceda https://sinuhe.dev
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * https://github.com/sinuhedev/nextia
8
+ */
9
+
10
+ import { useCallback, useEffect, useState } from 'react'
11
+ import { flushSync } from 'react-dom'
12
+
13
+ /**
14
+ * env
15
+ */
16
+
17
+ const env = import.meta.env
18
+
19
+ /**
20
+ * View Transition
21
+ */
22
+
23
+ async function startViewTransition(fun = () => {}, ref, animation = 'fade') {
24
+ if (!document.startViewTransition || env.PUBLIC_VIEW_TRANSITION === 'false')
25
+ return fun()
26
+
27
+ ref.style.viewTransitionName = animation
28
+ await document.startViewTransition(() => flushSync(fun)).finished
29
+ ref.style.viewTransitionName = ''
30
+ }
31
+
32
+ /**
33
+ * hooks
34
+ */
35
+
36
+ function useQueryString() {
37
+ const getQueryString = useCallback(
38
+ () => ({
39
+ hash: window.location.hash.split('?')[0],
40
+ queryString: Object.fromEntries(
41
+ new URLSearchParams(window.location.hash.split('?')[1])
42
+ )
43
+ }),
44
+ []
45
+ )
46
+
47
+ const [queryString, setQueryString] = useState(getQueryString)
48
+
49
+ useEffect(() => {
50
+ const handlePopState = () => setQueryString(getQueryString())
51
+
52
+ window.addEventListener('popstate', handlePopState)
53
+ return () => {
54
+ window.removeEventListener('popstate', handlePopState)
55
+ }
56
+ }, [getQueryString])
57
+
58
+ return queryString
59
+ }
60
+
61
+ function useResize() {
62
+ const getResize = useCallback(
63
+ () => ({
64
+ width: window.innerWidth,
65
+ height: window.innerHeight
66
+ }),
67
+ []
68
+ )
69
+
70
+ const [resize, setResize] = useState(getResize)
71
+
72
+ useEffect(() => {
73
+ const handleResize = () => setResize(getResize())
74
+
75
+ window.addEventListener('resize', handleResize)
76
+ return () => {
77
+ window.removeEventListener('resize', handleResize)
78
+ }
79
+ }, [getResize])
80
+
81
+ return resize
82
+ }
83
+
84
+ /**
85
+ * util
86
+ */
87
+
88
+ function css(...classNames) {
89
+ return classNames
90
+ .reduce((accumulator, currentValue) => {
91
+ if (typeof currentValue === 'string') {
92
+ accumulator.push(currentValue.trim())
93
+ } else if (
94
+ !Array.isArray(currentValue) &&
95
+ typeof currentValue === 'object'
96
+ ) {
97
+ for (const e in currentValue) {
98
+ if (currentValue[e]) accumulator.push(e.trim())
99
+ }
100
+ }
101
+ return accumulator
102
+ }, [])
103
+ .filter((e) => e)
104
+ .join(' ')
105
+ }
106
+
107
+ export { css, env, startViewTransition, useQueryString, useResize }
@@ -0,0 +1,29 @@
1
+ # TEMPLATE
2
+
3
+ # To start
4
+ Open http://localhost:3000 to view it in the browser.
5
+
6
+ ```sh
7
+ npm install
8
+ #
9
+ node --run dev
10
+ node --run test
11
+ node --run build <ENV>
12
+ node --run preview
13
+ ```
14
+
15
+ # env
16
+ ```.env
17
+ .env # loaded in all cases
18
+ .env.[ENV] # only loaded in specified ENV [ dev, test, prod ]
19
+ ```
20
+
21
+ * .env.dev
22
+ * .env.prod
23
+ * .env.test
24
+
25
+ ```env
26
+ PUBLIC_TITLE=TITLE
27
+ PUBLIC_LOGGER=true
28
+ PUBLIC_VIEW_TRANSITION=true
29
+ ```
@@ -0,0 +1,4 @@
1
+ PUBLIC_TITLE=dev
2
+ PUBLIC_LOGGER=true
3
+ PUBLIC_VIEW_TRANSITION=true
4
+ PUBLIC_API=https://65fd14fb9fc4425c653119c5.mockapi.io/api/v1
@@ -0,0 +1 @@
1
+ PUBLIC_TITLE=prod
@@ -0,0 +1 @@
1
+ PUBLIC_TITLE=test