vue-loader 源码分析

先简介一下 webapck 中 loader 中的工作原理。
webpack 是只识别 js 代码的。但是我们写的代码中有很多类型的文件,比如:.vue.yaml.json.css.tsx 等等这些后缀文件,这些文件都是不被 webpack 所识别的,因此需要在webapck 打包前进行“翻译”。loader 的作用就是“翻译”的作用,将非 js 文件进行转义成 js 代码,然后 webpack 才会识别。

废话不多说,下面进入正题。
下载 vue-loader 源码:

yarn add vue-loader vue-template-compiler --dev

node_modules 中找到 vue-loader 文件夹下 lib/index.js

可以找到这块代码

  const descriptor = parse({
    
    
    source,
    compiler: options.compiler || loadTemplateCompiler(loaderContext),
    filename,
    sourceRoot,
    needMap: sourceMap
  })

通过上面代码的运行 parse 函数,传入文件的 source 源码就会将整个文件解析成

下面用一段测试代码:

<template>
  <div>
    这里是div标签
    <a>这是a标签</a>
  </div>
</template>

<script>
export default {
    
    
  data() {
    
    
    return {
    
    };
  },
};
</script>

输出descriptor:

descriptor {
    
    
  template: {
    
    
    type: 'template',
    content: '\n<div>\n  这里是div标签\n  <a>这是a标签</a>\n</div>\n',
    start: 10,
    attrs: {
    
    },
    end: 58
  },
  script: {
    
    
    type: 'script',
    content: '//\n' +
      '//\n' +
      '//\n' +
      '//\n' +
      '//\n' +
      '//\n' +
      '//\n' +
      '\n' +
      'export default {\n' +
      '  data() {\n' +
      '    return {};\n' +
      '  },\n' +
      '};\n',
    start: 79,
    attrs: {
    
    },
    end: 131
  },
  styles: [],
  customBlocks: [],
  errors: []
}

已经将 vue 文件中的 html 标签和 script 标签和 style 标签区分出来了。
那么原理是什么呢?

上面 parse 函数中传如 compiler 参数作为编译函数,loadTemplateCompiler 如下

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.`
      ))
    }
  }
}

可以看到引入了 vue-template-compiler 这个文件node_modules/vue-template-compiler/build.js
其中 parseHTML 这个函数起到了解析源码的作用

function parseHTML (html, options) {
    
    
...
	while (html) {
    
    
		if (!lastTag || !isPlainTextElement(lastTag)) {
    
    
			var textEnd = html.indexOf('<');
			if (textEnd === 0) {
    
    
				var endTagMatch = html.match(endTag);
				if (comment.test(html)) {
    
    
				...
				continue
				}
        		if (endTagMatch) {
    
    
				...
          		continue
        		}
        		if (startTagMatch) {
    
    
				...
				continue
				}
			}
		}
	}
}

原理是,html 是.vue 后缀文件的源码(string 类型),然后通过正则不断的去匹配标签,然后将不同标签类型都进行一次判断:

扫描二维码关注公众号,回复: 12028516 查看本文章

parseComponent这个函数中,进行判断每个标签是否是特殊标签:<template><script><style>,如果是的话就按照对应的 block 存起来。最后返回的结果就是上面的 descriptor 结果。

function parseComponent (
  content,
  options
) {
    
    
	...
	//这里是 start 函数,每次正则匹配到 < 开始的标签时就会判断是不是特殊标签,如果是的话就存到sfc 变量里。sfc是 single file component 单文件组件。
  function start (
    tag,
    attrs,
    unary,
    start,
    end
  ) {
    
    
    if (depth === 0) {
    
    
      currentBlock = {
    
    
        type: tag,
        content: '',
        start: end,
        attrs: attrs.reduce(function (cumulated, ref) {
    
    
          var name = ref.name;
          var value = ref.value;
          cumulated[name] = value || true;
          return cumulated
        }, {
    
    })
      };
      if (isSpecialTag(tag)) {
    
    
        checkAttrs(currentBlock, attrs);
        if (tag === 'style') {
    
    
          sfc.styles.push(currentBlock);
        } else {
    
    
          sfc[tag] = currentBlock;
        }
      } else {
    
     // custom blocks
        sfc.customBlocks.push(currentBlock);
      }
    }
  }
  //这里是匹配到结尾时,把开头和结尾中的内容放在 sfc对应 block 的 content 里面
  function end (tag, start) {
    
    
    if (depth === 1 && currentBlock) {
    
    
      currentBlock.end = start;
      var text = content.slice(currentBlock.start, currentBlock.end);
      if (options.deindent !== false) {
    
    
        text = deindent(text);
      }
      // pad content so that linters and pre-processors can output correct
      // line numbers in errors and warnings
      if (currentBlock.type !== 'template' && options.pad) {
    
    
        text = padContent(currentBlock, options.pad) + text;
      }
      currentBlock.content = text;
      currentBlock = null;
    }
    depth--;
  }
//这里调用了上面写的 parseHTML 函数
  parseHTML(content, {
    
    
    warn: warn,
    start: start,
    end: end,
    outputSourceRange: options.outputSourceRange
  });

  return sfc
}

猜你喜欢

转载自blog.csdn.net/qq_42535651/article/details/109155806