123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719 |
- 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 <style>)
- ` var newLocals = ${requireString}\n` +
- // 3. compare new and old locals to see if selectors changed
- ` if (JSON.stringify(newLocals) === JSON.stringify(oldLocals)) return\n` +
- // 4. locals changed. Update and force re-render.
- ` cssModules["${moduleName}"] = newLocals\n` +
- ` require("${hotReloadAPIPath}").rerender("${moduleId}")\n` +
- `})\n`
- }
- }
- } else {
- styleInjectionCode += invokeStyle(requireString)
- }
- })
- styleInjectionCode += '}\n'
- output += styleInjectionCode
- }
- // we require the component normalizer function, and call it like so:
- // normalizeComponent(
- // scriptExports,
- // compiledTemplate,
- // functionalTemplate,
- // injectStyles,
- // scopeId,
- // moduleIdentifier (server only)
- // )
- output +=
- 'var normalizeComponent = require(' +
- loaderUtils.stringifyRequest(loaderContext, '!' + componentNormalizerPath) +
- ')\n'
- // <script>
- output += '/* script */\n'
- const script = parts.script
- if (script) {
- if (options.esModule) {
- output += script.src
- ? (
- getNamedExportForImport('script', script) + '\n' +
- getImportForImport('script', script)
- )
- : (
- getNamedExport('script', script) + '\n' +
- getImport('script', script)
- ) + '\n'
- } else {
- output +=
- 'var __vue_script__ = ' +
- (script.src
- ? getRequireForImport('script', script)
- : getRequire('script', script)) +
- '\n'
- }
- // inject loader interop
- if (query.inject) {
- output += '__vue_script__ = __vue_script__(injections)\n'
- }
- } else {
- output += 'var __vue_script__ = null\n'
- }
- // <template>
- output += '/* template */\n'
- const template = parts.template
- if (template) {
- if (options.esModule) {
- output +=
- (template.src
- ? getImportForImport('template', template)
- : getImport('template', template)) + '\n'
- } else {
- output +=
- 'var __vue_template__ = ' +
- (template.src
- ? getRequireForImport('template', template)
- : getRequire('template', template)) +
- '\n'
- }
- } else {
- output += 'var __vue_template__ = null\n'
- }
- // template functional
- output += '/* template functional */\n'
- output +=
- 'var __vue_template_functional__ = ' +
- (functionalTemplate ? 'true' : 'false') +
- '\n'
- // style
- output += '/* styles */\n'
- output +=
- 'var __vue_styles__ = ' +
- (parts.styles.length ? 'injectStyle' : 'null') +
- '\n'
- // scopeId
- output += '/* scopeId */\n'
- output +=
- 'var __vue_scopeId__ = ' +
- (hasScoped ? JSON.stringify(moduleId) : 'null') +
- '\n'
- // moduleIdentifier (server only)
- output += '/* moduleIdentifier (server only) */\n'
- output +=
- 'var __vue_module_identifier__ = ' +
- (isServer ? JSON.stringify(hash(this.request)) : 'null') +
- '\n'
- // close normalizeComponent call
- output +=
- 'var Component = normalizeComponent(\n' +
- ' __vue_script__,\n' +
- ' __vue_template__,\n' +
- ' __vue_template_functional__,\n' +
- ' __vue_styles__,\n' +
- ' __vue_scopeId__,\n' +
- ' __vue_module_identifier__\n' +
- ')\n'
- // development-only code
- if (!isProduction) {
- // add filename in dev
- output +=
- 'Component.options.__file = ' + JSON.stringify(shortFilePath) + '\n'
- }
- // add requires for customBlocks
- if (parts.customBlocks && parts.customBlocks.length) {
- let addedPrefix = false
- parts.customBlocks.forEach((customBlock, i) => {
- if (loaders[customBlock.type]) {
- // require customBlock
- customBlock.src = customBlock.attrs.src
- const requireString = customBlock.src
- ? getRequireForImport(customBlock.type, customBlock)
- : getRequire(customBlock.type, customBlock, i)
- if (!addedPrefix) {
- output += '\n/* customBlocks */\n'
- addedPrefix = true
- }
- output +=
- 'var customBlock = ' + requireString + '\n' +
- 'if (customBlock && customBlock.__esModule) {\n' +
- ' customBlock = customBlock.default\n' +
- '}\n' +
- 'if (typeof customBlock === "function") {\n' +
- ' customBlock(Component)\n' +
- '}\n'
- }
- })
- output += '\n'
- }
- if (!query.inject) {
- // hot reload
- if (needsHotReload) {
- output +=
- '\n/* hot reload */\n' +
- 'if (module.hot) {(function () {\n' +
- ' var hotAPI = require("' + hotReloadAPIPath + '")\n' +
- ' hotAPI.install(require("vue"), false)\n' +
- ' if (!hotAPI.compatible) return\n' +
- ' module.hot.accept()\n' +
- ' if (!module.hot.data) {\n' +
- // initial insert
- ' hotAPI.createRecord("' + moduleId + '", Component.options)\n' +
- ' } else {\n'
- // update
- if (cssModules) {
- output +=
- ' if (module.hot.data.cssModules && Object.keys(module.hot.data.cssModules) !== Object.keys(cssModules)) {\n' +
- ' delete Component.options._Ctor\n' +
- ' }\n'
- }
- output +=
- ` hotAPI.${
- functionalTemplate ? 'rerender' : 'reload'
- }("${moduleId}", Component.options)\n }\n`
- // dispose
- output +=
- ' module.hot.dispose(function (data) {\n' +
- (cssModules ? ' data.cssModules = cssModules\n' : '') +
- ' disposed = true\n' +
- ' })\n'
- output += '})()}\n'
- }
- // final export
- if (options.esModule) {
- output += '\nexport default Component.exports\n'
- } else {
- output += '\nmodule.exports = Component.exports\n'
- }
- } else {
- // inject-loader support
- output =
- '\n/* dependency injection */\n' +
- 'module.exports = function (injections) {\n' +
- output +
- '\n' +
- '\nreturn Component.exports\n}'
- }
- // done
- return output
- // --- helpers ---
- function getRequire (type, part, index, scoped) {
- return 'require(' + getRequireString(type, part, index, scoped) + ')'
- }
- function getImport (type, part, index, scoped) {
- return (
- 'import __vue_' + type + '__ from ' +
- getRequireString(type, part, index, scoped)
- )
- }
- function getNamedExport (type, part, index, scoped) {
- return (
- 'export * from ' +
- getRequireString(type, part, index, scoped)
- )
- }
- function getRequireString (type, part, index, scoped) {
- return loaderUtils.stringifyRequest(
- loaderContext,
- // disable all configuration loaders
- '!!' +
- // get loader string for pre-processors
- getLoaderString(type, part, index, scoped) +
- // select the corresponding part from the vue file
- getSelectorString(type, index || 0) +
- // the url to the actual vue file, including remaining requests
- rawRequest
- )
- }
- function getRequireForImport (type, impt, scoped) {
- return 'require(' + getRequireForImportString(type, impt, scoped) + ')'
- }
- function getImportForImport (type, impt, scoped) {
- return (
- 'import __vue_' + type + '__ from ' +
- getRequireForImportString(type, impt, scoped)
- )
- }
- function getNamedExportForImport (type, impt, scoped) {
- return (
- 'export * from ' +
- getRequireForImportString(type, impt, scoped)
- )
- }
- function getRequireForImportString (type, impt, scoped) {
- return loaderUtils.stringifyRequest(
- loaderContext,
- '!!' + getLoaderString(type, impt, -1, scoped) + impt.src
- )
- }
- function addCssModulesToLoader (loader, part, index) {
- if (!part.module) return loader
- const option = options.cssModules || {}
- const DEFAULT_OPTIONS = {
- modules: true
- }
- const OPTIONS = {
- localIdentName: '[hash:base64]',
- importLoaders: true
- }
- return loader.replace(/((?:^|!)css(?:-loader)?)(\?[^!]*)?/, (m, $1, $2) => {
- // $1: !css-loader
- // $2: ?a=b
- const query = loaderUtils.parseQuery($2 || '?')
- Object.assign(query, OPTIONS, option, DEFAULT_OPTIONS)
- if (index !== -1) {
- // Note:
- // Class name is generated according to its filename.
- // Different <style> tags in the same .vue file may generate same names.
- // Append `_[index]` to class name to avoid this.
- query.localIdentName += '_' + index
- }
- return $1 + '?' + JSON.stringify(query)
- })
- }
- function buildCustomBlockLoaderString (attrs) {
- const noSrcAttrs = Object.assign({}, attrs)
- delete noSrcAttrs.src
- const qs = querystring.stringify(noSrcAttrs)
- return qs ? '?' + qs : qs
- }
- // stringify an Array of loader objects
- function stringifyLoaders (loaders) {
- return loaders
- .map(
- obj =>
- obj && typeof obj === 'object' && typeof obj.loader === 'string'
- ? obj.loader +
- (obj.options ? '?' + JSON.stringify(obj.options) : '')
- : obj
- )
- .join('!')
- }
- function getLoaderString (type, part, index, scoped) {
- let loader = getRawLoaderString(type, part, index, scoped)
- const lang = getLangString(type, part)
- if (preLoaders[lang]) {
- loader = loader + ensureBang(preLoaders[lang])
- }
- if (postLoaders[lang]) {
- loader = ensureBang(postLoaders[lang]) + loader
- }
- return loader
- }
- function getLangString (type, { lang }) {
- if (type === 'script' || type === 'template' || type === 'styles') {
- return lang || defaultLang[type]
- } else {
- return type
- }
- }
- function getRawLoaderString (type, part, index, scoped) {
- let lang = part.lang || defaultLang[type]
- let styleCompiler = ''
- if (type === 'styles') {
- // style compiler that needs to be applied for all styles
- styleCompiler =
- styleCompilerPath +
- '?' +
- JSON.stringify({
- // a marker for vue-style-loader to know that this is an import from a vue file
- vue: true,
- id: moduleId,
- scoped: !!scoped,
- hasInlineConfig: !!query.postcss
- }) +
- '!'
- // normalize scss/sass/postcss if no specific loaders have been provided
- if (!loaders[lang]) {
- if (postcssExtensions.indexOf(lang) !== -1) {
- lang = 'css'
- } else if (lang === 'sass') {
- lang = 'sass?indentedSyntax'
- } else if (lang === 'scss') {
- lang = 'sass'
- }
- }
- }
- let loader =
- options.extractCSS && type === 'styles'
- ? loaders[lang] || getCSSExtractLoader(lang)
- : loaders[lang]
- const injectString =
- type === 'script' && query.inject ? 'inject-loader!' : ''
- if (loader != null) {
- if (Array.isArray(loader)) {
- loader = stringifyLoaders(loader)
- } else if (typeof loader === 'object') {
- loader = stringifyLoaders([loader])
- }
- if (type === 'styles') {
- // add css modules
- loader = addCssModulesToLoader(loader, part, index)
- // inject rewriter before css loader for extractTextPlugin use cases
- if (rewriterInjectRE.test(loader)) {
- loader = loader.replace(
- rewriterInjectRE,
- (m, $1) => ensureBang($1) + styleCompiler
- )
- } else {
- loader = ensureBang(loader) + styleCompiler
- }
- }
- // if user defines custom loaders for html, add template compiler to it
- if (type === 'template' && loader.indexOf(defaultLoaders.html) < 0) {
- loader = defaultLoaders.html + '!' + loader
- }
- return injectString + ensureBang(loader)
- } else {
- // unknown lang, infer the loader to be used
- switch (type) {
- case 'template':
- return (
- defaultLoaders.html +
- '!' +
- templatePreprocessorPath +
- '?engine=' +
- lang +
- '!'
- )
- case 'styles':
- loader = addCssModulesToLoader(defaultLoaders.css, part, index)
- return loader + '!' + styleCompiler + ensureBang(ensureLoader(lang))
- case 'script':
- return injectString + ensureBang(ensureLoader(lang))
- default:
- loader = loaders[type]
- if (Array.isArray(loader)) {
- loader = stringifyLoaders(loader)
- }
- return ensureBang(loader + buildCustomBlockLoaderString(part.attrs))
- }
- }
- }
- // sass => sass-loader
- // sass-loader => sass-loader
- // sass?indentedSyntax!css => sass-loader?indentedSyntax!css-loader
- function ensureLoader (lang) {
- return lang
- .split('!')
- .map(loader =>
- loader.replace(
- /^([\w-]+)(\?.*)?/,
- (_, name, query) =>
- (/-loader$/.test(name) ? name : name + '-loader') + (query || '')
- )
- )
- .join('!')
- }
- function getSelectorString (type, index) {
- return (
- selectorPath +
- '?type=' +
- (type === 'script' || type === 'template' || type === 'styles'
- ? type
- : 'customBlocks') +
- '&index=' + index +
- '!'
- )
- }
- function ensureBang (loader) {
- if (loader.charAt(loader.length - 1) !== '!') {
- return loader + '!'
- } else {
- return loader
- }
- }
- function getCSSExtractLoader (lang) {
- let extractor
- const op = options.extractCSS
- // extractCSS option is an instance of ExtractTextPlugin
- if (typeof op.extract === 'function') {
- extractor = op
- } else {
- extractor = tryRequire('extract-text-webpack-plugin')
- if (!extractor) {
- throw new Error(
- '[vue-loader] extractCSS: true requires extract-text-webpack-plugin ' +
- 'as a peer dependency.'
- )
- }
- }
- const langLoader = lang ? ensureBang(ensureLoader(lang)) : ''
- return extractor.extract({
- use: 'css-loader' + cssLoaderOptions + '!' + langLoader,
- fallback: 'vue-style-loader'
- })
- }
- }
|