vue-loader处理vue文件

loader:"vue-loader" ,引导vue文件被vue-loader/lib/index.js处理
第一步:解析vue文件
const utils = require('@vue/component-compiler-utils')
utils.parse(.vue文件),返回一个json:

{
  "template": {
    "type": "template",
    "content": "
<div @click="setName">
    app
</div>
",
    "start": 10,
    "attrs": {},
    "end": 61
  },
  "script": {
    "type": "script",
    "content": "//
//
//
//
//
//

export default {
    name: "app",
    methods:{
        setName(){
            console.log('my name');
        }
    }
}
",
    "start": 82,
    "attrs": {},
    "end": 236
  },
  "styles": [
    {
      "type": "style",
      "content": "

















div{
     300px;
}
",
      "start": 261,
      "attrs": {
        "scoped": true
      },
      "scoped": true,
      "end": 299
    }
  ],
  "customBlocks": [],
  "errors": []
}

第二步:生成代码
在vue-loader/lib/index.js中有这样的一个代码:

let code = `
${templateImport}
${scriptImport}
${stylesCode}


/*   */
/* normalize component */ 
/* normalizer 函数式在chrome中执行,并不是在node中执行*/
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() + `
`
code += `
export default component.exports`

这个模板字符串最终形式如下:
这是一个模块 有import 和export ,和我们自己写的代码一样。

import { render, staticRenderFns } from "./app.vue?vue&type=template&id=6940f262&scoped=true&"
import script from "./app.vue?vue&type=script&lang=js&"
export * from "./app.vue?vue&type=script&lang=js&"
import style0 from "./app.vue?vue&type=style&index=0&id=6940f262&scoped=true&lang=css&"

import normalizer from "!../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js"
var component = normalizer(
  script,
  render,
  staticRenderFns,
  false,
  null,
  "6940f262",
  null
)
export default component.exports

这样的代码被webpack接收之后发现有import语句,重新调用vue-loader第二次对同一个vue文件的处理。

不过这次query上有了type类型,一进到vue-loader就被截胡了,直接根据type参数跳转到另外的loader处理。跳转代码如下:

if (incomingQuery.type) {
  return selectBlock(
    descriptor,
    loaderContext,
    incomingQuery,
    !!options.appendExtension
  )
}

另外loader是从哪里来的呢,这就要说到new VueLoaderPlugin()的用意了。
VueLoaderPlugin的用处是利用webpack钩子在旧rules上添加了其他loader。
如果type=script,最终的loader如下:

-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0
!../../../node_modules/babel-loader/lib/index.js
!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0
!../../../node_modules/vue-loader/lib/index.js??vue-loader-options
!./topnav.vue?vue&type=script&lang=js&"

如果type=template,最终的loader如下:

-!cache-loader?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"57422ecc-vue-loader-template"}
!../../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options
!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0
!../../../node_modules/vue-loader/lib/index.js??vue-loader-options
!./history.vue?vue&type=template&id=f89b51d2&scoped=true&"

如果type=style,最终的loader如下:

-!../../../../node_modules/vue-style-loader/index.js??ref--6-oneOf-1-0
!../../../../node_modules/css-loader/index.js??ref--6-oneOf-1-1
!../../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js
!../../../../node_modules/postcss-loader/src/index.js??ref--6-oneOf-1-2
!../../../../node_modules/cache-loader/dist/cjs.js??ref--0-0
!../../../../node_modules/vue-loader/lib/index.js??vue-loader-options
!./voice.vue?vue&type=style&index=0&id=b09c9dcc&scoped=true&lang=css&"

1.加⼊!前缀, 不使⽤config loader中的normal loader,例如require('!a-loader!./a.j s');
2.加⼊!!前缀,不使⽤config loader中的pre loader,normal loader,post loader,例如 require('!!a-loader!./a.js');
3.加⼊-!前缀,不使⽤config loader中的normal loader,pre loader,例如require('-!a -loader!./a.js');

上面的loader处理之后分别会生成如下的代码:
utils.compileTemplate处理template,返回值被 normalizer的 render, staticRenderFns接收

{
  ast: {
    type: 1,
    tag: 'div',
    attrsList: [ [Object] ],
    attrsMap: { '@click': 'setName', class: 'sdsd' },
    rawAttrsMap: {},
    parent: undefined,
    children: [ [Object], [Object] ],
    plain: false,
    staticClass: '"sdsd"',
    hasBindings: true,
    events: { click: [Object] },
    static: false,
    staticRoot: false
  },
  code: 'var render = function() {
' +
    '  var _vm = this
' +
    '  var _h = _vm.$createElement
' +
    '  var _c = _vm._self._c || _h
' +
    '  return _c("div", { staticClass: "sdsd", on: { click: _vm.setName } }, [
' +
    '    _vm._v("\n    app\n    "),
' +
    '    _c("img", { attrs: { src: "./a.png", alt: "" } })
' +
    '  ])
' +
    '}
' +
    'var staticRenderFns = []
' +
    'render._withStripped = true
',
  source: '
' +
    '<div @click="setName" class="sdsd">
' +
    '    app
' +
    '    <img src="./a.png" alt="">
' +
    '</div>
',
  tips: [],
  errors: []
}

const { code } = compiled
return code + `
export { render, staticRenderFns }`

utils.compiledStyle处理的结果如下, 返回值被 normalizer的 style0接收

const { code, map, errors } = {
  code: '
div[data-v-12]{
     300px;
}
p[data-v-12]{
    background: red;
}
',
  map: undefined,
  errors: [],
  rawResult: LazyResult {
    stringified: true,
    processed: true,
    result: Result {
      processor: [Processor],
      messages: [],
      root: [Root],
      opts: [Object],
      css: '
' +
        'div[data-v-12]{
' +
        '     300px;
' +
        '}
' +
        'p[data-v-12]{
' +
        '    background: red;
' +
        '}
',
      map: undefined,
      lastPlugin: [Function]
    }
  }
}
this.callback(null, code, map)

脚本部分没有添加额外的处理,直接返回了vue文件中的script部分,返回值被 normalizer的 script接收

最后回到normalizer,这个在浏览器执行的方法会接收到如下参数:
script: 就是在vue组件中export default中定义的所有内容
render: 由template生成的render函数,在上面能看到
staticRenderFns :由template生成,上面能看到
false:代表不是函数组件
null: 代表是否在页面插入组件行内样式,此处没有使用sytle-loader处理,结果为null
6940f262 : 组件中样式的scopeId

如此一个组件就编译完了。
中间的一些状态可以执行这个文件地址

原文地址:https://www.cnblogs.com/walkermag/p/13494794.html