Skip to content

Commit a9f6d5d

Browse files
Merge pull request #94 from express-vue/develop
Develop
2 parents 792fb90 + a4f1dcb commit a9f6d5d

File tree

10 files changed

+9872
-9152
lines changed

10 files changed

+9872
-9152
lines changed

package-lock.json

Lines changed: 9717 additions & 9088 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,41 +66,44 @@
6666
"repository": "express-vue/express-vue-renderer",
6767
"license": "Apache-2.0",
6868
"dependencies": {
69-
"babel-eslint": "^7.2.3",
69+
"babel-core": "^6.26.0",
7070
"babel-preset-es2015": "^6.24.1",
7171
"butternut": "^0.4.6",
7272
"camel-case": "^3.0.0",
73-
"clean-css": "^4.1.7",
73+
"clean-css": "^4.1.9",
74+
"debug": "^2.6.9",
7475
"dedupe": "^2.1.0",
75-
"deepmerge": "^1.5.1",
76-
"html-minifier": "^3.5.3",
76+
"deepmerge": "^1.5.2",
77+
"html-minifier": "^3.5.5",
7778
"lru-cache": "^4.1.1",
78-
"pug": "^2.0.0-rc.3",
79-
"require-from-string": "^1.2.1",
79+
"no-case": "^2.3.2",
80+
"pug": "^2.0.0-rc.4",
81+
"require-from-string": "^2.0.1",
8082
"string-hash": "^1.1.3",
81-
"vue": "^2.4.2",
82-
"vue-server-renderer": "^2.4.2",
83-
"vue-template-compiler": "^2.4.2",
83+
"vue": "^2.4.4",
84+
"vue-server-renderer": "^2.4.4",
85+
"vue-template-compiler": "^2.4.4",
8486
"xss": "^0.3.4"
8587
},
8688
"devDependencies": {
8789
"ava": "^0.22.0",
8890
"babel-cli": "^6.26.0",
91+
"babel-eslint": "^8.0.0",
8992
"babel-plugin-transform-flow-strip-types": "^6.22.0",
9093
"babel-preset-env": "^1.6.0",
9194
"babel-preset-flow": "^6.23.0",
9295
"codecov": "^2.3.0",
93-
"coveralls": "^2.13.1",
94-
"eslint": "^4.5.0",
96+
"coveralls": "^3.0.0",
97+
"eslint": "^4.7.2",
9598
"eslint-config-xo-space": "^0.16.0",
96-
"eslint-plugin-flowtype": "^2.35.0",
97-
"express": "^4.15.4",
98-
"flow-bin": "^0.54.0",
99+
"eslint-plugin-flowtype": "^2.36.0",
100+
"express": "^4.16.0",
101+
"flow-bin": "^0.56.0",
99102
"flow-remove-types": "^1.2.1",
100-
"generate-release": "^0.13.1",
101-
"nodemon": "^1.11.0",
102-
"nsp": "^2.7.0",
103-
"nyc": "^11.1.0",
103+
"generate-release": "^0.14.0",
104+
"nodemon": "^1.12.1",
105+
"nsp": "^2.8.1",
106+
"nyc": "^11.2.1",
104107
"uuid": "^3.1.0"
105108
}
106109
}

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ class ExpressVueRenderer {
4646
// Options.mergeDataObject(data);
4747
if (vueOptions) {
4848
Options.vue = Models.Defaults.mergeObjects(Options.vue, vueOptions);
49+
if (vueOptions.layout) {
50+
Options.layout = Models.Defaults.mergeObjects(Options.layout, vueOptions.layout);
51+
}
4952
}
5053
Options.component = componentPath;
5154

src/utils/head.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ class HeadUtil {
5252
const href = metaItem.href ? `href="${metaItem.href}" ` : '';
5353
const sizes = metaItem.sizes ? `sizes="${metaItem.sizes}" ` : '';
5454
this.metaTags += `<link ${rel}${type}${href}${sizes}>\n`;
55+
} else if (metaItem.srcContents) {
56+
this.metaTags += `${metaItem.srcContents}\n`;
5557
}
5658
}
5759
}

src/utils/string.js

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,104 @@
11
// @flow
22
const xss = require('xss');
33

4+
class PropClass {
5+
type: any;
6+
required: boolean;
7+
default: any;
8+
constructor(prop: any) {
9+
prop.required ? this.required = prop.required : null;
10+
prop.default ? this.default = prop.default : null;
11+
}
12+
}
413

