咱们晓得,Vue举荐应用单文件组件(Single File Component,简称SFC),能够说SFC是Vue框架的特色。

然而,咱们在学习和练习的时候,如果想要用非常简单的形式在一个惯例的HTML文件,或者简略的Playground(比方JSBin或者CodePen)外面应用Vue的SFC形式,是不太容易的。

因而Vue官网提供了专门的SFC Playground来不便大家学习Vue。

不过,有没有方法不必SFC Playground,在本地单个HTML文件或者CodePen和JSBin这样的平台应用Vue-SFC呢?

方法是有的,我先放一个例子:

这是一个在CodePen中写的Vue组件

这是怎么做到的呢?

其实要分成三个步骤。

第一步 嵌入SFC内容
首先是要在一般的HTML文件中内联嵌入Vue-SFC组件。这里的麻烦之处在于,SFC中蕴含有HTML标签,而且还有<script>标签,因而,将它放在页面中内联,浏览器就会解析这些标签。只管咱们能够通过给<script>设置type来防止浏览器执行脚本,然而仍然不能阻止浏览器解析这些标签自身。

那有同学就动脑筋想了,咱们是否能够把SFC的内容放到一个不解析HTML内容的元素中,比方<textarea>标签。这样当然是能够的,然而也有些许麻烦,就是咱们要给这个textarea元素设置款式将它暗藏起来。

实际上,有个不容易想到的,更简略的标签,那就是<noscript>。失常状况下,浏览器不会解析<noscript>标签中的元素,而且又能够和其余标签一样,通过textConent获取其中的内容,所以咱们用<noscript>标签来搁置SFC的内容是再适合不过的了。

第二步 编译SFC组件
接着,咱们要编译SFC组件。这个能够通过官网提供的 vue/compile-sfc 模块来实现。

compile-sfc 如何应用,官网文档里写得非常简单,但这不障碍咱们通过钻研@vitejs/plugin-vue和webpack插件vue-loader来找到它的用法。用法其实也不简单,外围就是先parse源代码,成为descriptor对象,而后再一一编译script、template和styles。最终,再把这些模块拼起来,拼接的时候,如果不思考兼容性,最简略的形式是间接应用ES-Module来拼接。

以下是编译SFC的外围代码。

import * as compiler from '@vue/compiler-sfc';function generateID() {  return Math.random().toString(36).slice(2, 12);}function transformVueSFC(source, filename) {  const {descriptor, errors} = compiler.parse(source, {filename});  if(errors.length) throw new Error(errors.toString());  const id = generateID();  const hasScoped = descriptor.styles.some(e => e.scoped);  const scopeId = hasScoped ? `data-v-${id}` : undefined;  const templateOptions = {    id,    source: descriptor.template.content,    filename: descriptor.filename,    scoped: hasScoped,    slotted: descriptor.slotted,    compilerOptions: {      scopeId: hasScoped ? scopeId : undefined,      mode: 'module',    },  };  const script = compiler.compileScript(descriptor, {id, templateOptions, sourceMap:true});  if(script.map) {    script.content = `${script.content}\n//# sourceMappingURL=data:application/json;base64,${btoa(JSON.stringify(script.map))}`;  }  const template = compiler.compileTemplate({...templateOptions, sourceMap: true});  if(template.map) {    template.map.sources[0] = `${template.map.sources[0]}?template`;    template.code = `${template.code}\n//# sourceMappingURL=data:application/json;base64,${btoa(JSON.stringify(template.map))}`;  }  let cssInJS = '';  if(descriptor.styles) {    const styled = descriptor.styles.map((style) => {      return compiler.compileStyle({        id,        source: style.content,        scoped: style.scoped,        preprocessLang: style.lang,      });    });    if(styled.length) {      const cssCode = styled.map(s => s.code).join('\n');      cssInJS = `(function(){const el = document.createElement('style');el.innerHTML = \`${cssCode}\`;document.body.appendChild(el);}());`;    }  }  const moduleCode = `  import script from '${getBlobURL(script.content)}';  import {render} from '${getBlobURL(template.code)}';  script.render = render;  ${filename ? `script.__file = '${filename}'` : ''};  ${scopeId ? `script.__scopeId = '${scopeId}'` : ''};  ${cssInJS}  export default script;  `;  return moduleCode;}复制代码

