Skip to content

Commit 8badc35

Browse files
committed
init
0 parents  commit 8badc35

11 files changed

+5942
-0
lines changed

.babelrc

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"presets": [
3+
["env", { "modules": false }]
4+
]
5+
}

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.DS_Store
2+
test
3+
node_modules
4+
*.log

lib/assemble.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module.exports = function assemble (sourcePath, descriptor) {
2+
const templateImport = descriptor.template
3+
? `import { render, staticRenderFns } from '${sourcePath}?template'`
4+
: ``
5+
6+
const scriptImport = descriptor.script
7+
? `import script from '${sourcePath}?script'`
8+
: `const script = {}`
9+
10+
const styleImports = descriptor.styles.map((_, i) => {
11+
return `import style${i} from '${sourcePath}?style&index=${i}'`
12+
}).join('\n')
13+
14+
return `
15+
${templateImport}
16+
${scriptImport}
17+
${styleImports}
18+
script.render = render
19+
script.staticRenderFns = staticRenderFns
20+
export default script
21+
`.trim()
22+
}

lib/index.js

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
const path = require('path')
2+
const hash = require('hash-sum')
3+
const qs = require('querystring')
4+
const parse = require('./parse')
5+
const assemble = require('./assemble')
6+
const loaderUtils = require('loader-utils')
7+
const compileTemplate = require('./template-compiler')
8+
9+
module.exports = function (source) {
10+
const { resourcePath, resourceQuery, target, minimize, sourceMap } = this
11+
const isServer = target === 'node'
12+
const isProduction = minimize || process.env.NODE_ENV === 'production'
13+
const options = loaderUtils.getOptions(this) || {}
14+
const fileName = path.basename(resourcePath)
15+
const context = this.rootContext || process.cwd()
16+
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
20+
21+
const descriptor = parse(
22+
source,
23+
fileName,
24+
sourceRoot,
25+
sourceMap,
26+
needCssSourceMap
27+
)
28+
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+
}
51+
}
52+
53+
// assemble
54+
return assemble(resourcePath, descriptor)
55+
}

lib/parse.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const hash = require('hash-sum')
2+
const cache = require('lru-cache')(100)
3+
const compiler = require('vue-template-compiler')
4+
const SourceMapGenerator = require('source-map').SourceMapGenerator
5+
6+
const splitRE = /\r?\n/g
7+
const emptyRE = /^(?:\/\/)?\s*$/
8+
9+
module.exports = (content, filename, sourceRoot, needMap, needCSSMap) => {
10+
const cacheKey = hash(filename + content)
11+
let output = cache.get(cacheKey)
12+
if (output) return output
13+
output = compiler.parseComponent(content, { pad: 'line' })
14+
if (needMap) {
15+
if (output.script && !output.script.src) {
16+
output.script.map = generateSourceMap(
17+
filename,
18+
content,
19+
output.script.content,
20+
sourceRoot
21+
)
22+
}
23+
if (needCSSMap && output.styles) {
24+
output.styles.forEach(style => {
25+
if (!style.src) {
26+
style.map = generateSourceMap(
27+
filename,
28+
content,
29+
style.content,
30+
sourceRoot
31+
)
32+
}
33+
})
34+
}
35+
}
36+
cache.set(cacheKey, output)
37+
return output
38+
}
39+
40+
function generateSourceMap (filename, source, generated, sourceRoot) {
41+
const map = new SourceMapGenerator({ sourceRoot })
42+
map.setSourceContent(filename, source)
43+
generated.split(splitRE).forEach((line, index) => {
44+
if (!emptyRE.test(line)) {
45+
map.addMapping({
46+
source: filename,
47+
original: {
48+
line: index + 1,
49+
column: 0
50+
},
51+
generated: {
52+
line: index + 1,
53+
column: 0
54+
}
55+
})
56+
}
57+
})
58+
return map.toJSON()
59+
}

