@newlogic-digital/core 2.0.0-alpha.1 → 2.0.0-alpha.11

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/index.js CHANGED
@@ -1,16 +1,20 @@
1
- import fs from 'fs'
2
- import os from 'os'
3
- import { dirname, resolve, join } from 'path'
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import { dirname, resolve, join, relative } from 'node:path'
4
4
  import postHtml from 'posthtml'
5
5
  import vituum from 'vituum'
6
6
  import posthtml from '@vituum/vite-plugin-posthtml'
7
7
  import latte from '@vituum/vite-plugin-latte'
8
+ import twig from '@vituum/vite-plugin-twig'
8
9
  import juice from '@vituum/vite-plugin-juice'
9
10
  import send from '@vituum/vite-plugin-send'
10
11
  import tailwindcss from '@vituum/vite-plugin-tailwindcss'
11
12
  import { getPackageInfo, merge } from 'vituum/utils/common.js'
12
- import minifier from 'html-minifier-terser'
13
- import highlight from './prism.js'
13
+ import highlight from './src/prism.js'
14
+ import twigOptions from './src/twig.js'
15
+ import FastGlob from 'fast-glob'
16
+ import fse from 'fs-extra'
17
+ import pc from 'picocolors'
14
18
 
15
19
  const { name } = getPackageInfo(import.meta.url)
16
20
 
@@ -35,39 +39,34 @@ const posthtmlPrism = {
35
39
  }
36
40
  }
37
41
 
38
- const parseMinifyHtml = async (input, name) => {
39
- const minify = await minifier.minify(input, {
40
- collapseWhitespace: true,
41
- collapseInlineTagWhitespace: false,
42
- minifyCSS: true,
43
- removeAttributeQuotes: true,
44
- quoteCharacter: '\'',
45
- minifyJS: true
46
- })
47
-
48
- if (name) {
49
- return JSON.stringify({
50
- [name]: minify
51
- })
52
- } else {
53
- return JSON.stringify(minify)
54
- }
55
- }
56
-
57
42
  /**
58
43
  * @type {import('@newlogic-digital/core/types').PluginUserConfig}
59
44
  */
60
45
  const defaultOptions = {
46
+ mode: null,
61
47
  cert: 'localhost',
48
+ format: ['latte'],
62
49
  emails: {
63
50
  outputDir: resolve(process.cwd(), 'public/email'),
64
51
  appDir: resolve(process.cwd(), 'app/Templates/Emails')
65
52
  },
66
- vituum: {},
67
- posthtml: {},
68
- juice: {},
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
+ },
69
64
  tailwindcss: {},
70
- send: {},
65
+ send: {
66
+ host: 'smtp.newlogic.cz',
67
+ from: 'noreply@newlogic.cz',
68
+ user: 'noreply@newlogic.cz'
69
+ },
71
70
  latte: {
72
71
  renderTransformedHtml: (filename) => dirname(filename).endsWith('email'),
73
72
  globals: {
@@ -80,56 +79,115 @@ const defaultOptions = {
80
79
  }
81
80
  },
82
81
  filters: {
83
- json: async (input, name) => {
84
- return await parseMinifyHtml(input, name)
85
- },
82
+ json: resolve(process.cwd(), 'node_modules/@newlogic-digital/core/latte/JsonFilter.js'),
86
83
  code: 'node_modules/@newlogic-digital/core/latte/CodeFilter.php'
87
84
  },
88
- ignoredPaths: ['**/views/email/**/!(*.test).latte']
89
- }
85
+ ignoredPaths: ['**/views/email/**/!(*.test).latte', '**/emails/!(*.test).latte']
86
+ },
87
+ twig: twigOptions
90
88
  }
91
89
 
92
90
  /**
93
91
  * @param {import('@newlogic-digital/core/types').PluginUserConfig} options
94
- * @returns import('vite').Plugin
92
+ * @returns [import('vite').Plugin]
95
93
  */
