UNPKG

5.69 kBPlain TextView Raw
1import {
2 VueTemplateCompiler,
3 VueTemplateCompilerOptions,
4 ErrorWithRange
5} from './types'
6
7import assetUrlsModule, {
8 AssetURLOptions,
9 TransformAssetUrlsOptions
10} from './templateCompilerModules/assetUrl'
11import srcsetModule from './templateCompilerModules/srcset'
12
13const consolidate = require('consolidate')
14const transpile = require('vue-template-es2015-compiler')
15
16export interface TemplateCompileOptions {
17 source: string
18 filename: string
19 compiler: VueTemplateCompiler
20 compilerOptions?: VueTemplateCompilerOptions
21 transformAssetUrls?: AssetURLOptions | boolean
22 transformAssetUrlsOptions?: TransformAssetUrlsOptions
23 preprocessLang?: string
24 preprocessOptions?: any
25 transpileOptions?: any
26 isProduction?: boolean
27 isFunctional?: boolean
28 optimizeSSR?: boolean
29 prettify?: boolean
30}
31
32export interface TemplateCompileResult {
33 ast: Object | undefined
34 code: string
35 source: string
36 tips: (string | ErrorWithRange)[]
37 errors: (string | ErrorWithRange)[]
38}
39
40export function compileTemplate(
41 options: TemplateCompileOptions
42): TemplateCompileResult {
43 const { preprocessLang } = options
44 const preprocessor = preprocessLang && consolidate[preprocessLang]
45 if (preprocessor) {
46 return actuallyCompile(
47 Object.assign({}, options, {
48 source: preprocess(options, preprocessor)
49 })
50 )
51 } else if (preprocessLang) {
52 return {
53 ast: {},
54 code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
55 source: options.source,
56 tips: [
57 `Component ${options.filename} uses lang ${preprocessLang} for template. Please install the language preprocessor.`
58 ],
59 errors: [
60 `Component ${options.filename} uses lang ${preprocessLang} for template, however it is not installed.`
61 ]
62 }
63 } else {
64 return actuallyCompile(options)
65 }
66}
67
68function preprocess(
69 options: TemplateCompileOptions,
70 preprocessor: any
71): string {
72 const { source, filename, preprocessOptions } = options
73
74 const finalPreprocessOptions = Object.assign(
75 {
76 filename
77 },
78 preprocessOptions
79 )
80
81 // Consolidate exposes a callback based API, but the callback is in fact
82 // called synchronously for most templating engines. In our case, we have to
83 // expose a synchronous API so that it is usable in Jest transforms (which
84 // have to be sync because they are applied via Node.js require hooks)
85 let res: any, err
86 preprocessor.render(
87 source,
88 finalPreprocessOptions,
89 (_err: Error | null, _res: string) => {
90 if (_err) err = _err
91 res = _res
92 }
93 )
94
95 if (err) throw err
96 return res
97}
98
99function actuallyCompile(
100 options: TemplateCompileOptions
101): TemplateCompileResult {
102 const {
103 source,
104 compiler,
105 compilerOptions = {},
106 transpileOptions = {},
107 transformAssetUrls,
108 transformAssetUrlsOptions,
109 isProduction = process.env.NODE_ENV === 'production',
110 isFunctional = false,
111 optimizeSSR = false,
112 prettify = true
113 } = options
114
115 const compile =
116 optimizeSSR && compiler.ssrCompile ? compiler.ssrCompile : compiler.compile
117
118 let finalCompilerOptions = compilerOptions
119 if (transformAssetUrls) {
120 const builtInModules = [
121 transformAssetUrls === true
122 ? assetUrlsModule(undefined, transformAssetUrlsOptions)
123 : assetUrlsModule(transformAssetUrls, transformAssetUrlsOptions),
124 srcsetModule(transformAssetUrlsOptions)
125 ]
126 finalCompilerOptions = Object.assign({}, compilerOptions, {
127 modules: [...builtInModules, ...(compilerOptions.modules || [])],
128 filename: options.filename
129 })
130 }
131
132 const { ast, render, staticRenderFns, tips, errors } = compile(
133 source,
134 finalCompilerOptions
135 )
136
137 if (errors && errors.length) {
138 return {
139 ast,
140 code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
141 source,
142 tips,
143 errors
144 }
145 } else {
146 const finalTranspileOptions = Object.assign({}, transpileOptions, {
147 transforms: Object.assign({}, transpileOptions.transforms, {
148 stripWithFunctional: isFunctional
149 })
150 })
151
152 const toFunction = (code: string): string => {
153 return `function (${isFunctional ? `_h,_vm` : ``}) {${code}}`
154 }
155
156 // transpile code with vue-template-es2015-compiler, which is a forked
157 // version of Buble that applies ES2015 transforms + stripping `with` usage
158 let code =
159 transpile(
160 `var __render__ = ${toFunction(render)}\n` +
161 `var __staticRenderFns__ = [${staticRenderFns.map(toFunction)}]`,
162 finalTranspileOptions
163 ) + `\n`
164
165 // #23 we use __render__ to avoid `render` not being prefixed by the
166 // transpiler when stripping with, but revert it back to `render` to
167 // maintain backwards compat
168 code = code.replace(/\s__(render|staticRenderFns)__\s/g, ' $1 ')
169
170 if (!isProduction) {
171 // mark with stripped (this enables Vue to use correct runtime proxy
172 // detection)
173 code += `render._withStripped = true`
174
175 if (prettify) {
176 try {
177 code = require('prettier').format(code, {
178 semi: false,
179 parser: 'babel'
180 })
181 } catch (e) {
182 if (e.code === 'MODULE_NOT_FOUND') {
183 tips.push(
184 'The `prettify` option is on, but the dependency `prettier` is not found.\n' +
185 'Please either turn off `prettify` or manually install `prettier`.'
186 )
187 }
188 tips.push(
189 `Failed to prettify component ${options.filename} template source after compilation.`
190 )
191 }
192 }
193 }
194
195 return {
196 ast,
197 code,
198 source,
199 tips,
200 errors
201 }
202 }
203}