From 3b91629f7b4a7a8588a8c7c8dc2e4c5859c9f23f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubom=C3=ADr=20Bla=C5=BEek?= Date: Tue, 27 Jun 2023 10:27:33 +0200 Subject: [PATCH] feat: 1.0 --- .eslintrc | 6 +- README.md | 49 +++-------- index.js | 211 +++++++++++++++++++++++------------------------ package.json | 31 +++++-- tsconfig.json | 25 ++++++ types/index.d.ts | 20 +++++ 6 files changed, 187 insertions(+), 155 deletions(-) create mode 100644 tsconfig.json create mode 100644 types/index.d.ts diff --git a/.eslintrc b/.eslintrc index 03f2432..eafdd75 100644 --- a/.eslintrc +++ b/.eslintrc @@ -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"] } diff --git a/README.md b/README.md index 74e9c07..f648ee9 100644 --- a/README.md +++ b/README.md @@ -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 - - -``` -or -```html - + {{ title }} ``` or ```html - + { "template": "path/to/template.twig", "title": "Hello world" @@ -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/) diff --git a/index.js b/index.js index 35a821c..362540f 100644 --- a/index.js +++ b/index.js @@ -1,13 +1,22 @@ -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, @@ -15,55 +24,53 @@ const defaultOptions = { 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) @@ -71,7 +78,8 @@ const renderTemplate = async(filename, content, options) => { 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) }) } @@ -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('