最近想解決個場景,在給 ve-charts
編寫文檔的時候,想作一個代碼示例演示功能,在改動代碼後能夠直觀的看到組件的變化。以前版本中文檔是用的 docsify
,docsify 中自帶了一個 vuep
。vuep 就是解決我須要的場景的。不過 vuep 版本比較老了。目前還不支持 vue3 組件。因此想獨立開發一個運行代碼示例的組件。css
ES modules(ESM) 是 JavaScript 官方的標準化模塊系統
在 ES6 以前,社區內已經有咱們熟悉的模塊加載方案 CommonJS
和 AMD
,前者用於服務器 即 Node.js
,然後者藉助第三方庫實現瀏覽器加載模塊。html
在前端工程裏,應用範圍比較廣的仍是 CommonJS
,從三個方面咱們能夠看出:前端
NPM
上的第三方模塊,大部分都打包默認支持 CommonJS
Webpack
構建的前端資源是兼容 Node.js
環境的 CommonJS
ESM
代碼 須要經過 Babel
轉換爲 CommonJS
好消息是,瀏覽器已經開始原生支持模塊功能了,而且 Node.js
也在持續推動支持 ES Modules 模塊功能vue
ESM 標準化還在道路上node
自 Node.js v13.2.0
開始,有兩種方式能夠正確解析 ESM
標準的模塊,在此之間還須要加上 --experimental-modules
纔可使用 ESM 模塊。webpack
.mjs
結尾的文件.js
結尾的文件,且在 package.json
中聲明字段 type
爲 module
// esmA/index.mjs export default esmA // or // esmB/index.js export default esmB // esmB/package.json { "type": "module" }
.cjs
結尾的文件,將繼續解析爲 CommonJS
模塊現代瀏覽器已經原生支持加載 ES Modules
須要將 type="module"
放到 <script>
標籤中,聲明這個腳本是一個模塊。git
這樣就能夠在腳本中使用 import
、export
語句了github
<script type="module"> // include script here </script>
現代前端工程開發環境中,會根據 package.json
來描述模塊之間的依賴關係,安裝模塊後,全部模塊會放在 node_modules
文件夾下。例如 package.json 中描述依賴了 lodash
:web
{ "name": "test", "version": "0.0.1", "dependencies": { "lodash": "^4.17.21" } }
相似的,在瀏覽器中處理模塊之間的依賴關係,目前有一個新的提案 import-mapsnpm
經過聲明 <script>
標籤的屬性 type
爲 importmap
,來定義模塊的名稱和模塊地址之間的映射關係
例如:
<script type="importmap"> { "imports": { "lodash": "https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js" } } </script>
importmap
仍然處於提案階段,目前瀏覽器兼容狀況還很緩慢,可是將來會持續兼容。咱們可使用 es-module-shims 使瀏覽器兼容。
<!-- UNPKG --> <script async src="https://unpkg.com/es-module-shims@0.10.1/dist/es-module-shims.js"></script> <!-- 聲明依賴 --> <script type="importmap"> { "imports": { "app": "./src/app.js" } } </script> <!-- 使用模塊 --> <script type="module"> import 'app' </script>
Vue 生態裏 SFC 是 single-file components (單文件組件) 的縮寫
經過擴展名 .vue
來描述了一個 Vue 組件
功能特性:
代碼示例:
Vue 工程須要藉助 vue-loader
或者 rollup-plugin-vue
來將 SFC 文件編譯轉化爲可執行的 JS
Vue 2
vue-loader 依賴:
Vue 3
vue-loader@next 依賴:
Vite 2
@vitejs/plugin-vue 依賴:
編譯一個 Vue SFC 組件,須要分別編譯組件的 template
、script
和 style
API
+--------------------+ | | | script transform | +----->+ | | +--------------------+ | +--------------------+ | +--------------------+ | | | | | | facade transform +----------->+ template transform | | | | | | +--------------------+ | +--------------------+ | | +--------------------+ +----->+ | | style transform | | | +--------------------+
facade module,最終會編譯爲以下結構有 render
方法的組件僞代碼
// main script import script from '/project/foo.vue?vue&type=script' // template compiled to render function import { render } from '/project/foo.vue?vue&type=template&id=xxxxxx' // css import '/project/foo.vue?vue&type=style&index=0&id=xxxxxx' // attach render function to script script.render = render // attach additional metadata // some of these should be dev only script.__file = 'example.vue' script.__scopeId = 'xxxxxx' // additional tooling-specific HMR handling code // using __VUE_HMR_API__ global export default script
基於 @vue/compiler-sfc
構建的官方應用有 Vite
與 Vue SFC Playground
,前者運行在服務端,後者運行在瀏覽器端。
@vitejs/plugin-vue
提供 Vue 3 單文件組件支持@vue/compiler-sfc
@vue/compiler-sfc
SFC Playground
是基於 @vue/compiler-sfc/dist/compiler-sfc.esm-browser.js 編譯 ES Modules 的SFC Playground
中模塊的編譯源自 Vite
中對 SSR
的支持
Vite
SFC Playground
HelloWorld.vue
組件的區別?Vite
// /components/HelloWorld.vue import {defineComponent} from "/node_modules/.vite/vue.js?v=49d3ccd8"; const _sfc_main = defineComponent({ name: "HelloWorld", props: { msg: { type: String, required: true } } }); import { toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "/node_modules/.vite/vue.js?v=49d3ccd8" function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("h1", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)) } _sfc_main.render = _sfc_render _sfc_main.__file = "/Users/xiaoyunwei/GitHub/private/slides-vite-demo/src/components/HelloWorld.vue" export default _sfc_main
SFC Playground
// ./HelloWorld.vue const __sfc__ = { name: "HelloWorld", props: { msg: { type: String, required: true } } } import { toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue" function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("h1", null, _toDisplayString($props.msg), 1 /* TEXT */)) } __sfc__.render = render __sfc__.__file = "HelloWorld.vue" export default __sfc__
App.vue
組件的區別?Vite
// ./App.vue import {defineComponent} from "/node_modules/.vite/vue.js?v=49d3ccd8"; import HelloWorld from "/src/components/HelloWorld.vue"; const _sfc_main = defineComponent({ name: "App", components: { HelloWorld } }); import { resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock } from "/node_modules/.vite/vue.js?v=49d3ccd8" function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { const _component_HelloWorld = _resolveComponent("HelloWorld") return (_openBlock(), _createBlock(_component_HelloWorld, { msg: "Hello Vue 3 + TypeScript + Vite" })) } _sfc_main.render = _sfc_render _sfc_main.__file = "/Users/xiaoyunwei/GitHub/private/slides-vite-demo/src/App.vue" export default _sfc_main
SFC Playground
// ./App.vue import HelloWorld from './HelloWorld.vue' const __sfc__ = { name: 'App', components: { HelloWorld } } import { resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock } from "vue" function render(_ctx, _cache, $props, $setup, $data, $options) { const _component_HelloWorld = _resolveComponent("HelloWorld") return (_openBlock(), _createBlock(_component_HelloWorld, { msg: "Hello Vue SFC Playground" })) } __sfc__.render = render __sfc__.__file = "App.vue" export default __sfc__
能夠看出在編譯 SFC 時,底層邏輯基本是一致的。
借鑑 Vue SFC Playground ,造了兩個輪子 🎡
感興趣能夠點擊去 GitHub
關注
將 Vue SFC 編譯爲 ES modules.
.vue/.js
文件).vue-sfc2esm
內部實現了一個虛擬的 📁 文件系統,用來記錄文件和代碼的關係。vue-sfc2esm
會基於 @vue/compiler-sfc 將 SFC 代碼編譯成 ES Modules
。ES Modules
代碼能夠直接應用於現代瀏覽器中。編譯 App.vue
示例代碼:
<script type="module"> import { createApp as _createApp } from "vue" if (window.__app__) { window.__app__.unmount() document.getElementById('app').innerHTML = '' } document.getElementById('__sfc-styles').innerHTML = window.__css__ const app = window.__app__ = _createApp(__modules__["DefaultDemo.vue"].default) app.config.errorHandler = e => console.error(e) app.mount('#app') </script>
💡 使用 ES Modules 模塊前,須要提早引入 Vue
<script type="importmap"> { "imports": { "vue": "https://cdn.jsdelivr.net/npm/vue@next/dist/vue.esm-browser.js" } } </script>
vue-sfc-sandbox
是vue-sfc2esm
的上層應用,同時也基於@vue/compiler-sfc
開發,提供實時編輯 & 預覽 SFC 的沙盒組件。
🗳️ SFC 沙盒
.vue/.js
文件)✏️ 編輯器面板
👓 預覽面板
✨ 功能
SFC
文件 / Vue 3
組件CDN
Import Maps
,傳入 URL 須要爲 ESM💠 將來
React
組件💉 痛點
CommonJS
或 UMD
格式的包🖖 破局
CommonJS
To ES Modules
方案
相似 sfc-sandbox
,基於 Vue
技術棧能夠在線提供編輯器 + 演示的工具
雖然瀏覽器目前能夠加載使用 ES Modules
了,可是它仍是存在着一些上述提到的痛點中的問題的。
不過 2021 年的今天,已經涌現出了一批新的,能夠稱之爲下一代的前端構建工具,例如 esbuild
、snowpack
、vite
、wmr
等等。
能夠看看這篇文章《Comparing the New Generation of Build Tools》,從工具配置、開發服務、生產構建、構建SSR等方面分析比較了前端下一代的構建工具。
關注個人技術公號,一樣也能夠找到本文。
原文: https://transpile-vue-sfc-to-...