├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .npmrc
├── .prettierrc
├── LICENSE
├── README.md
├── package.json
├── src
└── index.js
├── tests
├── __snapshots__
│ └── smoke.test.js.snap
├── fixtures
│ ├── button.twig
│ ├── drupal-functions.twig
│ ├── error-include.twig
│ ├── error.twig
│ ├── jabba
│ │ ├── atoms
│ │ │ └── badge
│ │ │ │ └── badge.twig
│ │ ├── button
│ │ │ └── button.twig
│ │ └── card
│ │ │ └── card.html.twig
│ ├── macro.twig
│ ├── menu.twig
│ ├── mockup.twig
│ └── section.twig
└── smoke.test.js
└── vite.config.js
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | name: validate
2 | on:
3 | push:
4 | branches:
5 | - 'main'
6 | - 'beta'
7 | pull_request: {}
8 | jobs:
9 | main:
10 | strategy:
11 | matrix:
12 | node: [18, 20]
13 | vite: [4, 5, 6]
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: 🛑 Cancel Previous Runs
17 | uses: styfle/cancel-workflow-action@0.9.0
18 |
19 | - name: ⬇️ Checkout repo
20 | uses: actions/checkout@v2
21 |
22 | - name: ⎔ Setup node
23 | uses: actions/setup-node@v2
24 | with:
25 | node-version: ${{ matrix.node }}
26 |
27 | - name: 📥 Download deps
28 | uses: bahmutov/npm-install@v1
29 | with:
30 | useLockFile: false
31 |
32 | - name: ⚡️ Setup vite
33 | run: npm install vite@${{ matrix.vite }}
34 |
35 | - name: 🧹 Linting
36 | run: npm run lint
37 |
38 | - name: ✅ Tests
39 | run: npm run test
40 |
41 | release:
42 | needs: main
43 | runs-on: ubuntu-latest
44 | if:
45 | ${{ github.repository == 'larowlan/vite-plugin-twig-drupal' &&
46 | contains('refs/heads/main',github.ref) && github.event_name == 'push' }}
47 | steps:
48 | - name: 🛑 Cancel Previous Runs
49 | uses: styfle/cancel-workflow-action@0.9.0
50 |
51 | - name: ⬇️ Checkout repo
52 | uses: actions/checkout@v2
53 |
54 | - name: ⎔ Setup node
55 | uses: actions/setup-node@v2
56 | with:
57 | node-version: 18
58 |
59 | - name: 📥 Download deps
60 | uses: bahmutov/npm-install@v1
61 | with:
62 | useLockFile: false
63 |
64 | - name: 🏗 Run build script
65 | run: npm run build
66 |
67 | - name: 🚀 Release
68 | uses: cycjimmy/semantic-release-action@v4
69 | with:
70 | semantic_version: 22
71 | branches: |
72 | [
73 | 'main'
74 | ]
75 | env:
76 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
77 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
78 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage
2 | dist
3 | .idea
4 | node_modules
5 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org/
2 | package-lock=false
3 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "endOfLine": "lf",
3 | "semi": false,
4 | "singleQuote": false,
5 | "tabWidth": 2,
6 | "trailingComma": "es5",
7 | "bracketSpacing": true
8 | }
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Lee Rowlands
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
Vite Plugin Twig Drupal
3 |
4 |
10 |
11 |
12 |
A Vite plugin that handles transforming twig files into a Javascript function that can be used with Storybook.
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | [](https://github.com/larowlan/vite-plugin-twig-drupal/actions/workflows/node.js.yml)
21 | [![version][version-badge]][package] [![downloads][downloads-badge]][npmtrends]
22 | [![MIT License][license-badge]][license]
23 | [![PRs Welcome][prs-badge]][prs]
24 |
25 | [![Watch on GitHub][github-watch-badge]][github-watch]
26 | [![Star on GitHub][github-star-badge]][github-star]
27 | [![Tweet][twitter-badge]][twitter]
28 |
29 |
30 | ## Table of Contents
31 |
32 |
33 |
34 |
35 | - [The problem](#the-problem)
36 | - [This solution](#this-solution)
37 | - [Installation](#installation)
38 | - [Examples](#examples)
39 | - [Usage with React](#usage-with-react)
40 | - [Issues](#issues)
41 | - [🐛 Bugs](#-bugs)
42 | - [💡 Feature Requests](#-feature-requests)
43 | - [❓ Questions](#-questions)
44 | - [LICENSE](#license)
45 |
46 |
47 |
48 | ## The problem
49 |
50 | You are working with Twig in a styleguide-driven-development process. You are writing isolated components
51 | that consist of css, twig and Javascript.
52 | You want to be able to use twig to render your components for [Storybook](https://storybook.js.org).
53 | You want fast refresh with Vite.
54 | You want Twig embeds, includes and extends to work.
55 | You want to use Drupal specific twig features like create_attributes etc.
56 |
57 | ## This solution
58 |
59 | The `Vite plugin Twig Drupal` is a Vite plugin based on [Twig JS](https://github.com/twigjs/twig.js) for
60 | compiling Twig-based components into a Javascript function so that they can be used as components with Storybook.
61 | It allows you to import twig files into your story as though they are Javascript files.
62 |
63 | ### Comparison to other solutions.
64 |
65 | * [Vite plugin twig loader](https://github.com/dark-kitt/vite-plugin-twig-loader) Doesn't handle nested includes/embeds/extends. These are a fairly crucial feature of twig when building a component library as they allow re-use and DRY principles
66 | * [Components library server](https://www.drupal.org/project/cl_server) Requires you to have a running Drupal site. Whilst this ensures your twig output is identical to that of Drupal (because Drupal is doing the rendering), it is a bit more involved to setup. If you're going to use [single directory components](https://www.drupal.org/project/cl_components) or a similar Drupal module like [UI patterns](https://www.drupal.org/project/ui_patterns) then this may be a better option for you.
67 |
68 | ## Installation
69 |
70 | This module is distributed via [npm][npm] which is bundled with [node][node] and
71 | should be installed as one of your project's `devDependencies`:
72 |
73 | ```
74 | npm install --save-dev vite-plugin-twig-drupal
75 | ```
76 |
77 | You then need to configure your vite.config.js.
78 |
79 | ```javascript
80 | import { defineConfig } from "vite"
81 | import twig from 'vite-plugin-twig-drupal';
82 | import { join } from "node:path"
83 |
84 | export default defineConfig({
85 | plugins: [
86 | // Other vite plugins.
87 | twig({
88 | namespaces: {
89 | components: join(__dirname, "/path/to/your/components"),
90 | // Other namespaces as required.
91 | },
92 | // Optional if you are using React storybook renderer. The default is 'html' and works with storybook's html
93 | // renderer.
94 | // framework: 'react'
95 | functions: {
96 | // You can add custom functions - each is a function that is passed the active Twig instance and should call
97 | // e.g. extendFunction to register a function
98 | reverse: (twigInstance) => twigInstance.extendFunction("reverse", () => (text) => text.split(' ').reverse().join(' ')),
99 | // e.g. extendFilter to register a filter
100 | clean_unique_id: (twigInstance) => twigInstance.extendFilter("clean_unique_id", () => (text) => text.split(' ').reverse().join(' ')),
101 | },
102 | globalContext: {
103 | // Global variables that should be present in all templates.
104 | active_theme: 'my_super_theme',
105 | is_front_page: false,
106 | },
107 | }),
108 | // Other vite plugins.
109 | ],
110 | })
111 | ```
112 |
113 | With this config in place you should be able to import twig files into your story files.
114 |
115 | ## Examples
116 |
117 | ```javascript
118 | // stories/Button.stories.js
119 |
120 | // Button will be a Javascript function that accepts variables for the twig template.
121 | import Button from './button.twig';
122 |
123 | // Import stylesheets, this could be a sass or postcss file too.
124 | import './path/to/button.css';
125 | // You may also have JavaScript for the component.
126 | import './path/to/some/javascript/button.js';
127 |
128 | export default {
129 | title: 'Components/Button',
130 | tags: ['autodocs'],
131 | argTypes: {
132 | title: {
133 | control: { type: 'text' },
134 | },
135 | modifier: {
136 | control: { type: 'select' },
137 | options: ['primary', 'secondary', 'tertiary'],
138 | },
139 | },
140 | // Just pass along the imported variable.
141 | component: Button,
142 | };
143 |
144 | // Set default variables in the story.
145 | export const Default = {
146 | args: { title: 'Click me' },
147 | };
148 |
149 | export const Primary = {
150 | args: { title: 'Click me', modifier: 'primary' },
151 | };
152 |
153 | // Advanced example.
154 | export const ButtonStrip = {
155 | name: 'Button group',
156 | render: () => `
157 | ${Button({title: 'Button 1', modifier: 'primary'})}
158 | ${Button({title: 'Button 2', modifier: 'secondary'})}
159 | `
160 | }
161 | ```
162 |
163 | ## Usage with React
164 |
165 | When adding `framework: 'react'` to vite.config.js twig files will output React JSX functions
166 | that can be used inside a React Storybook instance.
167 |
168 | This way Twig components can be rendered alongside React components.
169 |
170 | However, you will need to revert to a straight TwigJS function import so you can nest Twig components
171 | inside other Twig components. In these instances append `?twig` to your component import. This will return a JavaScript function instead of a React component.
172 | When nesting
173 | Twig components inside React components this is not needed. Nesting React components inside Twig components
174 | does not work currently.
175 |
176 | ```javascript
177 | import Button from './button.twig';
178 | import OtherComponent from ../other-component.twig?twig
179 |
180 | export default {
181 | title: 'Components/Button',
182 | tags: ['autodocs'],
183 | args: {
184 | // Render by calling the component as a function.
185 | // You can pass any variables down as an object.
186 | otherComponent: OtherComponent(),
187 | },
188 | component: Button,
189 | };
190 |
191 | ```
192 |
193 | ## Issues
194 |
195 | ### 🐛 Bugs
196 |
197 | Please file an issue for bugs, missing documentation, or unexpected behavior.
198 |
199 | [**See Bugs**][bugs]
200 |
201 | ### 💡 Feature Requests
202 |
203 | Please file an issue to suggest new features. Vote on feature requests by adding
204 | a 👍. This helps maintainers prioritize what to work on.
205 |
206 | [**See Feature Requests**][requests]
207 |
208 | ### ❓ Questions
209 |
210 | For questions related to using the library, please visit a support community
211 | instead of filing an issue on GitHub.
212 |
213 | - [Drupal Slack #frontend channel](https://drupal.org/slack)
214 |
215 | ## LICENSE
216 |
217 | [MIT](LICENSE)
218 |
219 |
220 |
221 | [npm]: https://www.npmjs.com/
222 | [node]: https://nodejs.org
223 | [version-badge]: https://img.shields.io/npm/v/vite-plugin-twig-drupal.svg?style=flat-square
224 | [package]: https://www.npmjs.com/package/vite-plugin-twig-drupal
225 | [downloads-badge]: https://img.shields.io/npm/dm/vite-plugin-twig-drupal.svg?style=flat-square
226 | [npmtrends]: http://www.npmtrends.com/vite-plugin-twig-drupal
227 | [license-badge]: https://img.shields.io/npm/l/vite-plugin-twig-drupal.svg?style=flat-square
228 | [license]: https://github.com/larowlan/vite-plugin-twig-drupal/blob/master/LICENSE
229 | [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square
230 | [prs]: http://makeapullrequest.com
231 | [github-watch-badge]: https://img.shields.io/github/watchers/larowlan/vite-plugin-twig-drupal.svg?style=social
232 | [github-watch]: https://github.com/larowlan/vite-plugin-twig-drupal/watchers
233 | [github-star-badge]: https://img.shields.io/github/stars/larowlan/vite-plugin-twig-drupal.svg?style=social
234 | [github-star]: https://github.com/larowlan/vite-plugin-twig-drupal/stargazers
235 | [twitter]: https://twitter.com/intent/tweet?text=Check%20out%20vite-plugin-twig-drupal%20by%20%40larowlan%20https%3A%2F%2Fgithub.com%2Flarowlan%2Fvite-plugin-twig-drupal%20%F0%9F%91%8D
236 | [twitter-badge]: https://img.shields.io/twitter/url/https/github.com/larowlan/vite-plugin-twig-drupal.svg?style=social
237 | [bugs]: https://github.com/larowlan/vite-plugin-twig-drupal/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Acreated-desc
238 | [requests]: https://github.com/larowlan/vite-plugin-twig-drupal/issues?q=is%3Aissue+sort%3Areactions-%2B1-desc+label%3Aenhancement+is%3Aopen
239 | [good-first-issue]: https://github.com/larowlan/vite-plugin-twig-drupal/issues?utf8=✓&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3A"good+first+issue"+
240 |
241 |
242 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-plugin-twig-drupal",
3 | "version": "1.0.0",
4 | "description": "Provides a ⚡️ Vite plugin to transform 🌱 Twig into HTML with a 💧 Drupal flavour",
5 | "keywords": [
6 | "Vite",
7 | "Vite plugin",
8 | "Twig",
9 | "Storybook",
10 | "Drupal"
11 | ],
12 | "author": "larowlan <23667-larowlan@users.noreply.drupalcode.org>",
13 | "license": "GPL-2.0-or-later",
14 | "main": "./src/index.js",
15 | "type": "module",
16 | "scripts": {
17 | "build": "rm -rf dist && vite build",
18 | "format": "prettier --write \"{test,src}/**/*.js\"",
19 | "lint": "prettier --check \"{test,src}/**/*.js\"",
20 | "test": "vitest run",
21 | "pretest": "npm run-script build",
22 | "semantic-release": "semantic-release"
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "https://github.com/larowlan/vite-plugin-twig-drupal.git"
27 | },
28 | "dependencies": {
29 | "drupal-attribute": "^1.0.2",
30 | "drupal-twig-extensions": "^1.0.0-beta.5",
31 | "twig": "^1.16.0"
32 | },
33 | "peerDependencies": {
34 | "vite": "^4.4.11 || ^5 || ^6"
35 | },
36 | "devDependencies": {
37 | "prettier": "^3.0.3",
38 | "semantic-release": "^22.0.5",
39 | "vite": "^4.4.11 || ^ 5",
40 | "vitest": "^0.34.6"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Twig from "twig"
2 | import { join, resolve, dirname } from "node:path"
3 | import { existsSync, readdirSync } from "node:fs"
4 | import { normalizePath } from "vite"
5 |
6 | const { twig } = Twig
7 |
8 | const FRAMEWORK_REACT = "react"
9 | const FRAMEWORK_HTML = "html"
10 |
11 | const defaultOptions = {
12 | namespaces: {},
13 | filters: {},
14 | functions: {},
15 | globalContext: {},
16 | framework: FRAMEWORK_HTML,
17 | pattern: /\.(twig)(\?.*)?$/,
18 | }
19 | Twig.cache(false)
20 |
21 | const includeTokenTypes = [
22 | "Twig.logic.type.embed",
23 | "Twig.logic.type.include",
24 | "Twig.logic.type.extends",
25 | "Twig.logic.type.import",
26 | ]
27 |
28 | const findInChildDirectories = (directory, component) => {
29 | const files = readdirSync(directory, { recursive: true })
30 | for (const file of files) {
31 | const filePath = join(directory, file)
32 | if (file.endsWith(`/${component}.twig`)) {
33 | return filePath
34 | }
35 | }
36 |
37 | return null
38 | }
39 |
40 | const resolveFile = (directory, file) => {
41 | const filesToTry = [file, `${file}.twig`, `${file}.html.twig`]
42 | for (const ix in filesToTry) {
43 | const path = resolve(filesToTry[ix])
44 | if (existsSync(path)) {
45 | return normalizePath(path)
46 | }
47 | const withDir = resolve(directory, filesToTry[ix])
48 | if (existsSync(withDir)) {
49 | return normalizePath(withDir)
50 | }
51 | }
52 |
53 | return normalizePath(resolve(directory, file))
54 | }
55 |
56 | const pluckIncludes = (tokens) => {
57 | return [
58 | ...tokens
59 | .filter((token) => includeTokenTypes.includes(token.token?.type))
60 | .reduce(
61 | (carry, token) => [
62 | ...carry,
63 | ...token.token.stack.map((stack) => stack.value),
64 | ],
65 | []
66 | ),
67 | ...tokens.reduce(
68 | (carry, token) => [...carry, ...pluckIncludes(token.token?.output || [])],
69 | []
70 | ),
71 | ].filter((value, index, array) => {
72 | return array.indexOf(value) === index
73 | })
74 | }
75 |
76 | const resolveNamespaceOrComponent = (namespaces, template) => {
77 | let resolveTemplate = template
78 | const isNamespace = template.includes(":")
79 |
80 | // Support for SDC.
81 | if (isNamespace) {
82 | const [namespace, component] = template.split(":")
83 | resolveTemplate = `@${namespace}/${component}/${component}`
84 | }
85 | let expandedPath = Twig.path.expandNamespace(namespaces, resolveTemplate)
86 |
87 | // If file not found and we are in namespace -> search deeper.
88 | if (!existsSync(expandedPath) && isNamespace) {
89 | const [namespace, component] = template.split(":")
90 | let foundFile = findInChildDirectories(namespaces[namespace], component)
91 | if (existsSync(foundFile)) {
92 | expandedPath = foundFile
93 | }
94 | }
95 |
96 | return expandedPath
97 | }
98 |
99 | const compileTemplate = (id, file, { namespaces }) => {
100 | return new Promise((resolve, reject) => {
101 | const options = { namespaces, rethrow: true, allowInlineIncludes: true }
102 | twig({
103 | id,
104 | path: file,
105 | error: reject,
106 | allowInlineIncludes: true,
107 | load(template) {
108 | if (typeof template.tokens === "undefined") {
109 | reject("Error compiling twig file")
110 | return
111 | }
112 | resolve({
113 | includes: pluckIncludes(template.tokens),
114 | code: template.compile(options),
115 | })
116 | },
117 | })
118 | })
119 | }
120 |
121 | Twig.cache(false)
122 |
123 | const errorHandler =
124 | (id, isDefault = true) =>
125 | (e) => {
126 | if (isDefault) {
127 | return {
128 | code: `export default () => 'An error occurred whilst rendering ${id}: ${e.toString()} ${
129 | e.stack
130 | }';`,
131 | map: null,
132 | }
133 | }
134 | return {
135 | code: null,
136 | map: null,
137 | }
138 | }
139 |
140 | const plugin = (options = {}) => {
141 | options = { ...defaultOptions, ...options }
142 | return {
143 | name: "vite-plugin-twig-drupal",
144 | config: ({ root }) => {
145 | if (!options.root) {
146 | options.root = root
147 | }
148 | },
149 | async shouldTransformCachedModule(src, id) {
150 | return options.pattern.test(id)
151 | },
152 | async transform(src, id) {
153 | if (options.pattern.test(id)) {
154 | let frameworkInclude = ""
155 | let frameworkTransform = "const frameworkTransform = (html) => html;"
156 |
157 | let asTwigJs = id.match(/\?twig$/)
158 |
159 | if (options.framework === FRAMEWORK_REACT && !asTwigJs) {
160 | frameworkInclude = `import React from 'react'`
161 | frameworkTransform = `const frameworkTransform = (html) => React.createElement('div', {dangerouslySetInnerHTML: {'__html': html}});;`
162 | }
163 |
164 | if (asTwigJs) {
165 | // Tidy up file path by remove ?twig
166 | id = id.slice(0, -5)
167 | }
168 |
169 | let embed,
170 | embeddedIncludes,
171 | functions,
172 | code,
173 | includes,
174 | seen = []
175 |
176 | try {
177 | const result = await compileTemplate(id, id, options).catch(
178 | errorHandler(id)
179 | )
180 | if ("map" in result) {
181 | // An error occurred.
182 | return result
183 | }
184 | code = result.code
185 | includes = result.includes
186 | const includePromises = []
187 | const processIncludes = (template) => {
188 | const file = resolveFile(
189 | dirname(id),
190 | resolveNamespaceOrComponent(options.namespaces, template)
191 | )
192 | if (!seen.includes(template)) {
193 | includePromises.push(
194 | new Promise(async (resolve, reject) => {
195 | const { includes, code } = await compileTemplate(
196 | template,
197 | file,
198 | options
199 | ).catch(errorHandler(template, false))
200 | if (includes) {
201 | includes.forEach(processIncludes)
202 | }
203 | resolve(code)
204 | })
205 | )
206 | seen.push(template)
207 | }
208 | }
209 | includes.forEach(processIncludes)
210 | embed = includes
211 | .filter((template) => template !== "_self")
212 | .map(
213 | (template) =>
214 | `import '${resolveFile(
215 | dirname(id),
216 | resolveNamespaceOrComponent(options.namespaces, template)
217 | )}';`
218 | )
219 | .join("\n")
220 |
221 | functions = Object.entries(options.functions)
222 | .map(([name, value]) => {
223 | return `
224 | const ${name} = ${value};
225 | ${name}(Twig);
226 | `
227 | })
228 | .join("\n")
229 |
230 | const includeResult = await Promise.all(includePromises).catch(
231 | errorHandler(id)
232 | )
233 | if (!Array.isArray(includeResult) && "map" in includeResult) {
234 | // An error occurred.
235 | return includeResult
236 | }
237 | embeddedIncludes = includeResult.reverse().join("\n")
238 | } catch (e) {
239 | return errorHandler(id)(e)
240 | }
241 | const output = `
242 | import Twig, { twig } from 'twig';
243 | import DrupalAttribute from 'drupal-attribute';
244 | import { addDrupalExtensions } from 'drupal-twig-extensions/twig';
245 | ${frameworkInclude}
246 |
247 | ${embed}
248 |
249 | ${functions}
250 |
251 | addDrupalExtensions(Twig);
252 |
253 | // Disable caching.
254 | Twig.cache(false);
255 |
256 |
257 | ${embeddedIncludes};
258 | ${frameworkTransform};
259 | export default (context = {}) => {
260 | const component = ${code}
261 | ${includes ? `component.options.allowInlineIncludes = true;` : ""}
262 | try {
263 | let defaultAttributes = context.defaultAttributes ? context.defaultAttributes : [];
264 | if (!Array.isArray(defaultAttributes)) {
265 | // We were passed a map, turn it into an array.
266 | defaultAttributes = Object.entries(defaultAttributes);
267 | }
268 | return frameworkTransform(component.render({
269 | attributes: new DrupalAttribute(defaultAttributes),
270 | ...${JSON.stringify(options.globalContext)},
271 | ...context
272 | }));
273 | }
274 | catch (e) {
275 | return frameworkTransform('An error occurred whilst rendering ${id}: ' + e.toString());
276 | }
277 | }`
278 | return {
279 | code: output,
280 | map: null,
281 | dependencies: seen,
282 | }
283 | }
284 | },
285 | }
286 | }
287 |
288 | export default plugin
289 |
--------------------------------------------------------------------------------
/tests/__snapshots__/smoke.test.js.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Basic smoke test > Should cast default attributes to attributes 1`] = `
4 | "
5 |
Include
6 |
7 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
8 |
9 |
10 |
11 |
Embed
12 |
13 | Lorem ipsum dolor sit amet.
14 |
15 | IT WORKS!
16 |
17 |
18 |
19 |
20 |
21 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
22 |
23 |
24 |
25 |
Relative include
26 |
27 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
28 |
29 |
30 |
"
47 | `;
48 |
49 | exports[`Basic smoke test > Should support default attributes as a map 1`] = `
50 | "
51 |
Include
52 |
53 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
54 |
55 |
56 |
57 |
Embed
58 |
59 | Lorem ipsum dolor sit amet.
60 |
61 | IT WORKS!
62 |
63 |
64 |
65 |
66 |
67 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
68 |
69 |
70 |
71 |
Relative include
72 |
73 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
74 |
75 |
76 |
SDC
77 |
Card
78 |
atom badge from nested dir 🙌
79 |
80 |
81 |
82 |
Include card
83 |
🏆️ winning
84 |
atom badge from nested dir 🙌
85 |
86 |
hey there
87 | "
88 | `;
89 |
90 | exports[`Basic smoke test > Should support global context and functions 1`] = `
91 | "
92 |
Include
93 |
94 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
95 |
96 |
97 |
98 |
Embed
99 |
100 | Lorem ipsum dolor sit amet.
101 |
102 | IT WORKS!
103 |
104 |
105 |
106 |
107 |
108 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
109 |
110 |
111 |
112 |
Relative include
113 |
114 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
115 |
116 |
117 |
SDC
118 |
Card
119 |
atom badge from nested dir 🙌
120 |
121 |
122 |
123 |
Include card
124 |
🏆️ winning
125 |
atom badge from nested dir 🙌
126 |
127 |
hey there
128 | "
129 | `;
130 |
131 | exports[`Basic smoke test > Should support includes 1`] = `
132 | "
133 |
Include
134 |
135 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
136 |
137 |
138 |
139 |
Embed
140 |
141 | Lorem ipsum dolor sit amet.
142 |
143 | IT WORKS!
144 |
145 |
146 |
147 |
148 |
149 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
150 |
151 |
152 |
153 |
Relative include
154 |
155 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
156 |
157 |
158 |
SDC
159 |
Card
160 |
atom badge from nested dir 🙌
161 |
162 |
163 |
164 |
Include card
165 |
🏆️ winning
166 |
atom badge from nested dir 🙌
167 |
168 |
hey there
169 | "
170 | `;
171 |
172 | exports[`Basic smoke test > Should support macros 1`] = `
173 | "
186 | "
187 | `;
188 |
189 | exports[`Basic smoke test > Should support nested SDC 1`] = `
190 | "
191 |
Include
192 |
193 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
194 |
195 |
196 |
197 |
Embed
198 |
199 | Lorem ipsum dolor sit amet.
200 |
201 | IT WORKS!
202 |
203 |
204 |
205 |
206 |
207 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
208 |
209 |
210 |
211 |
Relative include
212 |
213 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
214 |
215 |
216 |
SDC
217 |
Card
218 |
atom badge from nested dir 🙌
219 |
220 |
221 |
222 |
Include card
223 |
🏆️ winning
224 |
atom badge from nested dir 🙌
225 |
226 |
hey there
227 | "
228 | `;
229 |
230 | exports[`Basic smoke test > Should support variables 1`] = `
231 | "
232 |
Include
233 |
234 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
235 |
236 |
237 |
238 |
Embed
239 |
240 | Lorem ipsum dolor sit amet.
241 |
242 | IT WORKS!
243 |
244 |
245 |
246 |
Pickle Fixie
247 |
248 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
249 |
250 |
251 |
252 |
Relative include
253 |
254 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. At dignissimos fugiat inventore laborum maiores molestiae neque quia quo unde veniam?
255 |
256 |
257 |