posthtml-component 1.0.0-RC.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,119 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const {existsSync} = require('fs');
5
+
6
+ const folderSeparator = '.';
7
+
8
+ /**
9
+ * Find component path from tag name
10
+ *
11
+ * @param {String} tag Tag name
12
+ * @param {Object} options Plugin options
13
+ * @return {String|boolean} Full path or boolean false
14
+ */
15
+ module.exports = (tag, options) => {
16
+ const fileNameFromTag = tag
17
+ .replace(options.tagPrefix, '')
18
+ .split(folderSeparator)
19
+ .join(path.sep)
20
+ .concat(folderSeparator, options.fileExtension);
21
+
22
+ try {
23
+ return tag.includes(options.namespaceSeparator) ?
24
+ searchInNamespaces(tag, fileNameFromTag.split(options.namespaceSeparator), options) :
25
+ searchInFolders(tag, fileNameFromTag, options);
26
+ } catch (error) {
27
+ if (options.strict) {
28
+ throw new Error(error.message);
29
+ }
30
+ }
31
+
32
+ return false;
33
+ };
34
+
35
+ /**
36
+ * Search component file in root
37
+ *
38
+ * @param {String} tag [tag name]
39
+ * @param {String} fileNameFromTag [filename converted from tag name]
40
+ * @param {Object} options [posthtml options]
41
+ * @return {String|boolean} [custom tag root where the module is found]
42
+ */
43
+ function searchInFolders(tag, fileNameFromTag, options) {
44
+ const componentPath = search(options.root, options.folders, fileNameFromTag, options.fileExtension);
45
+
46
+ if (!componentPath) {
47
+ throw new Error(`[components] For the tag ${tag} was not found any template in defined root path ${options.folders.join(', ')}`);
48
+ }
49
+
50
+ return componentPath;
51
+ }
52
+
53
+ /**
54
+ * Search component file within all defined namespaces
55
+ *
56
+ * @param {String} tag [tag name with namespace]
57
+ * @param {String} namespace [tag's namespace]
58
+ * @param {String} fileNameFromTag [filename converted from tag name]
59
+ * @param {Object} options [posthtml options]
60
+ * @return {String|boolean} [custom tag root where the module is found]
61
+ */
62
+ function searchInNamespaces(tag, [namespace, fileNameFromTag], options) {
63
+ const namespaceOption = options.namespaces.find(n => n.name === namespace.replace(options.tagPrefix, ''));
64
+
65
+ if (!namespaceOption) {
66
+ throw new Error(`[components] Unknown component namespace ${namespace}.`);
67
+ }
68
+
69
+ let componentPath;
70
+
71
+ // 1) Check in custom root
72
+ if (namespaceOption.custom) {
73
+ componentPath = search(namespaceOption.custom, options.folders, fileNameFromTag, options.fileExtension);
74
+ }
75
+
76
+ // 2) Check in base root
77
+ if (!componentPath) {
78
+ componentPath = search(namespaceOption.root, options.folders, fileNameFromTag, options.fileExtension);
79
+ }
80
+
81
+ // 3) Check in fallback root
82
+ if (!componentPath && namespaceOption.fallback) {
83
+ componentPath = search(namespaceOption.fallback, options.folders, fileNameFromTag, options.fileExtension);
84
+ }
85
+
86
+ if (!componentPath && options.strict) {
87
+ throw new Error(`[components] For the tag ${tag} was not found any template in the defined namespace's base path ${namespaceOption.root}.`);
88
+ }
89
+
90
+ return componentPath;
91
+ }
92
+
93
+ /**
94
+ * Main search component file function
95
+ *
96
+ * @param {String} root Base root or namespace root from options
97
+ * @param {Array} folders Folders from options
98
+ * @param {String} fileName Filename converted from tag name
99
+ * @param {String} extension File extension from options
100
+ * @return {String|boolean} [custom tag root where the module is found]
101
+ */
102
+ function search(root, folders, fileName, extension) {
103
+ let componentPath;
104
+
105
+ let componentFound = folders.some(folder => {
106
+ componentPath = path.join(path.resolve(root, folder), fileName);
107
+ return existsSync(componentPath);
108
+ });
109
+
110
+ if (!componentFound) {
111
+ fileName = fileName.replace(`.${extension}`, `${path.sep}index.${extension}`);
112
+ componentFound = folders.some(folder => {
113
+ componentPath = path.join(path.resolve(root, folder), fileName);
114
+ return existsSync(componentPath);
115
+ });
116
+ }
117
+
118
+ return componentFound ? componentPath : false;
119
+ }
package/src/index.js ADDED
@@ -0,0 +1,287 @@
1
+ 'use strict';
2
+
3
+ const {readFileSync, existsSync} = require('fs');
4
+ const path = require('path');
5
+ const {parser} = require('posthtml-parser');
6
+ const {match, walk} = require('posthtml/lib/api');
7
+ const expressions = require('posthtml-expressions');
8
+ const findPathFromTag = require('./find-path');
9
+ const processProps = require('./process-props');
10
+ const processAttributes = require('./process-attributes');
11
+ const {processPushes, processStacks} = require('./process-stacks');
12
+ const {setFilledSlots, processSlotContent, processFillContent} = require('./process-slots');
13
+ const log = require('./log');
14
+ const each = require('lodash/each');
15
+ const defaults = require('lodash/defaults');
16
+ const assignWith = require('lodash/assignWith');
17
+ const mergeWith = require('lodash/mergeWith');
18
+ const template = require('lodash/template');
19
+ const get = require('lodash/get');
20
+ const has = require('lodash/has');
21
+ const isObjectLike = require('lodash/isObjectLike');
22
+ const isArray = require('lodash/isArray');
23
+ const isEmpty = require('lodash/isEmpty');
24
+ const isBoolean = require('lodash/isBoolean');
25
+ const isUndefined = require('lodash/isUndefined'); // value === undefined
26
+ const isNull = require('lodash/isNull'); // value === null
27
+ const isNil = require('lodash/isNil'); // value == null
28
+ const uniqueId = require('lodash/uniqueId');
29
+ const transform = require('lodash/transform');
30
+ const assign = require('lodash/assign');
31
+ const isPlainObject = require('lodash/isPlainObject');
32
+
33
+ /* eslint-disable complexity */
34
+ module.exports = (options = {}) => tree => {
35
+ options.root = path.resolve(options.root || './');
36
+ options.folders = options.folders || [''];
37
+ options.tagPrefix = options.tagPrefix || 'x-';
38
+ options.tag = options.tag || false;
39
+ options.attribute = options.attribute || 'src';
40
+ options.namespaces = options.namespaces || [];
41
+ options.namespaceSeparator = options.namespaceSeparator || '::';
42
+ options.fileExtension = options.fileExtension || 'html';
43
+ options.yield = options.yield || 'yield';
44
+ options.slot = options.slot || 'slot';
45
+ options.fill = options.fill || 'fill';
46
+ options.slotSeparator = options.slotSeparator || ':';
47
+ options.push = options.push || 'push';
48
+ options.stack = options.stack || 'stack';
49
+ options.propsScriptAttribute = options.propsScriptAttribute || 'props';
50
+ options.propsContext = options.propsContext || 'props';
51
+ options.propsAttribute = options.propsAttribute || 'props';
52
+ options.propsSlot = options.propsSlot || 'props';
53
+ options.expressions = options.expressions || {};
54
+ options.plugins = options.plugins || [];
55
+ options.attrsParserRules = options.attrsParserRules || {};
56
+ options.strict = typeof options.strict === 'undefined' ? true : options.strict;
57
+ options.utilities = options.utilities || {
58
+ each,
59
+ defaults,
60
+ assign: assignWith,
61
+ merge: mergeWith,
62
+ template,
63
+ get,
64
+ has,
65
+ isPlainObject,
66
+ isObject: isObjectLike,
67
+ isArray,
68
+ isEmpty,
69
+ isBoolean,
70
+ isUndefined,
71
+ isNull,
72
+ isNil,
73
+ uniqueId,
74
+ isEnabled: prop => prop === true || prop === ''
75
+ };
76
+ // Additional element attributes, in case already exist in valid-attributes.js it will replace all attributes
77
+ // It should be an object with key as tag name and as value a function modifier which receive
78
+ // the default attributes and return an array of attributes. Example:
79
+ // { TAG: (attrsibutes) => { attributes[] = 'attribute-name'; return attributes; } }
80
+ options.elementAttributes = isPlainObject(options.elementAttributes) ? options.elementAttributes : {};
81
+ options.safelistAttributes = Array.isArray(options.safelistAttributes) ? options.safelistAttributes : [];
82
+ options.blacklistAttributes = Array.isArray(options.blacklistAttributes) ? options.blacklistAttributes : [];
83
+
84
+ // Merge customizer callback passed to lodash mergeWith
85
+ // for merge attribute `props` and all attributes starting with `merge:`
86
+ // @see https://lodash.com/docs/4.17.15#mergeWith
87
+ options.mergeCustomizer = options.mergeCustomizer || ((objectValue, sourceValue) => {
88
+ if (Array.isArray(objectValue)) {
89
+ return objectValue.concat(sourceValue);
90
+ }
91
+ });
92
+
93
+ if (!(options.slot instanceof RegExp)) {
94
+ options.slot = new RegExp(`^${options.slot}${options.slotSeparator}`, 'i');
95
+ }
96
+
97
+ if (!(options.fill instanceof RegExp)) {
98
+ options.fill = new RegExp(`^${options.fill}${options.slotSeparator}`, 'i');
99
+ }
100
+
101
+ if (!(options.tagPrefix instanceof RegExp)) {
102
+ options.tagPrefix = new RegExp(`^${options.tagPrefix}`, 'i');
103
+ }
104
+
105
+ if (!Array.isArray(options.matcher)) {
106
+ options.matcher = [];
107
+ if (options.tagPrefix) {
108
+ options.matcher.push({tag: options.tagPrefix});
109
+ }
110
+
111
+ if (options.tag) {
112
+ options.matcher.push({tag: options.tag});
113
+ }
114
+ }
115
+
116
+ options.folders = Array.isArray(options.folders) ? options.folders : [options.folders];
117
+ options.namespaces = Array.isArray(options.namespaces) ? options.namespaces : [options.namespaces];
118
+ options.namespaces.forEach((namespace, index) => {
119
+ options.namespaces[index].root = path.resolve(namespace.root);
120
+ if (namespace.fallback) {
121
+ options.namespaces[index].fallback = path.resolve(namespace.fallback);
122
+ }
123
+
124
+ if (namespace.custom) {
125
+ options.namespaces[index].custom = path.resolve(namespace.custom);
126
+ }
127
+ });
128
+
129
+ options.props = {...options.expressions.locals};
130
+ options.aware = {};
131
+
132
+ const pushedContent = {};
133
+
134
+ log('Start of processing..', 'init', 'success');
135
+
136
+ return processStacks(
137
+ processPushes(
138
+ processTree(options)(
139
+ expressions(options.expressions)(tree)
140
+ ),
141
+ pushedContent,
142
+ options.push
143
+ ),
144
+ pushedContent,
145
+ options.stack
146
+ );
147
+ };
148
+ /* eslint-enable complexity */
149
+
150
+ // Used for reset aware props
151
+ let processCounter = 0;
152
+
153
+ /**
154
+ * @param {Object} options Plugin options
155
+ * @return {Object} PostHTML tree
156
+ */
157
+
158
+ function processTree(options) {
159
+ const filledSlots = {};
160
+
161
+ return function (tree) {
162
+ log(`Processing tree number ${processCounter}..`, 'processTree');
163
+
164
+ if (options.plugins.length > 0) {
165
+ tree = applyPluginsToTree(tree, options.plugins);
166
+ }
167
+
168
+ match.call(tree, options.matcher, currentNode => {
169
+ log(`Match found for tag "${currentNode.tag}"..`, 'processTree');
170
+
171
+ if (!currentNode.attrs) {
172
+ currentNode.attrs = {};
173
+ }
174
+
175
+ const componentPath = getComponentPath(currentNode, options);
176
+
177
+ if (!componentPath) {
178
+ return currentNode;
179
+ }
180
+
181
+ log(`${++processCounter}) Processing "${currentNode.tag}" from "${componentPath}"`, 'processTree');
182
+
183
+ let nextNode = parser(readFileSync(componentPath, 'utf8'));
184
+
185
+ // Set filled slots
186
+ setFilledSlots(currentNode, filledSlots, options);
187
+
188
+ const aware = transform(options.aware, (result, value) => {
189
+ assign(result, value);
190
+ }, {});
191
+
192
+ // Reset options.expressions.locals and keep aware locals
193
+ options.expressions.locals = {...options.props, ...aware};
194
+
195
+ const {attributes, props} = processProps(currentNode, nextNode, filledSlots, options, componentPath, processCounter);
196
+
197
+ options.expressions.locals = attributes;
198
+ options.expressions.locals.$slots = filledSlots;
199
+ // const plugins = [...options.plugins, expressions(options.expressions)];
200
+ nextNode = expressions(options.expressions)(nextNode);
201
+
202
+ if (options.plugins.length > 0) {
203
+ nextNode = applyPluginsToTree(nextNode, options.plugins);
204
+ }
205
+
206
+ // Process <yield> tag
207
+ const content = match.call(nextNode, {tag: options.yield}, nextNode => {
208
+ // Fill <yield> with current node content or default <yield>
209
+ return currentNode.content || nextNode.content;
210
+ });
211
+
212
+ nextNode = processTree(options)(nextNode);
213
+
214
+ // Process <fill> tags
215
+ processFillContent(nextNode, filledSlots, options);
216
+
217
+ // Process <slot> tags
218
+ processSlotContent(nextNode, filledSlots, options);
219
+
220
+ // Remove component tag and replace content with <yield>
221
+ currentNode.tag = false;
222
+ currentNode.content = content;
223
+
224
+ processAttributes(currentNode, attributes, props, options, aware);
225
+
226
+ // Remove attributes when value is 'null' or 'undefined'
227
+ // so we can conditionally add an attribute by setting value to 'undefined' or 'null'.
228
+ walk.call(currentNode, node => {
229
+ if (node && node.attrs) {
230
+ each(node.attrs, (value, key) => {
231
+ if (['undefined', 'null'].includes(value)) {
232
+ delete node.attrs[key];
233
+ }
234
+ });
235
+ }
236
+
237
+ return node;
238
+ });
239
+
240
+ log(`Done processing number ${processCounter}.`, 'processTree', 'success');
241
+
242
+ // Reset options.aware for current processCounter
243
+ delete options.aware[processCounter];
244
+
245
+ // Decrement counter
246
+ processCounter--;
247
+
248
+ return currentNode;
249
+ });
250
+
251
+ if (processCounter === 0) {
252
+ log('End of processing', 'processTree', 'success');
253
+ }
254
+
255
+ return tree;
256
+ };
257
+ }
258
+
259
+ function getComponentPath(currentNode, options) {
260
+ const componentFile = currentNode.attrs[options.attribute];
261
+
262
+ if (componentFile) {
263
+ const componentPath = path.join(options.root, componentFile);
264
+
265
+ if (!existsSync(componentPath)) {
266
+ if (options.strict) {
267
+ throw new Error(`[components] The component was not found in ${componentPath}.`);
268
+ } else {
269
+ return false;
270
+ }
271
+ }
272
+
273
+ // Delete attribute used as path
274
+ delete currentNode.attrs[options.attribute];
275
+
276
+ return componentPath;
277
+ }
278
+
279
+ return findPathFromTag(currentNode.tag, options);
280
+ }
281
+
282
+ function applyPluginsToTree(tree, plugins) {
283
+ return plugins.reduce((tree, plugin) => {
284
+ tree = plugin(tree);
285
+ return tree;
286
+ }, tree);
287
+ }
package/src/log.js ADDED
@@ -0,0 +1,27 @@
1
+ const {inspect} = require('util');
2
+
3
+ const debug = false;
4
+
5
+ // Colors
6
+ const colors = {
7
+ reset: '\u001B[0m',
8
+ error: '\u001B[31m',
9
+ success: '\u001B[32m',
10
+ warning: '\u001B[33m',
11
+
12
+ errorHighlight: '\u001B[41m',
13
+ successHighlight: '\u001B[42m',
14
+ warningHighlight: '\u001B[43m',
15
+
16
+ highlighted: '\u001B[45m'
17
+ };
18
+
19
+ module.exports = (message, method, level = 'reset', object = null) => {
20
+ if (debug === true || method === debug) {
21
+ if (object) {
22
+ console.log(`[${colors.highlighted}x-components ${method}()${colors.reset}]`, colors[level] || 'reset', message, inspect(object, false, null, true), colors.reset);
23
+ } else {
24
+ console.log(`[${colors.highlighted}x-components ${method}()${colors.reset}]`, colors[level] || 'reset', message, colors.reset);
25
+ }
26
+ }
27
+ };
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+
3
+ const {match} = require('posthtml/lib/api');
4
+ const parseAttrs = require('posthtml-attrs-parser');
5
+ const styleToObject = require('style-to-object');
6
+ const validAttributes = require('./valid-attributes');
7
+ const keys = require('lodash/keys');
8
+ const union = require('lodash/union');
9
+ const pick = require('lodash/pick');
10
+ const difference = require('lodash/difference');
11
+ const each = require('lodash/each');
12
+ const has = require('lodash/has');
13
+ const extend = require('lodash/extend');
14
+ const isString = require('lodash/isString');
15
+ const isObject = require('lodash/isObject');
16
+ const isEmpty = require('lodash/isEmpty');
17
+
18
+ /**
19
+ * Map component attributes that it's not defined as props to first element of node
20
+ *
21
+ * @param {Object} currentNode
22
+ * @param {Object} attributes
23
+ * @param {Object} props
24
+ * @param {Object} options
25
+ * @param {Object} aware
26
+ * @return {void}
27
+ */
28
+ module.exports = (currentNode, attributes, props, options, aware) => {
29
+ let mainNode;
30
+ match.call(currentNode, {attrs: {attributes: ''}}, node => {
31
+ delete node.attrs.attributes;
32
+ mainNode = node;
33
+
34
+ return node;
35
+ });
36
+
37
+ if (!mainNode) {
38
+ const index = currentNode.content.findIndex(content => typeof content === 'object');
39
+
40
+ if (index === -1) {
41
+ return;
42
+ }
43
+
44
+ mainNode = currentNode.content[index];
45
+ }
46
+
47
+ const nodeAttrs = parseAttrs(mainNode.attrs, options.attrsParserRules);
48
+
49
+ // Merge elementAttributes and blacklistAttributes with options provided
50
+ validAttributes.blacklistAttributes = union(validAttributes.blacklistAttributes, options.blacklistAttributes);
51
+ validAttributes.safelistAttributes = union(validAttributes.safelistAttributes, options.safelistAttributes);
52
+
53
+ // Merge or override elementAttributes from options provided
54
+ if (!isEmpty(options.elementAttributes)) {
55
+ each(options.elementAttributes, (tagName, modifier) => {
56
+ if (typeof modifier === 'function' && isString(tagName)) {
57
+ tagName = tagName.toUpperCase();
58
+ const attributes = modifier(validAttributes.elementAttributes[tagName]);
59
+ if (Array.isArray(attributes)) {
60
+ validAttributes.elementAttributes[tagName] = attributes;
61
+ }
62
+ }
63
+ });
64
+ }
65
+
66
+ // Attributes to be excluded
67
+ const excludeAttributes = union(validAttributes.blacklistAttributes, keys(props), keys(aware), keys(options.props), ['$slots']);
68
+ // All valid HTML attributes for the main element
69
+ const allValidElementAttributes = isString(mainNode.tag) && has(validAttributes.elementAttributes, mainNode.tag.toUpperCase()) ? validAttributes.elementAttributes[mainNode.tag.toUpperCase()] : [];
70
+ // Valid HTML attributes without the excluded
71
+ const validElementAttributes = difference(allValidElementAttributes, excludeAttributes);
72
+ // Add override attributes
73
+ validElementAttributes.push('override:style');
74
+ validElementAttributes.push('override:class');
75
+ // Pick valid attributes from passed
76
+ const mainNodeAttributes = pick(attributes, validElementAttributes);
77
+
78
+ // Get additional specified attributes
79
+ each(attributes, (value, attr) => {
80
+ each(validAttributes.safelistAttributes, additionalAttr => {
81
+ if (additionalAttr === attr || (additionalAttr.endsWith('*') && attr.startsWith(additionalAttr.replace('*', '')))) {
82
+ mainNodeAttributes[attr] = value;
83
+ }
84
+ });
85
+ });
86
+
87
+ each(mainNodeAttributes, (value, key) => {
88
+ if (['class', 'style'].includes(key)) {
89
+ if (!has(nodeAttrs, key)) {
90
+ nodeAttrs[key] = key === 'class' ? [] : {};
91
+ }
92
+
93
+ if (key === 'class') {
94
+ nodeAttrs.class.push(attributes.class);
95
+ } else {
96
+ nodeAttrs.style = extend(nodeAttrs.style, styleToObject(attributes.style));
97
+ }
98
+ } else {
99
+ nodeAttrs[key.replace('override:', '')] = attributes[key];
100
+ }
101
+
102
+ delete attributes[key];
103
+ });
104
+
105
+ // The plugin posthtml-attrs-parser compose() method expects a string,
106
+ // but since we are JSON parsing, values like "-1" become number -1.
107
+ // So below we convert non string values to string.
108
+ each(nodeAttrs, (value, key) => {
109
+ if (key !== 'compose' && !isObject(nodeAttrs[key]) && !isString(nodeAttrs[key])) {
110
+ nodeAttrs[key] = nodeAttrs[key].toString();
111
+ }
112
+ });
113
+
114
+ mainNode.attrs = nodeAttrs.compose();
115
+ };
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ const processScript = require('./process-script');
4
+ const pick = require('lodash/pick');
5
+ const each = require('lodash/each');
6
+ const assign = require('lodash/assign');
7
+ const mergeWith = require('lodash/mergeWith');
8
+
9
+ const attributeTypes = ['aware', 'merge'];
10
+
11
+ /**
12
+ * Parse props from attributes, globals and via script
13
+ *
14
+ * @param {Object} currentNode - PostHTML tree
15
+ * @param {Array} nextNode - PostHTML tree
16
+ * @param {Object} filledSlots - Filled slots
17
+ * @param {Object} options - Plugin options
18
+ * @param {string} componentPath - Component path
19
+ * @param {number} processCounter
20
+ * @return {Object} - Attribute props and script props
21
+ */
22
+ module.exports = (currentNode, nextNode, filledSlots, options, componentPath, processCounter) => {
23
+ let attributes = {...currentNode.attrs};
24
+
25
+ const attributesByTypeName = {};
26
+ each(attributeTypes, type => {
27
+ attributesByTypeName[type] = [];
28
+ });
29
+
30
+ each(attributes, (value, key, attrs) => {
31
+ let newKey = key;
32
+ each(attributeTypes, type => {
33
+ if (key.startsWith(`${type}:`)) {
34
+ newKey = newKey.replace(`${type}:`, '');
35
+ attributesByTypeName[type].push(newKey);
36
+ }
37
+ });
38
+
39
+ if (newKey !== key) {
40
+ attrs[newKey] = value;
41
+ delete attrs[key];
42
+ }
43
+ });
44
+
45
+ // Parse JSON attributes
46
+ each(attributes, (value, key, attrs) => {
47
+ try {
48
+ attrs[key] = JSON.parse(value);
49
+ } catch {}
50
+ });
51
+
52
+ // Merge or extend attribute props
53
+ if (attributes[options.propsAttribute]) {
54
+ if (attributesByTypeName.merge.includes(options.propsAttribute)) {
55
+ attributesByTypeName.merge.splice(attributesByTypeName.merge.indexOf(options.propsAttribute), 1);
56
+ mergeWith(attributes, attributes[options.propsAttribute], options.mergeCustomizer);
57
+ } else {
58
+ assign(attributes, attributes[options.propsAttribute]);
59
+ }
60
+
61
+ delete attributes[options.propsAttribute];
62
+ }
63
+
64
+ // Merge with global
65
+ attributes = mergeWith({}, options.expressions.locals, attributes, options.mergeCustomizer);
66
+
67
+ // Process props from <script props>
68
+ const {props} = processScript(nextNode, {props: {...attributes}, $slots: filledSlots, propsScriptAttribute: options.propsScriptAttribute, propsContext: options.propsContext, utilities: options.utilities}, componentPath.replace(`.${options.fileExtension}`, '.js'));
69
+
70
+ if (props) {
71
+ assign(attributes, props);
72
+ // if (attributesByTypeName.merge.length > 0) {
73
+ // assign(attributes, mergeWith(pick(locals, attributesByTypeName.merge), pick(attributes, attributesByTypeName.merge), options.mergeCustomizer));
74
+ // }
75
+ }
76
+
77
+ // Set aware attributes
78
+ if (attributesByTypeName.aware.length > 0) {
79
+ options.aware[processCounter] = pick(attributes, attributesByTypeName.aware);
80
+ }
81
+
82
+ return {attributes, props};
83
+ };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const vm = require('vm');
4
+ const {existsSync, readFileSync} = require('fs');
5
+ const {render} = require('posthtml-render');
6
+ const {match} = require('posthtml/lib/api');
7
+
8
+ const ctx = vm.createContext({module, require});
9
+
10
+ /**
11
+ * Get the script tag with props from a node list and return process props.
12
+ * Custom posthtml-expressions/lib/locals.
13
+ *
14
+ * @param {Array} tree Nodes
15
+ * @param {Object} options Options
16
+ * @param {string} scriptPath - Component path
17
+ * @return {Object} {} Locals
18
+ */
19
+ module.exports = (tree, options, scriptPath) => {
20
+ const props = {};
21
+ const propsContext = options.props;
22
+ const utilities = {...options.utilities};
23
+ const context = {...utilities, ...ctx, [options.propsContext]: propsContext, $slots: options.$slots};
24
+
25
+ const runInContext = code => {
26
+ try {
27
+ const parsedContext = vm.createContext(context);
28
+ const parsedProps = vm.runInContext(code, parsedContext);
29
+
30
+ Object.assign(props, parsedProps);
31
+ } catch {}
32
+ };
33
+
34
+ if (existsSync(scriptPath)) {
35
+ runInContext(readFileSync(scriptPath, 'utf8'));
36
+ }
37
+
38
+ match.call(tree, {tag: 'script', attrs: {[options.propsScriptAttribute]: ''}}, node => {
39
+ if (node.content) {
40
+ runInContext(render(node.content));
41
+ }
42
+
43
+ return '';
44
+ });
45
+
46
+ return {
47
+ props
48
+ };
49
+ };