Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
81 changed files
with
3,875 additions
and
162 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module.exports = { | ||
root: true, | ||
extends: ['plugin:vue-libs/recommended'] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
.DS_Store | ||
test | ||
exploration | ||
node_modules | ||
*.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
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. |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,55 +1,146 @@ | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
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)}` | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
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 | ||
} |
Oops, something went wrong.