@newlogic-digital/core 1.1.1 → 2.0.0-alpha.10
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 +142 -262
- package/latte/JsonFilter.js +7 -0
- package/package.json +22 -9
- package/src/minify.js +22 -0
- package/src/twig.js +174 -0
- /package/{prism.js → src/prism.js} +0 -0
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,36 @@
|
|
1
|
-
import
|
2
|
-
import
|
3
|
-
import
|
4
|
-
import latte from '@vituum/latte'
|
5
|
-
import lodash from 'lodash'
|
6
|
-
import minifier from 'html-minifier-terser'
|
7
|
-
import fs from 'fs'
|
8
|
-
import fse from 'fs-extra'
|
9
|
-
import { dirname, resolve } from 'path'
|
1
|
+
import fs from 'node:fs'
|
2
|
+
import os from 'node:os'
|
3
|
+
import { dirname, resolve, join, relative } from 'node:path'
|
10
4
|
import postHtml from 'posthtml'
|
11
|
-
import
|
12
|
-
import
|
13
|
-
import
|
14
|
-
import
|
15
|
-
import
|
16
|
-
import
|
17
|
-
import
|
18
|
-
import
|
19
|
-
import
|
5
|
+
import vituum from 'vituum'
|
6
|
+
import posthtml from '@vituum/vite-plugin-posthtml'
|
7
|
+
import latte from '@vituum/vite-plugin-latte'
|
8
|
+
import twig from '@vituum/vite-plugin-twig'
|
9
|
+
import juice from '@vituum/vite-plugin-juice'
|
10
|
+
import send from '@vituum/vite-plugin-send'
|
11
|
+
import tailwindcss from '@vituum/vite-plugin-tailwindcss'
|
12
|
+
import { getPackageInfo, merge } from 'vituum/utils/common.js'
|
13
|
+
import highlight from './src/prism.js'
|
14
|
+
import twigOptions from './src/twig.js'
|
20
15
|
import FastGlob from 'fast-glob'
|
16
|
+
import fse from 'fs-extra'
|
17
|
+
import pc from 'picocolors'
|
18
|
+
|
19
|
+
const { name } = getPackageInfo(import.meta.url)
|
21
20
|
|
22
21
|
const posthtmlPrism = {
|
23
|
-
name: '@
|
22
|
+
name: '@newlogic-digital/vite-plugin-posthtml-prism',
|
24
23
|
enforce: 'post',
|
25
24
|
transformIndexHtml: {
|
26
25
|
enforce: 'post',
|
27
|
-
transform: async(html, { filename }) => {
|
26
|
+
transform: async (html, { filename }) => {
|
28
27
|
filename = filename.replace('?raw', '')
|
29
28
|
|
30
|
-
if (!filename.
|
29
|
+
if (!filename.replace('.html', '').endsWith('ui.json')) {
|
31
30
|
return
|
32
31
|
}
|
33
32
|
|
34
|
-
const plugins = [highlight({ inline: false
|
33
|
+
const plugins = [highlight({ inline: false })]
|
35
34
|
|
36
35
|
const result = await postHtml(plugins).process(html)
|
37
36
|
|
@@ -40,206 +39,36 @@ const posthtmlPrism = {
|
|
40
39
|
}
|
41
40
|
}
|
42
41
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
const parseMinifyHtml = async (input, name) => {
|
68
|
-
const minify = await minifier.minify(input, {
|
69
|
-
collapseWhitespace: true,
|
70
|
-
collapseInlineTagWhitespace: false,
|
71
|
-
minifyCSS: true,
|
72
|
-
removeAttributeQuotes: true,
|
73
|
-
quoteCharacter: '\'',
|
74
|
-
minifyJS: true
|
75
|
-
})
|
76
|
-
|
77
|
-
if (name) {
|
78
|
-
return JSON.stringify({
|
79
|
-
[name]: minify
|
80
|
-
})
|
81
|
-
} else {
|
82
|
-
return JSON.stringify(minify)
|
83
|
-
}
|
84
|
-
}
|
85
|
-
|
86
|
-
const defaultConfig = {
|
87
|
-
format: 'twig',
|
42
|
+
/**
|
43
|
+
* @type {import('@newlogic-digital/core/types').PluginUserConfig}
|
44
|
+
*/
|
45
|
+
const defaultOptions = {
|
46
|
+
mode: null,
|
47
|
+
cert: 'localhost',
|
48
|
+
format: ['latte'],
|
88
49
|
emails: {
|
89
50
|
outputDir: resolve(process.cwd(), 'public/email'),
|
90
51
|
appDir: resolve(process.cwd(), 'app/Templates/Emails')
|
91
52
|
},
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
]
|
53
|
+
vituum: {
|
54
|
+
pages: {
|
55
|
+
dir: './src/views'
|
56
|
+
}
|
57
|
+
},
|
58
|
+
posthtml: {
|
59
|
+
root: resolve(process.cwd(), 'src')
|
60
|
+
},
|
61
|
+
juice: {
|
62
|
+
paths: ['src/views/email']
|
63
|
+
},
|
64
|
+
tailwindcss: {},
|
65
|
+
send: {
|
66
|
+
host: 'smtp.newlogic.cz',
|
67
|
+
from: 'noreply@newlogic.cz',
|
68
|
+
user: 'noreply@newlogic.cz'
|
240
69
|
},
|
241
70
|
latte: {
|
242
|
-
|
71
|
+
renderTransformedHtml: (filename) => dirname(filename).endsWith('email'),
|
243
72
|
globals: {
|
244
73
|
srcPath: resolve(process.cwd(), 'src'),
|
245
74
|
templatesPath: resolve(process.cwd(), 'src/templates')
|
@@ -250,64 +79,115 @@ const defaultConfig = {
|
|
250
79
|
}
|
251
80
|
},
|
252
81
|
filters: {
|
253
|
-
json:
|
254
|
-
return await parseMinifyHtml(input, name)
|
255
|
-
},
|
82
|
+
json: resolve(process.cwd(), 'node_modules/@newlogic-digital/core/latte/JsonFilter.js'),
|
256
83
|
code: 'node_modules/@newlogic-digital/core/latte/CodeFilter.php'
|
257
84
|
},
|
258
|
-
ignoredPaths: ['**/views/email/**/!(*.test).latte']
|
85
|
+
ignoredPaths: ['**/views/email/**/!(*.test).latte', '**/emails/!(*.test).latte']
|
259
86
|
},
|
260
|
-
|
261
|
-
noIsPseudoSelector: true
|
262
|
-
}
|
87
|
+
twig: twigOptions
|
263
88
|
}
|
264
89
|
|
265
|
-
|
266
|
-
|
90
|
+
/**
|
91
|
+
* @param {import('@newlogic-digital/core/types').PluginUserConfig} options
|
92
|
+
* @returns [import('vite').Plugin]
|
93
|
+
*/
|
94
|
+
const plugin = (options = {}) => {
|
95
|
+
options = merge(defaultOptions, options)
|
267
96
|
|
268
|
-
|
269
|
-
config: {
|
270
|
-
integrations: [posthtml(userConfig.posthtml), juice(userConfig.juice), twig(userConfig.twig), latte(userConfig.latte), {
|
271
|
-
task: {
|
272
|
-
name: 'emails',
|
273
|
-
action: async () => {
|
274
|
-
const emails = FastGlob.sync(`${resolve(process.cwd(), userConfig.emails.outputDir)}/**`).filter(entry => !entry.endsWith('test.html'))
|
275
|
-
const emailsProd = emails.map(path => {
|
276
|
-
return path.replace(resolve(process.cwd(), userConfig.emails.outputDir), resolve(process.cwd(), userConfig.emails.appDir)).replace('.html', '.latte')
|
277
|
-
})
|
97
|
+
const templatesPlugins = []
|
278
98
|
|
279
|
-
|
280
|
-
|
281
|
-
|
99
|
+
if (options.format.includes('twig')) {
|
100
|
+
templatesPlugins.push(twig(options.twig))
|
101
|
+
}
|
282
102
|
|
283
|
-
|
284
|
-
|
103
|
+
if (options.format.includes('latte')) {
|
104
|
+
templatesPlugins.push(latte(options.latte))
|
105
|
+
}
|
106
|
+
|
107
|
+
const plugins = [
|
108
|
+
vituum(options.vituum),
|
109
|
+
tailwindcss(options.tailwindcss),
|
110
|
+
posthtml(options.posthtml),
|
111
|
+
...templatesPlugins,
|
112
|
+
juice(options.juice),
|
113
|
+
send(options.send),
|
114
|
+
posthtmlPrism
|
115
|
+
]
|
116
|
+
|
117
|
+
return [{
|
118
|
+
name,
|
119
|
+
enforce: 'pre',
|
120
|
+
config (userConfig, userEnv) {
|
121
|
+
const isHttps = userConfig?.server?.https !== false &&
|
122
|
+
fs.existsSync(join(os.homedir(), `.ssh/${options.cert}.pem`)) &&
|
123
|
+
fs.existsSync(join(os.homedir(), `.ssh/${options.cert}-key.pem`))
|
124
|
+
|
125
|
+
let defaultInput = [
|
126
|
+
'./src/views/**/*.{json,latte,twig,liquid,njk,hbs,pug,html}',
|
127
|
+
'!./src/views/**/*.{latte,twig,liquid,njk,hbs,pug,html}.json',
|
128
|
+
'./src/styles/*.{css,pcss,scss,sass,less,styl,stylus}',
|
129
|
+
'./src/scripts/*.{js,ts,mjs}'
|
130
|
+
]
|
131
|
+
|
132
|
+
if (!options.mode) {
|
133
|
+
options.mode = userEnv.mode
|
134
|
+
}
|
135
|
+
|
136
|
+
if (userEnv.mode === 'headless') {
|
137
|
+
userEnv.mode = 'production'
|
138
|
+
|
139
|
+
defaultInput = [
|
140
|
+
'./src/styles/*.{css,pcss,scss,sass,less,styl,stylus}',
|
141
|
+
'./src/scripts/*.{js,ts,mjs}'
|
142
|
+
]
|
143
|
+
} else if (userEnv.mode === 'emails') {
|
144
|
+
userEnv.mode = 'production'
|
145
|
+
|
146
|
+
defaultInput = [
|
147
|
+
'./src/views/email/**/*.{json,latte,twig,liquid,njk,hbs,pug,html}',
|
148
|
+
'!./src/views/email/**/*.{latte,twig,liquid,njk,hbs,pug,html}.json'
|
149
|
+
]
|
150
|
+
}
|
151
|
+
|
152
|
+
userConfig.build = Object.assign({
|
153
|
+
manifest: true,
|
154
|
+
emptyOutDir: false,
|
155
|
+
modulePreload: false,
|
156
|
+
assetsInlineLimit: 0,
|
157
|
+
outDir: resolve(userConfig.root ?? process.cwd(), 'public'),
|
158
|
+
rollupOptions: {
|
159
|
+
input: defaultInput
|
285
160
|
}
|
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
|
161
|
+
}, userConfig.build ?? {})
|
162
|
+
|
163
|
+
userConfig.server = Object.assign({
|
164
|
+
host: true,
|
165
|
+
fsServe: {
|
166
|
+
strict: false
|
302
167
|
},
|
303
|
-
|
304
|
-
|
305
|
-
|
168
|
+
https: isHttps
|
169
|
+
? {
|
170
|
+
key: fs.readFileSync(join(os.homedir(), `.ssh/${options.cert}-key.pem`)),
|
171
|
+
cert: fs.readFileSync(join(os.homedir(), `.ssh/${options.cert}.pem`))
|
306
172
|
}
|
307
|
-
|
173
|
+
: false
|
174
|
+
}, userConfig.server ?? {})
|
175
|
+
},
|
176
|
+
writeBundle: async () => {
|
177
|
+
if (options.mode === 'emails') {
|
178
|
+
const emails = FastGlob.sync(`${resolve(process.cwd(), options.emails.outputDir)}/**`).filter(entry => !entry.endsWith('test.html'))
|
179
|
+
const emailsProd = emails.map(path => {
|
180
|
+
return path.replace(resolve(process.cwd(), options.emails.outputDir), resolve(process.cwd(), options.emails.appDir)).replace('.html', '.latte')
|
181
|
+
})
|
182
|
+
|
183
|
+
await Promise.all(emails.map((file, i) =>
|
184
|
+
fse.move(file, emailsProd[i], { overwrite: true })
|
185
|
+
))
|
186
|
+
|
187
|
+
console.info(`${pc.cyan('@newlogic-digital/core')} ${pc.green(`all email files were moved to ${relative(process.cwd(), options.emails.appDir)}`)}`)
|
308
188
|
}
|
309
189
|
}
|
310
|
-
}
|
190
|
+
}, ...plugins]
|
311
191
|
}
|
312
192
|
|
313
|
-
export default
|
193
|
+
export default plugin
|
package/package.json
CHANGED
@@ -1,30 +1,43 @@
|
|
1
1
|
{
|
2
2
|
"name": "@newlogic-digital/core",
|
3
3
|
"type": "module",
|
4
|
-
"version": "
|
4
|
+
"version": "2.0.0-alpha.10",
|
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.
|
15
|
-
"@vituum/
|
16
|
-
"@vituum/
|
17
|
-
"tailwindcss": "^
|
14
|
+
"@vituum/vite-plugin-posthtml": "^1.0.0-alpha.3",
|
15
|
+
"@vituum/vite-plugin-juice": "^1.0.0-alpha.2",
|
16
|
+
"@vituum/vite-plugin-latte": "^1.0.0-alpha.8",
|
17
|
+
"@vituum/vite-plugin-twig": "^1.0.0-alpha.6",
|
18
|
+
"@vituum/vite-plugin-tailwindcss": "^1.0.0-alpha.2",
|
19
|
+
"@vituum/vite-plugin-send": "^1.0.0-alpha.2",
|
20
|
+
"vituum": "^1.0.0-alpha.18",
|
21
|
+
"posthtml": "^0.16.6",
|
18
22
|
"posthtml-prism": "^2.0.0",
|
19
23
|
"prismjs": "^1.29.0",
|
20
24
|
"html-minifier-terser": "^7.2.0",
|
21
25
|
"lodash": "^4.17.21",
|
22
|
-
"
|
26
|
+
"fast-glob": "^3.2.12",
|
27
|
+
"fs-extra": "^11.1.1",
|
28
|
+
"picocolors": "^1.0.0"
|
29
|
+
},
|
30
|
+
"devDependencies": {
|
31
|
+
"@types/node": "^20.3.1",
|
32
|
+
"eslint": "^8.43.0",
|
33
|
+
"eslint-config-standard": "^17.1.0",
|
34
|
+
"typescript": "^5.1.3",
|
35
|
+
"vite": "^4.3.9"
|
23
36
|
},
|
24
37
|
"files": [
|
25
38
|
"latte",
|
26
39
|
"index.js",
|
27
|
-
"
|
40
|
+
"src"
|
28
41
|
],
|
29
42
|
"engines": {
|
30
43
|
"node": ">=16.0.0",
|
package/src/minify.js
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
import minifier from 'html-minifier-terser'
|
2
|
+
|
3
|
+
const parseMinifyHtml = async (input, name) => {
|
4
|
+
const minify = await minifier.minify(input, {
|
5
|
+
collapseWhitespace: true,
|
6
|
+
collapseInlineTagWhitespace: false,
|
7
|
+
minifyCSS: true,
|
8
|
+
removeAttributeQuotes: true,
|
9
|
+
quoteCharacter: '\'',
|
10
|
+
minifyJS: true
|
11
|
+
})
|
12
|
+
|
13
|
+
if (name) {
|
14
|
+
return JSON.stringify({
|
15
|
+
[name]: minify
|
16
|
+
})
|
17
|
+
} else {
|
18
|
+
return JSON.stringify(minify)
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
export default parseMinifyHtml
|
package/src/twig.js
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
import fs from 'fs'
|
2
|
+
import { resolve } from 'path'
|
3
|
+
import parseMinifyHtml from './minify.js'
|
4
|
+
|
5
|
+
const wrapPreCode = (code, lang) => {
|
6
|
+
return `<pre class="language-${lang}"><code class="language-${lang}">${code}</code></pre>`
|
7
|
+
}
|
8
|
+
|
9
|
+
const stripIndent = (string) => {
|
10
|
+
const indent = () => {
|
11
|
+
const match = string.match(/^[ \t]*(?=\S)/gm)
|
12
|
+
|
13
|
+
if (!match) {
|
14
|
+
return 0
|
15
|
+
}
|
16
|
+
|
17
|
+
return match.reduce((r, a) => Math.min(r, a.length), Infinity)
|
18
|
+
}
|
19
|
+
|
20
|
+
if (indent() === 0) {
|
21
|
+
return string
|
22
|
+
}
|
23
|
+
|
24
|
+
const regex = new RegExp(`^[ \\t]{${indent()}}`, 'gm')
|
25
|
+
|
26
|
+
return string.replace(regex, '')
|
27
|
+
}
|
28
|
+
|
29
|
+
export default {
|
30
|
+
namespaces: {
|
31
|
+
src: resolve(process.cwd(), 'src'),
|
32
|
+
templates: resolve(process.cwd(), 'src/templates')
|
33
|
+
},
|
34
|
+
functions: {
|
35
|
+
pages: () => {
|
36
|
+
return fs.readdirSync('src/views').filter(file => fs.statSync('src/views/' + file).isFile())
|
37
|
+
},
|
38
|
+
fetch: (data) => {
|
39
|
+
if (typeof data !== 'undefined') {
|
40
|
+
if (data.indexOf('http') > -1) {
|
41
|
+
return data
|
42
|
+
} else {
|
43
|
+
let slash = data.indexOf('/') + 1
|
44
|
+
if (slash > 1) {
|
45
|
+
slash = 0
|
46
|
+
}
|
47
|
+
|
48
|
+
return fs.readFileSync(process.cwd() + '/' + data.substring(slash, data.length), 'utf8').toString()
|
49
|
+
}
|
50
|
+
}
|
51
|
+
},
|
52
|
+
randomColor: () => {
|
53
|
+
return '#' + Math.random().toString(16).slice(2, 8)
|
54
|
+
},
|
55
|
+
placeholder: (width, height) => {
|
56
|
+
const colors = ['333', '444', '666', '222', '777', '888', '111']
|
57
|
+
return 'https://via.placeholder.com/' + width + 'x' + height + '/' + colors[Math.floor(Math.random() * colors.length)] + '.webp'
|
58
|
+
},
|
59
|
+
lazy: (width, height) => {
|
60
|
+
const svg = encodeURIComponent(stripIndent('<svg width="' + width + '" height="' + height + '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + width + ' ' + height + '"></svg>'))
|
61
|
+
|
62
|
+
return 'data:image/svg+xml;charset=UTF-8,' + svg
|
63
|
+
},
|
64
|
+
ratio: (width, height) => {
|
65
|
+
return (height / width) * 100
|
66
|
+
}
|
67
|
+
},
|
68
|
+
filters: {
|
69
|
+
asset: (url) => {
|
70
|
+
return url
|
71
|
+
},
|
72
|
+
rem: (value) => {
|
73
|
+
return `${value / 16}rem`
|
74
|
+
},
|
75
|
+
encode64: (path) => {
|
76
|
+
const svg = encodeURIComponent(stripIndent(path))
|
77
|
+
|
78
|
+
return 'data:image/svg+xml;charset=UTF-8,' + svg
|
79
|
+
},
|
80
|
+
exists: (path) => {
|
81
|
+
if (path.indexOf('/') === 0) {
|
82
|
+
path = path.slice(1)
|
83
|
+
}
|
84
|
+
|
85
|
+
return fs.existsSync(resolve(process.cwd(), path))
|
86
|
+
},
|
87
|
+
tel: (value) => {
|
88
|
+
return value.replace(/\s+/g, '').replace('(', '').replace(')', '')
|
89
|
+
}
|
90
|
+
},
|
91
|
+
extensions: [
|
92
|
+
(Twig) => {
|
93
|
+
Twig.exports.extendTag({
|
94
|
+
type: 'json',
|
95
|
+
regex: /^json\s+(.+)$|^json$/,
|
96
|
+
next: ['endjson'],
|
97
|
+
open: true,
|
98
|
+
compile: function (token) {
|
99
|
+
const expression = token.match[1] ?? '\'_null\''
|
100
|
+
|
101
|
+
token.stack = Reflect.apply(Twig.expression.compile, this, [{
|
102
|
+
type: Twig.expression.type.expression,
|
103
|
+
value: expression
|
104
|
+
}]).stack
|
105
|
+
|
106
|
+
delete token.match
|
107
|
+
return token
|
108
|
+
},
|
109
|
+
parse: async function (token, context, chain) {
|
110
|
+
const name = Reflect.apply(Twig.expression.parse, this, [token.stack, context])
|
111
|
+
const output = this.parse(token.output, context)
|
112
|
+
|
113
|
+
if (name === '_null') {
|
114
|
+
return {
|
115
|
+
chain,
|
116
|
+
output: await parseMinifyHtml(output)
|
117
|
+
}
|
118
|
+
} else {
|
119
|
+
return {
|
120
|
+
chain,
|
121
|
+
output: await parseMinifyHtml(output, name)
|
122
|
+
}
|
123
|
+
}
|
124
|
+
}
|
125
|
+
})
|
126
|
+
Twig.exports.extendTag({
|
127
|
+
type: 'endjson',
|
128
|
+
regex: /^endjson$/,
|
129
|
+
next: [],
|
130
|
+
open: false
|
131
|
+
})
|
132
|
+
},
|
133
|
+
(Twig) => {
|
134
|
+
Twig.exports.extendTag({
|
135
|
+
type: 'code',
|
136
|
+
regex: /^code\s+(.+)$/,
|
137
|
+
next: ['endcode'], // match the type of the end tag
|
138
|
+
open: true,
|
139
|
+
compile: function (token) {
|
140
|
+
const expression = token.match[1]
|
141
|
+
|
142
|
+
token.stack = Reflect.apply(Twig.expression.compile, this, [{
|
143
|
+
type: Twig.expression.type.expression,
|
144
|
+
value: expression
|
145
|
+
}]).stack
|
146
|
+
|
147
|
+
delete token.match
|
148
|
+
return token
|
149
|
+
},
|
150
|
+
parse: function (token, context, chain) {
|
151
|
+
let type = Reflect.apply(Twig.expression.parse, this, [token.stack, context])
|
152
|
+
const output = this.parse(token.output, context)
|
153
|
+
let mirror = false
|
154
|
+
|
155
|
+
if (type.includes(':mirror')) {
|
156
|
+
mirror = true
|
157
|
+
type = type.replace(':mirror', '')
|
158
|
+
}
|
159
|
+
|
160
|
+
return {
|
161
|
+
chain,
|
162
|
+
output: `${mirror ? output : ''}${wrapPreCode(output, type)}`
|
163
|
+
}
|
164
|
+
}
|
165
|
+
})
|
166
|
+
Twig.exports.extendTag({
|
167
|
+
type: 'endcode',
|
168
|
+
regex: /^endcode$/,
|
169
|
+
next: [],
|
170
|
+
open: false
|
171
|
+
})
|
172
|
+
}
|
173
|
+
]
|
174
|
+
}
|
File without changes
|