├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── debug ├── cache │ └── .gitignore ├── components │ ├── layout.php │ ├── page.php │ └── partials │ │ └── mypartial.php ├── expressions.php └── index.php ├── examples ├── cache │ └── .gitignore ├── css │ └── shop.css ├── main.php └── templates │ ├── partials │ ├── product-list.php │ └── product.php │ └── shop.php ├── phpunit.xml.dist ├── src ├── CacheTemplate.php ├── ConvertJsExpression.php ├── Engine.php ├── Node.php └── Undefined.php └── tests └── integration ├── VuePreTest.php ├── cache └── .gitignore └── templates ├── all.html └── all.result.html /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Lorenz Vandevelde 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 | # VuePre 3 | VuePre is a package to prerender vue templates. This is useful for SEO and avoiding blank pages on page load. What VuePre does, is translating the Vue template to a PHP template, then replaces the javascript expressions with PHP expressions and caches it. Once cached, you can render thousands of templates per second depending on your hardware. 4 | 5 | ## PROS 6 | ``` 7 | - Very fast 8 | - No dependencies 9 | ``` 10 | 11 | ## CONS 12 | ``` 13 | - Some javascript expressions are not supported (yet). 14 | ``` 15 | 16 | ## Installation 17 | ``` 18 | composer require ctxkiwi/vue-pre 19 | ``` 20 | 21 | ## Basic usage 22 | 23 | ```php 24 | $vue = new \VuePre\Engine(); 25 | $vue->setCacheDirectory(__DIR__ . '/cache'); 26 | 27 | // Method 1 28 | $data = ["name" => "world"]; 29 | $html = $vue->renderHtml('
...
176 |Rererererererer
83 |';
225 | echo 'Error: ' . $error . "\n";
226 | echo 'Line:' . $lineNr . "\n";
227 | echo 'Template:' . "\n";
228 |
229 | if (is_int($lineNr)) {
230 | $lines = explode("\n", $template);
231 | $before = array_slice($lines, 0, $lineNr);
232 | $line = array_splice($lines, $lineNr, 1)[0];
233 | $after = array_slice($lines, $lineNr);
234 | echo htmlspecialchars(implode("\n", $before)) . "\n";
235 | echo '' . htmlspecialchars($line) . '' . "\n";
236 | echo htmlspecialchars(implode("\n", $after));
237 | } else {
238 | echo htmlspecialchars($template);
239 | }
240 |
241 | echo '
';
242 | exit;
243 | }
244 |
245 | }
--------------------------------------------------------------------------------
/src/ConvertJsExpression.php:
--------------------------------------------------------------------------------
1 | expression = $expr;
13 |
14 | // Check options
15 | if (isset($options['inAttribute'])) {
16 | $ex->inAttribute = $options['inAttribute'];
17 | }
18 |
19 | //
20 | return $ex->parse();
21 | }
22 |
23 | public function parse() {
24 |
25 | $expr = $this->expression;
26 |
27 | try {
28 | // dump('O: ' . $expr);
29 | $result = $this->parseValue($expr);
30 | // dump('R: ' . $result);
31 | } catch (\Exception $e) {
32 | $this->fail();
33 | }
34 |
35 | return $result;
36 | }
37 |
38 | public function parseValue($expr, $inParams = false) {
39 |
40 | $expr = trim($expr);
41 |
42 | if ($expr === '') {
43 | return '';
44 | }
45 |
46 | $newExpr = '';
47 | $length = strlen($expr);
48 | $expects = [['value']];
49 | // $inString = false;
50 | // $inStringEndChar = null;
51 | $plus = false;
52 |
53 | $getString = function ($i, $endChar) use (&$expr) {
54 | $prevChar = $endChar;
55 | $string = $endChar;
56 | $i++;
57 | while (isset($expr[$i])) {
58 | $char = $expr[$i];
59 | if ($char === $endChar && $prevChar !== '\\') {
60 | break;
61 | }
62 | $string .= $char;
63 | $prevChar = $char;
64 | $i++;
65 | }
66 | $string .= $endChar;
67 | return $string;
68 | };
69 |
70 | $parseBetween = function ($i, $openChar, $endChar, $allowCommas = false, $isObject = false) use (&$expr, &$getString) {
71 | $startIndex = $i;
72 | $depth = 0;
73 | $subExpr = '';
74 | $subExpressions = [];
75 | $i++;
76 | while (isset($expr[$i])) {
77 | $subChar = $expr[$i];
78 |
79 | if ($allowCommas) {
80 | if ($subChar == ',' && $depth === 0) {
81 | $subExpressions[] = $subExpr;
82 | $subExpr = '';
83 | $i++;
84 | continue;
85 | }
86 | }
87 |
88 | if ($depth === 0 && $subChar == $endChar) {
89 | $subExpressions[] = $subExpr;
90 | $phpExpressions = [];
91 | if ($isObject) {
92 | $result = '[';
93 | foreach ($subExpressions as $subEx) {
94 |
95 | // Get Key
96 | $signIndex = strpos($subEx, ':');
97 | if ($signIndex === false) {
98 | throw new \Exception('Invalid object key "' . $subEx . '"');
99 | }
100 | $key = substr($subEx, 0, $signIndex);
101 | if (empty($key) || !preg_match('/[a-zA-Z0-9_]/', $key)) {
102 | throw new \Exception('Invalid object key syntax "' . $subEx . '"');
103 | }
104 |
105 | $result .= '"' . trim($key) . '" => ';
106 | $result .= $this->parseValue(substr($subEx, $signIndex + 1));
107 | }
108 | $result .= ']';
109 | return (object) ['length' => $i - $startIndex + 1, 'expr' => $result];
110 | } else {
111 | foreach ($subExpressions as $subEx) {
112 | $phpExpressions[] = $this->parseValue($subEx);
113 | }
114 | return (object) ['length' => $i - $startIndex + 1, 'expr' => $openChar . implode(',', $phpExpressions) . $endChar];
115 | }
116 |
117 | }
118 |
119 | if (preg_match('/[\'\"]/', $subChar)) {
120 | $str = $getString($i, $subChar);
121 | $subExpr .= $str;
122 | $i += strlen($str);
123 | continue;
124 | }
125 |
126 | $subExpr .= $subChar;
127 |
128 | if ($subChar == $openChar) {
129 | $depth++;
130 | }
131 |
132 | if ($subChar == $endChar) {
133 | $depth--;
134 | }
135 |
136 | $i++;
137 | }
138 | throw new \Exception('Could not find closing character "' . $endChar . '"');
139 | };
140 |
141 | for ($i = 0; $i < $length; $i++) {
142 | $char = $expr[$i];
143 |
144 | if ($char == ' ') {
145 | $newExpr .= ' ';
146 | continue;
147 | }
148 |
149 | $expect = array_pop($expects);
150 |
151 | $foundExpectation = false;
152 |
153 | foreach ($expect as $ex) {
154 |
155 | //////////////////////////
156 |
157 | if ($ex == 'value') {
158 |
159 | // Valid starting characters
160 | if (preg_match('/[a-zA-Z0-9_\!\[\{\(\"\']/', $char)) {
161 | $dollarPos = null;
162 | $valueExpr = '';
163 | $funcName = '';
164 |
165 | $pathOfExpr = '';
166 | $path = '';
167 |
168 | if ($char == '(') {
169 | // (...)
170 | $subExpr = $parseBetween($i, '(', ')', false, false);
171 | $valueExpr .= $subExpr->expr;
172 | $i += $subExpr->length;
173 | }
174 |
175 | if (preg_match('/[\'\"]/', $char)) {
176 | $str = $getString($i, $char);
177 | $valueExpr .= $str;
178 | $i += strlen($str);
179 | }
180 |
181 | if ($char == '{') {
182 | // Object
183 | $subExpr = $parseBetween($i, '{', '}', true, true);
184 | if ($this->inAttribute) {
185 | $valueExpr .= '\VuePre\ConvertJsExpression::handleArrayInAttribute("' . $this->inAttribute . '", ';
186 | } else {
187 | $valueExpr .= '\VuePre\ConvertJsExpression::handleArrayToString(';
188 | }
189 | $valueExpr .= $subExpr->expr;
190 | $valueExpr .= ')';
191 | $i += $subExpr->length;
192 | }
193 |
194 | if ($char == '[') {
195 | // Array
196 | $subExpr = $parseBetween($i, '[', ']', true, false);
197 | $valueExpr .= $subExpr->expr;
198 | $i += $subExpr->length;
199 | }
200 |
201 | if ($char == '!') {
202 | $valueExpr .= '!';
203 | $i++;
204 | }
205 |
206 | while (isset($expr[$i])) {
207 | $subChar = $expr[$i];
208 | if (!preg_match('/[a-zA-Z0-9_\.\-\(\[]/', $subChar)) {
209 | break;
210 | }
211 |
212 | if ($subChar == '.') {
213 | if (empty($pathOfExpr) && is_numeric($funcName)) {
214 | $valueExpr .= '.';
215 | } else {
216 | if (empty($pathOfExpr)) {
217 | $pathOfExpr = $valueExpr;
218 | } else {
219 | $path .= '.';
220 | }
221 | }
222 | $funcName = '';
223 | $i++;
224 | continue;
225 | }
226 |
227 | if ($subChar == '(') {
228 | // Function params
229 | if ($funcName == '') {
230 | break (2);
231 | }
232 | if (!empty($pathOfExpr)) {
233 | if ($funcName == 'indexOf') {
234 | $path = substr($path, 0, -8);
235 | if (!$path) {$path = '';}
236 | }
237 | if (!empty($path)) {
238 | $pre = '';
239 | if ($pathOfExpr[0] === '!') {
240 | $pre = '!';
241 | $pathOfExpr = substr($pathOfExpr, 1);
242 | }
243 | $valueExpr = $pre . '\VuePre\ConvertJsExpression::getObjectValue(' . $pathOfExpr . ', "' . $path . '", $this)';
244 | } else {
245 | $valueExpr = $pathOfExpr;
246 | }
247 | $pathOfExpr = '';
248 | $path = '';
249 | }
250 | $subExpr = $parseBetween($i, '(', ')', true, false);
251 | if ($funcName == 'indexOf') {
252 | $valueExpr = '\VuePre\ConvertJsExpression::indexOf(' . $valueExpr . ', ' . $subExpr->expr . ')';
253 | } else {
254 | $valueExpr .= $subExpr->expr;
255 | }
256 | $i += $subExpr->length;
257 | } elseif ($subChar == '[') {
258 | // Object key (no commas allowed)
259 | $subExpr = $parseBetween($i, '[', ']', false, false);
260 | $valueExpr .= $subExpr->expr;
261 | $i += $subExpr->length;
262 | } else {
263 | // Var name
264 | if ($dollarPos === null) {
265 | $dollarPos = strlen($valueExpr);
266 | $valueExpr .= '$';
267 | }
268 | if (!empty($pathOfExpr)) {
269 | $path .= $subChar;
270 | } else {
271 | $valueExpr .= $subChar;
272 | }
273 | $funcName .= $subChar;
274 | }
275 |
276 | $i++;
277 | }
278 | $i--;
279 |
280 | if (!empty($pathOfExpr)) {
281 | $pre = '';
282 | if ($pathOfExpr[0] === '!') {
283 | $pre = '!';
284 | $pathOfExpr = substr($pathOfExpr, 1);
285 | }
286 | $valueExpr = $pre . '\VuePre\ConvertJsExpression::getObjectValue(' . $pathOfExpr . ', "' . $path . '", $this)';
287 | }
288 |
289 | if (strlen($valueExpr) > 0) {
290 | $checkExpr = substr($valueExpr, 1);
291 | if (in_array(strtolower($checkExpr), ['true', 'false', 'null'], true)) {
292 | $valueExpr = $checkExpr;
293 | }
294 | if (is_numeric($checkExpr)) {
295 | $valueExpr = $checkExpr;
296 | }
297 | }
298 |
299 | $newExpr .= $valueExpr;
300 |
301 | if ($plus) {
302 | $newExpr .= ')';
303 | $plus = false;
304 | }
305 |
306 | $expects[] = ['operator'];
307 | $foundExpectation = true;
308 | break;
309 | }
310 |
311 | }
312 |
313 | //////////////////////////
314 |
315 | if ($ex == 'operator') {
316 | $operator = '';
317 | $operators = '/[=+\-<>?:*%!&|]/';
318 | while (isset($expr[$i]) && preg_match($operators, $expr[$i])) {
319 | $operator .= $expr[$i];
320 | $i++;
321 | }
322 | $i--;
323 |
324 | $allowed = ['==', '===', '!=', '!==', '<=', '>=', '<', '>', '&&', '||', '?', ':', '+', '+=', '-', '-=', '*', '*=', '/', '/='];
325 |
326 | if (empty($operator)) {
327 | throw new \Exception('Expected an operator, but instead found "' . $char . '" at index: ' . $i);
328 | }
329 |
330 | if (!in_array($operator, $allowed, true)) {
331 | throw new \Exception('Unexpected operator "' . $operator . '"');
332 | }
333 |
334 | if ($operator === '+') {
335 | $plus = true;
336 | $newExpr = '\VuePre\ConvertJsExpression::plus(' . $newExpr . ', ';
337 | } elseif ($operator === '?') {
338 | $newExpr = '\VuePre\ConvertJsExpression::toBool(' . $newExpr . ') ?';
339 | } else {
340 | $newExpr .= $operator;
341 | }
342 |
343 | $expects[] = ['value'];
344 | $foundExpectation = true;
345 | }
346 |
347 | //////////////////////////
348 |
349 | }
350 |
351 | if (!$foundExpectation) {
352 | throw new \Exception('Unexpected character "' . $char . '"');
353 | }
354 | }
355 |
356 | return $newExpr;
357 | }
358 |
359 | public function fail() {
360 | throw new \Exception('VuePre doesnt understand this JS expression: "' . $this->expression . '"', 100);
361 | }
362 |
363 | // Runtime functions
364 |
365 | // Handle plus sign between 2 values
366 | public static function plus($val1, $val2) {
367 | if (is_object($val1) && get_class($val1) == 'VuePre\Undefined') {
368 | $val1 = 'undefined';
369 | }
370 | if (is_object($val2) && get_class($val2) == 'VuePre\Undefined') {
371 | $val2 = 'undefined';
372 | }
373 | if (is_string($val1)) {
374 | return $val1 . $val2;
375 | }
376 | if (is_numeric($val1)) {
377 | return $val1 + $val2;
378 | }
379 |
380 | return $val1 . $val2;
381 | }
382 |
383 | public static function getObjectValue($obj, $path, $cacheTemplate) {
384 | $path = explode('.', $path);
385 | foreach ($path as $key) {
386 | if ($key === 'length') {
387 | $obj = static::length($obj);
388 | continue;
389 | }
390 | if (is_array($obj)) {
391 | if (!array_key_exists($key, $obj)) {
392 | // return '';
393 | return new Undefined($cacheTemplate);
394 | }
395 | $obj = $obj[$key];
396 | } else {
397 | if (!property_exists($obj, $key)) {
398 | // return '';
399 | return new Undefined($cacheTemplate);
400 | }
401 | $obj = $obj->$key;
402 | }
403 | }
404 |
405 | return $obj;
406 | }
407 |
408 | public static function indexOf($haystack, $needle) {
409 | if (is_array($haystack)) {
410 | $res = array_search($needle, $haystack, true); // true for strict
411 | if ($res === false) {return -1;}
412 | return $res;
413 | }
414 | if (is_string($haystack)) {
415 | $res = strpos($haystack, $needle);
416 | if ($res === false) {return -1;}
417 | return $res;
418 | }
419 |
420 | throw new Exception('indexOf() : variable was not a string or array');
421 | }
422 |
423 | public static function length($value) {
424 | if (is_array($value)) {
425 | return count($value);
426 | }
427 | if (is_string($value)) {
428 | return mb_strlen($value);
429 | }
430 |
431 | throw new Exception('length() : variable was not a string or array');
432 | }
433 |
434 | public static function toBool($value) {
435 | if (gettype($value) == 'object' && get_class($value) === 'VuePre\Undefined') {
436 | return false;
437 | }
438 |
439 | return $value;
440 | }
441 |
442 | public static function typeof($value) {
443 | $type = gettype($value);
444 | if ($type == 'object' && get_class($value) === 'VuePre\Undefined') {
445 | return 'undefined';
446 | }
447 |
448 | return $type;
449 | }
450 |
451 | public static function handleArrayInAttribute($name, $array) {
452 | if ($name == 'class') {
453 | $classes = [];
454 | foreach ($array as $className => $value) {
455 | $className = str_replace("'", '', $className);
456 | if ($value) {
457 | $classes[] = $className;
458 | }
459 | }
460 | return implode(' ', $classes);
461 | }
462 | if ($name == 'style') {
463 | $styles = [];
464 | foreach ($array as $prop => $value) {
465 | $prop = str_replace("'", '', $prop);
466 | if ($value) {
467 | $styles[] = $prop . ': ' . $value . ';';
468 | }
469 | }
470 | return implode(' ', $styles);
471 | }
472 | return '';
473 | }
474 | public static function handleArrayToString($array) {
475 | return json_encode($array);
476 | }
477 |
478 | }
--------------------------------------------------------------------------------
/src/Engine.php:
--------------------------------------------------------------------------------
1 | globalData['typeof'] = function ($value) {
40 | $type = gettype($value);
41 | if ($type == 'object' && get_class($value) === 'VuePre\Undefined') {
42 | return 'undefined';
43 | }
44 |
45 | return $type;
46 | };
47 | }
48 |
49 | /////////////////////////
50 | // Settings
51 | /////////////////////////
52 |
53 | public function setCacheDirectory(String $dir) {
54 | $dir = realpath($dir);
55 | if (!file_exists($dir) || !is_dir($dir)) {
56 | throw new Exception('Cache directory not found: ' . $dir);
57 | }
58 | $this->cacheDir = $dir;
59 | }
60 |
61 | public function setComponentDirectory(String $dir) {
62 | $dir = realpath($dir);
63 | if (!file_exists($dir) || !is_dir($dir)) {
64 | throw new Exception('Component directory not found: ' . $dir);
65 | }
66 | $this->componentDir = $dir;
67 | if (!$this->disableAutoScan) {
68 | $this->scanComponentDirectoryForComponents();
69 | }
70 | }
71 |
72 | public function ignoreAttributes($ignores) {
73 | foreach ($ignores as $ig) {
74 | $this->_ignoreAttributes[] = $ig;
75 | }
76 | $this->_ignoreAttributes = array_unique($this->_ignoreAttributes);
77 | }
78 | public function unignoreAttributes($ignores) {
79 | foreach ($ignores as $ig) {
80 | $index = array_search($ig, $this->_ignoreAttributes);
81 | if ($index !== false) {
82 | array_splice($this->_ignoreAttributes, $index, 1);
83 | }
84 | }
85 | }
86 |
87 | /////////////////////////
88 | // Getters / Setters
89 | /////////////////////////
90 |
91 | public function getRenderedComponentNames() {
92 | return array_values($this->renderedComponentNames);
93 | }
94 |
95 | public function setGlobals(Array $data) {
96 | $this->globalData = array_merge($this->globalData, $data);
97 | }
98 |
99 | public function getGlobals() {
100 | return $this->globalData;
101 | }
102 |
103 | public function getIgnoredAttributes() {
104 | return $this->_ignoreAttributes;
105 | }
106 |
107 | /////////////////////////
108 | // Component finding
109 | /////////////////////////
110 |
111 | public function setComponentAlias(array $aliasses) {
112 | foreach ($aliasses as $componentName => $alias) {
113 | if (is_string($componentName) && is_string($alias)) {
114 | $this->componentAlias[$componentName] = $alias;
115 | }
116 | }
117 | }
118 |
119 | public function getComponentAlias($componentName, $default = null) {
120 | if (isset($this->componentAlias[$componentName])) {
121 | return $this->componentAlias[$componentName];
122 | }
123 | if ($default) {
124 | return $default;
125 | }
126 | throw new \Exception('Cannot find alias for "' . $componentName . '"');
127 | }
128 |
129 | /////////////////////////
130 | // Helper functions
131 | /////////////////////////
132 |
133 | public function loadComponent($componentName) {
134 | if (!isset($this->components[$componentName])) {
135 | $component = [
136 | 'settings' => null,
137 | 'template' => null,
138 | 'js' => null,
139 | ];
140 |
141 | $alias = $this->getComponentAlias($componentName);
142 | $dirPath = $this->componentDir . '/' . implode('/', explode('.', $alias));
143 | $path = $dirPath . '.php';
144 |
145 | if (!file_exists($path)) {
146 | throw new Exception('Component file not found: ' . $path);
147 | }
148 |
149 | $content = "\n" . file_get_contents($path);
150 |
151 | $php = static::getStringBetweenTags($content, '\n<\?php\s', '\n\?>');
152 | $template = static::getStringBetweenTags($content, '\n]*>', '\n<\/template>');
153 | $js = static::getStringBetweenTags($content, '\n';
226 | }
227 | return $result;
228 | }
229 |
230 | public function getJsScripts() {
231 | $result = '';
232 | foreach ($this->renderedComponentNames as $componentName => $c) {
233 | $result .= $this->getJsScript($componentName, '');
234 | }
235 | return $result;
236 | }
237 |
238 | public function getTemplateScript($componentName, $default = null, $idPrefix = 'vue-template-') {
239 | $template = $this->getComponentTemplate($componentName);
240 | return '';
241 | }
242 |
243 | public function getJsScript($componentName, $default = null) {
244 | return '';
245 | }
246 |
247 | public function getScripts($componentName = null, $idPrefix = 'vue-template-') {
248 | if ($componentName) {
249 | $result = '';
250 | $result .= $this->getTemplateScript($componentName, null, $idPrefix);
251 | $result .= $this->getJsScript($componentName);
252 | return $result;
253 | }
254 | return $this->getTemplateScripts($idPrefix) . $this->getJsScripts();
255 | }
256 |
257 | public function getVueInstanceScript($el, $componentName, $data) {
258 |
259 | $jsData = [];
260 | $bindings = '';
261 | foreach ($data as $k => $v) {
262 | if (!is_callable($v)) {
263 | $jsData[$k] = $v;
264 | $bindings .= ' :' . $k . '="componentData.' . $k . '"';
265 | }
266 | }
267 |
268 | $html = '';
277 | return $html;
278 | }
279 |
280 | /////////////////////////
281 | // Scan for aliasses
282 | /////////////////////////
283 |
284 | private function scanComponentDirectoryForComponents() {
285 | $this->scanDirectoryForComponents($this->componentDir);
286 | }
287 |
288 | public function scanDirectoryForComponents($dir) {
289 | if (!$this->componentDir) {
290 | throw new Exception('"componentDirectory" not set');
291 | }
292 | $dir = realpath($dir);
293 | if (strpos($dir, $this->componentDir) !== 0) {
294 | throw new Exception('scanDirectoryForComponents: directory must be a sub directory from "componentDirectory"');
295 | }
296 | $files = static::recursiveGlob($dir . '/*.php');
297 | foreach ($files as $file) {
298 | $fn = basename($file);
299 | $alias = substr($file, 0, -strlen('.php'));
300 | $name = basename($alias);
301 | $alias = str_replace('/', '.', substr($alias, strlen($this->componentDir . '/')));
302 | $this->componentAlias[$name] = $alias;
303 | }
304 | }
305 |
306 | private static function recursiveGlob($pattern, $flags = 0) {
307 | $files = glob($pattern, $flags);
308 | foreach (glob(dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
309 | $files = array_merge($files, static::recursiveGlob($dir . '/' . basename($pattern), $flags));
310 | }
311 | return $files;
312 | }
313 |
314 | /////////////////////////
315 | // Cache
316 | /////////////////////////
317 |
318 | private function createCachedTemplate($html, $options) {
319 |
320 | if (!isset($options['isComponent'])) {
321 | $options['isComponent'] = false;
322 | }
323 |
324 | if ($options['isComponent']) {
325 | $dom = $this->parseHtml($html);
326 | // Check for single rootNode
327 | $this->getRootNode($dom);
328 | }
329 |
330 | $dom = $this->parseHtml(''; 459 | // echo htmlspecialchars($html); 460 | // echo ''; 461 | // exit; 462 | //TODO html5 tags can fail parsing 463 | //TODO Throw an exception 464 | } 465 | return $document; 466 | } 467 | 468 | private function getRootNode(DOMDocument $document) { 469 | $rootNodes = $document->documentElement->childNodes->item(0)->childNodes; 470 | if ($rootNodes->length > 1) { 471 | echo '
' . htmlspecialchars($this->getBodyHtml($document)) . ''; 473 | exit; 474 | } 475 | return $rootNodes->item(0); 476 | } 477 | 478 | private function setErrorHint($line, $expression) { 479 | $this->errorLine = $line; 480 | $this->errorExpression = $expression; 481 | } 482 | 483 | } 484 | -------------------------------------------------------------------------------- /src/Node.php: -------------------------------------------------------------------------------- 1 | template = $template; 17 | 18 | $this->settings = (object) [ 19 | 'line' => null, 20 | 'nodeType' => null, 21 | 'content' => null, 22 | // 23 | 'childNodes' => [], 24 | // 25 | 'isTemplate' => false, 26 | 'isRootEl' => false, 27 | 'isComponent' => null, 28 | // 29 | 'vfor' => null, 30 | 'vforIndexName' => null, 31 | 'vforAsName' => null, 32 | 'vif' => null, 33 | 'velseif' => null, 34 | 'velse' => null, 35 | 'vhtml' => null, 36 | // 37 | 'class' => null, 38 | 'style' => null, 39 | 'mustacheValues' => [], 40 | 'bindedValues' => [], 41 | // Slots 42 | 'vslot' => null, 43 | 'slotNodes' => (object) [], 44 | ]; 45 | } 46 | 47 | public function parseDomNode(DOMNode $node, $options = []) { 48 | 49 | try { 50 | 51 | $lineNr = $node->getLineNo() - 1; 52 | $this->errorLineNr = $lineNr; 53 | 54 | $this->settings->line = $lineNr; 55 | $this->settings->nodeType = $node->nodeType; 56 | $this->settings->content = $node->nodeType === 3 ? $node->textContent : null; 57 | 58 | $this->replaceMustacheVariables($node); 59 | if ($node->nodeType === 1) { 60 | 61 | $this->stripEventHandlers($node); 62 | $this->handleFor($node, $options); 63 | $this->handleIf($node, $options); 64 | $this->handleTemplateTag($node, $options); 65 | $this->handleSlot($node, $options); 66 | $this->handleAttributeBinding($node); 67 | $this->handleComponent($node, $options); 68 | $this->handleRawHtml($node); 69 | 70 | $subNodes = iterator_to_array($node->childNodes); 71 | $this->settings->childNodes = []; 72 | foreach ($subNodes as $index => $childNode) { 73 | if (!$this->settings->isComponent) { 74 | $newNode = new Node($this->template); 75 | $this->settings->childNodes[] = $newNode->parseDomNode($childNode); 76 | } 77 | $node->removeChild($childNode); 78 | } 79 | 80 | $newNode = $node->ownerDocument->createTextNode('_VUEPRE_HTML_PLACEHOLDER_'); 81 | $node->appendChild($newNode); 82 | $this->settings->content = $node->ownerDocument->saveHTML($node); 83 | } 84 | 85 | } catch (\Exception $e) { 86 | if ($e->getCode() === 100) { 87 | $this->parseError($e); 88 | } else { 89 | throw $e; 90 | } 91 | } 92 | 93 | return $this; 94 | } 95 | 96 | public function parseError($e) { 97 | $template = $this->template->engine->errorCurrentTemplate; 98 | $lineNr = $this->errorLineNr; 99 | $error = $e->getMessage(); 100 | CacheTemplate::templateError($template, $lineNr, $error); 101 | } 102 | 103 | public function export(): \stdClass{ 104 | $result = (object) []; 105 | $settings = $this->settings; 106 | 107 | if ($settings->isTemplate) { 108 | $result->isTemplate = true; 109 | } 110 | 111 | $copyIfNotNull = ['line', 'nodeType', 'content', 'isComponent', 'vfor', 'vforIndexName', 'vforAsName', 'vif', 'velseif', 'velse', 'vhtml', 'class', 'style', 'vslot']; 112 | foreach ($copyIfNotNull as $k) { 113 | if ($settings->$k !== null) { 114 | $result->$k = $settings->$k; 115 | } 116 | } 117 | 118 | $copyIfHasItems = ['mustacheValues', 'bindedValues']; 119 | foreach ($copyIfHasItems as $k) { 120 | if (count($settings->$k) > 0) { 121 | $result->$k = $settings->$k; 122 | } 123 | } 124 | 125 | if (count($settings->childNodes) > 0) { 126 | $result->childNodes = []; 127 | foreach ($settings->childNodes as $k => $v) { 128 | $result->childNodes[$k] = $v->export(); 129 | } 130 | } 131 | 132 | if (count((array) $settings->slotNodes) > 0) { 133 | $result->slotNodes = (object) []; 134 | foreach ($settings->slotNodes as $k => $v) { 135 | foreach ($settings->slotNodes->$k as $kk => $vv) { 136 | $result->slotNodes->$k[$kk] = $vv->export(); 137 | } 138 | } 139 | } 140 | 141 | return $result; 142 | } 143 | 144 | private function stripEventHandlers(DOMNode $node) { 145 | foreach ($node->attributes as $attribute) { 146 | if (strpos($attribute->name, 'v-on:') === 0) { 147 | $node->removeAttribute($attribute->name); 148 | } 149 | } 150 | } 151 | 152 | private function replaceMustacheVariables(DOMNode $node) { 153 | if ($node->nodeType === 3) { 154 | // $text = $node->nodeValue; 155 | $text = $this->settings->content; 156 | $count = 0; 157 | $this->settings->mustacheValues = []; 158 | $regex = '/\{\{(?P