Skip to content

Commit 5503e2a

Browse files
committed
feat/test: template features
1 parent ad3cebd commit 5503e2a

File tree

8 files changed

+304
-8
lines changed

8 files changed

+304
-8
lines changed

Diff for: lib/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ const path = require('path')
22
const hash = require('hash-sum')
33
const parse = require('./parse')
44
const qs = require('querystring')
5-
const loaderUtils = require('loader-utils')
6-
const selectBlock = require('./selector')
75
const plugin = require('./plugin')
6+
const selectBlock = require('./select')
7+
const loaderUtils = require('loader-utils')
88
const { genHotReloadCode } = require('./hotReload')
99
const componentNormalizerPath = require.resolve('./runtime/componentNormalizer')
1010

Diff for: lib/pitch.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ module.exports.pitch = function (remainingRequest) {
3030
if (query.type === `template`) {
3131
const beforeLoaders = this.loaders.slice(1).map(l => l.request)
3232
const request = '-!' + [
33-
templateLoaderPath,
33+
templateLoaderPath + `??vue-loader-options`,
3434
...beforeLoaders,
3535
this.resourcePath + this.resourceQuery
3636
].join('!')

Diff for: lib/plugin.js

+38-1
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,46 @@ module.exports = class VueLoaderPlugin {
2323

2424
// find the normalized version of the vue rule
2525
const normalizedVueRule = rawNormalizedRules[vueRuleIndex]
26-
2726
// get the normlized "use" for vue files
2827
const normalizedVueUse = normalizedVueRule.use.map(cleanUse)
28+
// get vue-loader options
29+
const vueLoaderUseIndex = normalizedVueUse.findIndex(u => {
30+
return /^vue-loader|\/vue-loader/.test(u.loader)
31+
})
32+
33+
if (vueLoaderUseIndex < 0) {
34+
throw new Error(
35+
`VueLoaderPlugin Error: no matching use for vue-loader is found.`
36+
)
37+
}
38+
39+
// make sure vue-loader options has a known ident so that we can share
40+
// options by reference in the template-loader by using a ref query like
41+
// template-loader??vue-loader-options
42+
const ident = 'vue-loader-options'
43+
const vueLoaderUse = normalizedVueUse[vueLoaderUseIndex]
44+
// has options, just set ident
45+
if (vueLoaderUse.options) {
46+
vueLoaderUse.options.ident = ident
47+
} else {
48+
// user provided no options, but we must ensure the options is present
49+
// otherwise RuleSet throws error if no option for a given ref is found.
50+
if (vueRule.loader || vueRule.loaders) {
51+
vueRule.options = { ident }
52+
} else if (vueRule.use) {
53+
const use = vueRule.use[vueLoaderUseIndex]
54+
if (typeof use === 'string') {
55+
vueRule.use[vueLoaderUseIndex] = { loader: use, options: { ident }}
56+
} else {
57+
use.options = { ident }
58+
}
59+
} else {
60+
throw new Error(
61+
`VueLoaderPlugin Error: this should not happen. Please open an issue ` +
62+
`with your webpack config.`
63+
)
64+
}
65+
}
2966

3067
// get new rules without the vue rule
3168
const baseRules = rawRules.filter(r => r !== vueRule)

Diff for: lib/selector.js renamed to lib/select.js

File renamed without changes.

Diff for: package.json

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"css-loader": "^0.28.11",
3939
"eslint": "^4.19.0",
4040
"eslint-plugin-vue-libs": "^2.1.0",
41+
"file-loader": "^1.1.11",
4142
"javascript-stringify": "^1.6.0",
4243
"jest": "^22.4.2",
4344
"jsdom": "^11.6.2",
@@ -50,6 +51,7 @@
5051
"stylus": "^0.54.5",
5152
"stylus-loader": "^3.0.2",
5253
"sugarss": "^1.0.1",
54+
"url-loader": "^1.0.1",
5355
"vue": "^2.5.16",
5456
"vue-template-compiler": "^2.5.16",
5557
"webpack": "^4.1.0",

Diff for: test/fixtures/markdown.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<template>## {{msg}}</template>
1+
<template lang="md">## {{msg}}</template>

Diff for: test/template.spec.js

+243-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,245 @@
1-
test('template', () => {
1+
const path = require('path')
2+
const {
3+
mockRender,
4+
mockBundleAndRun
5+
} = require('./utils')
26

7+
test('template with comments', done => {
8+
mockBundleAndRun({
9+
entry: 'template-comment.vue'
10+
}, ({ window, module, rawModule }) => {
11+
expect(module.comments).toBe(true)
12+
const vnode = mockRender(module, {
13+
msg: 'hi'
14+
})
15+
expect(vnode.tag).toBe('div')
16+
expect(vnode.children.length).toBe(2)
17+
expect(vnode.children[0].data.staticClass).toBe('red')
18+
expect(vnode.children[0].children[0].text).toBe('hi')
19+
expect(vnode.children[1].isComment).toBe(true)
20+
expect(vnode.children[1].text).toBe(' comment here ')
21+
done()
22+
})
23+
})
24+
25+
test('transpile ES2015 features in template', done => {
26+
mockBundleAndRun({
27+
entry: 'es2015.vue'
28+
}, ({ window, module }) => {
29+
const vnode = mockRender(module, {
30+
a: 'hello',
31+
b: true
32+
})
33+
// <div :class="{[a]:true}"></div>
34+
expect(vnode.tag).toBe('div')
35+
expect(vnode.data.class['test-hello']).toBe(true)
36+
expect(vnode.data.class['b']).toBe(true)
37+
done()
38+
})
39+
})
40+
41+
test('transform relative URLs and respects resolve alias', done => {
42+
mockBundleAndRun({
43+
entry: 'resolve.vue',
44+
resolve: {
45+
alias: {
46+
fixtures: path.resolve(__dirname, './fixtures')
47+
}
48+
},
49+
module: {
50+
rules: [
51+
{ test: /\.png$/, loader: 'file-loader?name=[name].[hash:6].[ext]' }
52+
]
53+
}
54+
}, ({ window, module }) => {
55+
const vnode = mockRender(module)
56+
// <div>
57+
// <img src="logo.c9e00e.png">
58+
// <img src="logo.c9e00e.png">
59+
// </div>
60+
expect(vnode.children[0].tag).toBe('img')
61+
expect(vnode.children[0].data.attrs.src).toBe('logo.c9e00e.png')
62+
expect(vnode.children[2].tag).toBe('img')
63+
expect(vnode.children[2].data.attrs.src).toBe('logo.c9e00e.png')
64+
65+
const style = window.document.querySelector('style').textContent
66+
expect(style).toContain('html { background-image: url(logo.c9e00e.png); }')
67+
expect(style).toContain('body { background-image: url(logo.c9e00e.png); }')
68+
done()
69+
})
70+
})
71+
72+
test('transform srcset', done => {
73+
mockBundleAndRun({
74+
entry: 'transform.vue',
75+
module: {
76+
rules: [
77+
{
78+
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
79+
loader: 'url-loader'
80+
}
81+
]
82+
}
83+
}, ({ window, module }) => {
84+
function includeDataURL (s) {
85+
return !!s.match(/\s*data:([a-z]+\/[a-z]+(;[a-z\-]+\=[a-z\-]+)?)?(;base64)?,[a-z0-9\!\$\&\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*/i)
86+
}
87+
const vnode = mockRender(module)
88+
// img tag
89+
expect(includeDataURL(vnode.children[0].data.attrs.src)).toBe(true)
90+
// image tag (SVG)
91+
expect(includeDataURL(vnode.children[2].children[0].data.attrs['xlink:href'])).toBe(true)
92+
const style = window.document.querySelector('style').textContent
93+
94+
const dataURL = vnode.children[0].data.attrs.src
95+
96+
// image tag with srcset
97+
expect(vnode.children[4].data.attrs.srcset).toBe(dataURL)
98+
expect(vnode.children[6].data.attrs.srcset).toBe(dataURL + ' 2x')
99+
// image tag with multiline srcset
100+
expect(vnode.children[8].data.attrs.srcset).toBe(dataURL + ', ' + dataURL + ' 2x')
101+
expect(vnode.children[10].data.attrs.srcset).toBe(dataURL + ' 2x, ' + dataURL)
102+
expect(vnode.children[12].data.attrs.srcset).toBe(dataURL + ' 2x, ' + dataURL + ' 3x')
103+
expect(vnode.children[14].data.attrs.srcset).toBe(dataURL + ', ' + dataURL + ' 2x, ' + dataURL + ' 3x')
104+
expect(vnode.children[16].data.attrs.srcset).toBe(dataURL + ' 2x, ' + dataURL + ' 3x')
105+
106+
// style
107+
expect(includeDataURL(style)).toBe(true)
108+
done()
109+
})
110+
})
111+
112+
// test('functional component with styles', done => {
113+
// mockBundleAndRun({
114+
// entry: 'functional-style.vue'
115+
// }, ({ window, module, rawModule }) => {
116+
// expect(module.functional).toBe(true)
117+
// const vnode = mockRender(module)
118+
// // <div class="foo">hi</div>
119+
// expect(vnode.tag).toBe('div')
120+
// expect(vnode.data.class).toBe('foo')
121+
// expect(vnode.children[0].text).toBe('functional')
122+
123+
// let style = window.document.querySelector('style').textContent
124+
// style = normalizeNewline(style)
125+
// expect(style).toContain('.foo { color: red;\n}')
126+
// done()
127+
// })
128+
// })
129+
130+
test('functional template', done => {
131+
mockBundleAndRun({
132+
entry: 'functional-root.vue',
133+
vue: {
134+
compilerOptions: {
135+
preserveWhitespace: false
136+
}
137+
}
138+
}, ({ window, module }) => {
139+
expect(module.components.Functional._compiled).toBe(true)
140+
expect(module.components.Functional.functional).toBe(true)
141+
expect(module.components.Functional.staticRenderFns).toBeDefined()
142+
expect(typeof module.components.Functional.render).toBe('function')
143+
144+
const vnode = mockRender(module, {
145+
fn () {
146+
done()
147+
}
148+
}).children[0]
149+
150+
// Basic vnode
151+
expect(vnode.children[0].data.staticClass).toBe('red')
152+
expect(vnode.children[0].children[0].text).toBe('hello')
153+
// Default slot vnode
154+
expect(vnode.children[1].tag).toBe('span')
155+
expect(vnode.children[1].children[0].text).toBe('hello')
156+
// Named slot vnode
157+
expect(vnode.children[2].tag).toBe('div')
158+
expect(vnode.children[2].children[0].text).toBe('Second slot')
159+
// // Scoped slot vnode
160+
expect(vnode.children[3].text).toBe('hello')
161+
// // Static content vnode
162+
expect(vnode.children[4].tag).toBe('div')
163+
expect(vnode.children[4].children[0].text).toBe('Some ')
164+
expect(vnode.children[4].children[1].tag).toBe('span')
165+
expect(vnode.children[4].children[1].children[0].text).toBe('text')
166+
// // v-if vnode
167+
expect(vnode.children[5].text).toBe('')
168+
169+
vnode.children[6].data.on.click()
170+
})
171+
})
172+
173+
// test('customizing template loaders', done => {
174+
// mockBundleAndRun({
175+
// entry: 'markdown.vue'
176+
// }, ({ window, module }) => {
177+
// const vnode = mockRender(module, {
178+
// msg: 'hi'
179+
// })
180+
// // <h2 id="-msg-">{{msg}}</h2>
181+
// expect(vnode.tag).toBe('h2')
182+
// expect(vnode.data.attrs.id).toBe('-msg-')
183+
// expect(vnode.children[0].text).toBe('hi')
184+
// done()
185+
// })
186+
// })
187+
188+
test('custom compiler modules', done => {
189+
mockBundleAndRun({
190+
entry: 'custom-module.vue',
191+
vue: {
192+
compilerOptions: {
193+
modules: [
194+
{
195+
postTransformNode: el => {
196+
if (el.staticStyle) {
197+
el.staticStyle = `$processStyle(${el.staticStyle})`
198+
}
199+
if (el.styleBinding) {
200+
el.styleBinding = `$processStyle(${el.styleBinding})`
201+
}
202+
}
203+
}
204+
]
205+
}
206+
}
207+
}, ({ window, module }) => {
208+
const results = []
209+
// var vnode =
210+
mockRender(
211+
Object.assign(module, { methods: { $processStyle: style => results.push(style) }}),
212+
{ transform: 'translateX(10px)' }
213+
)
214+
expect(results).toEqual([
215+
{ 'flex-direction': 'row' },
216+
{ 'transform': 'translateX(10px)' }
217+
])
218+
done()
219+
})
220+
})
221+
222+
test('custom compiler directives', done => {
223+
mockBundleAndRun({
224+
entry: 'custom-directive.vue',
225+
vue: {
226+
compilerOptions: {
227+
directives: {
228+
i18n (el, dir) {
229+
if (dir.name === 'i18n' && dir.value) {
230+
el.i18n = dir.value
231+
if (!el.props) {
232+
el.props = []
233+
}
234+
el.props.push({ name: 'textContent', value: `_s(${JSON.stringify(dir.value)})` })
235+
}
236+
}
237+
}
238+
}
239+
}
240+
}, ({ window, module }) => {
241+
const vnode = mockRender(module)
242+
expect(vnode.data.domProps.textContent).toBe('keypath')
243+
done()
244+
})
3245
})

Diff for: yarn.lock

+17-2
Original file line numberDiff line numberDiff line change
@@ -2796,6 +2796,13 @@ file-entry-cache@^2.0.0:
27962796
flat-cache "^1.2.1"
27972797
object-assign "^4.0.1"
27982798

2799+
file-loader@^1.1.11:
2800+
version "1.1.11"
2801+
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-1.1.11.tgz#6fe886449b0f2a936e43cabaac0cdbfb369506f8"
2802+
dependencies:
2803+
loader-utils "^1.0.2"
2804+
schema-utils "^0.4.5"
2805+
27992806
filename-regex@^2.0.0:
28002807
version "2.0.1"
28012808
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
@@ -5013,7 +5020,7 @@ [email protected]:
50135020
version "1.4.1"
50145021
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
50155022

5016-
mime@^2.1.0:
5023+
mime@^2.0.3, mime@^2.1.0:
50175024
version "2.2.0"
50185025
resolved "https://registry.yarnpkg.com/mime/-/mime-2.2.0.tgz#161e541965551d3b549fa1114391e3a3d55b923b"
50195026

@@ -6860,7 +6867,7 @@ sax@^1.2.4, sax@~1.2.1:
68606867
version "1.2.4"
68616868
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
68626869

6863-
schema-utils@^0.4.0, schema-utils@^0.4.2:
6870+
schema-utils@^0.4.0, schema-utils@^0.4.2, schema-utils@^0.4.3, schema-utils@^0.4.5:
68646871
version "0.4.5"
68656872
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e"
68666873
dependencies:
@@ -7805,6 +7812,14 @@ url-join@^4.0.0:
78057812
version "4.0.0"
78067813
resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a"
78077814

7815+
url-loader@^1.0.1:
7816+
version "1.0.1"
7817+
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-1.0.1.tgz#61bc53f1f184d7343da2728a1289ef8722ea45ee"
7818+
dependencies:
7819+
loader-utils "^1.1.0"
7820+
mime "^2.0.3"
7821+
schema-utils "^0.4.3"
7822+
78087823
url-parse-lax@^1.0.0:
78097824
version "1.0.0"
78107825
resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"

0 commit comments

Comments
 (0)