514
function routesToString(routes: Object[]): string {
6-
let string = '';
7-
routes.forEach(script => string += scriptToString(script) + ',');
8-
return `[${string}]`;
15+
let routeString = '';
16+
routes.forEach(script => routeString += scriptToString(script) + ',');
17+
return `[${routeString}]`.replace(new RegExp(/,}/, 'g'), '}').replace(new RegExp(/,]/, 'g'), ']');
918
}
1019

1120
function routeComponentsToString(script: Object): string {
12-
let string = '';
21+
let componentString = '';
1322
for (let member in script) {
14-
string += member + ': __' + script[member] + ',';
23+
componentString += member + ': __' + script[member] + ',';
1524
}
16-
return `{${string}}`;
25+
return `{${componentString}}`;
1726
}
1827

1928
function mixinsToString(mixins: Array < Object > ): string {
20-
var string = '';
29+
var mixinString = '';
2130
for (var mixin of mixins) {
22-
string += `${scriptToString(mixin)},`;
31+
mixinString += `${scriptToString(mixin)},`;
2332
}
24-
return string;
33+
return mixinString;
2534
}
2635

27-
function scriptToString(script: Object): string {
28-
let string = '';
36+
function propsToString(props: Object): string {
37+
let propString = '';
38+
if (props[Object.keys(props)[0]].type === null) {
39+
var propsArray = Object.keys(props);
40+
propString = xss(JSON.stringify(propsArray));
41+
} else {
42+
let tempProp = {};
43+
for (var prop in props) {
44+
if (props.hasOwnProperty(prop)) {
45+
var element = new PropClass(props[prop]);
46+
tempProp[prop] = element;
47+
}
48+
}
49+
propString = scriptToString(tempProp);
50+
}
51+
return propString;
52+
}
53+
54+
function scriptToString(script: Object | any): string {
55+
let scriptString = '';
2956
for (let member in script) {
3057
switch (typeof script[member]) {
3158
case 'function':
3259
if (member === 'data') {
3360
const dataObj = xss(JSON.stringify(script[member]()));
34-
string += `${member}: function(){return ${dataObj}},`;
61+
scriptString += `${member}: function(){return ${dataObj}},`;
3562
} else {
36-
string += member + ': ' + String(script[member]) + ',';
63+
scriptString += member + ': ' + String(script[member]) + ',';
3764
}
3865
break;
3966
case 'object':
4067
if (member === 'data') {
41-
string += member + ': ' + xss(JSON.stringify(script[member])) + ',';
68+
scriptString += member + ': ' + xss(JSON.stringify(script[member])) + ',';
4269
} else if (member === 'routes' || member === 'children') {
43-
string += member + ': ' + routesToString(script[member]) + ',';
70+
scriptString += member + ': ' + routesToString(script[member]) + ',';
4471
} else if (member === 'components' && script['path'] !== undefined) { // Checks if 'components' is in a route object
45-
string += member + ': ' + routeComponentsToString(script[member]) + ',';
72+
scriptString += member + ': ' + routeComponentsToString(script[member]) + ',';
4673
} else if (member === 'mixins') {
47-
string += member + ': [' + mixinsToString(script[member]) + '],';
74+
scriptString += member + ': [' + mixinsToString(script[member]) + '],';
4875
} else if (script[member].constructor === Array) {
49-
string += member + ': ' + xss(JSON.stringify(script[member])) + ',';
76+
scriptString += member + ': ' + xss(JSON.stringify(script[member])) + ',';
5077
} else if (member === 'props') {
51-
const propsArray = Object.keys(script[member]);
52-
string += member + ': ' + xss(JSON.stringify(propsArray)) + ',';
78+
if (script[member][Object.keys(script[member])[0]].type === null) {
79+
var propsArray = Object.keys(script[member]);
80+
scriptString += member + ': ' + xss(JSON.stringify(propsArray)) + ',';
81+
} else {
82+
// scriptString += member + ': ' + scriptToString(script[member]) + ',';
83+
const propsString = propsToString(script[member]);
84+
scriptString += `${member}: ${propsString},`;
85+
}
86+
5387
} else {
54-
string += member + ': ' + scriptToString(script[member]) + ',';
88+
scriptString += member + ': ' + scriptToString(script[member]) + ',';
5589
}
5690
break;
5791
default:
5892
if (member === 'component' && script['path'] !== undefined) { // Checks if 'component' is in a route object
59-
string += member + ': __' + script[member] + ',';
93+
scriptString += member + ': __' + script[member] + ',';
6094
} else {
61-
string += member + ': ' + JSON.stringify(script[member]) + ',';
95+
scriptString += member + ': ' + JSON.stringify(script[member]) + ',';
6296
}
6397
break;
6498
}
6599
}
66-
return `{${string}}`;
100+
let finalScriptString = `{${scriptString}}`.replace(new RegExp(/,}/, 'g'), '}').replace(new RegExp(/,]/, 'g'), ']');
101+
return finalScriptString;
67102
}
68103

