1 | const path = require('path')
|
2 | const hash = require('hash-sum')
|
3 | const qs = require('querystring')
|
4 | const plugin = require('./plugin')
|
5 | const selectBlock = require('./select')
|
6 | const loaderUtils = require('loader-utils')
|
7 | const { attrsToQuery } = require('./codegen/utils')
|
8 | const { parse } = require('@vue/component-compiler-utils')
|
9 | const genStylesCode = require('./codegen/styleInjection')
|
10 | const { genHotReloadCode } = require('./codegen/hotReload')
|
11 | const genCustomBlocksCode = require('./codegen/customBlocks')
|
12 | const componentNormalizerPath = require.resolve('./runtime/componentNormalizer')
|
13 | const { NS } = require('./plugin')
|
14 |
|
15 | let errorEmitted = false
|
16 |
|
17 | function 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 |
|
28 | module.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 |
|
72 |
|
73 |
|
74 | if (incomingQuery.type) {
|
75 | return selectBlock(descriptor, loaderContext, incomingQuery)
|
76 | }
|
77 |
|
78 |
|
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 |
|
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 |
|
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 |
|
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}`
|
124 | )
|
125 | }
|
126 |
|
127 |
|
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
|
138 | )
|
139 | }
|
140 |
|
141 | let code = `
|
142 | ${templateImport}
|
143 | ${scriptImport}
|
144 | ${stylesCode}
|
145 |
|
146 | /* normalize component */
|
147 | import normalizer from ${stringifyRequest(`!${componentNormalizerPath}`)}
|
148 | var 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 |
|
174 | if (!isProduction) {
|
175 | code += `\ncomponent.options.__file = ${JSON.stringify(rawShortFilePath)}`
|
176 | }
|
177 |
|
178 | code += `\nexport default component.exports`
|
179 |
|
180 | return code
|
181 | }
|
182 |
|
183 | module.exports.VueLoaderPlugin = plugin
|