lib/template-compiler/index.js

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
const prettier = require('prettier')
2+
const loaderUtils = require('loader-utils')
3+
const compiler = require('vue-template-compiler')
4+
const transpile = require('vue-template-es2015-compiler')
5+
const transformRequire = require('./modules/transform-require')
6+
const transformSrcset = require('./modules/transform-srcset')
7+
const hotReloadAPIPath = require.resolve('vue-hot-reload-api')
8+
9+
module.exports = function compileTemplate (descriptor, loaderContext, id) {
10+
const sourceTemplate = descriptor.template.content
11+
const isServer = loaderContext.target === 'node'
12+
const isProduction = loaderContext.minimize || process.env.NODE_ENV === 'production'
13+
const options = loaderUtils.getOptions(loaderContext) || {}
14+
const needsHotReload = !isServer && !isProduction && options.hotReload !== false
15+
const defaultModules = [transformRequire(options.transformToRequire), transformSrcset()]
16+
const hasScoped = descriptor.styles.some(({ scoped }) => scoped)
17+
const templateAttrs = descriptor.template.attrs || {}
18+
const hasComment = !!templateAttrs.comments
19+
const hasFunctionalTemplate = !!templateAttrs.functional
20+
21+
const compilerOptions = {
22+
preserveWhitespace: options.preserveWhitespace,
23+
modules: defaultModules.concat(options.compilerModules || []),
24+
directives: options.compilerDirectives || {},
25+
scopeId: hasScoped ? id : null,
26+
comments: hasComment
27+
}
28+
29+
const compile =
30+
isServer && compiler.ssrCompile && options.optimizeSSR !== false
31+
? compiler.ssrCompile
32+
: compiler.compile
33+
34+
const compiled = compile(sourceTemplate, compilerOptions)
35+
36+
// tips
37+
if (compiled.tips && compiled.tips.length) {
38+
compiled.tips.forEach(tip => {
39+
loaderContext.emitWarning(tip)
40+
})
41+
}
42+
43+
let code
44+
if (compiled.errors && compiled.errors.length) {
45+
loaderContext.emitError(
46+
`\n Error compiling template:\n${pad(sourceTemplate)}\n` +
47+
compiled.errors.map(e => ` - ${e}`).join('\n') +
48+
'\n'
49+
)
50+
code =
51+
`export var render = function () {}\n` +
52+
`export var staticRenderFns = []`
53+
} else {
54+
const bubleOptions = options.buble || {
55+
transforms: {
56+
stripWithFunctional: hasFunctionalTemplate
57+
}
58+
}
59+
const staticRenderFns = compiled.staticRenderFns.map(fn =>
60+
toFunction(fn, hasFunctionalTemplate)
61+
)
62+
63+
code =
64+
transpile(
65+
'var render = ' +
66+
toFunction(compiled.render, hasFunctionalTemplate) +
67+
'\n' +
68+
'var staticRenderFns = [' +
69+
staticRenderFns.join(',') +
70+
']',
71+
bubleOptions
72+
) + '\n'
73+
74+
// prettify render fn
75+
if (!isProduction) {
76+
code = prettier.format(code, { semi: false })
77+
}
78+
79+
// mark with stripped (this enables Vue to use correct runtime proxy detection)
80+
if (!isProduction && bubleOptions.transforms.stripWith !== false) {
81+
code += `render._withStripped = true\n`
82+
}
83+
code += `export { render, staticRenderFns }`
84+
}
85+
// hot-reload
86+
if (needsHotReload) {
87+
code +=
88+
'\nif (module.hot) {\n' +
89+
' module.hot.accept()\n' +
90+
' if (module.hot.data) {\n' +
91+
' require("' + hotReloadAPIPath + '")' +
92+
' .rerender("' + options.id + '", { render: render, staticRenderFns: staticRenderFns })\n' +
93+
' }\n' +
94+
'}'
95+
}
96+
97+
return code
98+
}
99+
100+
function toFunction (code, hasFunctionalTemplate) {
101+
return (
102+
'function (' + (hasFunctionalTemplate ? '_h,_vm' : '') + ') {' + code + '}'
103+
)
104+
}
105+
106+
function pad (sourceTemplate) {
107+
return sourceTemplate
108+
.split(/\r?\n/)
109+
.map(line => ` ${line}`)
110+
.join('\n')
111+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// vue compiler module for transforming `<tag>:<attribute>` to `require`
2+
3+
const urlToRequire = require('../url-to-require')
4+
5+
const defaultOptions = {
6+
video: ['src', 'poster'],
7+
source: 'src',
8+
img: 'src',
9+
image: 'xlink:href'
10+
}
11+
12+
module.exports = userOptions => {
13+
const options = userOptions
14+
? Object.assign({}, defaultOptions, userOptions)
15+
: defaultOptions
16+
17+
return {
18+
postTransformNode: node => {
19+
transform(node, options)
20+
}
21+
}
22+
}
23+
24+
function transform (node, options) {
25+
for (const tag in options) {
26+
if ((tag === '*' || node.tag === tag) && node.attrs) {
27+
const attributes = options[tag]
28+
if (typeof attributes === 'string') {
29+
node.attrs.some(attr => rewrite(attr, attributes))
30+
} else if (Array.isArray(attributes)) {
31+
attributes.forEach(item => node.attrs.some(attr => rewrite(attr, item)))
32+
}
33+
}
34+
}
35+
}
36+
37+
function rewrite (attr, name) {
38+
if (attr.name === name) {
39+
const value = attr.value
40+
// only transform static URLs
41+
if (value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') {
42+
attr.value = urlToRequire(value.slice(1, -1))
43+
return true
44+
}
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// vue compiler module for transforming `img:srcset` to a number of `require`s
2+
3+
const urlToRequire = require('../url-to-require')
4+
5+
module.exports = () => ({
6+
postTransformNode: node => {
7+
transform(node)
8+
}
9+
})
10+
11+
function transform (node) {
12+
const tags = ['img', 'source']
13+
14+
if (tags.indexOf(node.tag) !== -1 && node.attrs) {
15+
node.attrs.forEach(attr => {
16+
if (attr.name === 'srcset') {
17+
// same logic as in transform-require.js
18+
const value = attr.value
19+
const isStatic = value.charAt(0) === '"' && value.charAt(value.length - 1) === '"'
20+
if (!isStatic) {
21+
return
22+
}
23+
24+
// http://w3c.github.io/html/semantics-embedded-content.html#ref-for-image-candidate-string-5
25+
const escapedSpaceCharacters = /( |\\t|\\n|\\f|\\r)+/g
26+
27+
const imageCandidates = value.substr(1, value.length - 2).split(',').map(s => {
28+
// The attribute value arrives here with all whitespace, except normal spaces, represented by escape sequences
29+
const [url, descriptor] = s.replace(escapedSpaceCharacters, ' ').trim().split(' ', 2)
30+
return { require: urlToRequire(url), descriptor: descriptor }
31+
})
32+
33+
// "require(url1)"
34+
// "require(url1) 1x"
35+
// "require(url1), require(url2)"
36+
// "require(url1), require(url2) 2x"
37+
// "require(url1) 1x, require(url2)"
38+
// "require(url1) 1x, require(url2) 2x"
39+
const code = imageCandidates.map(
40+
({ require, descriptor }) => `${require} + "${descriptor ? ' ' + descriptor : ''}, " + `
41+
).join('').slice(0, -6).concat('"').replace(/ \+ ""$/, '')
42+
43+
attr.value = code
44+
}
45+
})
46+
}
47+
}
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module.exports = function urlToRequire (url) {
2+
// same logic as in transform-require.js
3+
const firstChar = url.charAt(0)
4+
if (firstChar === '.' || firstChar === '~' || firstChar === '@') {
5+
if (firstChar === '~') {
6+
const secondChar = url.charAt(1)
7+
url = url.slice(secondChar === '/' ? 2 : 1)
8+
}
9+
return `require("${url}")`
10+
} else {
11+
return `"${url}"`
12+
}
13+
}

0 commit comments

Comments
 (0)