120 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			120 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const qs = require('querystring')
 | |
| const loaderUtils = require('loader-utils')
 | |
| const { resolveCompiler } = require('../compiler')
 | |
| const { getDescriptor } = require('../descriptorCache')
 | |
| const { resolveScript } = require('../resolveScript')
 | |
| 
 | |
| // Loader that compiles raw template into JavaScript functions.
 | |
| // This is injected by the global pitcher (../pitch) for template
 | |
| // selection requests initiated from vue files.
 | |
| module.exports = function (source) {
 | |
|   const loaderContext = this
 | |
|   const filename = this.resourcePath
 | |
|   const ctx = this.rootContext
 | |
|   const query = qs.parse(this.resourceQuery.slice(1))
 | |
| 
 | |
|   // although this is not the main vue-loader, we can get access to the same
 | |
|   // vue-loader options because we've set an ident in the plugin and used that
 | |
|   // ident to create the request for this loader in the pitcher.
 | |
|   const options = loaderUtils.getOptions(loaderContext) || {}
 | |
|   const { id } = query
 | |
|   const isServer = loaderContext.target === 'node'
 | |
|   const isProduction =
 | |
|     options.productionMode ||
 | |
|     loaderContext.minimize ||
 | |
|     process.env.NODE_ENV === 'production'
 | |
|   const isFunctional = query.functional
 | |
| 
 | |
|   const compilerOptions = Object.assign(
 | |
|     {
 | |
|       outputSourceRange: true
 | |
|     },
 | |
|     options.compilerOptions,
 | |
|     {
 | |
|       scopeId: query.scoped ? `data-v-${id}` : null,
 | |
|       comments: query.comments
 | |
|     }
 | |
|   )
 | |
| 
 | |
|   const { compiler, templateCompiler } = resolveCompiler(ctx, loaderContext)
 | |
| 
 | |
|   const descriptor = getDescriptor(filename, options, loaderContext)
 | |
|   const script = resolveScript(descriptor, id, options, loaderContext)
 | |
| 
 | |
|   // Prettier 3 APIs are all async
 | |
|   // but `vue/compiler-sfc` and `@vue/component-compiler-utils` can only use sync function to format code
 | |
|   let hasCompatiblePrettier = false
 | |
|   try {
 | |
|     const prettier = require('prettier/package.json')
 | |
|     const major = parseInt(prettier.version.split('.')[0], 10)
 | |
|     if (major === 1 || major === 2) {
 | |
|       hasCompatiblePrettier = true
 | |
|     }
 | |
|   } catch (e) {}
 | |
| 
 | |
|   // for vue/compiler-sfc OR @vue/component-compiler-utils
 | |
|   const finalOptions = {
 | |
|     source,
 | |
|     filename: this.resourcePath,
 | |
|     compiler: options.compiler || templateCompiler,
 | |
|     compilerOptions,
 | |
|     // allow customizing behavior of vue-template-es2015-compiler
 | |
|     transpileOptions: options.transpileOptions,
 | |
|     transformAssetUrls: options.transformAssetUrls || true,
 | |
|     isProduction,
 | |
|     isFunctional,
 | |
|     optimizeSSR: isServer && options.optimizeSSR !== false,
 | |
|     prettify: options.prettify === undefined ? hasCompatiblePrettier : options.prettify,
 | |
|     bindings: script ? script.bindings : undefined
 | |
|   }
 | |
| 
 | |
|   const compiled = compiler.compileTemplate(finalOptions)
 | |
| 
 | |
|   // tips
 | |
|   if (compiled.tips && compiled.tips.length) {
 | |
|     compiled.tips.forEach((tip) => {
 | |
|       loaderContext.emitWarning(typeof tip === 'object' ? tip.msg : tip)
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   // errors
 | |
|   if (compiled.errors && compiled.errors.length) {
 | |
|     const generateCodeFrame =
 | |
|       (templateCompiler && templateCompiler.generateCodeFrame) ||
 | |
|       compiler.generateCodeFrame
 | |
|     // 2.6 compiler outputs errors as objects with range
 | |
|     if (generateCodeFrame && finalOptions.compilerOptions.outputSourceRange) {
 | |
|       // TODO account for line offset in case template isn't placed at top
 | |
|       // of the file
 | |
|       loaderContext.emitError(
 | |
|         `\n\n  Errors compiling template:\n\n` +
 | |
|           compiled.errors
 | |
|             .map(({ msg, start, end }) => {
 | |
|               const frame = generateCodeFrame(source, start, end)
 | |
|               return `  ${msg}\n\n${pad(frame)}`
 | |
|             })
 | |
|             .join(`\n\n`) +
 | |
|           '\n'
 | |
|       )
 | |
|     } else {
 | |
|       loaderContext.emitError(
 | |
|         `\n  Error compiling template:\n${pad(compiled.source)}\n` +
 | |
|           compiled.errors.map((e) => `  - ${e}`).join('\n') +
 | |
|           '\n'
 | |
|       )
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const { code } = compiled
 | |
| 
 | |
|   // finish with ESM exports
 | |
|   return code + `\nexport { render, staticRenderFns }`
 | |
| }
 | |
| 
 | |
| function pad(source) {
 | |
|   return source
 | |
|     .split(/\r?\n/)
 | |
|     .map((line) => `  ${line}`)
 | |
|     .join('\n')
 | |
| }
 |