69104
module.exports.scriptToString = scriptToString;

tests/example/app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const options = {
2727
content: 'Page Title'
2828
},
2929
{
30-
script: 'https://unpkg.com/vue@2.4.2/dist/vue.js'
30+
script: 'https://unpkg.com/vue@2.4.4/dist/vue.js'
3131
}, {
3232
name: 'viewport',
3333
content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'

tests/example/components/uuid.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88
<script>
99
import inner from '../components/inner.vue';
1010
export default {
11-
props: ['uuid'],
11+
props: {
12+
uuid: {
13+
type: String,
14+
default: 'missing',
15+
required: true
16+
}
17+
},
1218
data: function () {
1319
return {}
1420
},

tests/index.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const vueOptions = {
2121
}
2222

2323
const exampleHead = `<head>\n<title>Page Title</title>\n<style>.red{color:#9acd32}.pink{color:pink;text-decoration:underline}.test{color:#00f}</style></head>`;
24-
const exampleScript = `<script>(function(){"use strict";var t=function(){return new Vue({mixins:[{methods:{hello:function(){console.log('Hello')}}}],data:function(){return{"title":"Express Vue","message":"Hello world","uuid":"farts"}},methods:{test:function(){console.error('test')}},components:{uuid:{props:["uuid"],data:function(){return{}},components:{inner:{data:function(){return{}},template:"<div><p class=\\"pink\\">Inner Text</p></div>"}},styles:".pink{color:pink;text-decoration:underline}",template:"<div><inner></inner><h2 class=\\"test\\">Uuid: {{uuid ? uuid : 'no uuid'}}</h2></div>"},uuid2:{props:["uuid2"],data:function(){return{}},template:"<div><h3 class=\\"red\\">Uuid2: {{uuid2 ? uuid2 : 'no uuid'}}</h3></div>"}},styles:".pink{color:pink;text-decoration:underline}.test{color:#00f}.red{color:#9acd32}",template:"<div><h1>{{title}}</h1><p>Welcome to the {{title}} demo. Click a link:</p><input v-model=\\"message\\" placeholder=\\"edit me\\"><p>{{message}}</p><uuid :uuid=\\"uuid\\"></uuid><uuid2 :uuid2=\\"uuid2\\"></uuid2><button type=\\"button\\" name=\\"button\\" v-on:click=\\"this.hello\\">Test mixin</button> <button type=\\"button\\" name=\\"button\\" v-on:click=\\"this.test\\">Test method</button></div>"})};typeof module!=='undefined'&&module.exports?(module.exports=t):(this.app=t())}).call(this),app.$mount('#app')</script>`;
24+
const exampleScript = `<script>(function(){"use strict";var t=function(){return new Vue({mixins:[{methods:{hello:function(){console.log('Hello')}}}],data:function(){return{"title":"Express Vue","message":"Hello world","uuid":"farts"}},methods:{test:function(){console.error('test')}},components:{uuid:{props:{uuid:{required:!0,default:"missing"}},data:function(){return{}},components:{inner:{data:function(){return{}},template:"<div><p class=\\"pink\\">Inner Text</p></div>"}},styles:".pink{color:pink;text-decoration:underline}",template:"<div><inner></inner><h2 class=\\"test\\">Uuid: {{uuid ? uuid : 'no uuid'}}</h2></div>"},uuid2:{props:["uuid2"],data:function(){return{}},template:"<div><h3 class=\\"red\\">Uuid2: {{uuid2 ? uuid2 : 'no uuid'}}</h3></div>"}},styles:".pink{color:pink;text-decoration:underline}.test{color:#00f}.red{color:#9acd32}",template:"<div><h1>{{title}}</h1><p>Welcome to the {{title}} demo. Click a link:</p><input v-model=\\"message\\" placeholder=\\"edit me\\"><p>{{message}}</p><uuid :uuid=\\"uuid\\"></uuid><uuid2 :uuid2=\\"uuid2\\"></uuid2><button type=\\"button\\" name=\\"button\\" v-on:click=\\"this.hello\\">Test mixin</button> <button type=\\"button\\" name=\\"button\\" v-on:click=\\"this.test\\">Test method</button></div>"})};typeof module!=='undefined'&&module.exports?(module.exports=t):(this.app=t())}).call(this),app.$mount('#app')</script>`;
2525

2626
test('renders App object', t => {
2727
const renderer = new ExpressVueRenderer(options);
@@ -34,3 +34,36 @@ test('renders App object', t => {
3434
t.fail(error.stack);
3535
});
3636
});
37+
38+
39+
test('renders App object with custom layout', t => {
40+
const vueOptionsWithLayout = {
41+
layout: {
42+
html: {
43+
start: 'html-start',
44+
end: 'html-end',
45+
},
46+
body: {
47+
start: 'body-start',
48+
end: 'body-end'
49+
},
50+
template: {
51+
start: "template-start",
52+
end: "template-end"
53+
},
54+
}
55+
};
56+
const renderer = new ExpressVueRenderer(options);
57+
return renderer.createAppObject('main', data, vueOptionsWithLayout)
58+
.then(app => {
59+
t.is(app.template.html.start, "html-start");
60+
t.is(app.template.html.end, "html-end");
61+
t.is(app.template.body.start, "body-start");
62+
t.is(app.template.body.end, "body-end");
63+
t.is(app.template.template.start, "template-start");
64+
t.is(app.template.template.end, "template-end");
65+
})
66+
.catch(error => {
67+
t.fail(error.stack);
68+
});
69+
});

tests/utils/head.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ const newHead = {
4646
type: 'image/png',
4747
href: '/assets/favicons/favicon-32x32.png',
4848
sizes: '32x32'
49+
},
50+
{
51+
srcContents: '<script>var foo = false;</script>'
4952
}
5053
],
5154
structuredData: {
@@ -57,13 +60,14 @@ const newHead = {
5760
const newMetaString = new HeadUtils(newHead).toString();
5861

5962
//New Tests
60-
const newStringIsCorrect = '<head>\n<title>It was a Pleasure</title>\n<meta name="application-name" content="Name of my application"/>\n<meta name="description" content="A description of the page"/>\n<meta name="twitter:title" content="Content Title"/>\n<meta property="fb:app_id" content="123456789"/>\n<meta property="og:title" content="Content Title"/>\n<script src="/assets/scripts/hammer.min.js" charset="utf-8"></script>\n<script src="/assets/scripts/vue-touch.min.js" charset="utf-8"></script>\n<link rel="stylesheet" type="text/css" href="/assets/rendered/style.css">\n<link rel="stylesheet" type="text/css" href="/assets/rendered/style.css">\n<link rel="icon" type="image/png" href="/assets/favicons/favicon-32x32.png" sizes="32x32" >\n<script type="application/ld+json">\n{"foo":true}\n</script>\n</head>'
63+
const newStringIsCorrect = '<head>\n<title>It was a Pleasure</title>\n<meta name="application-name" content="Name of my application"/>\n<meta name="description" content="A description of the page"/>\n<meta name="twitter:title" content="Content Title"/>\n<meta property="fb:app_id" content="123456789"/>\n<meta property="og:title" content="Content Title"/>\n<script src="/assets/scripts/hammer.min.js" charset="utf-8"></script>\n<script src="/assets/scripts/vue-touch.min.js" charset="utf-8"></script>\n<link rel="stylesheet" type="text/css" href="/assets/rendered/style.css">\n<link rel="stylesheet" type="text/css" href="/assets/rendered/style.css">\n<link rel="icon" type="image/png" href="/assets/favicons/favicon-32x32.png" sizes="32x32" >\n<script>var foo = false;</script>\n<script type="application/ld+json">\n{"foo":true}\n</script>\n</head>'
6164
const newHasTitle = newMetaString.includes('<title>It was a Pleasure</title>');
6265
const newHasMetaName = newMetaString.includes(`<meta name="application-name" content="Name of my application"/>`);
6366
const newHasMetaProperty = newMetaString.includes(`<meta property="og:title" content="Content Title"/>`);
6467
const newHasScript = newMetaString.includes(`<script src="/assets/scripts/hammer.min.js" charset="utf-8">`)
6568
const newHasStyle = newMetaString.includes(`<link rel="stylesheet" type="text/css" href="/assets/rendered/style.css">`)
6669
const newHasStructured = newMetaString.includes(`<script type="application/ld+json">\n{"foo":true}\n</script>`);
70+
const newHasSrcContents = newMetaString.includes(`<script>var foo = false;</script>`);
6771

6872
test('Head is correct', t => {
6973
t.is(newMetaString, newStringIsCorrect);
@@ -92,3 +96,7 @@ test('Head has style 🎷', t => {
9296
test('Head has Structured Data', t => {
9397
t.is(newHasStructured, true);
9498
});
99+
100+
test('Head has srcContents', t => {
101+
t.is(newHasSrcContents, true);
102+
});

0 commit comments

Comments
 (0)