123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- const path = require('path')
- const hash = require('hash-sum')
- const qs = require('querystring')
- const plugin = require('./plugin')
- const selectBlock = require('./select')
- const loaderUtils = require('loader-utils')
- const { attrsToQuery } = require('./codegen/utils')
- const genStylesCode = require('./codegen/styleInjection')
- const { genHotReloadCode } = require('./codegen/hotReload')
- const genCustomBlocksCode = require('./codegen/customBlocks')
- const componentNormalizerPath = require.resolve('./runtime/componentNormalizer')
- const { NS } = require('./plugin')
- const { resolveCompiler } = require('./compiler')
- const { setDescriptor } = require('./descriptorCache')
- let errorEmitted = false
- module.exports = function (source) {
- const loaderContext = this
- if (!errorEmitted && !loaderContext['thread-loader'] && !loaderContext[NS]) {
- loaderContext.emitError(
- new Error(
- `vue-loader was used without the corresponding plugin. ` +
- `Make sure to include VueLoaderPlugin in your webpack config.`
- )
- )
- errorEmitted = true
- }
- const stringifyRequest = (r) => loaderUtils.stringifyRequest(loaderContext, r)
- const {
- mode,
- target,
- request,
- minimize,
- sourceMap,
- rootContext,
- resourcePath,
- resourceQuery = ''
- } = loaderContext
- const rawQuery = resourceQuery.slice(1)
- const inheritQuery = `&${rawQuery}`
- const incomingQuery = qs.parse(rawQuery)
- const options = loaderUtils.getOptions(loaderContext) || {}
- const isServer = target === 'node'
- const isShadow = !!options.shadowMode
- const isProduction =
- mode === 'production' ||
- options.productionMode ||
- minimize ||
- process.env.NODE_ENV === 'production'
- const filename = path.basename(resourcePath)
- const context = rootContext || process.cwd()
- const sourceRoot = path.dirname(path.relative(context, resourcePath))
- const { compiler, templateCompiler } = resolveCompiler(context, loaderContext)
- const descriptor = compiler.parse({
- source,
- compiler: options.compiler || templateCompiler,
- filename,
- sourceRoot,
- needMap: sourceMap
- })
- // cache descriptor
- setDescriptor(resourcePath, descriptor)
- // module id for scoped CSS & hot-reload
- const rawShortFilePath = path
- .relative(context, resourcePath)
- .replace(/^(\.\.[\/\\])+/, '')
- const shortFilePath = rawShortFilePath.replace(/\\/g, '/')
- const id = hash(
- isProduction
- ? shortFilePath + '\n' + source.replace(/\r\n/g, '\n')
- : shortFilePath
- )
- // if the query has a type field, this is a language block request
- // e.g. foo.vue?type=template&id=xxxxx
- // and we will return early
- if (incomingQuery.type) {
- return selectBlock(
- descriptor,
- id,
- options,
- loaderContext,
- incomingQuery,
- !!options.appendExtension
- )
- }
- // feature information
- const hasScoped = descriptor.styles.some((s) => s.scoped)
- const hasFunctional =
- descriptor.template && descriptor.template.attrs.functional
- const needsHotReload =
- !isServer &&
- !isProduction &&
- (descriptor.script || descriptor.scriptSetup || descriptor.template) &&
- options.hotReload !== false
- // script
- let scriptImport = `var script = {}`
- // let isTS = false
- const { script, scriptSetup } = descriptor
- if (script || scriptSetup) {
- // const lang = script?.lang || scriptSetup?.lang
- // isTS = !!(lang && /tsx?/.test(lang))
- const src = (script && !scriptSetup && script.src) || resourcePath
- const attrsQuery = attrsToQuery((scriptSetup || script).attrs, 'js')
- const query = `?vue&type=script${attrsQuery}${inheritQuery}`
- const request = stringifyRequest(src + query)
- scriptImport =
- `import script from ${request}\n` + `export * from ${request}` // support named exports
- }
- // template
- let templateImport = `var render, staticRenderFns`
- let templateRequest
- if (descriptor.template) {
- const src = descriptor.template.src || resourcePath
- const idQuery = `&id=${id}`
- const scopedQuery = hasScoped ? `&scoped=true` : ``
- const attrsQuery = attrsToQuery(descriptor.template.attrs)
- // const tsQuery =
- // options.enableTsInTemplate !== false && isTS ? `&ts=true` : ``
- const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
- const request = (templateRequest = stringifyRequest(src + query))
- templateImport = `import { render, staticRenderFns } from ${request}`
- }
- // styles
- let stylesCode = ``
- if (descriptor.styles.length) {
- stylesCode = genStylesCode(
- loaderContext,
- descriptor.styles,
- id,
- resourcePath,
- stringifyRequest,
- needsHotReload,
- isServer || isShadow, // needs explicit injection?
- isProduction
- )
- }
- let code =
- `
- ${templateImport}
- ${scriptImport}
- ${stylesCode}
- /* normalize component */
- import normalizer from ${stringifyRequest(`!${componentNormalizerPath}`)}
- var component = normalizer(
- script,
- render,
- staticRenderFns,
- ${hasFunctional ? `true` : `false`},
- ${/injectStyles/.test(stylesCode) ? `injectStyles` : `null`},
- ${hasScoped ? JSON.stringify(id) : `null`},
- ${isServer ? JSON.stringify(hash(request)) : `null`}
- ${isShadow ? `,true` : ``}
- )
- `.trim() + `\n`
- if (descriptor.customBlocks && descriptor.customBlocks.length) {
- code += genCustomBlocksCode(
- descriptor.customBlocks,
- resourcePath,
- resourceQuery,
- stringifyRequest
- )
- }
- if (needsHotReload) {
- code += `\n` + genHotReloadCode(id, hasFunctional, templateRequest)
- }
- // Expose filename. This is used by the devtools and Vue runtime warnings.
- if (!isProduction) {
- // Expose the file's full path in development, so that it can be opened
- // from the devtools.
- code += `\ncomponent.options.__file = ${JSON.stringify(
- rawShortFilePath.replace(/\\/g, '/')
- )}`
- } else if (options.exposeFilename) {
- // Libraries can opt-in to expose their components' filenames in production builds.
- // For security reasons, only expose the file's basename in production.
- code += `\ncomponent.options.__file = ${JSON.stringify(filename)}`
- }
- code += `\nexport default component.exports`
- return code
- }
- module.exports.VueLoaderPlugin = plugin
|