UNPKG

15.5 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const source_map_1 = require("source-map");
4const source_map_2 = require("./source-map");
5const path = require("path");
6function assemble(compiler, filename, result, options = {}) {
7 return assembleFromSource(compiler, options, {
8 filename,
9 scopeId: result.scopeId,
10 script: result.script && {
11 source: result.script.code,
12 map: result.script.map
13 },
14 template: result.template && Object.assign({}, result.template, { source: result.template.code, functional: result.template.functional }),
15 styles: result.styles.map(style => {
16 if (style.errors.length) {
17 console.error(style.errors);
18 }
19 return Object.assign({}, style, { source: style.code, media: style.media, scoped: style.scoped, moduleName: style.moduleName, module: style.module });
20 })
21 });
22}
23exports.assemble = assemble;
24function assembleFromSource(compiler, options, { filename, script, template, styles, scopeId }) {
25 script = script || { source: 'export default {}' };
26 template = template || { source: '' };
27 let map = undefined;
28 const mapGenerator = new source_map_1.SourceMapGenerator({ file: filename.replace(/\\/g, '/') });
29 const hasScopedStyle = styles.some(style => style.scoped === true);
30 const hasStyle = styles.some(style => style.source || style.module);
31 const e = (any) => JSON.stringify(any);
32 const createImport = (name, value) => value.startsWith('~')
33 ? `import ${name} from '${value.substr(1)}'`
34 : `const ${name} = ${value}`;
35 const o = e;
36 const IDENTIFIER = /^[a-z0-9]+$/i;
37 // language=JavaScript
38 const inlineCreateInjector = `function __vue_create_injector__() {
39 const styles = __vue_create_injector__.styles || (__vue_create_injector__.styles = {})
40 const isOldIE =
41 typeof navigator !== 'undefined' &&
42 /msie [6-9]\\\\b/.test(navigator.userAgent.toLowerCase())
43
44 return function addStyle(id, css) {
45 if (document.querySelector('style[data-vue-ssr-id~="' + id + '"]')) return // SSR styles are present.
46
47 const group = isOldIE ? css.media || 'default' : id
48 const style = styles[group] || (styles[group] = { ids: [], parts: [], element: undefined })
49
50 if (!style.ids.includes(id)) {
51 let code = css.source
52 let index = style.ids.length
53
54 style.ids.push(id)
55
56 if (${e(compiler.template.isProduction)} && css.map) {
57 // https://developer.chrome.com/devtools/docs/javascript-debugging
58 // this makes source maps inside style tags work properly in Chrome
59 code += '\\n/*# sourceURL=' + css.map.sources[0] + ' */'
60 // http://stackoverflow.com/a/26603875
61 code +=
62 '\\n/*# sourceMappingURL=data:application/json;base64,' +
63 btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) +
64 ' */'
65 }
66
67 if (isOldIE) {
68 style.element = style.element || document.querySelector('style[data-group=' + group + ']')
69 }
70
71 if (!style.element) {
72 const head = document.head || document.getElementsByTagName('head')[0]
73 const el = style.element = document.createElement('style')
74 el.type = 'text/css'
75
76 if (css.media) el.setAttribute('media', css.media)
77 if (isOldIE) {
78 el.setAttribute('data-group', group)
79 el.setAttribute('data-next-index', '0')
80 }
81
82 head.appendChild(el)
83 }
84
85 if (isOldIE) {
86 index = parseInt(style.element.getAttribute('data-next-index'))
87 style.element.setAttribute('data-next-index', index + 1)
88 }
89
90 if (style.element.styleSheet) {
91 style.parts.push(code)
92 style.element.styleSheet.cssText = style.parts
93 .filter(Boolean)
94 .join('\\n')
95 } else {
96 const textNode = document.createTextNode(code)
97 const nodes = style.element.childNodes
98 if (nodes[index]) style.element.removeChild(nodes[index])
99 if (nodes.length) style.element.insertBefore(textNode, nodes[index])
100 else style.element.appendChild(textNode)
101 }
102 }
103 }
104 }`;
105 const createInjector = options.styleInjector
106 ? createImport('__vue_create_injector__', options.styleInjector)
107 : inlineCreateInjector;
108 // language=JavaScript
109 const inlineCreateInjectorSSR = `function __vue_create_injector_ssr__(context) {
110 if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
111 context = __VUE_SSR_CONTEXT__
112 }
113
114 if (!context) return function () {}
115
116 if (!context.hasOwnProperty('styles')) {
117 Object.defineProperty(context, 'styles', {
118 enumerable: true,
119 get: () => context._styles
120 })
121 context._renderStyles = renderStyles
122 }
123
124 function renderStyles(styles) {
125 let css = ''
126 for (const {ids, media, parts} of styles) {
127 css +=
128 '<style data-vue-ssr-id="' + ids.join(' ') + '"' + (media ? ' media="' + media + '"' : '') + '>'
129 + parts.join('\\n') +
130 '</style>'
131 }
132
133 return css
134 }
135
136 return function addStyle(id, css) {
137 const group = ${e(compiler.template.isProduction)} ? css.media || 'default' : id
138 const style = context._styles[group] || (context._styles[group] = { ids: [], parts: [] })
139
140 if (!style.ids.includes(id)) {
141 style.media = css.media
142 style.ids.push(id)
143 let code = css.source
144 if (${e(!compiler.template.isProduction)} && css.map) {
145 // https://developer.chrome.com/devtools/docs/javascript-debugging
146 // this makes source maps inside style tags work properly in Chrome
147 code += '\\n/*# sourceURL=' + css.map.sources[0] + ' */'
148 // http://stackoverflow.com/a/26603875
149 code +=
150 '\\n/*# sourceMappingURL=data:application/json;base64,' +
151 btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) +
152 ' */'
153 }
154 style.parts.push(code)
155 }
156 }
157 }`;
158 const createInjectorSSR = options.styleInjectorSSR
159 ? createImport('__vue_create_injector_ssr__', options.styleInjectorSSR)
160 : inlineCreateInjectorSSR;
161 const inlineCreateInjectorShadow = `function __vue_create_injector_shadow__(__, shadowRoot) {
162 function createStyleElement(shadowRoot) {
163 var styleElement = document.createElement('style')
164 styleElement.type = 'text/css'
165 shadowRoot.appendChild(styleElement)
166
167 return styleElement
168 }
169
170 return function addStyle(id, css) {
171 const styleElement = createStyleElement(shadowRoot)
172 if (css.media) styleElement.setAttribute('media', css.media)
173
174 let code = css.source
175
176 if (${e(compiler.template.isProduction)} && css.map) {
177 // https://developer.chrome.com/devtools/docs/javascript-debugging
178 // this makes source maps inside style tags work properly in Chrome
179 code += '\\n/*# sourceURL=' + css.map.sources[0] + ' */'
180 // http://stackoverflow.com/a/26603875
181 code +=
182 '\\n/*# sourceMappingURL=data:application/json;base64,' +
183 btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) +
184 ' */'
185 }
186
187 if ('styleSheet' in styleElement) {
188 styleElement.styleSheet.cssText = code
189 } else {
190 while (styleElement.firstChild) {
191 styleElement.removeChild(styleElement.firstChild)
192 }
193 styleElement.appendChild(document.createTextNode(code))
194 }
195 }
196 }`;
197 const createInjectorShadow = options.styleInjectorShadow
198 ? createImport('__vue_create_injector_shadow__', options.styleInjectorShadow)
199 : inlineCreateInjectorShadow;
200 // language=JavaScript
201 const inlineNormalizeComponent = `function __vue_normalize__(
202 template, style, script,
203 scope, functional, moduleIdentifier, shadowMode,
204 createInjector, createInjectorSSR, createInjectorShadow
205 ) {
206 const component = (typeof script === 'function' ? script.options : script) || {}
207
208 // For security concerns, we use only base name in production mode.
209 component.__file = ${compiler.template.isProduction ? e(path.basename(filename)) : e(filename)}
210
211 if (!component.render) {
212 component.render = template.render
213 component.staticRenderFns = template.staticRenderFns
214 component._compiled = true
215
216 if (functional) component.functional = true
217 }
218
219 component._scopeId = scope
220
221 if (${e(hasStyle)}) {
222 let hook
223 if (${e(compiler.template.optimizeSSR)}) {
224 // In SSR.
225 hook = function(context) {
226 // 2.3 injection
227 context =
228 context || // cached call
229 (this.$vnode && this.$vnode.ssrContext) || // stateful
230 (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional
231 // 2.2 with runInNewContext: true
232 if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
233 context = __VUE_SSR_CONTEXT__
234 }
235 // inject component styles
236 if (style) {
237 style.call(this, createInjectorSSR(context))
238 }
239 // register component module identifier for async chunk inference
240 if (context && context._registeredComponents) {
241 context._registeredComponents.add(moduleIdentifier)
242 }
243 }
244 // used by ssr in case component is cached and beforeCreate
245 // never gets called
246 component._ssrRegister = hook
247 }
248 else if (style) {
249 hook = shadowMode
250 ? function(context) {
251 style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot))
252 }
253 : function(context) {
254 style.call(this, createInjector(context))
255 }
256 }
257
258 if (hook !== undefined) {
259 if (component.functional) {
260 // register for functional component in vue file
261 const originalRender = component.render
262 component.render = function renderWithStyleInjection(h, context) {
263 hook.call(context)
264 return originalRender(h, context)
265 }
266 } else {
267 // inject component registration as beforeCreate hook
268 const existing = component.beforeCreate
269 component.beforeCreate = existing ? [].concat(existing, hook) : [hook]
270 }
271 }
272 }
273
274 return component
275 }`;
276 const normalizeComponent = options.normalizer
277 ? createImport('__vue_normalize__', options.normalizer)
278 : inlineNormalizeComponent;
279 const DEFAULT_EXPORT = 'const __vue_script__ =';
280 // language=JavaScript
281 let code = `/* script */\n${script.source.replace(/export\s+default/, DEFAULT_EXPORT)}` +
282 `\n/* template */\n${template.source
283 .replace('var render =', 'var __vue_render__ =')
284 .replace('var staticRenderFns =', 'var __vue_staticRenderFns__ =')
285 .replace('render._withStripped =', '__vue_render__._withStripped =')}
286 /* style */
287 const __vue_inject_styles__ = ${hasStyle ? `function (inject) {
288 if (!inject) return
289 ${styles.map((style, index) => {
290 const source = IDENTIFIER.test(style.source)
291 ? style.source
292 : e(style.source);
293 const map = !compiler.template.isProduction
294 ? typeof style.map === 'string' && IDENTIFIER.test(style.map)
295 ? style.map
296 : o(style.map)
297 : undefined;
298 const tokens = typeof style.module === 'string' && IDENTIFIER.test(style.module)
299 ? style.module
300 : o(style.module);
301 return ((source
302 ? `inject("${scopeId +
303 '_' +
304 index}", { source: ${source}, map: ${map}, media: ${e(style.media)} })\n`
305 : '') +
306 (style.moduleName
307 ? `Object.defineProperty(this, "${style.moduleName}", { value: ${tokens} })` + '\n'
308 : ''));
309 })}
310 }` : 'undefined'}
311 /* scoped */
312 const __vue_scope_id__ = ${hasScopedStyle ? e(scopeId) : 'undefined'}
313 /* module identifier */
314 const __vue_module_identifier__ = ${compiler.template.optimizeSSR ? e(scopeId) : 'undefined'}
315 /* functional template */
316 const __vue_is_functional_template__ = ${e(template.functional)}
317 /* component normalizer */
318 ${normalizeComponent}
319 /* style inject */
320 ${hasStyle && !compiler.template.optimizeSSR && !options.isWebComponent ? createInjector : ''}
321 /* style inject SSR */
322 ${hasStyle && compiler.template.optimizeSSR ? createInjectorSSR : ''}
323 /* style inject shadow dom */
324 ${hasStyle && options.isWebComponent ? createInjectorShadow : ''}
325
326 `;
327 // generate source map.
328 {
329 const input = script.source.split('\n');
330 input.forEach((sourceLine, index) => {
331 if (!sourceLine)
332 return;
333 const matches = /export\s+default/.exec(sourceLine);
334 if (matches) {
335 const pos = sourceLine.indexOf(matches[0]);
336 if (pos > 0) {
337 mapGenerator.addMapping({
338 source: filename,
339 original: { line: index + 1, column: 0 },
340 generated: { line: index + 2, column: 0 }
341 });
342 }
343 mapGenerator.addMapping({
344 source: filename,
345 original: { line: index + 1, column: pos },
346 generated: { line: index + 2, column: pos }
347 });
348 if (sourceLine.substr(pos + matches[0].length)) {
349 mapGenerator.addMapping({
350 source: filename,
351 original: { line: index + 1, column: pos + matches[0].length },
352 generated: { line: index + 2, column: pos + DEFAULT_EXPORT.length }
353 });
354 }
355 }
356 else {
357 mapGenerator.addMapping({
358 source: filename,
359 original: { line: index + 1, column: 0 },
360 generated: { line: index + 2, column: 0 }
361 });
362 }
363 });
364 }
365 code += `
366 const __vue_component__ = /*#__PURE__*/__vue_normalize__(
367 ${code.indexOf('__vue_render__') > -1
368 ? '{ render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ }'
369 : '{}'},
370 __vue_inject_styles__,
371 ${code.indexOf('__vue_script__') > -1 ? '__vue_script__' : '{}'},
372 __vue_scope_id__,
373 __vue_is_functional_template__,
374 __vue_module_identifier__,
375 ${options.isWebComponent ? 'true' : 'false'},
376 ${code.indexOf('__vue_create_injector__') > -1
377 ? '__vue_create_injector__'
378 : 'undefined'},
379 ${code.indexOf('__vue_create_injector_ssr__') > -1
380 ? '__vue_create_injector_ssr__'
381 : 'undefined'},
382 ${code.indexOf('__vue_create_injector_shadow__') > -1
383 ? '__vue_create_injector_shadow__'
384 : 'undefined'}
385 )\n
386 export default __vue_component__`;
387 if (script.map) {
388 map = source_map_2.merge(script.map, JSON.parse(mapGenerator.toString()));
389 }
390 else {
391 map = JSON.parse(mapGenerator.toString());
392 }
393 return { code, map };
394}
395exports.assembleFromSource = assembleFromSource;