diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..03f2432
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,14 @@
+{
+ "extends": "standard",
+ "rules": {
+ "indent": ["error", 4],
+ "no-new": "off",
+ "space-before-function-paren": ["error", "never"],
+ "import/extensions": ["error", "ignorePackages"],
+ "multiline-ternary": ["error", "never"]
+ },
+ "env": {
+ "browser": true
+ },
+ "ignorePatterns": ["src", "public"]
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2cc5f6a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.idea
+node_modules
+package-lock.json
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..439b65c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,50 @@
+
+
+
+# ⚡️🌿 ViteTwig
+
+```js
+export default {
+ plugins: [
+ twig({
+ filters: {},
+ functions: {},
+ extensions: [],
+ namespaces: {},
+ data: '*.json',
+ globals: {
+ template: 'path/to/template.twig'
+ },
+ filetypes: {
+ html: /.(json.html|twig.json.html|twig.html)$/,
+ json: /.(json.twig.html)$/
+ }
+ })
+ ]
+}
+```
+
+```html
+
+
+```
+```html
+
+ {{ title }}
+```
+```html
+
+{
+ "template": "path/to/template.twig",
+ "title": "Hello world"
+}
+```
+
+### Requirements
+
+- [Node.js LTS (16.x)](https://nodejs.org/en/download/)
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..2476c55
--- /dev/null
+++ b/index.js
@@ -0,0 +1,150 @@
+import { dirname, extname, resolve, relative } from 'path'
+import fs from 'fs'
+import process from 'node:process'
+import FastGlob from 'fast-glob'
+import lodash from 'lodash'
+import Twig from 'twig'
+import chalk from 'chalk'
+import { fileURLToPath } from 'url'
+
+const { name } = JSON.parse(fs.readFileSync(resolve(dirname((fileURLToPath(import.meta.url))), 'package.json')).toString())
+const defaultOptions = {
+ filters: {},
+ functions: {},
+ extensions: [],
+ globals: {},
+ namespaces: {},
+ data: '',
+ filetypes: {
+ html: /.(json.html|twig.json.html|twig.html)$/,
+ json: /.(json.twig.html)$/
+ }
+}
+
+function processData(paths, data = {}) {
+ let context = {}
+
+ lodash.merge(context, data)
+
+ FastGlob.sync(paths).forEach(entry => {
+ const path = resolve(process.cwd(), entry)
+
+ context = lodash.merge(context, JSON.parse(fs.readFileSync(path).toString()))
+ })
+
+ return context
+}
+
+const renderTemplate = (filename, content, options) => {
+ const output = {}
+ const context = processData(options.data, options.globals)
+
+ Twig.cache(false)
+
+ if (!Array.isArray(options.extensions)) {
+ throw new TypeError('\'extensions\' needs to be an array of functions!')
+ } else {
+ options.extensions.forEach((name) => {
+ Twig.extend(name)
+ })
+ }
+
+ Object.keys(options.functions).forEach(name => {
+ if (typeof options.functions[name] !== 'function') {
+ throw new TypeError(`${name} needs to be a function!`)
+ }
+
+ Twig.extendFunction(name, options.functions[name])
+ })
+
+ Object.keys(options.filters).forEach(name => {
+ if (typeof options.filters[name] !== 'function') {
+ throw new TypeError(`${name} needs to be a function!`)
+ }
+
+ Twig.extendFilter(name, options.filters[name])
+ })
+
+ if (
+ filename.endsWith('.json.html') ||
+ filename.endsWith('.json')
+ ) {
+ lodash.merge(context, JSON.parse(fs.readFileSync(filename).toString()))
+
+ content = '{% include template %}'
+ filename = options.root
+
+ context.template = relative(process.cwd(), context.template)
+ } else if (fs.existsSync(filename + '.json')) {
+ lodash.merge(context, JSON.parse(fs.readFileSync(filename + '.json').toString()))
+ }
+
+ try {
+ output.content = Twig.twig({
+ async: true,
+ data: content,
+ path: filename,
+ namespaces: options.namespaces,
+ rethrow: true
+ }).render(context)
+ } catch (error) {
+ output.error = error
+ }
+
+ return output
+}
+
+const plugin = (options = {}) => {
+ options = lodash.merge(defaultOptions, options)
+
+ return {
+ name,
+ config: ({ root }) => {
+ options.root = root
+ },
+ transformIndexHtml: {
+ enforce: 'pre',
+ async transform(content, { path, filename, server }) {
+ if (
+ !options.filetypes.html.test(path) &&
+ !options.filetypes.json.test(path) &&
+ !content.startsWith('