Skip to content

Commit

Permalink
feat: 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
lubomirblazekcz authored Jun 27, 2023
1 parent 06accef commit 3b91629
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 155 deletions.
6 changes: 2 additions & 4 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
"rules": {
"indent": ["error", 4],
"no-new": "off",
"space-before-function-paren": ["error", "never"],
"import/extensions": ["error", "ignorePackages"],
"multiline-ternary": ["error", "never"]
"import/extensions": ["error", "ignorePackages"]
},
"env": {
"browser": true
},
"ignorePatterns": ["src", "public"]
"ignorePatterns": ["**/*.d.ts"]
}
49 changes: 13 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,29 @@
import twig from '@vituum/vite-plugin-twig'

export default {
plugins: [
twig({
reload: true,
root: null,
filters: {},
functions: {},
extensions: [],
namespaces: {},
globals: {
template: 'path/to/template.twig'
},
data: '*.json',
filetypes: {
html: /.(json.html|twig.json.html|twig.html)$/,
json: /.(json.twig.html)$/
},
twig: {
compileOptions: {},
renderOptions: {}
}
})
]
plugins: [
twig()
],
build: {
rollupOptions: {
input: ['index.twig.html']
}
}
}
```

Read the [docs](https://vituum.dev/config/integrations-options.html#vituum-twig) to learn more about the plugin options.
* Read the [docs](https://vituum.dev/plugins/twig.html) to learn more about the plugin options.
* Use with [Vituum](https://vituum.dev) to get multi-page support.

## Basic usage

```html
<!-- index.html -->
<script type="application/json" data-format="twig">
{
"template": "path/to/template.twig",
"title": "Hello world"
}
</script>
```
or
```html
<!-- index.twig.html -->
<!-- index.twig with index.twig.json -->
{{ title }}
```
or
```html
<!-- index.json.html or index.twig.json.html -->
<!-- index.json -->
{
"template": "path/to/template.twig",
"title": "Hello world"
Expand All @@ -62,4 +39,4 @@ or
### Requirements

- [Node.js LTS (16.x)](https://nodejs.org/en/download/)
- [Vite](https://vitejs.dev/) or [Vituum](https://vituum.dev/)
- [Vite](https://vitejs.dev/)
211 changes: 105 additions & 106 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,77 +1,85 @@
import { dirname, resolve, relative } from 'path'
import fs from 'fs'
import process from 'node:process'
import FastGlob from 'fast-glob'
import { relative, resolve } from 'node:path'
import fs from 'node:fs'
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())
import {
getPackageInfo,
merge,
pluginBundle,
pluginMiddleware,
pluginReload, pluginTransform,
processData
} from 'vituum/utils/common.js'
import { renameBuildEnd, renameBuildStart } from 'vituum/utils/build.js'

const { name } = getPackageInfo(import.meta.url)

/**
* @type {import('@vituum/vite-plugin-twig/types').PluginUserConfig}
*/
const defaultOptions = {
reload: true,
root: null,
filters: {},
functions: {},
extensions: [],
namespaces: {},
globals: {},
data: '',
filetypes: {
html: /.(json.html|twig.json.html|twig.html)$/,
json: /.(json.twig.html)$/
globals: {
format: 'twig'
},
twig: {
data: ['src/data/**/*.json'],
formats: ['twig', 'json.twig', 'json'],
ignoredPaths: [],
options: {
compileOptions: {},
renderOptions: {}
}
}

function processData(paths, data = {}) {
let context = {}

lodash.merge(context, data)

const normalizePaths = Array.isArray(paths) ? paths.map(path => path.replace(/\\/g, '/')) : paths.replace(/\\/g, '/')

FastGlob.sync(normalizePaths).forEach(entry => {
const path = resolve(process.cwd(), entry)

context = lodash.merge(context, JSON.parse(fs.readFileSync(path).toString()))
})

return context
}

const renderTemplate = async(filename, content, options) => {
const renderTemplate = async ({ filename, server, resolvedConfig }, content, options) => {
const initialFilename = filename.replace('.html', '')
const output = {}
const context = options.data ? processData(options.data, options.globals) : options.globals

const isJson = filename.endsWith('.json.html') || filename.endsWith('.json')
const isHtml = filename.endsWith('.html') && !options.filetypes.html.test(filename) && !options.filetypes.json.test(filename) && !isJson

if (isJson || isHtml) {
lodash.merge(context, isHtml ? content : JSON.parse(fs.readFileSync(filename).toString()))
const context = options.data
? processData({
paths: options.data,
root: resolvedConfig.root
}, options.globals)
: options.globals

if (initialFilename.endsWith('.json')) {
lodash.merge(context, JSON.parse(content))

if (!options.formats.includes(context.format)) {
return new Promise((resolve) => {
output.content = content
resolve(output)
})
}

content = '{% include template %}'

if (typeof context.template === 'undefined') {
console.error(chalk.red(name + ' template must be defined - ' + filename))
}
const error = `${name}: template must be defined for file ${initialFilename}`

context.template = relative(process.cwd(), context.template).startsWith(relative(process.cwd(), options.root)) ? resolve(process.cwd(), context.template) : resolve(options.root, context.template)
return new Promise((resolve) => {
output.error = error
resolve(output)
})
}

context.template = relative(resolvedConfig.root, context.template).startsWith(relative(resolvedConfig.root, options.root)) ? resolve(resolvedConfig.root, context.template) : resolve(options.root, context.template)
context.template = relative(options.root, context.template)
} else if (fs.existsSync(filename + '.json')) {
lodash.merge(context, JSON.parse(fs.readFileSync(filename + '.json').toString()))
} else if (fs.existsSync(initialFilename + '.json')) {
lodash.merge(context, JSON.parse(fs.readFileSync(`${initialFilename}.json`).toString()))
}

Twig.cache(false)

if (!Array.isArray(options.extensions)) {
throw new TypeError('\'extensions\' needs to be an array of functions!')
} else {
options.extensions.forEach((name) => {
options.extensions.forEach(name => {
// noinspection JSCheckFunctionSignatures
Twig.extend(name)
})
}
Expand All @@ -92,80 +100,71 @@ const renderTemplate = async(filename, content, options) => {
Twig.extendFilter(name, options.filters[name])
})

output.content = await Twig.twig(Object.assign({
allowAsync: true,
data: content,
path: options.root + '/',
namespaces: options.namespaces,
rethrow: true
}, options.twig.compileOptions)).renderAsync(context, options.twig.renderOptions).catch((error) => {
output.error = error
})
return new Promise((resolve) => {
const onError = (error) => {
output.error = error
resolve(output)
}

return output
const onSuccess = (content) => {
output.content = content
resolve(output)
}

Twig.twig(Object.assign({
allowAsync: true,
data: content,
path: options.root + '/',
namespaces: options.namespaces,
rethrow: true
}, options.options.compileOptions)).renderAsync(context, options.options.renderOptions).catch(onError).then(onSuccess)
})
}

/**
* @param {import('@vituum/vite-plugin-twig/types').PluginUserConfig} options
* @returns [import('vite').Plugin]
*/
const plugin = (options = {}) => {
options = lodash.merge(defaultOptions, options)
let resolvedConfig
let userEnv

options = merge(defaultOptions, options)

return {
return [{
name,
config: ({ root }) => {
config (userConfig, env) {
userEnv = env
},
configResolved (config) {
resolvedConfig = config

if (!options.root) {
options.root = root
options.root = config.root
}
},
transformIndexHtml: {
enforce: 'pre',
async transform(content, { path, filename, server }) {
path = path.replace('?raw', '')
filename = filename.replace('?raw', '')

if (
!options.filetypes.html.test(path) &&
!options.filetypes.json.test(path) &&
!content.startsWith('<script type="application/json" data-format="twig"')
) {
return content
}

if (content.startsWith('<script type="application/json" data-format="twig"')) {
const matches = content.matchAll(/<script\b[^>]*data-format="(?<format>[^>]+)"[^>]*>(?<data>[\s\S]+?)<\/script>/gmi)

for (const match of matches) {
content = JSON.parse(match.groups.data)
}
}

const render = await renderTemplate(filename, content, options)

if (render.error) {
if (!server) {
console.error(chalk.red(render.error))
return
}

setTimeout(() => server.ws.send({
type: 'error',
err: {
message: render.error.message,
plugin: name
}
}), 50)
}

return render.content
buildStart: async () => {
if (userEnv.command !== 'build' || !resolvedConfig.build.rollupOptions.input) {
return
}

await renameBuildStart(resolvedConfig.build.rollupOptions.input, options.formats)
},
handleHotUpdate({ file, server }) {
if (
(typeof options.reload === 'function' && options.reload(file)) ||
(typeof options.reload === 'boolean' && options.reload && (options.filetypes.html.test(file) || options.filetypes.json.test(file)))
) {
server.ws.send({ type: 'full-reload' })
buildEnd: async () => {
if (userEnv.command !== 'build' || !resolvedConfig.build.rollupOptions.input) {
return
}
}
}

await renameBuildEnd(resolvedConfig.build.rollupOptions.input, options.formats)
},
transformIndexHtml: {
order: 'pre',
async transform (content, { path, filename, server }) {
return pluginTransform(content, { path, filename, server }, { name, options, resolvedConfig, renderTemplate })
}
},
handleHotUpdate: ({ file, server }) => pluginReload({ file, server }, options)
}, pluginBundle(options.formats), pluginMiddleware(name, options.formats)]
}

export default plugin
31 changes: 22 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
{
"name": "@vituum/vite-plugin-twig",
"version": "0.1.7",
"version": "1.0.0",
"type": "module",
"main": "index.js",
"types": "types/index.d.ts",
"scripts": {
"tsc": "tsc",
"eslint": "eslint '**/*.js' --fix"
},
"dependencies": {
"twig": "^1.15.4",
"lodash": "^4.17.21",
"chalk": "^5.2.0",
"fast-glob": "^3.2.12"
"vituum": "^1.0.0",
"twig": "^1.16.0",
"lodash": "^4.17.21"
},
"devDependencies": {
"eslint": "^8.29.0",
"eslint-config-standard": "^17.0.0"
"@types/node": "^20.3.2",
"@types/twig": "^1.12.9",
"typescript": "^5.1.3",
"vite": "^4.3.9",
"eslint": "^8.43.0",
"eslint-config-standard": "^17.1.0"
},
"files": [
"index.js"
"index.js",
"types"
],
"exports": {
".": "./index.js",
"./types": "./types/*"
},
"engines": {
"node": ">=16.0.0",
"npm": ">=8.0.0"
"npm": ">=9.0.0"
},
"repository": {
"type": "git",
Expand Down
Loading

0 comments on commit 3b91629

Please sign in to comment.