diff --git a/lib/compiler.js b/lib/compiler.js new file mode 100644 index 000000000..e83df169b --- /dev/null +++ b/lib/compiler.js @@ -0,0 +1,50 @@ +// resolve compilers to use. + +let cached + +exports.resolveCompiler = function (loaderContext) { + if (cached) { + return cached + } + + const ctx = loaderContext.rootContext + // check 2.7 + try { + const pkg = loadFromContext('vue/package.json', ctx) + const [major, minor] = pkg.version.split('.') + if (major === '2' && minor === '7') { + return (cached = { + compiler: loadFromContext('vue/compiler-sfc', ctx), + templateCompiler: undefined + }) + } + } catch (e) {} + + return (cached = { + compiler: require('@vue/component-compiler-utils'), + templateCompiler: loadTemplateCompiler(loaderContext) + }) +} + +function loadFromContext (path, ctx) { + return require(require.resolve(path, { + paths: [ctx] + })) +} + +function loadTemplateCompiler (loaderContext) { + try { + return loadFromContext('vue-template-compiler', loaderContext.rootContext) + } catch (e) { + if (/version mismatch/.test(e.toString())) { + loaderContext.emitError(e) + } else { + loaderContext.emitError( + new Error( + `[vue-loader] vue-template-compiler must be installed as a peer dependency, ` + + `or a compatible compiler implementation must be passed via options.` + ) + ) + } + } +} diff --git a/lib/index.js b/lib/index.js index 7760df47b..853d2738d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -5,38 +5,25 @@ const plugin = require('./plugin') const selectBlock = require('./select') const loaderUtils = require('loader-utils') const { attrsToQuery } = require('./codegen/utils') -const { parse } = require('@vue/component-compiler-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') let errorEmitted = false -function loadTemplateCompiler (loaderContext) { - try { - return require('vue-template-compiler') - } catch (e) { - if (/version mismatch/.test(e.toString())) { - loaderContext.emitError(e) - } else { - loaderContext.emitError(new Error( - `[vue-loader] vue-template-compiler must be installed as a peer dependency, ` + - `or a compatible compiler implementation must be passed via options.` - )) - } - } -} - 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.` - )) + loaderContext.emitError( + new Error( + `vue-loader was used without the corresponding plugin. ` + + `Make sure to include VueLoaderPlugin in your webpack config.` + ) + ) errorEmitted = true } @@ -59,14 +46,17 @@ module.exports = function (source) { const isServer = target === 'node' const isShadow = !!options.shadowMode - const isProduction = options.productionMode || minimize || process.env.NODE_ENV === 'production' + const isProduction = + 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 descriptor = parse({ + const { compiler, templateCompiler } = resolveCompiler(loaderContext) + + const descriptor = compiler.parse({ source, - compiler: options.compiler || loadTemplateCompiler(loaderContext), + compiler: options.compiler || templateCompiler, filename, sourceRoot, needMap: sourceMap @@ -78,6 +68,7 @@ module.exports = function (source) { if (incomingQuery.type) { return selectBlock( descriptor, + options, loaderContext, incomingQuery, !!options.appendExtension @@ -93,19 +84,19 @@ module.exports = function (source) { const id = hash( isProduction - ? (shortFilePath + '\n' + source.replace(/\r\n/g, '\n')) + ? shortFilePath + '\n' + source.replace(/\r\n/g, '\n') : shortFilePath ) // feature information const hasScoped = descriptor.styles.some(s => s.scoped) - const hasFunctional = descriptor.template && descriptor.template.attrs.functional - const needsHotReload = ( + const hasFunctional = + descriptor.template && descriptor.template.attrs.functional + const needsHotReload = !isServer && !isProduction && - (descriptor.script || descriptor.template) && + (descriptor.script || descriptor.scriptSetup || descriptor.template) && options.hotReload !== false - ) // template let templateImport = `var render, staticRenderFns` @@ -116,21 +107,20 @@ module.exports = function (source) { const scopedQuery = hasScoped ? `&scoped=true` : `` const attrsQuery = attrsToQuery(descriptor.template.attrs) const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}` - const request = templateRequest = stringifyRequest(src + query) + const request = (templateRequest = stringifyRequest(src + query)) templateImport = `import { render, staticRenderFns } from ${request}` } // script let scriptImport = `var script = {}` - if (descriptor.script) { - const src = descriptor.script.src || resourcePath - const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js') + const { script, scriptSetup } = descriptor + if (script || scriptSetup) { + 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 - ) + scriptImport = + `import script from ${request}\n` + `export * from ${request}` // support named exports } // styles @@ -147,7 +137,8 @@ module.exports = function (source) { ) } - let code = ` + let code = + ` ${templateImport} ${scriptImport} ${stylesCode} @@ -183,7 +174,9 @@ var component = normalizer( 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, '/'))}` + 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. diff --git a/lib/loaders/stylePostLoader.js b/lib/loaders/stylePostLoader.js index de8d09eba..949fe0a5a 100644 --- a/lib/loaders/stylePostLoader.js +++ b/lib/loaders/stylePostLoader.js @@ -1,12 +1,13 @@ const qs = require('querystring') -const { compileStyle } = require('@vue/component-compiler-utils') +const { resolveCompiler } = require('../compiler') // This is a post loader that handles scoped CSS transforms. // Injected right before css-loader by the global pitcher (../pitch.js) // for any