Commit 5503e2a2 authored by Evan You's avatar Evan You

feat/test: template features

parent ad3cebdd
...@@ -2,9 +2,9 @@ const path = require('path') ...@@ -2,9 +2,9 @@ const path = require('path')
const hash = require('hash-sum') const hash = require('hash-sum')
const parse = require('./parse') const parse = require('./parse')
const qs = require('querystring') const qs = require('querystring')
const loaderUtils = require('loader-utils')
const selectBlock = require('./selector')
const plugin = require('./plugin') const plugin = require('./plugin')
const selectBlock = require('./select')
const loaderUtils = require('loader-utils')
const { genHotReloadCode } = require('./hotReload') const { genHotReloadCode } = require('./hotReload')
const componentNormalizerPath = require.resolve('./runtime/componentNormalizer') const componentNormalizerPath = require.resolve('./runtime/componentNormalizer')
......
...@@ -30,7 +30,7 @@ module.exports.pitch = function (remainingRequest) { ...@@ -30,7 +30,7 @@ module.exports.pitch = function (remainingRequest) {
if (query.type === `template`) { if (query.type === `template`) {
const beforeLoaders = this.loaders.slice(1).map(l => l.request) const beforeLoaders = this.loaders.slice(1).map(l => l.request)
const request = '-!' + [ const request = '-!' + [
templateLoaderPath, templateLoaderPath + `??vue-loader-options`,
...beforeLoaders, ...beforeLoaders,
this.resourcePath + this.resourceQuery this.resourcePath + this.resourceQuery
].join('!') ].join('!')
......
...@@ -23,9 +23,46 @@ module.exports = class VueLoaderPlugin { ...@@ -23,9 +23,46 @@ module.exports = class VueLoaderPlugin {
// find the normalized version of the vue rule // find the normalized version of the vue rule
const normalizedVueRule = rawNormalizedRules[vueRuleIndex] const normalizedVueRule = rawNormalizedRules[vueRuleIndex]
// get the normlized "use" for vue files // get the normlized "use" for vue files
const normalizedVueUse = normalizedVueRule.use.map(cleanUse) const normalizedVueUse = normalizedVueRule.use.map(cleanUse)
// get vue-loader options
const vueLoaderUseIndex = normalizedVueUse.findIndex(u => {
return /^vue-loader|\/vue-loader/.test(u.loader)
})
if (vueLoaderUseIndex < 0) {
throw new Error(
`VueLoaderPlugin Error: no matching use for vue-loader is found.`
)
}
// make sure vue-loader options has a known ident so that we can share
// options by reference in the template-loader by using a ref query like
// template-loader??vue-loader-options
const ident = 'vue-loader-options'
const vueLoaderUse = normalizedVueUse[vueLoaderUseIndex]
// has options, just set ident
if (vueLoaderUse.options) {
vueLoaderUse.options.ident = ident
} else {
// user provided no options, but we must ensure the options is present
// otherwise RuleSet throws error if no option for a given ref is found.
if (vueRule.loader || vueRule.loaders) {
vueRule.options = { ident }
} else if (vueRule.use) {
const use = vueRule.use[vueLoaderUseIndex]
if (typeof use === 'string') {
vueRule.use[vueLoaderUseIndex] = { loader: use, options: { ident }}
} else {
use.options = { ident }
}
} else {
throw new Error(
`VueLoaderPlugin Error: this should not happen. Please open an issue ` +
`with your webpack config.`
)
}
}
// get new rules without the vue rule // get new rules without the vue rule
const baseRules = rawRules.filter(r => r !== vueRule) const baseRules = rawRules.filter(r => r !== vueRule)
......
<template>## {{msg}}</template> <template lang="md">## {{msg}}</template>
test('template', () => { const path = require('path')
const {
mockRender,
mockBundleAndRun
} = require('./utils')
test('template with comments', done => {
mockBundleAndRun({
entry: 'template-comment.vue'
}, ({ window, module, rawModule }) => {
expect(module.comments).toBe(true)
const vnode = mockRender(module, {
msg: 'hi'
})
expect(vnode.tag).toBe('div')
expect(vnode.children.length).toBe(2)
expect(vnode.children[0].data.staticClass).toBe('red')
expect(vnode.children[0].children[0].text).toBe('hi')
expect(vnode.children[1].isComment).toBe(true)
expect(vnode.children[1].text).toBe(' comment here ')
done()
})
})
test('transpile ES2015 features in template', done => {
mockBundleAndRun({
entry: 'es2015.vue'
}, ({ window, module }) => {
const vnode = mockRender(module, {
a: 'hello',
b: true
})
// <div :class="{[a]:true}"></div>
expect(vnode.tag).toBe('div')
expect(vnode.data.class['test-hello']).toBe(true)
expect(vnode.data.class['b']).toBe(true)
done()
})
})
test('transform relative URLs and respects resolve alias', done => {
mockBundleAndRun({
entry: 'resolve.vue',
resolve: {
alias: {
fixtures: path.resolve(__dirname, './fixtures')
}
},
module: {
rules: [
{ test: /\.png$/, loader: 'file-loader?name=[name].[hash:6].[ext]' }
]
}
}, ({ window, module }) => {
const vnode = mockRender(module)
// <div>
// <img src="logo.c9e00e.png">
// <img src="logo.c9e00e.png">
// </div>
expect(vnode.children[0].tag).toBe('img')
expect(vnode.children[0].data.attrs.src).toBe('logo.c9e00e.png')
expect(vnode.children[2].tag).toBe('img')
expect(vnode.children[2].data.attrs.src).toBe('logo.c9e00e.png')
const style = window.document.querySelector('style').textContent
expect(style).toContain('html { background-image: url(logo.c9e00e.png); }')
expect(style).toContain('body { background-image: url(logo.c9e00e.png); }')
done()
})
})
test('transform srcset', done => {
mockBundleAndRun({
entry: 'transform.vue',
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader'
}
]
}
}, ({ window, module }) => {
function includeDataURL (s) {
return !!s.match(/\s*data:([a-z]+\/[a-z]+(;[a-z\-]+\=[a-z\-]+)?)?(;base64)?,[a-z0-9\!\$\&\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*/i)
}
const vnode = mockRender(module)
// img tag
expect(includeDataURL(vnode.children[0].data.attrs.src)).toBe(true)
// image tag (SVG)
expect(includeDataURL(vnode.children[2].children[0].data.attrs['xlink:href'])).toBe(true)
const style = window.document.querySelector('style').textContent
const dataURL = vnode.children[0].data.attrs.src
// image tag with srcset
expect(vnode.children[4].data.attrs.srcset).toBe(dataURL)
expect(vnode.children[6].data.attrs.srcset).toBe(dataURL + ' 2x')
// image tag with multiline srcset
expect(vnode.children[8].data.attrs.srcset).toBe(dataURL + ', ' + dataURL + ' 2x')
expect(vnode.children[10].data.attrs.srcset).toBe(dataURL + ' 2x, ' + dataURL)
expect(vnode.children[12].data.attrs.srcset).toBe(dataURL + ' 2x, ' + dataURL + ' 3x')
expect(vnode.children[14].data.attrs.srcset).toBe(dataURL + ', ' + dataURL + ' 2x, ' + dataURL + ' 3x')
expect(vnode.children[16].data.attrs.srcset).toBe(dataURL + ' 2x, ' + dataURL + ' 3x')
// style
expect(includeDataURL(style)).toBe(true)
done()
})
})
// test('functional component with styles', done => {
// mockBundleAndRun({
// entry: 'functional-style.vue'
// }, ({ window, module, rawModule }) => {
// expect(module.functional).toBe(true)
// const vnode = mockRender(module)
// // <div class="foo">hi</div>
// expect(vnode.tag).toBe('div')
// expect(vnode.data.class).toBe('foo')
// expect(vnode.children[0].text).toBe('functional')
// let style = window.document.querySelector('style').textContent
// style = normalizeNewline(style)
// expect(style).toContain('.foo { color: red;\n}')
// done()
// })
// })
test('functional template', done => {
mockBundleAndRun({
entry: 'functional-root.vue',
vue: {
compilerOptions: {
preserveWhitespace: false
}
}
}, ({ window, module }) => {
expect(module.components.Functional._compiled).toBe(true)
expect(module.components.Functional.functional).toBe(true)
expect(module.components.Functional.staticRenderFns).toBeDefined()
expect(typeof module.components.Functional.render).toBe('function')
const vnode = mockRender(module, {
fn () {
done()
}
}).children[0]
// Basic vnode
expect(vnode.children[0].data.staticClass).toBe('red')
expect(vnode.children[0].children[0].text).toBe('hello')
// Default slot vnode
expect(vnode.children[1].tag).toBe('span')
expect(vnode.children[1].children[0].text).toBe('hello')
// Named slot vnode
expect(vnode.children[2].tag).toBe('div')
expect(vnode.children[2].children[0].text).toBe('Second slot')
// // Scoped slot vnode
expect(vnode.children[3].text).toBe('hello')
// // Static content vnode
expect(vnode.children[4].tag).toBe('div')
expect(vnode.children[4].children[0].text).toBe('Some ')
expect(vnode.children[4].children[1].tag).toBe('span')
expect(vnode.children[4].children[1].children[0].text).toBe('text')
// // v-if vnode
expect(vnode.children[5].text).toBe('')
vnode.children[6].data.on.click()
})
})
// test('customizing template loaders', done => {
// mockBundleAndRun({
// entry: 'markdown.vue'
// }, ({ window, module }) => {
// const vnode = mockRender(module, {
// msg: 'hi'
// })
// // <h2 id="-msg-">{{msg}}</h2>
// expect(vnode.tag).toBe('h2')
// expect(vnode.data.attrs.id).toBe('-msg-')
// expect(vnode.children[0].text).toBe('hi')
// done()
// })
// })
test('custom compiler modules', done => {
mockBundleAndRun({
entry: 'custom-module.vue',
vue: {
compilerOptions: {
modules: [
{
postTransformNode: el => {
if (el.staticStyle) {
el.staticStyle = `$processStyle(${el.staticStyle})`
}
if (el.styleBinding) {
el.styleBinding = `$processStyle(${el.styleBinding})`
}
}
}
]
}
}
}, ({ window, module }) => {
const results = []
// var vnode =
mockRender(
Object.assign(module, { methods: { $processStyle: style => results.push(style) }}),
{ transform: 'translateX(10px)' }
)
expect(results).toEqual([
{ 'flex-direction': 'row' },
{ 'transform': 'translateX(10px)' }
])
done()
})
})
test('custom compiler directives', done => {
mockBundleAndRun({
entry: 'custom-directive.vue',
vue: {
compilerOptions: {
directives: {
i18n (el, dir) {
if (dir.name === 'i18n' && dir.value) {
el.i18n = dir.value
if (!el.props) {
el.props = []
}
el.props.push({ name: 'textContent', value: `_s(${JSON.stringify(dir.value)})` })
}
}
}
}
}
}, ({ window, module }) => {
const vnode = mockRender(module)
expect(vnode.data.domProps.textContent).toBe('keypath')
done()
})
}) })
...@@ -2796,6 +2796,13 @@ file-entry-cache@^2.0.0: ...@@ -2796,6 +2796,13 @@ file-entry-cache@^2.0.0:
flat-cache "^1.2.1" flat-cache "^1.2.1"
object-assign "^4.0.1" object-assign "^4.0.1"
file-loader@^1.1.11:
version "1.1.11"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-1.1.11.tgz#6fe886449b0f2a936e43cabaac0cdbfb369506f8"
dependencies:
loader-utils "^1.0.2"
schema-utils "^0.4.5"
filename-regex@^2.0.0: filename-regex@^2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
...@@ -5013,7 +5020,7 @@ mime@1.4.1: ...@@ -5013,7 +5020,7 @@ mime@1.4.1:
version "1.4.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
mime@^2.1.0: mime@^2.0.3, mime@^2.1.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.2.0.tgz#161e541965551d3b549fa1114391e3a3d55b923b" resolved "https://registry.yarnpkg.com/mime/-/mime-2.2.0.tgz#161e541965551d3b549fa1114391e3a3d55b923b"
...@@ -6860,7 +6867,7 @@ sax@^1.2.4, sax@~1.2.1: ...@@ -6860,7 +6867,7 @@ sax@^1.2.4, sax@~1.2.1:
version "1.2.4" version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
schema-utils@^0.4.0, schema-utils@^0.4.2: schema-utils@^0.4.0, schema-utils@^0.4.2, schema-utils@^0.4.3, schema-utils@^0.4.5:
version "0.4.5" version "0.4.5"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e"
dependencies: dependencies:
...@@ -7805,6 +7812,14 @@ url-join@^4.0.0: ...@@ -7805,6 +7812,14 @@ url-join@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a" resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a"
url-loader@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-1.0.1.tgz#61bc53f1f184d7343da2728a1289ef8722ea45ee"
dependencies:
loader-utils "^1.1.0"
mime "^2.0.3"
schema-utils "^0.4.3"
url-parse-lax@^1.0.0: url-parse-lax@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment