Skip to content

Commit

Permalink
Merge branch 'hotfix/3.1.0-alpha.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
titouanmathis committed Dec 15, 2021
2 parents 21c131d + 95b4f66 commit 6f704ec
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 11 deletions.
20 changes: 16 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@studiometa/webpack-config-root",
"version": "3.1.0-alpha.1",
"version": "3.1.0-alpha.2",
"private": true,
"type": "commonjs",
"workspaces": [
Expand Down
2 changes: 1 addition & 1 deletion packages/demo/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@studiometa/webpack-config-demo",
"version": "3.1.0-alpha.1",
"version": "3.1.0-alpha.2",
"private": true,
"type": "module",
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions packages/demo/src/templates/pages/index.twig
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
{% include '@components/component.twig' with { content: 'Lorem ipsum' } %}
{% set foo = 'value' %}
<h1 class="text-2xl font-bold">{{ foo }}</h1>
<main>
{% html_element 'main' %}
<div data-ref="vue">Loading...</div>
<div data-ref="content"></div>
<div data-component="Component"></div>
</main>
{% end_html_element %}
{% endblock %}
3 changes: 2 additions & 1 deletion packages/webpack-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@studiometa/webpack-config",
"version": "3.1.0-alpha.1",
"version": "3.1.0-alpha.2",
"description": "A basic webpack config",
"type": "module",
"main": "src/index.js",
Expand Down Expand Up @@ -70,6 +70,7 @@
"glob": "^7.2.0",
"html-webpack-plugin": "^5.5.0",
"js-yaml-loader": "^1.2.2",
"kebab-case": "^1.0.1",
"lodash.merge": "^4.6.2",
"mini-css-extract-plugin": "^2.4.5",
"postcss": "^8.3.11",
Expand Down
69 changes: 67 additions & 2 deletions packages/webpack-config/src/presets/prototyping.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import merge from 'lodash.merge';
import twigPreset from './twig.js';
import tailwindcssPreset from './tailwindcss.js';
import extendWebpackConfig from '../utils/extend-webpack-config.js';
import Html from '../utils/Html.js';

export default async (config, options) => {
const opts = merge(
Expand All @@ -21,14 +22,78 @@ export default async (config, options) => {

opts.twig.namespaces = glob.sync('./src/templates/*/').reduce((acc, file) => {
const name = path.basename(file);
acc[name] = file;
acc[name] = path.resolve(file);
return acc;
}, opts.twig.namespaces || {});

const extendTwig = typeof opts.twig.extend === 'function' ? opts.twig.extend : () => {};
opts.twig.functions = {
...(opts?.twig?.functions || {}),
// eslint-disable-next-line camelcase
html_styles(styles) {
return Html.renderStyleAttribute(styles);
},
// eslint-disable-next-line camelcase
html_attributes(attributes) {
return Html.renderAttributes(attributes);
},
// eslint-disable-next-line camelcase
html_classes(classes) {
return Html.renderClass(classes);
},
};

opts.twig.extend = (Twig) => {
extendTwig(Twig);

Twig.exports.extendTag({
type: 'end_html_element',
regex: /^end_html_element/,
next: [],
open: false,
});

Twig.exports.extendTag({
type: 'html_element',
regex: /^html_element\s+(.+?)(?:\s|$)(?:with\s+([\S\s]+?))?$/,
next: ['end_html_element'],
open: true,
compile(token) {
const { match } = token;
const expression = match[1].trim();
const withContext = match[2];

delete token.match;

token.stack = Twig.expression.compile.call(this, {
type: Twig.expression.type.expression,
value: expression,
}).stack;

if (withContext !== undefined) {
token.withStack = Twig.expression.compile.call(this, {
type: Twig.expression.type.expression,
value: withContext.trim(),
}).stack;
}

return token;
},
parse(token, context, chain) {
const tag = Twig.expression.parse.call(this, token.stack, context);
const attributes = token.withStack
? Twig.expression.parse.call(this, token.withStack, context)
: undefined;
const content = this.parse(token.output, context);
const output = Html.renderTag(tag, attributes, content);

return {
chain,
output,
};
},
});

// Add debug comments
Twig.Templates.registerParser('twig', (params) => {
if (params.id) {
Expand All @@ -46,7 +111,7 @@ export default async (config, options) => {
(file) =>
new HtmlWebpackPlugin({
...opts.html,
template: file,
template: path.resolve(file),
filename: file.replace('./src/templates/pages/', '').replace(/\.twig$/, '.html'),
})
);
Expand Down
164 changes: 164 additions & 0 deletions packages/webpack-config/src/utils/Html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/* eslint-disable no-restricted-syntax, no-continue, no-await-in-loop */
/**
* @link https://github.com/studiometa/twig-toolkit
* @copyright Studio Meta
* @license https://github.com/studiometa/twig-toolkit/blob/master/LICENSE
*/
import toKebab from 'kebab-case';

/**
* @typdef {string | Record<string, boolean> | Record<number, Classes>} Classes
*/

/**
* Html class.
*/
export default class Html {
static SELF_CLOSING_TAGS = [
'area',
'base',
'br',
'col',
'command',
'embed',
'hr',
'img',
'input',
'keygen',
'link',
'meta',
'param',
'source',
'track',
'wbr',
];

/**
* Render classes.
* @param {Classes} classes
* @return {string}
*/
static renderClass(classes) {
if (!classes) {
return '';
}

if (typeof classes === 'string') {
return classes;
}

if (Array.isArray(classes)) {
return classes.map((c) => Html.renderClass(c)).join(' ');
}

return Object.entries(classes)
.reduce((acc, [key, value]) => {
if (value && key !== '_keys') {
acc.push(key);
}

return acc;
}, [])
.join(' ');
}

/**
* Render a style attribute.
* @param {Record<string, string|number>} styles
* @return {string}
*/
static renderStyleAttribute(styles) {
if (!styles) {
return '';
}

const renderedStyles = [];

for (const [key, value] of Object.entries(styles)) {
if (key === '_keys' || (typeof value === 'boolean' && !value) || value === '') {
continue;
}
renderedStyles.push(`${toKebab(key)}: ${value};`);
}
return renderedStyles.join(' ');
}

/**
* Render attributes.
*
* @param {Record<string, any>} attributes
* @return {string}
*/
static renderAttributes(attributes) {
if (!attributes) {
return '';
}

const renderedAttributes = [''];

for (let [key, value] of Object.entries(attributes)) {
if (key === '_keys') {
continue;
}

key = toKebab(key);
if (typeof value === 'boolean') {
if (value) {
renderedAttributes.push(key);
}
continue;
}
if (key === 'class') {
value = Html.renderClass(value);
}
if (key === 'style' && typeof value !== 'string') {
value = Html.renderStyleAttribute(value);
}
if (typeof value !== 'string') {
if (value instanceof Map) {
value = JSON.stringify(Html.mapToObject(value));
} else {
value = JSON.stringify(value);
}
}

renderedAttributes.push(`${key}="${value}"`);
}

return renderedAttributes.join(' ');
}

/**
* Convert a map to an object.
*
* @param {Map} map
* @return {Record<string, any>}
*/
static mapToObject(map) {
const obj = {};
for (const [key, value] of map.entries()) {
if (value instanceof Map) {
obj[key] = Html.mapToObject(value);
} else {
obj[key] = value;
}
}
return obj;
}

/**
* Render a tag.
*
* @param {string} name
* @param {Record<string, any>} attributes
* @param {string} content
* @return {string}
*/
static renderTag(name, attributes, content = '') {
const formattedAttributes = Html.renderAttributes(attributes);
if (Html.SELF_CLOSING_TAGS.includes(name)) {
return `<${name}${formattedAttributes} />`;
}
return `<${name}${formattedAttributes}>\n${content}\n</${name}>`;
}
}

0 comments on commit 6f704ec

Please sign in to comment.