@newlogic-digital/core 1.1.1 → 2.0.0-alpha.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.
- package/README.md +13 -29
- package/index.js +87 -238
- package/package.json +18 -9
package/README.md
CHANGED
|
@@ -19,58 +19,42 @@ Starter for creating web applications. Powered by Vite and Vituum.
|
|
|
19
19
|
- 📦 Modular structure
|
|
20
20
|
- ✉️ Email templates
|
|
21
21
|
|
|
22
|
-
Newlogic Core is
|
|
22
|
+
Newlogic Core is a plugin for [Vite](https://vitejs.dev), and contains set of plugins that can be used to create modern web applications.
|
|
23
23
|
|
|
24
|
-
We use it as our main front-end
|
|
24
|
+
We use it as our main front-end set of tools at [Newlogic Digital](https://www.newlogic.cz/) to create wonders.
|
|
25
25
|
|
|
26
26
|
## 🛠️ Integrated tools
|
|
27
27
|
* **[Vite](https://vitejs.dev)** next-generation frontend tooling
|
|
28
28
|
* **[Vituum](https://vituum.dev)** fast prototyping with template engines
|
|
29
|
-
* **[PostCSS](https://postcss.org/)** with basic plugins
|
|
30
|
-
* **[
|
|
29
|
+
* **[PostCSS](https://postcss.org/)** with basic plugins
|
|
30
|
+
* **[TailwindCSS](https://tailwindcss.com/)** for utility classes
|
|
31
31
|
* **[Latte](https://github.com/vituum/vite-plugin-latte)** as template engine latte
|
|
32
32
|
|
|
33
|
-
### 💡 Basic principle
|
|
34
|
-
|
|
35
|
-
Most of today build tools are hard to configure and not focused primary on PHP server side applications.
|
|
36
|
-
|
|
37
|
-
PHP programmers often **don't want to configure anything**, basic idea is to add as many files you want to `src` and get output in `public/assets` - without worrying about anything.
|
|
38
|
-
|
|
39
|
-
It doesn't matter if you use Nette, Symfony or Laravel - the structure can be freely adjusted as needed - `resources` and` public`, `src` and` dist` or `app/assets` and` www`
|
|
40
|
-
|
|
41
|
-
It's up to you - all paths are freely configurable via `vite.config.js` config
|
|
42
|
-
|
|
43
|
-
### 📦 Modularity
|
|
44
|
-
|
|
45
|
-
Newlogic Core uses [Vituum](https://vituum.dev) and [Vite](https://vitejs.dev) for frontend tooling.
|
|
46
|
-
|
|
47
|
-
Source files are divided by modules inside `src` directory - styles, scripts, templates, data, emails, assets. It is optional which modules you want to use for the project, simple delete the directory. You really only use what you want to use.
|
|
48
|
-
|
|
49
33
|
## 🪄 Get started
|
|
50
34
|
|
|
51
35
|
```sh
|
|
52
36
|
npm i @newlogic-digital/core --save-dev
|
|
53
37
|
```
|
|
54
38
|
|
|
55
|
-
### Requirements
|
|
56
|
-
|
|
57
|
-
- [Node.js LTS (16.x)](https://nodejs.org/en/download/)
|
|
58
|
-
- [Vituum](https://vituum.dev/)
|
|
59
|
-
|
|
60
39
|
### Config
|
|
61
40
|
|
|
62
41
|
Each **Newlogic Core** project needs to have config via `vite.config.js`
|
|
63
42
|
|
|
64
43
|
```js
|
|
65
|
-
import { defineConfig } from 'vituum'
|
|
66
44
|
import core from "@newlogic-digital/core"
|
|
67
45
|
|
|
68
|
-
export default
|
|
69
|
-
|
|
70
|
-
}
|
|
46
|
+
export default {
|
|
47
|
+
plugins: [core()]
|
|
48
|
+
}
|
|
71
49
|
```
|
|
72
50
|
|
|
73
51
|
You can also try minimal example project [core-starter](https://github.com/newlogic-digital/core-starter)
|
|
74
52
|
|
|
53
|
+
### Requirements
|
|
54
|
+
|
|
55
|
+
- [Node.js LTS (18.x)](https://nodejs.org/en/download/)
|
|
56
|
+
- [Vite](https://vitejs.dev/)
|
|
57
|
+
- [PHP 8.2](https://www.php.net/) for Latte support
|
|
58
|
+
|
|
75
59
|
## Licence
|
|
76
60
|
MIT
|
package/index.js
CHANGED
|
@@ -1,37 +1,32 @@
|
|
|
1
|
-
import posthtml from '@vituum/posthtml'
|
|
2
|
-
import juice from '@vituum/juice'
|
|
3
|
-
import twig from '@vituum/twig'
|
|
4
|
-
import latte from '@vituum/latte'
|
|
5
|
-
import lodash from 'lodash'
|
|
6
|
-
import minifier from 'html-minifier-terser'
|
|
7
1
|
import fs from 'fs'
|
|
8
|
-
import
|
|
9
|
-
import { dirname, resolve } from 'path'
|
|
2
|
+
import os from 'os'
|
|
3
|
+
import { dirname, resolve, join } from 'path'
|
|
10
4
|
import postHtml from 'posthtml'
|
|
5
|
+
import vituum from 'vituum'
|
|
6
|
+
import posthtml from '@vituum/vite-plugin-posthtml'
|
|
7
|
+
import latte from '@vituum/vite-plugin-latte'
|
|
8
|
+
import juice from '@vituum/vite-plugin-juice'
|
|
9
|
+
import send from '@vituum/vite-plugin-send'
|
|
10
|
+
import tailwindcss from '@vituum/vite-plugin-tailwindcss'
|
|
11
|
+
import { getPackageInfo, merge } from 'vituum/utils/common.js'
|
|
12
|
+
import minifier from 'html-minifier-terser'
|
|
11
13
|
import highlight from './prism.js'
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import postcssImport from 'postcss-import'
|
|
15
|
-
import postcssNesting from 'postcss-nesting'
|
|
16
|
-
import postcssCustomMedia from 'postcss-custom-media'
|
|
17
|
-
import postcssCustomSelectors from 'postcss-custom-selectors'
|
|
18
|
-
import autoprefixer from 'autoprefixer'
|
|
19
|
-
import chalk from 'chalk'
|
|
20
|
-
import FastGlob from 'fast-glob'
|
|
14
|
+
|
|
15
|
+
const { name } = getPackageInfo(import.meta.url)
|
|
21
16
|
|
|
22
17
|
const posthtmlPrism = {
|
|
23
|
-
name: '@
|
|
18
|
+
name: '@newlogic-digital/vite-plugin-posthtml-prism',
|
|
24
19
|
enforce: 'post',
|
|
25
20
|
transformIndexHtml: {
|
|
26
21
|
enforce: 'post',
|
|
27
|
-
transform: async(html, { filename }) => {
|
|
22
|
+
transform: async (html, { filename }) => {
|
|
28
23
|
filename = filename.replace('?raw', '')
|
|
29
24
|
|
|
30
|
-
if (!filename.
|
|
25
|
+
if (!filename.replace('.html', '').endsWith('ui.json')) {
|
|
31
26
|
return
|
|
32
27
|
}
|
|
33
28
|
|
|
34
|
-
const plugins = [highlight({ inline: false
|
|
29
|
+
const plugins = [highlight({ inline: false })]
|
|
35
30
|
|
|
36
31
|
const result = await postHtml(plugins).process(html)
|
|
37
32
|
|
|
@@ -40,30 +35,6 @@ const posthtmlPrism = {
|
|
|
40
35
|
}
|
|
41
36
|
}
|
|
42
37
|
|
|
43
|
-
const wrapPreCode = (code, lang) => {
|
|
44
|
-
return `<pre class="language-${lang}"><code class="language-${lang}">${code}</code></pre>`
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const stripIndent = (string) => {
|
|
48
|
-
const indent = () => {
|
|
49
|
-
const match = string.match(/^[ \t]*(?=\S)/gm)
|
|
50
|
-
|
|
51
|
-
if (!match) {
|
|
52
|
-
return 0
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return match.reduce((r, a) => Math.min(r, a.length), Infinity)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (indent() === 0) {
|
|
59
|
-
return string
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const regex = new RegExp(`^[ \\t]{${indent()}}`, 'gm')
|
|
63
|
-
|
|
64
|
-
return string.replace(regex, '')
|
|
65
|
-
}
|
|
66
|
-
|
|
67
38
|
const parseMinifyHtml = async (input, name) => {
|
|
68
39
|
const minify = await minifier.minify(input, {
|
|
69
40
|
collapseWhitespace: true,
|
|
@@ -83,163 +54,32 @@ const parseMinifyHtml = async (input, name) => {
|
|
|
83
54
|
}
|
|
84
55
|
}
|
|
85
56
|
|
|
86
|
-
|
|
87
|
-
|
|
57
|
+
/**
|
|
58
|
+
* @type {import('@newlogic-digital/core/types').PluginUserConfig}
|
|
59
|
+
*/
|
|
60
|
+
const defaultOptions = {
|
|
61
|
+
cert: 'localhost',
|
|
88
62
|
emails: {
|
|
89
63
|
outputDir: resolve(process.cwd(), 'public/email'),
|
|
90
64
|
appDir: resolve(process.cwd(), 'app/Templates/Emails')
|
|
91
65
|
},
|
|
66
|
+
vituum: {
|
|
67
|
+
pages: {
|
|
68
|
+
dir: './src/views'
|
|
69
|
+
}
|
|
70
|
+
},
|
|
92
71
|
posthtml: {},
|
|
93
|
-
juice: {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
pages: () => {
|
|
102
|
-
return fs.readdirSync('src/views').filter(file => fs.statSync('src/views/' + file).isFile())
|
|
103
|
-
},
|
|
104
|
-
fetch: (data) => {
|
|
105
|
-
if (typeof data !== 'undefined') {
|
|
106
|
-
if (data.indexOf('http') > -1) {
|
|
107
|
-
return data
|
|
108
|
-
} else {
|
|
109
|
-
let slash = data.indexOf('/') + 1
|
|
110
|
-
if (slash > 1) {
|
|
111
|
-
slash = 0
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return fs.readFileSync(process.cwd() + '/' + data.substring(slash, data.length), 'utf8').toString()
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
randomColor: () => {
|
|
119
|
-
return '#' + Math.random().toString(16).slice(2, 8)
|
|
120
|
-
},
|
|
121
|
-
placeholder: (width, height) => {
|
|
122
|
-
const colors = ['333', '444', '666', '222', '777', '888', '111']
|
|
123
|
-
return 'https://via.placeholder.com/' + width + 'x' + height + '/' + colors[Math.floor(Math.random() * colors.length)] + '.webp'
|
|
124
|
-
},
|
|
125
|
-
lazy: (width, height) => {
|
|
126
|
-
const svg = encodeURIComponent(stripIndent('<svg width="' + width + '" height="' + height + '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + width + ' ' + height + '"></svg>'))
|
|
127
|
-
|
|
128
|
-
return 'data:image/svg+xml;charset=UTF-8,' + svg
|
|
129
|
-
},
|
|
130
|
-
ratio: (width, height) => {
|
|
131
|
-
return (height / width) * 100
|
|
132
|
-
}
|
|
133
|
-
},
|
|
134
|
-
filters: {
|
|
135
|
-
asset: (url) => {
|
|
136
|
-
return url.replace('/src/', '/')
|
|
137
|
-
},
|
|
138
|
-
rem: (value) => {
|
|
139
|
-
return `${value / 16}rem`
|
|
140
|
-
},
|
|
141
|
-
encode64: (path) => {
|
|
142
|
-
const svg = encodeURIComponent(stripIndent(path))
|
|
143
|
-
|
|
144
|
-
return 'data:image/svg+xml;charset=UTF-8,' + svg
|
|
145
|
-
},
|
|
146
|
-
exists: (path) => {
|
|
147
|
-
if (path.indexOf('/') === 0) {
|
|
148
|
-
path = path.slice(1)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return fs.existsSync(resolve(process.cwd(), path))
|
|
152
|
-
},
|
|
153
|
-
tel: (value) => {
|
|
154
|
-
return value.replace(/\s+/g, '').replace('(', '').replace(')', '')
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
extensions: [
|
|
158
|
-
(Twig) => {
|
|
159
|
-
Twig.exports.extendTag({
|
|
160
|
-
type: 'json',
|
|
161
|
-
regex: /^json\s+(.+)$|^json$/,
|
|
162
|
-
next: ['endjson'],
|
|
163
|
-
open: true,
|
|
164
|
-
compile: function(token) {
|
|
165
|
-
const expression = token.match[1] ?? '\'_null\''
|
|
166
|
-
|
|
167
|
-
token.stack = Reflect.apply(Twig.expression.compile, this, [{
|
|
168
|
-
type: Twig.expression.type.expression,
|
|
169
|
-
value: expression
|
|
170
|
-
}]).stack
|
|
171
|
-
|
|
172
|
-
delete token.match
|
|
173
|
-
return token
|
|
174
|
-
},
|
|
175
|
-
parse: async function(token, context, chain) {
|
|
176
|
-
const name = Reflect.apply(Twig.expression.parse, this, [token.stack, context])
|
|
177
|
-
const output = this.parse(token.output, context)
|
|
178
|
-
|
|
179
|
-
if (name === '_null') {
|
|
180
|
-
return {
|
|
181
|
-
chain,
|
|
182
|
-
output: await parseMinifyHtml(output)
|
|
183
|
-
}
|
|
184
|
-
} else {
|
|
185
|
-
return {
|
|
186
|
-
chain,
|
|
187
|
-
output: await parseMinifyHtml(output, name)
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
})
|
|
192
|
-
Twig.exports.extendTag({
|
|
193
|
-
type: 'endjson',
|
|
194
|
-
regex: /^endjson$/,
|
|
195
|
-
next: [],
|
|
196
|
-
open: false
|
|
197
|
-
})
|
|
198
|
-
},
|
|
199
|
-
(Twig) => {
|
|
200
|
-
Twig.exports.extendTag({
|
|
201
|
-
type: "code",
|
|
202
|
-
regex: /^code\s+(.+)$/,
|
|
203
|
-
next: ["endcode"], // match the type of the end tag
|
|
204
|
-
open: true,
|
|
205
|
-
compile: function (token) {
|
|
206
|
-
const expression = token.match[1];
|
|
207
|
-
|
|
208
|
-
token.stack = Reflect.apply(Twig.expression.compile, this, [{
|
|
209
|
-
type: Twig.expression.type.expression,
|
|
210
|
-
value: expression
|
|
211
|
-
}]).stack;
|
|
212
|
-
|
|
213
|
-
delete token.match;
|
|
214
|
-
return token;
|
|
215
|
-
},
|
|
216
|
-
parse: function (token, context, chain) {
|
|
217
|
-
let type = Reflect.apply(Twig.expression.parse, this, [token.stack, context]);
|
|
218
|
-
let output = this.parse(token.output, context);
|
|
219
|
-
let mirror = false;
|
|
220
|
-
|
|
221
|
-
if (type.includes(":mirror")) {
|
|
222
|
-
mirror = true;
|
|
223
|
-
type = type.replace(":mirror", "")
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
chain: chain,
|
|
228
|
-
output: `${mirror ? output : ""}${wrapPreCode(output, type)}`
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
Twig.exports.extendTag({
|
|
233
|
-
type: "endcode",
|
|
234
|
-
regex: /^endcode$/,
|
|
235
|
-
next: [ ],
|
|
236
|
-
open: false
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
]
|
|
72
|
+
juice: {
|
|
73
|
+
paths: ['src/views/email']
|
|
74
|
+
},
|
|
75
|
+
tailwindcss: {},
|
|
76
|
+
send: {
|
|
77
|
+
host: 'smtp.newlogic.cz',
|
|
78
|
+
from: 'noreply@newlogic.cz',
|
|
79
|
+
user: 'noreply@newlogic.cz'
|
|
240
80
|
},
|
|
241
81
|
latte: {
|
|
242
|
-
|
|
82
|
+
renderTransformedHtml: (filename) => dirname(filename).endsWith('email'),
|
|
243
83
|
globals: {
|
|
244
84
|
srcPath: resolve(process.cwd(), 'src'),
|
|
245
85
|
templatesPath: resolve(process.cwd(), 'src/templates')
|
|
@@ -255,59 +95,68 @@ const defaultConfig = {
|
|
|
255
95
|
},
|
|
256
96
|
code: 'node_modules/@newlogic-digital/core/latte/CodeFilter.php'
|
|
257
97
|
},
|
|
258
|
-
ignoredPaths: ['**/views/email/**/!(*.test).latte']
|
|
259
|
-
},
|
|
260
|
-
postcssNesting: {
|
|
261
|
-
noIsPseudoSelector: true
|
|
98
|
+
ignoredPaths: ['**/views/email/**/!(*.test).latte', '**/emails/!(*.test).latte']
|
|
262
99
|
}
|
|
263
100
|
}
|
|
264
101
|
|
|
265
|
-
|
|
266
|
-
|
|
102
|
+
/**
|
|
103
|
+
* @param {import('@newlogic-digital/core/types').PluginUserConfig} options
|
|
104
|
+
* @returns import('vite').Plugin
|
|
105
|
+
*/
|
|
106
|
+
const plugin = (options = {}) => {
|
|
107
|
+
options = merge(defaultOptions, options)
|
|
108
|
+
|
|
109
|
+
const plugins = [
|
|
110
|
+
vituum(options.vituum),
|
|
111
|
+
tailwindcss(options.tailwindcss),
|
|
112
|
+
posthtml(options.posthtml),
|
|
113
|
+
latte(options.latte),
|
|
114
|
+
juice(options.juice),
|
|
115
|
+
send(options.send),
|
|
116
|
+
posthtmlPrism
|
|
117
|
+
]
|
|
267
118
|
|
|
268
119
|
return {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
await Promise.all(emails.map((file, i) =>
|
|
280
|
-
fse.move(file, emailsProd[i], { overwrite: true })
|
|
281
|
-
))
|
|
120
|
+
name,
|
|
121
|
+
enforce: 'pre',
|
|
122
|
+
config (userConfig) {
|
|
123
|
+
if (!userConfig?.plugins) {
|
|
124
|
+
userConfig.plugins = plugins
|
|
125
|
+
} else if (userConfig.plugins) {
|
|
126
|
+
userConfig.plugins = plugins.concat(...userConfig.plugins)
|
|
127
|
+
}
|
|
282
128
|
|
|
283
|
-
|
|
284
|
-
|
|
129
|
+
const isHttps = userConfig?.server?.https !== false &&
|
|
130
|
+
fs.existsSync(join(os.homedir(), `.ssh/${options.cert}.pem`)) &&
|
|
131
|
+
fs.existsSync(join(os.homedir(), `.ssh/${options.cert}-key.pem`))
|
|
132
|
+
|
|
133
|
+
userConfig.build = Object.assign({
|
|
134
|
+
manifest: true,
|
|
135
|
+
emptyOutDir: false,
|
|
136
|
+
modulePreload: false,
|
|
137
|
+
outDir: resolve(userConfig.root ?? process.cwd(), 'public'),
|
|
138
|
+
rollupOptions: {
|
|
139
|
+
input: [
|
|
140
|
+
'./src/views/**/*.{json,latte,twig,liquid,njk,hbs,pug,html}',
|
|
141
|
+
'!./src/views/**/*.{latte,twig,liquid,njk,hbs,pug,html}.json'
|
|
142
|
+
]
|
|
285
143
|
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
server
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
},
|
|
293
|
-
templates: {
|
|
294
|
-
format: userConfig.format
|
|
295
|
-
},
|
|
296
|
-
imports: {
|
|
297
|
-
paths: ['./src/styles/**', './src/scripts/**', '!./src/styles/Utils/**']
|
|
298
|
-
},
|
|
299
|
-
vite: {
|
|
300
|
-
server: {
|
|
301
|
-
origin: fs.existsSync(resolve(process.cwd(), 'app/settings.php')) ? (fs.readFileSync(resolve(process.cwd(), 'app/settings.php')).toString().match(/VITE_URL = '(.+)';/) || [null, null])[1] : null
|
|
144
|
+
}, userConfig.build ?? {})
|
|
145
|
+
|
|
146
|
+
userConfig.server = Object.assign({
|
|
147
|
+
host: true,
|
|
148
|
+
fsServe: {
|
|
149
|
+
strict: false
|
|
302
150
|
},
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
151
|
+
https: isHttps
|
|
152
|
+
? {
|
|
153
|
+
key: fs.readFileSync(join(os.homedir(), `.ssh/${options.cert}-key.pem`)),
|
|
154
|
+
cert: fs.readFileSync(join(os.homedir(), `.ssh/${options.cert}.pem`))
|
|
306
155
|
}
|
|
307
|
-
|
|
308
|
-
}
|
|
156
|
+
: false
|
|
157
|
+
}, userConfig.server ?? {})
|
|
309
158
|
}
|
|
310
159
|
}
|
|
311
160
|
}
|
|
312
161
|
|
|
313
|
-
export default
|
|
162
|
+
export default plugin
|
package/package.json
CHANGED
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newlogic-digital/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0-alpha.2",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "New Logic Studio s.r.o.",
|
|
7
7
|
"description": "Set of tools that can be used to create modern web applications",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"scripts": {
|
|
10
|
-
"
|
|
10
|
+
"tsc": "tsc",
|
|
11
|
+
"eslint": "eslint '**/*.js' --fix"
|
|
11
12
|
},
|
|
12
13
|
"dependencies": {
|
|
13
|
-
"@vituum/posthtml": "^0.
|
|
14
|
-
"@vituum/juice": "^0.1
|
|
15
|
-
"@vituum/
|
|
16
|
-
"@vituum/
|
|
17
|
-
"
|
|
14
|
+
"@vituum/vite-plugin-posthtml": "^1.0.0-alpha.3",
|
|
15
|
+
"@vituum/vite-plugin-juice": "^1.0.0-alpha.1",
|
|
16
|
+
"@vituum/vite-plugin-latte": "^1.0.0-alpha.3",
|
|
17
|
+
"@vituum/vite-plugin-tailwindcss": "^1.0.0-alpha.1",
|
|
18
|
+
"@vituum/vite-plugin-send": "^1.0.0-alpha.1",
|
|
19
|
+
"vituum": "^1.0.0-alpha.16",
|
|
20
|
+
"posthtml": "^0.16.6",
|
|
18
21
|
"posthtml-prism": "^2.0.0",
|
|
19
22
|
"prismjs": "^1.29.0",
|
|
20
23
|
"html-minifier-terser": "^7.2.0",
|
|
21
|
-
"lodash": "^4.17.21"
|
|
22
|
-
|
|
24
|
+
"lodash": "^4.17.21"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.3.1",
|
|
28
|
+
"eslint": "^8.42.0",
|
|
29
|
+
"eslint-config-standard": "^17.1.0",
|
|
30
|
+
"typescript": "^5.1.3",
|
|
31
|
+
"vite": "^4.3.9"
|
|
23
32
|
},
|
|
24
33
|
"files": [
|
|
25
34
|
"latte",
|