const path = require('path') const hash = require('hash-sum') const parse = require('./parser') const querystring = require('querystring') const loaderUtils = require('loader-utils') const normalize = require('./utils/normalize') const tryRequire = require('./utils/try-require') // internal lib loaders const selectorPath = normalize.lib('selector') const styleCompilerPath = normalize.lib('style-compiler/index') const templateCompilerPath = normalize.lib('template-compiler/index') const templatePreprocessorPath = normalize.lib('template-compiler/preprocessor') const componentNormalizerPath = normalize.lib('component-normalizer') // dep loaders const styleLoaderPath = normalize.dep('vue-style-loader') const hotReloadAPIPath = normalize.dep('vue-hot-reload-api') // check whether default js loader exists const hasBabel = !!tryRequire('babel-loader') const hasBuble = !!tryRequire('buble-loader') const rewriterInjectRE = /\b(css(?:-loader)?(?:\?[^!]+)?)(?:!|$)/ const defaultLang = { template: 'html', styles: 'css', script: 'js' } const postcssExtensions = [ 'postcss', 'pcss', 'sugarss', 'sss' ] // When extracting parts from the source vue file, we want to apply the // loaders chained before vue-loader, but exclude some loaders that simply // produces side effects such as linting. function getRawRequest ( { resource, loaderIndex, loaders }, excludedPreLoaders = /eslint-loader/ ) { return loaderUtils.getRemainingRequest({ resource: resource, loaderIndex: loaderIndex, loaders: loaders.filter(loader => !excludedPreLoaders.test(loader.path)) }) } module.exports = function (content) { this.cacheable() const isServer = this.target === 'node' const isProduction = this.minimize || process.env.NODE_ENV === 'production' const loaderContext = this const query = loaderUtils.getOptions(this) || {} const options = Object.assign( { esModule: true }, this.options.vue, this.vue, query ) // disable esModule in inject mode // because import/export must be top-level if (query.inject) { options.esModule = false } // #824 avoid multiple webpack runs complaining about unknown option Object.defineProperty(this.options, '__vueOptions__', { value: options, enumerable: false, configurable: true }) const rawRequest = getRawRequest(this, options.excludedPreLoaders) const filePath = this.resourcePath const fileName = path.basename(filePath) const context = (this._compiler && this._compiler.context) || this.options.context || process.cwd() const sourceRoot = path.dirname(path.relative(context, filePath)) const shortFilePath = path.relative(context, filePath).replace(/^(\.\.[\\\/])+/, '').replace(/\\/g, '/') const moduleId = 'data-v-' + hash(isProduction ? (shortFilePath + '\n' + content) : shortFilePath) let cssLoaderOptions = '' const cssSourceMap = !isProduction && this.sourceMap && options.cssSourceMap !== false if (cssSourceMap) { cssLoaderOptions += '?sourceMap' } if (isProduction) { cssLoaderOptions += (cssLoaderOptions ? '&' : '?') + 'minimize' } const bubleOptions = hasBuble && options.buble ? '?' + JSON.stringify(options.buble) : '' let output = '' const parts = parse( content, fileName, this.sourceMap, sourceRoot, cssSourceMap ) const hasScoped = parts.styles.some(({ scoped }) => scoped) const templateAttrs = parts.template && parts.template.attrs && parts.template.attrs const hasComment = templateAttrs && templateAttrs.comments const functionalTemplate = templateAttrs && templateAttrs.functional const bubleTemplateOptions = Object.assign({}, options.buble) bubleTemplateOptions.transforms = Object.assign( {}, bubleTemplateOptions.transforms ) bubleTemplateOptions.transforms.stripWithFunctional = functionalTemplate const templateCompilerOptions = '?' + JSON.stringify({ id: moduleId, hasScoped, hasComment, transformToRequire: options.transformToRequire, preserveWhitespace: options.preserveWhitespace, buble: bubleTemplateOptions, // only pass compilerModules if it's a path string compilerModules: typeof options.compilerModules === 'string' ? options.compilerModules : undefined }) const defaultLoaders = { html: templateCompilerPath + templateCompilerOptions, css: options.extractCSS ? getCSSExtractLoader() : styleLoaderPath + '!' + 'css-loader' + cssLoaderOptions, js: hasBuble ? 'buble-loader' + bubleOptions : hasBabel ? 'babel-loader' : '' } // check if there are custom loaders specified via // webpack config, otherwise use defaults const loaders = Object.assign({}, defaultLoaders, options.loaders) const preLoaders = options.preLoaders || {} const postLoaders = options.postLoaders || {} const needsHotReload = !isServer && !isProduction && (parts.script || parts.template) && options.hotReload !== false if (needsHotReload) { output += 'var disposed = false\n' } // add requires for styles let cssModules if (parts.styles.length) { let styleInjectionCode = 'function injectStyle (ssrContext) {\n' if (needsHotReload) { styleInjectionCode += ` if (disposed) return\n` } if (isServer) { styleInjectionCode += `var i\n` } parts.styles.forEach((style, i) => { // require style let requireString = style.src ? getRequireForImport('styles', style, style.scoped) : getRequire('styles', style, i, style.scoped) const hasStyleLoader = requireString.indexOf('style-loader') > -1 const hasVueStyleLoader = requireString.indexOf('vue-style-loader') > -1 // vue-style-loader exposes inject functions during SSR so they are // always called const invokeStyle = isServer && hasVueStyleLoader ? code => `;(i=${code},i.__inject__&&i.__inject__(ssrContext),i)\n` : code => ` ${code}\n` const moduleName = style.module === true ? '$style' : style.module // setCssModule if (moduleName) { if (!cssModules) { cssModules = {} if (needsHotReload) { output += `var cssModules = {}\n` } } if (moduleName in cssModules) { loaderContext.emitError( 'CSS module name "' + moduleName + '" is not unique!' ) styleInjectionCode += invokeStyle(requireString) } else { cssModules[moduleName] = true // `(vue-)style-loader` exposes the name-to-hash map directly // `css-loader` exposes it in `.locals` // add `.locals` if the user configured to not use style-loader. if (!hasStyleLoader) { requireString += '.locals' } if (!needsHotReload) { styleInjectionCode += invokeStyle( 'this["' + moduleName + '"] = ' + requireString ) } else { // handle hot reload for CSS modules. // we store the exported locals in an object and proxy to it by // defining getters inside component instances' lifecycle hook. styleInjectionCode += invokeStyle(`cssModules["${moduleName}"] = ${requireString}`) + `Object.defineProperty(this, "${moduleName}", { get: function () { return cssModules["${moduleName}"] }})\n` const requirePath = style.src ? getRequireForImportString('styles', style, style.scoped) : getRequireString('styles', style, i, style.scoped) output += `module.hot && module.hot.accept([${requirePath}], function () {\n` + // 1. check if style has been injected ` var oldLocals = cssModules["${moduleName}"]\n` + ` if (!oldLocals) return\n` + // 2. re-import (side effect: updates the