@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
         |