UNPKG

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