Commit 31756338 authored by Evan You's avatar Evan You

initial work

parent 8badc35f
module.exports = {
root: true,
extends: ['plugin:vue-libs/recommended']
}
.DS_Store
test
exploration
node_modules
*.log
The MIT License (MIT)
Copyright (c) 2015-present Yuxi (Evan) You
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
module.exports = function assemble (sourcePath, descriptor) {
const templateImport = descriptor.template
? `import { render, staticRenderFns } from '${sourcePath}?template'`
: ``
const scriptImport = descriptor.script
? `import script from '${sourcePath}?script'`
: `const script = {}`
const styleImports = descriptor.styles.map((_, i) => {
return `import style${i} from '${sourcePath}?style&index=${i}'`
}).join('\n')
return `
${templateImport}
${scriptImport}
${styleImports}
script.render = render
script.staticRenderFns = staticRenderFns
export default script
`.trim()
}
const path = require('path')
const hash = require('hash-sum')
const qs = require('querystring')
const parse = require('./parse')
const assemble = require('./assemble')
const qs = require('querystring')
const loaderUtils = require('loader-utils')
const compileTemplate = require('./template-compiler')
const selectBlock = require('./selector')
const plugin = require('./plugin')
const componentNormalizerPath = require.resolve('./runtime/componentNormalizer')
module.exports = function (source) {
const { resourcePath, resourceQuery, target, minimize, sourceMap } = this
const loaderContext = this
const stringifyRequest = r => loaderUtils.stringifyRequest(loaderContext, r)
const {
target,
request,
minimize,
sourceMap,
rootContext,
resourcePath,
resourceQuery
} = loaderContext
const isServer = target === 'node'
const isProduction = minimize || process.env.NODE_ENV === 'production'
const options = loaderUtils.getOptions(this) || {}
const options = loaderUtils.getOptions(loaderContext) || {}
const fileName = path.basename(resourcePath)
const context = this.rootContext || process.cwd()
const context = rootContext || process.cwd()
const sourceRoot = path.dirname(path.relative(context, resourcePath))
const shortFilePath = path.relative(context, resourcePath).replace(/^(\.\.[\\\/])+/, '')
const moduleId = 'data-v-' + hash(isProduction ? (shortFilePath + '\n' + content) : shortFilePath)
const needCssSourceMap = !isProduction && sourceMap && options.cssSourceMap !== false
const descriptor = parse(
source,
fileName,
sourceRoot,
sourceMap,
needCssSourceMap
sourceMap
)
if (resourceQuery) {
const query = qs.parse(resourceQuery.slice(1))
// template
if (query.template != null) {
return compileTemplate(descriptor, this, moduleId)
}
// script
if (query.script != null) {
this.callback(
null,
descriptor.script.content,
descriptor.script.map
)
return
}
// styles
if (query.style != null && query.index != null) {
return descriptor.styles[query.index].content
}
// if the query has a type field, this is a language block request
// e.g. foo.vue?type=template&id=xxxxx
// and we will return early
const incomingQuery = qs.parse(resourceQuery.slice(1))
if (incomingQuery.type) {
return selectBlock(descriptor, loaderContext, incomingQuery)
}
// module id for scoped CSS & hot-reload
const shortFilePath = path
.relative(context, resourcePath)
.replace(/^(\.\.[\\\/])+/, '')
const id = hash(
isProduction
? (shortFilePath + '\n' + source)
: shortFilePath
)
// feature information
const hasScoped = descriptor.styles.some(s => s.scoped)
const templateAttrs = (descriptor.template && descriptor.template.attrs) || {}
const hasFunctional = templateAttrs.functional
const hasComment = templateAttrs.comments
const needsHotReload = (
!isServer &&
!isProduction &&
(descriptor.script || descriptor.template) &&
options.hotReload !== false
)
// template
let templateImport = `var render, staticRenderFns`
if (descriptor.template) {
const src = descriptor.template.src || resourcePath
const langQuery = getLangQuery(descriptor.template)
const idQuery = hasScoped ? `&id=${id}` : ``
const fnQuery = hasFunctional ? `&functional` : ``
const commentQuery = hasComment ? `&comment` : ``
const query = `?vue&type=template${idQuery}${langQuery}${fnQuery}${commentQuery}`
const request = stringifyRequest(src + query)
templateImport = `import { render, staticRenderFns } from ${request}`
}
// script
let scriptImport = `var script = {}`
if (descriptor.script) {
const src = descriptor.script.src || resourcePath
const langQuery = getLangQuery(descriptor.script, 'js')
const query = `?vue&type=script${langQuery}`
const request = stringifyRequest(src + query)
scriptImport = (
`import script from ${request}\n` +
`export * from ${request}` // support named exports
)
}
// styles
let styleImports = ``
if (descriptor.styles.length) {
styleImports = descriptor.styles.map((style, i) => {
const src = style.src || resourcePath
const langQuery = getLangQuery(style, 'css')
const scopedQuery = style.scoped ? `&scoped&id=${id}` : ``
const query = `?vue&type=style&index=${i}${langQuery}${scopedQuery}`
const request = stringifyRequest(src + query)
return `import style${i} from ${request}`
}).join('\n')
}
// assemble
return assemble(resourcePath, descriptor)
let code = `
${templateImport}
${scriptImport}
${styleImports}
import normalizer from ${stringifyRequest(`!${componentNormalizerPath}`)}
var component = normalizer(
script,
render,
staticRenderFns,
${hasFunctional ? `true` : `false`},
null, // TODO style injection
${JSON.stringify(id)},
${isServer ? JSON.stringify(hash(request)) : `null`}
${incomingQuery.shadow ? `,true` : ``}
)
`.trim()
if (descriptor.customBlocks && descriptor.customBlocks.length) {
// TODO custom blocks
}
if (!isProduction) {
code += `\ncomponent.options.__file = ${JSON.stringify(shortFilePath)}`
}
if (needsHotReload) {
// TODO hot reload
}
code += `\nexport default component.exports`
// console.log(code)
return code
}
function getLangQuery (block, fallback) {
const lang = block.lang || fallback
return lang ? `&lang=${lang}` : ``
}
module.exports.VueLoaderPlugin = plugin
......@@ -6,7 +6,7 @@ const SourceMapGenerator = require('source-map').SourceMapGenerator
const splitRE = /\r?\n/g
const emptyRE = /^(?:\/\/)?\s*$/
module.exports = (content, filename, sourceRoot, needMap, needCSSMap) => {
module.exports = (content, filename, sourceRoot, needMap) => {
const cacheKey = hash(filename + content)
let output = cache.get(cacheKey)
if (output) return output
......@@ -20,7 +20,7 @@ module.exports = (content, filename, sourceRoot, needMap, needCSSMap) => {
sourceRoot
)
}
if (needCSSMap && output.styles) {
if (output.styles) {
output.styles.forEach(style => {
if (!style.src) {
style.map = generateSourceMap(
......
const qs = require('querystring')
const loaderUtils = require('loader-utils')
const templateLoaderPath = require.resolve('./template-loader')
const stylePostLoaderPath = require.resolve('./style-post-loader')
module.exports = code => code
// This pitching loader is responsible for catching all src import requests
// from within vue files and transform it into appropriate requests
module.exports.pitch = function (remainingRequest) {
const query = qs.parse(this.resourceQuery.slice(1))
if (query.vue != null) {
// For Scoped CSS: inject style-post-loader before css-loader
if (query.type === `style` && query.scoped != null) {
const cssLoaderIndex = this.loaders.findIndex(l => /\/css-loader/.test(l.request))
if (cssLoaderIndex) {
const afterLoaders = this.loaders.slice(1, cssLoaderIndex + 1).map(l => l.request)
const beforeLoaders = this.loaders.slice(cssLoaderIndex + 1).map(l => l.request)
const request = '-!' + [
...afterLoaders,
stylePostLoaderPath,
...beforeLoaders,
this.resourcePath + this.resourceQuery
].join('!')
return `export * from ${loaderUtils.stringifyRequest(this, request)}`
}
}
// for templates: inject the template compiler
if (query.type === `template`) {
const beforeLoaders = this.loaders.slice(1).map(l => l.request)
const request = '-!' + [
templateLoaderPath,
...beforeLoaders,
this.resourcePath + this.resourceQuery
].join('!')
return `export * from ${loaderUtils.stringifyRequest(this, request)}`
}
}
}
const qs = require('querystring')
const RuleSet = require('webpack/lib/RuleSet')
// TODO handle vueRule with oneOf
module.exports = class VueLoaderPlugin {
apply (compiler) {
// get a hold of the raw rules
const rawRules = compiler.options.module.rules
// use webpack's RuleSet utility to normalize user rules
const rawNormalizedRules = new RuleSet(rawRules).rules
// find the rule that applies to vue files
const vueRuleIndex = rawRules.findIndex((rule, i) => {
return !rule.enforce && rawNormalizedRules[i].resource(`foo.vue`)
})
const vueRule = rawRules[vueRuleIndex]
if (!vueRule) {
throw new Error(
`VueLoaderPlugin Error: no matching rule for vue files are found.`
)
}
// find the normalized version of the vue rule
const normalizedVueRule = rawNormalizedRules[vueRuleIndex]
// get the normlized "use" for vue files
const normalizedVueUse = normalizedVueRule.use.map(cleanUse)
// get new rules without the vue rule
const baseRules = rawRules.filter(r => r !== vueRule)
const normalizedRules = rawNormalizedRules.filter(r => r !== normalizedVueRule)
// construct a new rule for vue file, with oneOf containing
// multiple rules with dynamic resourceQuery functions that applies to
// different language blocks in a raw vue file.
const constructedRule = {
test: /\.vue$/,
oneOf: baseRules.map((rule, i) => {
// for each user rule, create a cloned rule by checking if the rule
// matches the lang specified in the resourceQuery.
return cloneRule(rule, normalizedRules[i], normalizedVueUse)
}).concat(vueRule)
}
// replace the original vue rule with our new constructed rule.
rawRules.splice(vueRuleIndex, 1, constructedRule)
// inject global pitcher (responsible for injecting CSS post loader)
rawRules.unshift({
loader: require.resolve('./pitch')
})
}
}
function cloneRule (rule, normalizedRule, vueUse) {
if (rule.oneOf) {
return Object.assign({}, rule, {
oneOf: rule.oneOf.map((r, i) => cloneRule(r, normalizedRule.oneOf[i]))
})
}
// Assuming `test` and `resourceQuery` tests are executed in series and
// synchronously (which is true based on RuleSet's implementation), we can
// save the current resource being matched from `test` so that we can access
// it in `resourceQuery`. This ensures when we use the normalized rule's
// resource check, include/exclude are matched correctly.
let currentResource
const res = Object.assign({}, rule, {
test: resource => {
currentResource = resource
return true
},
resourceQuery: query => {
const parsed = qs.parse(query.slice(1))
if (parsed.lang == null) {
return false
}
return normalizedRule.resource(`${currentResource}.${parsed.lang}`)
},
use: [
...(normalizedRule.use || []).map(cleanUse),
...vueUse
]
})
// delete shorthand since we have normalized use
delete res.loader
delete res.options
return res
}
// "ident" is exposed on normalized uses, delete in case it
// interferes with another normalization
function cleanUse (use) {
const res = Object.assign({}, use)
delete res.ident
return res
}
/* globals __VUE_SSR_CONTEXT__ */
// IMPORTANT: Do NOT use ES2015 features in this file (except for modules).
// This module is a runtime utility for cleaner component module output and will
// be included in the final webpack user bundle.
export default function normalizeComponent (
scriptExports,
render,
staticRenderFns,
functionalTemplate,
injectStyles,
scopeId,
moduleIdentifier, /* server only */
shadowMode /* vue-cli only */
) {
// Vue.extend constructor export interop
var options = typeof scriptExports === 'function'
? scriptExports.options
: scriptExports
// render functions
if (render) {
options.render = render
options.staticRenderFns = staticRenderFns
options._compiled = true
}
// functional template
if (functionalTemplate) {
options.functional = true
}
// scopedId
if (scopeId) {
options._scopeId = 'data-v-' + scopeId
}
var hook
if (moduleIdentifier) { // server build
hook = function (context) {
// 2.3 injection
context =
context || // cached call
(this.$vnode && this.$vnode.ssrContext) || // stateful
(this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional
// 2.2 with runInNewContext: true
if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
context = __VUE_SSR_CONTEXT__
}
// inject component styles
if (injectStyles) {
injectStyles.call(this, context)
}
// register component module identifier for async chunk inferrence
if (context && context._registeredComponents) {
context._registeredComponents.add(moduleIdentifier)
}
}
// used by ssr in case component is cached and beforeCreate
// never gets called
options._ssrRegister = hook
} else if (injectStyles) {
hook = shadowMode
? function () { injectStyles.call(this, this.$root.$options.shadowRoot) }
: injectStyles
}
if (hook) {
if (options.functional) {
// for template-only hot-reload because in that case the render fn doesn't
// go through the normalizer
options._injectStyles = hook
// register for functioal component in vue file
var originalRender = options.render
options.render = function renderWithStyleInjection (h, context) {
hook.call(context)
return originalRender(h, context)
}
} else {
// inject component registration as beforeCreate hook
var existing = options.beforeCreate
options.beforeCreate = existing
? [].concat(existing, hook)
: [hook]
}
}
return {
exports: scriptExports,
options: options
}
}
module.exports = function selectBlock (descriptor, loaderContext, query) {
// template
if (query.type === `template`) {
loaderContext.callback(
null,
descriptor.template.content,
descriptor.template.map
)
return
}
// script
if (query.type === `script`) {
loaderContext.callback(
null,
descriptor.script.content,
descriptor.script.map
)
return
}
// styles
if (query.type === `style` && query.index != null) {
const style = descriptor.styles[query.index]
loaderContext.callback(
null,
style.content,
style.map
)
return
}
}
const qs = require('querystring')
const postcss = require('postcss')
const trim = require('./plugins/trim')
const scoped = require('./plugins/scoped')
module.exports = function (source, map) {
const cb = this.async()
const query = qs.parse(this.resourceQuery.slice(1))
const id = `data-v-${query.id}`
const plugins = [trim(), scoped({ id })]
const options = {
to: this.resourcePath,
from: this.resourcePath,
map: false
}
if (map) {
options.map = {
inline: false,
annotation: false,
prev: map
}
}
postcss(plugins)
.process(source, options)
.then(result => {
if (result.messages) {
result.messages.forEach(({ type, file }) => {
if (type === 'dependency') {
this.addDependency(file)
}
})
}
const map = result.map && result.map.toJSON()
cb(null, result.css, map)
return null // silence bluebird warning
})
.catch(cb)
}
const postcss = require('postcss')
const selectorParser = require('postcss-selector-parser')
module.exports = postcss.plugin('add-id', ({ id }) => root => {
const keyframes = Object.create(null)
root.each(function rewriteSelector (node) {
if (!node.selector) {
// handle media queries
if (node.type === 'atrule') {
if (node.name === 'media' || node.name === 'supports') {
node.each(rewriteSelector)
} else if (/-?keyframes$/.test(node.name)) {
// register keyframes
keyframes[node.params] = node.params = node.params + '-' + id
}
}
return
}
node.selector = selectorParser(selectors => {
selectors.each(selector => {
let node = null
selector.each(n => {
// ">>>" combinator
if (n.type === 'combinator' && n.value === '>>>') {
n.value = ' '
n.spaces.before = n.spaces.after = ''
return false
}
// /deep/ alias for >>>, since >>> doesn't work in SASS
if (n.type === 'tag' && n.value === '/deep/') {
const prev = n.prev()
if (prev && prev.type === 'combinator' && prev.value === ' ') {
prev.remove()
}
n.remove()
return false
}
if (n.type !== 'pseudo' && n.type !== 'combinator') {
node = n
}
})
selector.insertAfter(node, selectorParser.attribute({
attribute: id
}))
})
}).processSync(node.selector)
})
// If keyframes are found in this <style>, find and rewrite animation names
// in declarations.
// Caveat: this only works for keyframes and animation rules in the same
// <style> element.
if (Object.keys(keyframes).length) {
root.walkDecls(decl => {
// individual animation-name declaration
if (/-?animation-name$/.test(decl.prop)) {
decl.value = decl.value.split(',')
.map(v => keyframes[v.trim()] || v.trim())
.join(',')
}
// shorthand
if (/-?animation$/.test(decl.prop)) {
decl.value = decl.value.split(',')
.map(v => {
const vals = v.trim().split(/\s+/)
const i = vals.findIndex(val => keyframes[val])
if (i !== -1) {
vals.splice(i, 1, keyframes[vals[i]])
return vals.join(' ')
} else {
return v
}
})
.join(',')
}
})
}
})
const postcss = require('postcss')
module.exports = postcss.plugin('trim', opts => css => {
css.walk(({ type, raws }) => {
if (type === 'rule' || type === 'atrule') {
raws.before = raws.after = '\n'
}
})
})
const qs = require('querystring')
const prettier = require('prettier')
const consolidate = require('consolidate')
const loaderUtils = require('loader-utils')
const compiler = require('vue-template-compiler')
const transpile = require('vue-template-es2015-compiler')
const transformRequire = require('./modules/transform-require')
const transformSrcset = require('./modules/transform-srcset')
const transformAssetUrl = require('./modules/assetUrl')
const transformSrcset = require('./modules/srcset')
const hotReloadAPIPath = require.resolve('vue-hot-reload-api')
module.exports = function compileTemplate (descriptor, loaderContext, id) {
const sourceTemplate = descriptor.template.content
module.exports = function (template) {
const loaderContext = this
const query = qs.parse(this.resourceQuery.slice(1))
// TODO: actually get main vue-loader options
const options = loaderUtils.getOptions(loaderContext) || {}
const cb = loaderContext.async()
const compile = template => cb(null, actuallyCompile(
template,
options,
loaderContext,
query
))
if (query.lang) {
preprocess(
template,
options,
loaderContext,
query.lang,
(err, template) => {
if (err) return cb(err)
compile(template)
}
)
} else {
compile(template)
}
}
function preprocess (rawTemplate, options, loaderContext, lang, cb) {
if (!consolidate[lang]) {
return cb(
new Error(`Template engine "${lang}" is not supported by vue-loader.`)
)
}
const engineOptions = Object.assign({
filename: loaderContext.resourcePath
}, options.template)
consolidate[lang].render(rawTemplate, engineOptions, (err, template) => {
if (err) {
return cb(err)
}
cb(null, template)
})
}
function actuallyCompile (sourceTemplate, options, loaderContext, query) {
const { id } = query
const isServer = loaderContext.target === 'node'
const isProduction = loaderContext.minimize || process.env.NODE_ENV === 'production'
const options = loaderUtils.getOptions(loaderContext) || {}
const needsHotReload = !isServer && !isProduction && options.hotReload !== false
const defaultModules = [transformRequire(options.transformToRequire), transformSrcset()]
const hasScoped = descriptor.styles.some(({ scoped }) => scoped)
const templateAttrs = descriptor.template.attrs || {}
const hasComment = !!templateAttrs.comments
const hasFunctionalTemplate = !!templateAttrs.functional
const defaultModules = [transformAssetUrl(options.transformAssetUrl), transformSrcset()]
const hasComment = query.comment != null
const hasFunctionalTemplate = query.functional != null
const {
preserveWhitespace,
modules,
directives
} = options.compilerOptions || {}
const compilerOptions = {
preserveWhitespace: options.preserveWhitespace,
modules: defaultModules.concat(options.compilerModules || []),
directives: options.compilerDirectives || {},
scopeId: hasScoped ? id : null,
preserveWhitespace,
modules: defaultModules.concat(modules || []),
directives: directives || {},
scopeId: id,
comments: hasComment
}
......
// vue compiler module for transforming `<tag>:<attribute>` to `require`
const urlToRequire = require('../url-to-require')
const urlToRequire = require('./urlToRequire')
const defaultOptions = {
video: ['src', 'poster'],
......
// vue compiler module for transforming `img:srcset` to a number of `require`s
const urlToRequire = require('../url-to-require')
const urlToRequire = require('./urlToRequire')
module.exports = () => ({
postTransformNode: node => {
......
{
"name": "vue-loader-next",
"version": "1.0.0",
"description": "",
"main": "index.js",
"name": "vue-loader",
"version": "15.0.0",
"description": "Vue single-file component loader for Webpack",
"main": "lib/index.js",
"scripts": {
"build": "webpack --config test/webpack.config.js"
"test": "jest --env node",
"lint": "eslint lib test --fix",
"build": "webpack --config exploration/webpack.config.js"
},
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vue-loader/issues"
},
"homepage": "https://github.com/vuejs/vue-loader",
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"lib/**/*.js": [
"eslint --fix",
"git add"
],
"test/**/*.js": [
"eslint --fix",
"git add"
]
},
"peerDependencies": {
"css-loader": "*",
"vue-template-compiler": "^2.0.0"
},
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.4",
"babel-preset-env": "^1.6.1",
"css-loader": "^0.28.11",
"eslint": "^4.19.0",
"eslint-plugin-vue-libs": "^2.1.0",
"javascript-stringify": "^1.6.0",
"jest": "^22.4.2",
"jsdom": "^11.6.2",
"lint-staged": "^7.0.0",
"node-sass": "^4.7.2",
"normalize-newline": "^3.0.0",
"pug": "^2.0.1",
"sass-loader": "^6.0.7",
"stylus": "^0.54.5",
"stylus-loader": "^3.0.2",
"vue": "^2.5.16",
"vue-template-compiler": "^2.5.16",
"webpack": "^4.1.0",
"webpack-cli": "^2.0.10"
"webpack-cli": "^2.0.10",
"webpack-merge": "^4.1.2",
"yorkie": "^1.0.3"
},
"dependencies": {
"consolidate": "^0.15.0",
"debug": "^3.1.0",
"hash-sum": "^1.0.2",
"loader-utils": "^1.1.0",
"lru-cache": "^4.1.2",
"postcss": "^6.0.20",
"postcss-selector-parser": "^3.1.1",
"prettier": "^1.11.1",
"source-map": "^0.7.2",
"vue-component-compiler": "vuejs/vue-component-compiler#master",
......
module.exports = {
env: {
jest: true
}
}
const {
genId,
mockRender,
mockBundleAndRun,
initStylesForAllSubComponents
} = require('./utils')
const normalizeNewline = require('normalize-newline')
test('basic', done => {
mockBundleAndRun({
entry: 'basic.vue'
}, ({ window, module, rawModule }) => {
const vnode = mockRender(module, {
msg: 'hi'
})
// <h2 class="red">{{msg}}</h2>
expect(vnode.tag).toBe('h2')
expect(vnode.data.staticClass).toBe('red')
expect(vnode.children[0].text).toBe('hi')
expect(module.data().msg).toContain('Hello from Component A!')
let style = window.document.querySelector('style').textContent
style = normalizeNewline(style)
expect(style).toContain('comp-a h2 {\n color: #f00;\n}')
done()
})
})
test('pre-processors', done => {
mockBundleAndRun({
entry: 'pre.vue',
module: {
rules: [
{
test: /\.js/,
loader: 'babel-loader'
},
{
test: /\.stylus/,
use: [
'vue-style-loader',
'css-loader',
'stylus-loader'
]
}
]
}
}, ({ window, module, code }) => {
// make sure babel is actually applied
expect(code).toMatch('data: function data()')
const vnode = mockRender(module)
// div
// h1 This is the app
// comp-a
// comp-b
expect(vnode.children[0].tag).toBe('h1')
expect(vnode.children[1].tag).toBe('comp-a')
expect(vnode.children[2].tag).toBe('comp-b')
// script
expect(module.data().msg).toContain('Hello from Babel')
// style
const style = window.document.querySelector('style').textContent
expect(style).toContain('body {\n font: 100% Helvetica, sans-serif;\n color: #999;\n}')
done()
})
})
test('style import', done => {
mockBundleAndRun({
entry: 'style-import.vue'
}, ({ window }) => {
const styles = window.document.querySelectorAll('style')
expect(styles[0].textContent).toContain('h1 { color: red; }')
// import with scoped
const id = 'data-v-' + genId('style-import.vue')
expect(styles[1].textContent).toContain('h1[' + id + '] { color: green;\n}')
done()
})
})
test('style import for a same file twice', done => {
mockBundleAndRun({
entry: 'style-import-twice.vue'
}, ({ window, module }) => {
initStylesForAllSubComponents(module)
const styles = window.document.querySelectorAll('style')
expect(styles.length).toBe(3)
expect(styles[0].textContent).toContain('h1 { color: red; }')
// import with scoped
const id = 'data-v-' + genId('style-import-twice-sub.vue')
expect(styles[1].textContent).toContain('h1[' + id + '] { color: green;\n}')
const id2 = 'data-v-' + genId('style-import-twice.vue')
expect(styles[2].textContent).toContain('h1[' + id2 + '] { color: green;\n}')
done()
})
})
test('template import', done => {
mockBundleAndRun({
entry: 'template-import.vue'
}, ({ window, module }) => {
const vnode = mockRender(module)
// '<div><h1>hello</h1></div>'
expect(vnode.children[0].tag).toBe('h1')
expect(vnode.children[0].children[0].text).toBe('hello')
done()
})
})
test('script import', done => {
mockBundleAndRun({
entry: 'script-import.vue'
}, ({ window, module }) => {
expect(module.data().msg).toContain('Hello from Component A!')
done()
})
})
<template>
<h2 class="red">{{msg}}</h2>
</template>
<script>
export default {
data () {
return {
msg: 'Hello from Component A!'
}
}
}
</script>
<style>
comp-a h2 {
color: #f00;
}
</style>
<style module="style">
.red {
color: red;
}
@keyframes fade {
from { opacity: 1; } to { opacity: 0; }
}
.animate {
animation: fade 1s;
}
</style>
<style scoped lang="stylus" module>
.red
color: red
</style>
<script>
module.exports = {}
</script>
<i18n lang="yaml">
en:
hello: "hello world"
ja:
hello: "こんにちは、世界"
</i18n>
<blog>## foo</blog>
<esm>
export default function (Component) {
Component.options.foo = 1
}
</esm>
<template>
<div>
<h1>{{ msg }}</h1>
<p v-html="blog"></p>
</div>
</template>
<template>
<p v-i18n="keypath"></p>
</template>
<unit-test src="./unit-test.js">
</unit-test>
<template>
<h2 class="red">{{msg}}</h2>
</template>
<script>
export default {
data () {
return {
msg: 'Hello from Component A!'
}
}
}
</script>
<style>
comp-a h2 {
color: #f00;
}
</style>
<unit-test>
describe('example', function () {
it('basic', function (done) {
done();
})
})
</unit-test>
<documentation>
This is example documentation for a component.
</documentation>
<template>
<h2 class="red">{{msg}}</h2>
</template>
<script>
export default {
data () {
return {
msg: 'Hello from Component A!'
}
}
}
</script>
<style>
comp-a h2 {
color: #f00;
}
</style>
module.exports = [
{
postTransformNode: el => {
if (el.staticClass) {
el.staticClass = '"red blue"'
}
}
}
]
<template>
<div style="flex-direction: row">
<span :style="{ transform }"></span>
</div>
</template>
<unit-test foo="bar" opt2="value2">
describe('example', function () {
it('basic', function (done) {
done();
})
})
</unit-test>
const Component = require('~target')
if (typeof window !== 'undefined') {
window.vueModule = Component
}
module.exports = Component
<template>
<div :class="{ [`test-${a}`]: true, b }"></div>
</template>
<template>
<div>{{ msg }}</div>
</template>
<script>
import Vue from 'vue'
Vue.config.productionTip = false
export default Vue.extend({
data () {
return { msg: 'success' }
}
})
</script>
<style lang="stylus">
h1
color red
</style>
<style>
h2 {
color: green;
}
</style>
<template>
<div>
<functional>
<span>hello</span>
<div slot="slot2">Second slot</div>
<template slot="scoped" scope="scope">{{ scope.msg }}</template>
</functional>
</div>
</template>
<script>
import Functional from './functional.vue'
export default {
components: {
Functional
}
}
</script>
<script>
export default {
functional: true,
render (h) {
return h('div', { class: 'foo' }, ['functional'])
}
}
</script>
<style>
.foo { color: red; }
</style>
<template functional>
<div>
<h2 class="red">{{ props.msg }}</h2>
<slot></slot>
<slot name="slot2"></slot>
<slot :msg="props.msg" name="scoped"></slot>
<div>Some <span>text</span></div>
<div v-if="false">Not exist</div>
<div class="clickable" @click="parent.fn"></div>
</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
default: 'hello'
}
}
}
</script>
<template>## {{msg}}</template>
<style scoped>
@media print {
.foo {
color: #000;
}
}
</style>
<template>
<div class="multiple-rule">Hello World</div>
</template>
<style>
.multiple-rule-1 {
font-size: 7px;
}
</style>
<template>
<div class="multiple-rule">Hello World</div>
</template>
<style>
.multiple-rule-2 {
font-size: 28px;
}
</style>
import Rule1 from './multiple-rules-1.vue'
import Rule2 from './multiple-rules-2.vue'
window.rules = [Rule1, Rule2]
<script>
export default {
name: 'named-exports'
}
export function foo () {
return 1
}
</script>
<style lang="pcss">
h1 {
color: red;
font-size: 14px
}
</style>
<style>
h1
color: red
font-size: 14px
</style>
<style lang="stylus">
font-stack = Helvetica, sans-serif
primary-color = #999
body
font 100% font-stack
color primary-color
</style>
<template lang="pug">
div
h1 This is the app
comp-a
comp-b
</template>
<script>
export default {
data () {
return { msg: 'Hello from Babel' }
}
}
</script>
<template>
<div>
<img src="./logo.png">
<img src="~fixtures/logo.png">
<img src="~/fixtures/logo.png">
</div>
</template>
<style>
html { background-image: url(./logo.png); }
body { background-image: url(~fixtures/logo.png); }
body { background-image: url(~/fixtures/logo.png); }
</style>
<style scoped>
.test {
color: yellow;
}
.test:after {
content: 'bye!';
}
h1 {
color: green;
}
.anim {
animation: color 5s infinite, other 5s;
}
.anim-2 {
animation-name: color;
animation-duration: 5s;
}
.anim-3 {
animation: 5s color infinite, 5s other;
}
.anim-multiple {
animation: color 5s infinite, opacity 2s;
}
.anim-multiple-2 {
animation-name: color, opacity;
animation-duration: 5s, 2s;
}
@keyframes color {
from { color: red; }
to { color: green; }
}
@-webkit-keyframes color {
from { color: red; }
to { color: green; }
}
@keyframes opacity {
from { opacity: 0; }
to { opacity: 1; }
}
@-webkit-keyframes opacity {
from { opacity: 0; }
to { opacity: 1; }
}
.foo p >>> .bar {
color: red;
}
</style>
<template>
<div>
<div><h1>hi</h1></div>
<p class="abc def">hi</p>
<template v-if="ok"><p class="test">yo</p></template>
<svg><template><p></p></template></svg>
</div>
</template>
export default {
data () {
return {
msg: 'Hello from Component A!'
}
}
}
<script src="./script-import.js"></script>
\ No newline at end of file
import Vue from 'vue'
import App from './ssr-style.vue'
export default () => new Vue({
render: h => h(App)
})
<template>
<div>
<h1>Hello</h1>
<basic/>
<functional-style/>
</div>
</template>
<script>
import Basic from './basic.vue'
import FunctionalStyle from './functional-style.vue'
export default {
components: {
Basic,
FunctionalStyle
}
}
</script>
<style>
h1 { color: green; }
</style>
<style src="./style-import.css"></style>
<template><div></div></template>
<style src="./style-import.css"></style>
<style src="./style-import-scoped.css" scoped></style>
<template><SubComponent></SubComponent></template>
<style src="./style-import.css"></style>
<style src="./style-import-scoped.css" scoped></style>
<script>
import SubComponent from './style-import-twice-sub.vue'
export default {
components: { SubComponent }
}
</script>
<style src="./style-import.css"></style>
<style src="./style-import-scoped.css" scoped></style>
module.exports = {
plugins: {
autoprefixer: {}
}
}
<style>
body { display: flex }
</style>
<style scoped>
@supports ( color: #000 ) {
.foo {
color: #000;
}
}
</style>
<template comments>
<div>
<h2 class="red">{{msg}}</h2><!-- comment here -->
</div>
</template>
<script>
export default {
comments: true,
data () {
return {
msg: 'Hello from Component A!'
}
}
}
</script>
<template lang="pug" src="./template-import.pug"></template>
<template>
<div>
<img src="./logo.png">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink= "http://www.w3.org/1999/xlink">
<image xlink:href="./logo.png" />
</svg>
<img src="./logo.png" srcset="./logo.png">
<img src="./logo.png" srcset="./logo.png 2x">
<img src="./logo.png" srcset="./logo.png, ./logo.png 2x">
<img src="./logo.png" srcset="./logo.png 2x, ./logo.png">
<img src="./logo.png" srcset="./logo.png 2x, ./logo.png 3x">
<img src="./logo.png" srcset="./logo.png, ./logo.png 2x, ./logo.png 3x">
<img
src="./logo.png"
srcset="
./logo.png 2x,
./logo.png 3x
">
</div>
</template>
<style>
html { background-image: url(./logo.png); }
</style>
describe('example', function () {
it('basic', function (done) {
done()
})
})
function normalize (code) {
return code.split(/\r?\n/).map(function (line) { return line }).join('')
}
module.exports = function (source) {
var code = "module.exports = function (Component) { Component.options.__blog = '" + source + "' }"
return normalize(code)
}
module.exports = function (content) {
return content.replace(/#f00/, '#00f')
}
module.exports = function (source, map) {
this.callback(null,
'module.exports = function(Component) {Component.options.__docs = ' +
JSON.stringify(source) +
'}',
map)
}
module.exports = function (content) {
return content.replace(/red/, 'green')
}
module.exports = function (source) {
var value = typeof source === 'string' ? JSON.parse(source) : source
var jsonString = JSON.stringify(value).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029')
var code = 'module.exports = function (Component) { Component.options.__i18n = ' + JSON.stringify(jsonString) + ' }'
this.callback(null, code)
}
module.exports = function (source, map) {
this.callback(null, source, map)
}
module.exports = function (content) {
return content.replace(/Hello from Component A!/, 'Changed!')
}
var yaml = require('js-yaml')
module.exports = function (source) {
this.cacheable && this.cacheable()
var res = yaml.safeLoad(source)
return JSON.stringify(res)
}
const {
mockRender,
mockBundleAndRun
} = require('./utils')
test('allow exporting extended constructor', done => {
mockBundleAndRun({
entry: 'extend.vue'
}, ({ window, module }) => {
// extend.vue should export Vue constructor
const Component = module
const vnode = mockRender(Component.options, {
msg: 'success'
})
expect(vnode.tag).toBe('div')
expect(vnode.children[0].text).toBe('success')
expect(new Component().msg === 'success')
done()
})
})
test('named exports', done => {
mockBundleAndRun({
entry: 'named-exports.vue'
}, ({ window, rawModule }) => {
expect(rawModule.default.name).toBe('named-exports')
expect(rawModule.foo()).toBe(1)
done()
})
})
const normalizeNewline = require('normalize-newline')
const {
genId,
mockRender,
mockBundleAndRun
} = require('./utils')
test('scoped style', done => {
mockBundleAndRun({
entry: 'scoped-css.vue'
}, ({ window, module }) => {
const id = 'data-v-' + genId('scoped-css.vue')
expect(module._scopeId).toBe(id)
const vnode = mockRender(module, {
ok: true
})
// <div>
// <div><h1>hi</h1></div>
// <p class="abc def">hi</p>
// <template v-if="ok"><p class="test">yo</p></template>
// <svg><template><p></p></template></svg>
// </div>
expect(vnode.children[0].tag).toBe('div')
expect(vnode.children[1].text).toBe(' ')
expect(vnode.children[2].tag).toBe('p')
expect(vnode.children[2].data.staticClass).toBe('abc def')
expect(vnode.children[4].tag).toBe('p')
expect(vnode.children[4].data.staticClass).toBe('test')
let style = window.document.querySelector('style').textContent
style = normalizeNewline(style)
expect(style).toContain(`.test[${id}] {\n color: yellow;\n}`)
expect(style).toContain(`.test[${id}]:after {\n content: \'bye!\';\n}`)
expect(style).toContain(`h1[${id}] {\n color: green;\n}`)
// scoped keyframes
expect(style).toContain(`.anim[${id}] {\n animation: color-${id} 5s infinite, other 5s;`)
expect(style).toContain(`.anim-2[${id}] {\n animation-name: color-${id}`)
expect(style).toContain(`.anim-3[${id}] {\n animation: 5s color-${id} infinite, 5s other;`)
expect(style).toContain(`@keyframes color-${id} {`)
expect(style).toContain(`@-webkit-keyframes color-${id} {`)
expect(style).toContain(
`.anim-multiple[${id}] {\n animation: color-${id} 5s infinite,opacity-${id} 2s;`
)
expect(style).toContain(`.anim-multiple-2[${id}] {\n animation-name: color-${id},opacity-${id};`)
expect(style).toContain(`@keyframes opacity-${id} {`)
expect(style).toContain(`@-webkit-keyframes opacity-${id} {`)
// >>> combinator
expect(style).toContain(`.foo p[${id}] .bar {\n color: red;\n}`)
done()
})
})
test('media-query', done => {
mockBundleAndRun({
entry: 'media-query.vue'
}, ({ window }) => {
let style = window.document.querySelector('style').textContent
style = normalizeNewline(style)
const id = 'data-v-' + genId('media-query.vue')
expect(style).toContain('@media print {\n.foo[' + id + '] {\n color: #000;\n}\n}')
done()
})
})
test('supports-query', done => {
mockBundleAndRun({
entry: 'supports-query.vue',
suppressJSDOMConsole: true
}, ({ window }) => {
let style = window.document.querySelector('style').textContent
style = normalizeNewline(style)
const id = 'data-v-' + genId('supports-query.vue')
expect(style).toContain('@supports ( color: #000 ) {\n.foo[' + id + '] {\n color: #000;\n}\n}')
done()
})
})
const Vue = require('vue')
const path = require('path')
const hash = require('hash-sum')
const { JSDOM, VirtualConsole } = require('jsdom')
const webpack = require('webpack')
const merge = require('webpack-merge')
const MemoryFS = require('memory-fs')
const mfs = new MemoryFS()
const VueLoaderPlugin = require('../lib/plugin')
const baseConfig = {
mode: 'development',
devtool: false,
output: {
path: '/',
filename: 'test.build.js'
},
resolveLoader: {
alias: {
'vue-loader': require.resolve('../lib')
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
plugins: [
new VueLoaderPlugin(),
new webpack.optimize.ModuleConcatenationPlugin()
]
}
function genId (file) {
return hash(path.join('test', 'fixtures', file))
}
function bundle (options, cb, wontThrowError) {
let config = merge({}, baseConfig, options)
if (config.vue) {
const vueOptions = options.vue
delete config.vue
const vueIndex = config.module.rules.findIndex(r => r.test.test('.vue'))
const vueRule = config.module.rules[vueIndex]
config.module.rules[vueIndex] = Object.assign({}, vueRule, { options: vueOptions })
}
if (/\.vue$/.test(config.entry)) {
const vueFile = config.entry
config = merge(config, {
entry: require.resolve('./fixtures/entry'),
resolve: {
alias: {
'~target': path.resolve(__dirname, './fixtures', vueFile)
}
}
})
}
if (options.modify) {
delete config.modify
options.modify(config)
}
const webpackCompiler = webpack(config)
webpackCompiler.outputFileSystem = mfs
webpackCompiler.run((err, stats) => {
const errors = stats.compilation.errors
if (!wontThrowError) {
expect(err).toBeNull()
if (errors && errors.length) {
errors.forEach(error => {
console.error(error.message)
})
}
expect(errors).toHaveLength(0)
}
cb(mfs.readFileSync('/test.build.js').toString(), stats, err)
})
}
function mockBundleAndRun (options, assert, wontThrowError) {
const { suppressJSDOMConsole } = options
delete options.suppressJSDOMConsole
bundle(options, (code, bundleStats, bundleError) => {
let dom, jsdomError
try {
dom = new JSDOM(`<!DOCTYPE html><html><head></head><body></body></html>`, {
runScripts: 'outside-only',
virtualConsole: suppressJSDOMConsole ? new VirtualConsole() : null
})
dom.window.eval(code)
} catch (e) {
console.error(`JSDOM error:\n${e.stack}`)
jsdomError = e
}
const { window } = dom
const rawModule = window.vueModule
const module = interopDefault(window.vueModule)
const instance = {}
if (module && module.beforeCreate) {
module.beforeCreate.forEach(hook => hook.call(instance))
}
assert({
window,
module,
rawModule,
instance,
code,
jsdomError,
bundleStats,
bundleError
})
}, wontThrowError)
}
function mockRender (options, data = {}) {
const vm = new Vue(Object.assign({}, options, { data () { return data } }))
vm.$mount()
return vm._vnode
}
function interopDefault (module) {
return module
? module.default ? module.default : module
: module
}
function initStylesForAllSubComponents (module) {
if (module.components) {
for (const name in module.components) {
const sub = module.components[name]
const instance = {}
if (sub && sub.beforeCreate) {
sub.beforeCreate.forEach(hook => hook.call(instance))
}
initStylesForAllSubComponents(sub)
}
}
}
module.exports = {
mfs,
baseConfig,
genId,
bundle,
mockBundleAndRun,
mockRender,
interopDefault,
initStylesForAllSubComponents
}
This diff is collapsed.
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