最近想解决个场景,在给 ve-charts
编写文档的时候,想做一个代码示例演示功能,在改动代码后可以直观的看到组件的变化。之前版本中文档是用的 docsify
,docsify 中自带了一个 vuep
。vuep 就是解决我需要的场景的。不过 vuep 版本比较老了。目前还不支持 vue3 组件。所以想独立开发一个运行代码示例的组件。
ES modules(ESM) 是 JavaScript 官方的标准化模块系统
在 ES6 之前,社区内已经有我们熟悉的模块加载方案 CommonJS
和 AMD
,前者用于服务器 即 Node.js
,而后者借助第三方库实现浏览器加载模块。
在前端工程里,应用范围比较广的还是 CommonJS
,从三个方面我们可以看出:
NPM
上的第三方模块,大部分都打包默认支持 CommonJS
Webpack
构建的前端资源是兼容 Node.js
环境的 CommonJS
ESM
代码 需要通过 Babel
转换为 CommonJS
好消息是,浏览器已经开始原生支持模块功能了,并且 Node.js
也在持续推进支持 ES Modules 模块功能
ESM 标准化还在道路上
自 Node.js v13.2.0
开始,有两种方式可以正确解析 ESM
标准的模块,在此之间还需要加上 --experimental-modules
才可以使用 ESM 模块。
.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>
标签中,声明这个脚本是一个模块。
这样就可以在脚本中使用 import
、export
语句了
<script type="module">
// include script here
</script>
现代前端工程开发环境中,会根据 package.json
来描述模块之间的依赖关系,安装模块后,所有模块会放在 node_modules
文件夹下。例如 package.json 中描述依赖了 lodash
:
{
"name": "test",
"version": "0.0.1",
"dependencies": {
"lodash": "^4.17.21"
}
}
类似的,在浏览器中处理模块之间的依赖关系,目前有一个新的提案 import-maps
通过声明 <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-...
为了让页面在各不同浏览器之间显示效果一致,CSS样式清除和重置是前端开发必需要...
Dreamweaver设计网页的时候,会遇到出现乱码的情况,该怎么预防Dreamweaver中出...
五一期间,花了 3 天时间,边学 Vue3 和 Vite2,边重构自己的项目,终于都用 Vue...
1.embed是非法的 embed标签是Netscape的私有标签,虽然embed标签应用广泛存在,...
为什么要用css动画替换js动画 导致JavaScript效率低的两大原因:操作DOM和使用页...
基本语法 text-overflow的使用需配合hight,over-flow:hidden;white-space:nowrap...
ipad上如何进行页面调试? 以前用ios5的时候ipad上还可以通过safari中的开发人员...
简介: 在这篇博文中,我们将简要解释需要考虑的领域,KEDA 如何使应用自动伸缩...
Dreamweaver CS3中,表单对象包括文本字段、单选按钮、复选框以及按钮等,该怎么...
关于 HTML5 ,写了不少文章,总觉得相关的高级 API 都得过一遍。系统的了解,站...