那么最终,代码就编译进去了。

咱们能够用BlobURL来import模块,咱们还能够应用dataURL,来给编译好的代码设置soureMap,这样就不便了调试。

第三步 将编译好的代码利用于页面
这一步,有很多办法,其中一个比拟不便和优雅的办法依然是应用BlobURL,原理和我上一篇文章一样,咱们看一下代码。

function getBlobURL(jsCode) {  const blob = new Blob([jsCode], {type: 'text/javascript'});  const blobURL = URL.createObjectURL(blob);  return blobURL;}// https://github.com/WICG/import-mapsconst map = {  imports: {    vue: 'https://unpkg.com/vue@3/dist/vue.esm-browser.js',  },  scopes: { },};function makeComponent(component) {  const module = component.getAttribute('component');  let moduleName = module;  if(!/\.vue$/.test(module)) {    moduleName += '.vue';  }  component.setAttribute('module', moduleName);  if(module) {    return [getBlobURL(transformVueSFC(component.innerHTML, moduleName)), module];  }  return [];}const currentScript = document.currentScript || document.querySelector('script');function setup() {  const components = document.querySelectorAll('noscript[type="vue-sfc"]');  const importMap = {};  let mount = null;  [...components].forEach((component) => {    const [url, module] = makeComponent(component);    if(component.hasAttribute('mount')) {      if(mount) throw new Error('Not support multiple app entrances.');      mount = [module, component.getAttribute('mount')];    }    if(url) {      importMap[module] = url;    }  });  const importMapEl = document.querySelector('script[type="importmap"]');  if(importMapEl) {    // map = JSON.parse(mapEl.innerHTML);    throw new Error('Cannot setup after importmap is set. Use <script type="sfc-importmap"> instead.');  }  const externalMapEl = document.querySelector('script[type="sfc-importmap"]');  if(externalMapEl) {    const externalMap = JSON.parse(externalMapEl.textContent);    Object.assign(map.imports, externalMap.imports);    Object.assign(map.scopes, externalMap.scopes);  }  Object.assign(map.imports, importMap);  const mapEl = document.createElement('script');  mapEl.setAttribute('type', 'importmap');  mapEl.textContent = JSON.stringify(map);  currentScript.after(mapEl);  if(mount) {    const script = document.createElement('script');    script.setAttribute('type', 'module');    script.innerHTML = `      import {createApp} from 'vue';      import App from '${mount[0]}';      createApp(App).mount('${mount[1]}');        `;    document.body.appendChild(script);  }}setup();复制代码

这里不具体开展说了,代码也不是很简单,有趣味的同学能够钻研一下,有问题欢送在评论区探讨。

最终,实现的成果就是,咱们能够以上面示例代码的样子来间接内联的形式在一个独立的HTML页面中很不便地书写Vue-SFC了:

<noscript type="vue-sfc" component="MyComponent" mount="#app">  <script>    export default {      data() {        return {          count: 0        }      }    }  </script>  <template>    <button @click="count++">Count is: {{ count }}</button>  </template>  <style scoped>    button {      font-weight: bold;    }  </style></noscript><div id="app"></div><script src="https://unpkg.com/noscript-sfc/index.js"></script>

最初
如果你感觉此文对你有一丁点帮忙,点个赞。或者能够退出我的开发交换群:1025263163互相学习,咱们会有业余的技术答疑解惑

如果你感觉这篇文章对你有点用的话,麻烦请给咱们的开源我的项目点点star:http://github.crmeb.net/u/defu不胜感激 !

PHP学习手册:https://doc.crmeb.com
技术交换论坛:https://q.crmeb.com