96
94
  const plugin = (options = {}) => {
97
95
  options = merge(defaultOptions, options)
98
96
 
97
+ const templatesPlugins = []
98
+
99
+ if (options.format.includes('twig')) {
100
+ templatesPlugins.push(twig(options.twig))
101
+ }
102
+
103
+ if (options.format.includes('latte')) {
104
+ templatesPlugins.push(latte(options.latte))
105
+ }
106
+
99
107
  const plugins = [
100
108
  vituum(options.vituum),
101
109
  tailwindcss(options.tailwindcss),
102
110
  posthtml(options.posthtml),
103
- latte(options.latte),
111
+ ...templatesPlugins,
104
112
  juice(options.juice),
105
113
  send(options.send),
106
114
  posthtmlPrism
107
115
  ]
108
116
 
109
- return {
117
+ return [{
110
118
  name,
111
119
  enforce: 'pre',
112
- config (userConfig) {
113
- if (!userConfig?.plugins) {
114
- userConfig.plugins = plugins
115
- } else if (userConfig.plugins) {
116
- userConfig.plugins = plugins.concat(...userConfig.plugins)
117
- }
118
-
119
- if (
120
- userConfig?.server?.https !== false &&
120
+ config (userConfig, userEnv) {
121
+ const isHttps = userConfig?.server?.https !== false &&
121
122
  fs.existsSync(join(os.homedir(), `.ssh/${options.cert}.pem`)) &&
122
123
  fs.existsSync(join(os.homedir(), `.ssh/${options.cert}-key.pem`))
123
- ) {
124
- userConfig.server = Object.assign(userConfig.server ?? {}, {
125
- https: {
124
+
125
+ let defaultInput = [
126
+ './src/styles/*.{css,pcss,scss,sass,less,styl,stylus}',
127
+ './src/scripts/*.{js,ts,mjs}'
128
+ ]
129
+
130
+ if (!options.mode) {
131
+ options.mode = userEnv.mode
132
+ }
133
+
134
+ if (options.mode === 'development') {
135
+ defaultInput = [
136
+ './src/views/**/*.{json,latte,twig,liquid,njk,hbs,pug,html}',
137
+ '!./src/views/**/*.{latte,twig,liquid,njk,hbs,pug,html}.json',
138
+ './src/styles/*.{css,pcss,scss,sass,less,styl,stylus}',
139
+ './src/scripts/*.{js,ts,mjs}'
140
+ ]
141
+ }
142
+
143
+ if (options.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
160
+ }
161
+ }, userConfig.build ?? {})
162
+
163
+ userConfig.server = Object.assign({
164
+ host: true,
165
+ fsServe: {
166
+ strict: false
167
+ },
168
+ https: isHttps
169
+ ? {
126
170
  key: fs.readFileSync(join(os.homedir(), `.ssh/${options.cert}-key.pem`)),
127
171
  cert: fs.readFileSync(join(os.homedir(), `.ssh/${options.cert}.pem`))
128
172
  }
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')
129
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)}`)}`)
130
188
  }
131
189
  }
132
- }
190
+ }, ...plugins]
133
191
  }
134
192
 
135
193
  export default plugin
@@ -0,0 +1,7 @@
1
+ import parseMinifyHtml from '../src/minify.js'
2
+
3
+ const json = async (input, name) => {
4
+ return await parseMinifyHtml(input, name)
5
+ }
6
+
7
+ export default json
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@newlogic-digital/core",
3
3
  "type": "module",
4
- "version": "2.0.0-alpha.1",
4
+ "version": "2.0.0-alpha.11",
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",
@@ -12,20 +12,24 @@
12
12
  },
13
13
  "dependencies": {
14
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",
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",
20
21
  "posthtml": "^0.16.6",
21
22
  "posthtml-prism": "^2.0.0",
22
23
  "prismjs": "^1.29.0",
23
24
  "html-minifier-terser": "^7.2.0",
24
- "lodash": "^4.17.21"
25
+ "lodash": "^4.17.21",
26
+ "fast-glob": "^3.2.12",
27
+ "fs-extra": "^11.1.1",
28
+ "picocolors": "^1.0.0"
25
29
  },
26
30
  "devDependencies": {
27
31
  "@types/node": "^20.3.1",
28
- "eslint": "^8.42.0",
32
+ "eslint": "^8.43.0",
29
33
  "eslint-config-standard": "^17.1.0",
30
34
  "typescript": "^5.1.3",
31
35
  "vite": "^4.3.9"
@@ -33,7 +37,7 @@
33
37
  "files": [
34
38
  "latte",
35
39
  "index.js",
36
- "prism.js"
40
+ "src"
37
41
  ],
38
42
  "engines": {
39
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