UNPKG

5.5 kBJavaScriptView Raw
1const path = require('path')
2const hash = require('hash-sum')
3const qs = require('querystring')
4const plugin = require('./plugin')
5const selectBlock = require('./select')
6const loaderUtils = require('loader-utils')
7const { attrsToQuery } = require('./codegen/utils')
8const { parse } = require('@vue/component-compiler-utils')
9const genStylesCode = require('./codegen/styleInjection')
10const { genHotReloadCode } = require('./codegen/hotReload')
11const genCustomBlocksCode = require('./codegen/customBlocks')
12const componentNormalizerPath = require.resolve('./runtime/componentNormalizer')
13const { NS } = require('./plugin')
14
15let errorEmitted = false
16
17function loadTemplateCompiler () {
18 try {
19 return require('vue-template-compiler')
20 } catch (e) {
21 throw new Error(
22 `[vue-loader] vue-template-compiler must be installed as a peer dependency, ` +
23 `or a compatible compiler implementation must be passed via options.`
24 )
25 }
26}
27
28module.exports = function (source) {
29 const loaderContext = this
30
31 if (!errorEmitted && !loaderContext['thread-loader'] && !loaderContext[NS]) {
32 loaderContext.emitError(new Error(
33 `vue-loader was used without the corresponding plugin. ` +
34 `Make sure to include VueLoaderPlugin in your webpack config.`
35 ))
36 errorEmitted = true
37 }
38
39 const stringifyRequest = r => loaderUtils.stringifyRequest(loaderContext, r)
40
41 const {
42 target,
43 request,
44 minimize,
45 sourceMap,
46 rootContext,
47 resourcePath,
48 resourceQuery
49 } = loaderContext
50
51 const rawQuery = resourceQuery.slice(1)
52 const inheritQuery = `&${rawQuery}`
53 const incomingQuery = qs.parse(rawQuery)
54 const options = loaderUtils.getOptions(loaderContext) || {}
55
56 const isServer = target === 'node'
57 const isShadow = !!options.shadowMode
58 const isProduction = options.productionMode || minimize || process.env.NODE_ENV === 'production'
59 const filename = path.basename(resourcePath)
60 const context = rootContext || process.cwd()
61 const sourceRoot = path.dirname(path.relative(context, resourcePath))
62
63 const descriptor = parse({
64 source,
65 compiler: options.compiler || loadTemplateCompiler(),
66 filename,
67 sourceRoot,
68 needMap: sourceMap
69 })
70
71 // if the query has a type field, this is a language block request
72 // e.g. foo.vue?type=template&id=xxxxx
73 // and we will return early
74 if (incomingQuery.type) {
75 return selectBlock(descriptor, loaderContext, incomingQuery)
76 }
77
78 // module id for scoped CSS & hot-reload
79 const rawShortFilePath = path
80 .relative(context, resourcePath)
81 .replace(/^(\.\.[\/\\])+/, '')
82
83 const shortFilePath = rawShortFilePath.replace(/\\/g, '/') + resourceQuery
84
85 const id = hash(
86 isProduction
87 ? (shortFilePath + '\n' + source)
88 : shortFilePath
89 )
90
91 // feature information
92 const hasScoped = descriptor.styles.some(s => s.scoped)
93 const hasFunctional = descriptor.template && descriptor.template.attrs.functional
94 const needsHotReload = (
95 !isServer &&
96 !isProduction &&
97 (descriptor.script || descriptor.template) &&
98 options.hotReload !== false
99 )
100
101 // template
102 let templateImport = `var render, staticRenderFns`
103 let templateRequest
104 if (descriptor.template) {
105 const src = descriptor.template.src || resourcePath
106 const idQuery = `&id=${id}`
107 const scopedQuery = hasScoped ? `&scoped=true` : ``
108 const attrsQuery = attrsToQuery(descriptor.template.attrs)
109 const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
110 const request = templateRequest = stringifyRequest(src + query)
111 templateImport = `import { render, staticRenderFns } from ${request}`
112 }
113
114 // script
115 let scriptImport = `var script = {}`
116 if (descriptor.script) {
117 const src = descriptor.script.src || resourcePath
118 const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js')
119 const query = `?vue&type=script${attrsQuery}${inheritQuery}`
120 const request = stringifyRequest(src + query)
121 scriptImport = (
122 `import script from ${request}\n` +
123 `export * from ${request}` // support named exports
124 )
125 }
126
127 // styles
128 let stylesCode = ``
129 if (descriptor.styles.length) {
130 stylesCode = genStylesCode(
131 loaderContext,
132 descriptor.styles,
133 id,
134 resourcePath,
135 stringifyRequest,
136 needsHotReload,
137 isServer || isShadow // needs explicit injection?
138 )
139 }
140
141 let code = `
142${templateImport}
143${scriptImport}
144${stylesCode}
145
146/* normalize component */
147import normalizer from ${stringifyRequest(`!${componentNormalizerPath}`)}
148var component = normalizer(
149 script,
150 render,
151 staticRenderFns,
152 ${hasFunctional ? `true` : `false`},
153 ${/injectStyles/.test(stylesCode) ? `injectStyles` : `null`},
154 ${hasScoped ? JSON.stringify(id) : `null`},
155 ${isServer ? JSON.stringify(hash(request)) : `null`}
156 ${isShadow ? `,true` : ``}
157)
158 `.trim() + `\n`
159
160 if (descriptor.customBlocks && descriptor.customBlocks.length) {
161 code += genCustomBlocksCode(
162 descriptor.customBlocks,
163 resourcePath,
164 resourceQuery,
165 stringifyRequest
166 )
167 }
168
169 if (needsHotReload) {
170 code += `\n` + genHotReloadCode(id, hasFunctional, templateRequest)
171 }
172
173 // Expose filename. This is used by the devtools and vue runtime warnings.
174 if (!isProduction) {
175 code += `\ncomponent.options.__file = ${JSON.stringify(rawShortFilePath)}`
176 }
177
178 code += `\nexport default component.exports`
179 // console.log(code)
180 return code
181}
182
183module.exports.VueLoaderPlugin = plugin