Skip to content

Commit 3175633

Browse files
committed
initial work
1 parent 8badc35 commit 3175633

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+3875
-162
lines changed

.eslintrc.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
root: true,
3+
extends: ['plugin:vue-libs/recommended']
4+
}

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
.DS_Store
2-
test
2+
exploration
33
node_modules
44
*.log

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2015-present Yuxi (Evan) You
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

lib/assemble.js

Lines changed: 0 additions & 22 deletions
This file was deleted.

lib/index.js

Lines changed: 126 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,146 @@
11
const path = require('path')
22
const hash = require('hash-sum')
3-
const qs = require('querystring')
43
const parse = require('./parse')
5-
const assemble = require('./assemble')
4+
const qs = require('querystring')
65
const loaderUtils = require('loader-utils')
7-
const compileTemplate = require('./template-compiler')
6+
const selectBlock = require('./selector')
7+
const plugin = require('./plugin')
8+
const componentNormalizerPath = require.resolve('./runtime/componentNormalizer')
89

910
module.exports = function (source) {
10-
const { resourcePath, resourceQuery, target, minimize, sourceMap } = this
11+
const loaderContext = this
12+
const stringifyRequest = r => loaderUtils.stringifyRequest(loaderContext, r)
13+
14+
const {
15+
target,
16+
request,
17+
minimize,
18+
sourceMap,
19+
rootContext,
20+
resourcePath,
21+
resourceQuery
22+
} = loaderContext
23+
1124
const isServer = target === 'node'
1225
const isProduction = minimize || process.env.NODE_ENV === 'production'
13-
const options = loaderUtils.getOptions(this) || {}
26+
const options = loaderUtils.getOptions(loaderContext) || {}
1427
const fileName = path.basename(resourcePath)
15-
const context = this.rootContext || process.cwd()
28+
const context = rootContext || process.cwd()
1629
const sourceRoot = path.dirname(path.relative(context, resourcePath))
17-
const shortFilePath = path.relative(context, resourcePath).replace(/^(\.\.[\\\/])+/, '')
18-
const moduleId = 'data-v-' + hash(isProduction ? (shortFilePath + '\n' + content) : shortFilePath)
19-
const needCssSourceMap = !isProduction && sourceMap && options.cssSourceMap !== false
2030

2131
const descriptor = parse(
2232
source,
2333
fileName,
2434
sourceRoot,
25-
sourceMap,
26-
needCssSourceMap
35+
sourceMap
2736
)
2837

29-
if (resourceQuery) {
30-
const query = qs.parse(resourceQuery.slice(1))
31-
32-
// template
33-
if (query.template != null) {
34-
return compileTemplate(descriptor, this, moduleId)
35-
}
36-
37-
// script
38-
if (query.script != null) {
39-
this.callback(
40-
null,
41-
descriptor.script.content,
42-
descriptor.script.map
43-
)
44-
return
45-
}
46-
47-
// styles
48-
if (query.style != null && query.index != null) {
49-
return descriptor.styles[query.index].content
50-
}
38+
// if the query has a type field, this is a language block request
39+
// e.g. foo.vue?type=template&id=xxxxx
40+
// and we will return early
41+
const incomingQuery = qs.parse(resourceQuery.slice(1))
42+
if (incomingQuery.type) {
43+
return selectBlock(descriptor, loaderContext, incomingQuery)
44+
}
45+
46+
// module id for scoped CSS & hot-reload
47+
const shortFilePath = path
48+
.relative(context, resourcePath)
49+
.replace(/^(\.\.[\\\/])+/, '')
50+
const id = hash(
51+
isProduction
52+
? (shortFilePath + '\n' + source)
53+
: shortFilePath
54+
)
55+
56+
// feature information
57+
const hasScoped = descriptor.styles.some(s => s.scoped)
58+
const templateAttrs = (descriptor.template && descriptor.template.attrs) || {}
59+
const hasFunctional = templateAttrs.functional
60+
const hasComment = templateAttrs.comments
61+
const needsHotReload = (
62+
!isServer &&
63+
!isProduction &&
64+
(descriptor.script || descriptor.template) &&
65+
options.hotReload !== false
66+
)
67+
68+
// template
69+
let templateImport = `var render, staticRenderFns`
70+
if (descriptor.template) {
71+
const src = descriptor.template.src || resourcePath
72+
const langQuery = getLangQuery(descriptor.template)
73+
const idQuery = hasScoped ? `&id=${id}` : ``
74+
const fnQuery = hasFunctional ? `&functional` : ``
75+
const commentQuery = hasComment ? `&comment` : ``
76+
const query = `?vue&type=template${idQuery}${langQuery}${fnQuery}${commentQuery}`
77+
const request = stringifyRequest(src + query)
78+
templateImport = `import { render, staticRenderFns } from ${request}`
79+
}
80+
81+
// script
82+
let scriptImport = `var script = {}`
83+
if (descriptor.script) {
84+
const src = descriptor.script.src || resourcePath
85+
const langQuery = getLangQuery(descriptor.script, 'js')
86+
const query = `?vue&type=script${langQuery}`
87+
const request = stringifyRequest(src + query)
88+
scriptImport = (
89+
`import script from ${request}\n` +
90+
`export * from ${request}` // support named exports
91+
)
92+
}
93+
94+
// styles
95+
let styleImports = ``
96+
if (descriptor.styles.length) {
97+
styleImports = descriptor.styles.map((style, i) => {
98+
const src = style.src || resourcePath
99+
const langQuery = getLangQuery(style, 'css')
100+
const scopedQuery = style.scoped ? `&scoped&id=${id}` : ``
101+
const query = `?vue&type=style&index=${i}${langQuery}${scopedQuery}`
102+
const request = stringifyRequest(src + query)
103+
return `import style${i} from ${request}`
104+
}).join('\n')
51105
}
52106

53-
// assemble
54-
return assemble(resourcePath, descriptor)
107+
let code = `
108+
${templateImport}
109+
${scriptImport}
110+
${styleImports}
111+
import normalizer from ${stringifyRequest(`!${componentNormalizerPath}`)}
112+
var component = normalizer(
113+
script,
114+
render,
115+
staticRenderFns,
116+
${hasFunctional ? `true` : `false`},
117+
null, // TODO style injection
118+
${JSON.stringify(id)},
119+
${isServer ? JSON.stringify(hash(request)) : `null`}
120+
${incomingQuery.shadow ? `,true` : ``}
121+
)
122+
`.trim()
123+
124+
if (descriptor.customBlocks && descriptor.customBlocks.length) {
125+
// TODO custom blocks
126+
}
127+
128+
if (!isProduction) {
129+
code += `\ncomponent.options.__file = ${JSON.stringify(shortFilePath)}`
130+
}
131+
132+
if (needsHotReload) {
133+
// TODO hot reload
134+
}
135+
136+
code += `\nexport default component.exports`
137+
// console.log(code)
138+
return code
55139
}
140+
141+
function getLangQuery (block, fallback) {
142+
const lang = block.lang || fallback
143+
return lang ? `&lang=${lang}` : ``
144+
}
145+
146+
module.exports.VueLoaderPlugin = plugin

lib/parse.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const SourceMapGenerator = require('source-map').SourceMapGenerator
66
const splitRE = /\r?\n/g
77
const emptyRE = /^(?:\/\/)?\s*$/
88

9-
module.exports = (content, filename, sourceRoot, needMap, needCSSMap) => {
9+
module.exports = (content, filename, sourceRoot, needMap) => {
1010
const cacheKey = hash(filename + content)
1111
let output = cache.get(cacheKey)
1212
if (output) return output
@@ -20,7 +20,7 @@ module.exports = (content, filename, sourceRoot, needMap, needCSSMap) => {
2020
sourceRoot
2121
)
2222
}
23-
if (needCSSMap && output.styles) {
23+
if (output.styles) {
2424
output.styles.forEach(style => {
2525
if (!style.src) {
2626
style.map = generateSourceMap(

lib/pitch.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const qs = require('querystring')
2+
const loaderUtils = require('loader-utils')
3+
const templateLoaderPath = require.resolve('./template-loader')
4+
const stylePostLoaderPath = require.resolve('./style-post-loader')
5+
6+
module.exports = code => code
7+
8+
// This pitching loader is responsible for catching all src import requests
9+
// from within vue files and transform it into appropriate requests
10+
module.exports.pitch = function (remainingRequest) {
11+
const query = qs.parse(this.resourceQuery.slice(1))
12+
if (query.vue != null) {
13+
// For Scoped CSS: inject style-post-loader before css-loader
14+
if (query.type === `style` && query.scoped != null) {
15+
const cssLoaderIndex = this.loaders.findIndex(l => /\/css-loader/.test(l.request))
16+
if (cssLoaderIndex) {
17+
const afterLoaders = this.loaders.slice(1, cssLoaderIndex + 1).map(l => l.request)
18+
const beforeLoaders = this.loaders.slice(cssLoaderIndex + 1).map(l => l.request)
19+
const request = '-!' + [
20+
...afterLoaders,
21+
stylePostLoaderPath,
22+
...beforeLoaders,
23+
this.resourcePath + this.resourceQuery
24+
].join('!')
25+
return `export * from ${loaderUtils.stringifyRequest(this, request)}`
26+
}
27+
}
28+
29+
// for templates: inject the template compiler
30+
if (query.type === `template`) {
31+
const beforeLoaders = this.loaders.slice(1).map(l => l.request)
32+
const request = '-!' + [
33+
templateLoaderPath,
34+
...beforeLoaders,
35+
this.resourcePath + this.resourceQuery
36+
].join('!')
37+
return `export * from ${loaderUtils.stringifyRequest(this, request)}`
38+
}
39+
}
40+
}

lib/plugin.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
const qs = require('querystring')
2+
const RuleSet = require('webpack/lib/RuleSet')
3+
4+
// TODO handle vueRule with oneOf
5+
module.exports = class VueLoaderPlugin {
6+
apply (compiler) {
7+
// get a hold of the raw rules
8+
const rawRules = compiler.options.module.rules
9+
// use webpack's RuleSet utility to normalize user rules
10+
const rawNormalizedRules = new RuleSet(rawRules).rules
11+
12+
// find the rule that applies to vue files
13+
const vueRuleIndex = rawRules.findIndex((rule, i) => {
14+
return !rule.enforce && rawNormalizedRules[i].resource(`foo.vue`)
15+
})
16+
const vueRule = rawRules[vueRuleIndex]
17+
18+
if (!vueRule) {
19+
throw new Error(
20+
`VueLoaderPlugin Error: no matching rule for vue files are found.`
21+
)
22+
}
23+
24+
// find the normalized version of the vue rule
25+
const normalizedVueRule = rawNormalizedRules[vueRuleIndex]
26+
27+
// get the normlized "use" for vue files
28+
const normalizedVueUse = normalizedVueRule.use.map(cleanUse)
29+
30+
// get new rules without the vue rule
31+
const baseRules = rawRules.filter(r => r !== vueRule)
32+
const normalizedRules = rawNormalizedRules.filter(r => r !== normalizedVueRule)
33+
34+
// construct a new rule for vue file, with oneOf containing
35+
// multiple rules with dynamic resourceQuery functions that applies to
36+
// different language blocks in a raw vue file.
37+
const constructedRule = {
38+
test: /\.vue$/,
39+
oneOf: baseRules.map((rule, i) => {
40+
// for each user rule, create a cloned rule by checking if the rule
41+
// matches the lang specified in the resourceQuery.
42+
return cloneRule(rule, normalizedRules[i], normalizedVueUse)
43+
}).concat(vueRule)
44+
}
45+
46+
// replace the original vue rule with our new constructed rule.
47+
rawRules.splice(vueRuleIndex, 1, constructedRule)
48+
49+
// inject global pitcher (responsible for injecting CSS post loader)
50+
rawRules.unshift({
51+
loader: require.resolve('./pitch')
52+
})
53+
}
54+
}
55+
56+
function cloneRule (rule, normalizedRule, vueUse) {
57+
if (rule.oneOf) {
58+
return Object.assign({}, rule, {
59+
oneOf: rule.oneOf.map((r, i) => cloneRule(r, normalizedRule.oneOf[i]))
60+
})
61+
}
62+
63+
// Assuming `test` and `resourceQuery` tests are executed in series and
64+
// synchronously (which is true based on RuleSet's implementation), we can
65+
// save the current resource being matched from `test` so that we can access
66+
// it in `resourceQuery`. This ensures when we use the normalized rule's
67+
// resource check, include/exclude are matched correctly.
68+
let currentResource
69+
const res = Object.assign({}, rule, {
70+
test: resource => {
71+
currentResource = resource
72+
return true
73+
},
74+
resourceQuery: query => {
75+
const parsed = qs.parse(query.slice(1))
76+
if (parsed.lang == null) {
77+
return false
78+
}
79+
return normalizedRule.resource(`${currentResource}.${parsed.lang}`)
80+
},
81+
use: [
82+
...(normalizedRule.use || []).map(cleanUse),
83+
...vueUse
84+
]
85+
})
86+
87+
// delete shorthand since we have normalized use
88+
delete res.loader
89+
delete res.options
90+
91+
return res
92+
}
93+
94+
// "ident" is exposed on normalized uses, delete in case it
95+
// interferes with another normalization
96+
function cleanUse (use) {
97+
const res = Object.assign({}, use)
98+
delete res.ident
99+
return res
100+
}

0 commit comments

Comments
 (0)