chore(sfc-playground): use standalone version of @vue/repl

This commit is contained in:
Evan You 2021-09-06 18:02:27 -04:00
parent 4178d5d7d9
commit d80d40a9c1
23 changed files with 47 additions and 2343 deletions

13
packages/global.d.ts vendored
View File

@ -1,3 +1,5 @@
/// <reference types="vite/client" />
// Global compile-time constants // Global compile-time constants
declare var __DEV__: boolean declare var __DEV__: boolean
declare var __TEST__: boolean declare var __TEST__: boolean
@ -25,10 +27,6 @@ declare namespace jest {
} }
declare module '*.vue' {} declare module '*.vue' {}
declare module '*?raw' {
const content: string
export default content
}
declare module 'file-saver' { declare module 'file-saver' {
export function saveAs(blob: any, name: any): void export function saveAs(blob: any, name: any): void
@ -39,3 +37,10 @@ declare module 'stream/web' {
const t: typeof TransformStream const t: typeof TransformStream
export { r as ReadableStream, t as TransformStream } export { r as ReadableStream, t as TransformStream }
} }
declare module '@vue/repl' {
import { ComponentOptions } from '@vue/runtime-core'
const Repl: ComponentOptions
const ReplStore: any
export { Repl, ReplStore }
}

View File

@ -8,14 +8,12 @@
"serve": "vite preview" "serve": "vite preview"
}, },
"devDependencies": { "devDependencies": {
"@types/codemirror": "^0.0.108",
"@vitejs/plugin-vue": "^1.2.0", "@vitejs/plugin-vue": "^1.2.0",
"codemirror": "^5.60.0",
"vite": "^2.4.0" "vite": "^2.4.0"
}, },
"dependencies": { "dependencies": {
"@vue/repl": "^0.1.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"jszip": "^3.6.0", "jszip": "^3.6.0"
"sucrase": "^3.19.0"
} }
} }

View File

@ -1,22 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import Header from './Header.vue' import Header from './Header.vue'
import SplitPane from './SplitPane.vue' import { Repl, ReplStore } from '@vue/repl'
import Editor from './editor/Editor.vue' import { watchEffect } from 'vue'
import Output from './output/Output.vue'
document.documentElement.style.setProperty('--vh', window.innerHeight + `px`)
const store = new ReplStore({
serializedState: location.hash.slice(1),
defaultVueRuntimeURL: import.meta.env.PROD
? `${location.origin}/vue.runtime.esm-browser.js`
: `${location.origin}/src/vue-dev-proxy`
})
// persist state
watchEffect(() => history.replaceState({}, '', store.serialize()))
</script> </script>
<template> <template>
<Header /> <Header :store="store" />
<div class="wrapper"> <Repl :store="store" :showCompileOutput="true" />
<SplitPane>
<template #left>
<Editor />
</template>
<template #right>
<Output />
</template>
</SplitPane>
</div>
</template> </template>
<style> <style>
@ -24,18 +26,13 @@ body {
font-size: 13px; font-size: 13px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
color: var(--base);
margin: 0; margin: 0;
background-color: #f8f8f8;
--base: #444; --base: #444;
--nav-height: 50px; --nav-height: 50px;
--font-code: 'Source Code Pro', monospace;
--color-branding: #3ca877;
--color-branding-dark: #416f9c;
} }
.wrapper { .vue-repl {
height: calc(100vh - var(--nav-height)); height: calc(var(--vh) - var(--nav-height));
} }
button { button {

View File

@ -1,8 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { downloadProject } from './download/download' import { downloadProject } from './download/download'
import { setVersion, resetVersion } from './transform'
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
// @ts-ignore
const { store } = defineProps(['store'])
const currentCommit = __COMMIT__ const currentCommit = __COMMIT__
const activeVersion = ref(`@${currentCommit}`) const activeVersion = ref(`@${currentCommit}`)
const publishedVersions = ref<string[]>() const publishedVersions = ref<string[]>()
@ -17,13 +19,13 @@ async function toggle() {
async function setVueVersion(v: string) { async function setVueVersion(v: string) {
activeVersion.value = `loading...` activeVersion.value = `loading...`
await setVersion(v) await store.setVueVersion(v)
activeVersion.value = `v${v}` activeVersion.value = `v${v}`
expanded.value = false expanded.value = false
} }
function resetVueVersion() { function resetVueVersion() {
resetVersion() store.resetVueVersion()
activeVersion.value = `@${currentCommit}` activeVersion.value = `@${currentCommit}`
expanded.value = false expanded.value = false
} }
@ -113,7 +115,7 @@ async function fetchVersions(): Promise<string[]> {
</g> </g>
</svg> </svg>
</button> </button>
<button class="download" @click="downloadProject"> <button class="download" @click="downloadProject(store)">
<svg width="1.7em" height="1.7em" viewBox="0 0 24 24"> <svg width="1.7em" height="1.7em" viewBox="0 0 24 24">
<g fill="#626262"> <g fill="#626262">
<rect x="4" y="18" width="16" height="2" rx="1" ry="1" /> <rect x="4" y="18" width="16" height="2" rx="1" ry="1" />
@ -241,7 +243,7 @@ h1 img {
} }
.versions a:hover { .versions a:hover {
color: var(--color-branding); color: #3ca877;
} }
.versions.expanded { .versions.expanded {

View File

@ -1,81 +0,0 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { CompilerError } from '@vue/compiler-sfc'
const props = defineProps(['err', 'warn'])
const dismissed = ref(false)
watch(
() => [props.err, props.warn],
() => {
dismissed.value = false
}
)
function formatMessage(err: string | Error): string {
if (typeof err === 'string') {
return err
} else {
let msg = err.message
const loc = (err as CompilerError).loc
if (loc && loc.start) {
msg = `(${loc.start.line}:${loc.start.column}) ` + msg
}
return msg
}
}
</script>
<template>
<Transition name="fade">
<pre
v-if="!dismissed && (err || warn)"
class="msg"
:class="err ? 'err' : 'warn'"
@click="dismissed = true"
>{{ formatMessage(err || warn) }}</pre
>
</Transition>
</template>
<style scoped>
.msg {
position: absolute;
bottom: 0;
left: 8px;
right: 8px;
z-index: 10;
padding: 14px 20px;
border: 2px solid transparent;
border-radius: 6px;
font-family: var(--font-code);
white-space: pre-wrap;
max-height: calc(100% - 50px);
overflow-y: scroll;
}
.msg.err {
color: red;
border-color: red;
background-color: #ffd7d7;
}
.msg.warn {
--color: rgb(105, 95, 27);
color: var(--color);
border-color: var(--color);
background-color: rgb(247, 240, 205);
}
.fade-enter-active,
.fade-leave-active {
transition: all 0.15s ease-out;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: translate(0, 10px);
}
</style>

View File

@ -1,87 +0,0 @@
<script setup lang="ts">
import { ref, reactive } from 'vue'
const container = ref()
const state = reactive({
dragging: false,
split: 50
})
function boundSplit() {
const { split } = state
return split < 20 ? 20 : split > 80 ? 80 : split
}
let startPosition = 0
let startSplit = 0
function dragStart(e: MouseEvent) {
state.dragging = true
startPosition = e.pageX
startSplit = boundSplit()
}
function dragMove(e: MouseEvent) {
if (state.dragging) {
const position = e.pageX
const totalSize = container.value.offsetWidth
const dp = position - startPosition
state.split = startSplit + ~~((dp / totalSize) * 100)
}
}
function dragEnd() {
state.dragging = false
}
</script>
<template>
<div
ref="container"
class="split-pane"
:class="{ dragging: state.dragging }"
@mousemove="dragMove"
@mouseup="dragEnd"
@mouseleave="dragEnd"
>
<div class="left" :style="{ width: boundSplit() + '%' }">
<slot name="left" />
<div class="dragger" @mousedown.prevent="dragStart" />
</div>
<div class="right" :style="{ width: 100 - boundSplit() + '%' }">
<slot name="right" />
</div>
</div>
</template>
<style scoped>
.split-pane {
display: flex;
height: 100%;
}
.split-pane.dragging {
cursor: ew-resize;
}
.dragging .left,
.dragging .right {
pointer-events: none;
}
.left,
.right {
position: relative;
height: 100%;
}
.left {
border-right: 1px solid #ccc;
}
.dragger {
position: absolute;
z-index: 99;
top: 0;
bottom: 0;
right: -5px;
width: 10px;
cursor: ew-resize;
}
</style>

View File

@ -1,83 +0,0 @@
<template>
<div class="editor" ref="el"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, watchEffect } from 'vue'
import { debounce } from '../utils'
import CodeMirror from './codemirror'
const el = ref()
const props = defineProps({
mode: {
type: String,
default: 'htmlmixed'
},
value: {
type: String,
default: ''
},
readonly: {
type: Boolean,
default: false
}
})
const emit = defineEmits<(e: 'change', value: string) => void>()
onMounted(() => {
const addonOptions = {
autoCloseBrackets: true,
autoCloseTags: true,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter']
}
const editor = CodeMirror(el.value!, {
value: '',
mode: props.mode,
readOnly: props.readonly,
tabSize: 2,
lineWrapping: true,
lineNumbers: true,
...addonOptions
})
editor.on('change', () => {
emit('change', editor.getValue())
})
watchEffect(() => {
editor.setValue(props.value)
})
watchEffect(() => {
editor.setOption('mode', props.mode)
})
window.addEventListener(
'resize',
debounce(() => {
editor.refresh()
})
)
setTimeout(() => {
editor.refresh()
}, 50)
})
</script>
<style>
.editor {
position: relative;
height: 100%;
width: 100%;
overflow: hidden;
}
.CodeMirror {
font-family: 'Source Code Pro', monospace;
height: 100%;
}
</style>

View File

@ -1,506 +0,0 @@
/* BASICS */
.CodeMirror {
--base: #545281;
--comment: hsl(210, 25%, 60%);
--keyword: #af4ab1;
--variable: #0055d1;
--function: #c25205;
--string: #2ba46d;
--number: #c25205;
--tags: #dd0000;
--qualifier: #ff6032;
--important: var(--string);
direction: ltr;
font-family: var(--font-code);
height: auto;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: transparent;
white-space: nowrap;
}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: var(--comment);
white-space: nowrap;
opacity: 0.6;
}
.CodeMirror-guttermarker {
color: black;
}
.CodeMirror-guttermarker-subtle {
color: #999;
}
/* FOLD GUTTER */
.CodeMirror-foldmarker {
color: #414141;
text-shadow: #ff9966 1px 1px 2px, #ff9966 -1px -1px 2px, #ff9966 1px -1px 2px,
#ff9966 -1px 1px 2px;
font-family: arial;
line-height: 0.3;
cursor: pointer;
}
.CodeMirror-foldgutter {
width: 0.7em;
}
.CodeMirror-foldgutter-open,
.CodeMirror-foldgutter-folded {
cursor: pointer;
}
.CodeMirror-foldgutter-open:after,
.CodeMirror-foldgutter-folded:after {
content: '>';
font-size: 0.8em;
opacity: 0.8;
transition: transform 0.2s;
display: inline-block;
top: -0.1em;
position: relative;
transform: rotate(90deg);
}
.CodeMirror-foldgutter-folded:after {
transform: none;
}
/* CURSOR */
.CodeMirror-cursor {
border-left: 1px solid black;
border-right: none;
width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0 !important;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-fat-cursor-mark {
background-color: rgba(20, 255, 20, 0.5);
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
background-color: #7e7;
}
@-moz-keyframes blink {
0% {
}
50% {
background-color: transparent;
}
100% {
}
}
@-webkit-keyframes blink {
0% {
}
50% {
background-color: transparent;
}
100% {
}
}
@keyframes blink {
0% {
}
50% {
background-color: transparent;
}
100% {
}
}
.cm-tab {
display: inline-block;
text-decoration: inherit;
}
.CodeMirror-rulers {
position: absolute;
left: 0;
right: 0;
top: -50px;
bottom: -20px;
overflow: hidden;
}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
top: 0;
bottom: 0;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default.CodeMirror {
background-color: transparent;
}
.cm-s-default .cm-header {
color: blue;
}
.cm-s-default .cm-quote {
color: #090;
}
.cm-negative {
color: #d44;
}
.cm-positive {
color: #292;
}
.cm-header,
.cm-strong {
font-weight: bold;
}
.cm-em {
font-style: italic;
}
.cm-link {
text-decoration: underline;
}
.cm-strikethrough {
text-decoration: line-through;
}
.cm-s-default .cm-atom,
.cm-s-default .cm-def,
.cm-s-default .cm-property,
.cm-s-default .cm-variable-2,
.cm-s-default .cm-variable-3,
.cm-s-default .cm-punctuation {
color: var(--base);
}
.cm-s-default .cm-hr,
.cm-s-default .cm-comment {
color: var(--comment);
}
.cm-s-default .cm-attribute,
.cm-s-default .cm-keyword {
color: var(--keyword);
}
.cm-s-default .cm-variable {
color: var(--variable);
}
.cm-s-default .cm-bracket,
.cm-s-default .cm-tag {
color: var(--tags);
}
.cm-s-default .cm-number {
color: var(--number);
}
.cm-s-default .cm-string,
.cm-s-default .cm-string-2 {
color: var(--string);
}
.cm-s-default .cm-type {
color: #085;
}
.cm-s-default .cm-meta {
color: #555;
}
.cm-s-default .cm-qualifier {
color: var(--qualifier);
}
.cm-s-default .cm-builtin {
color: #7539ff;
}
.cm-s-default .cm-link {
color: var(--flash);
}
.cm-s-default .cm-error {
color: #ff008c;
}
.cm-invalidchar {
color: #ff008c;
}
.CodeMirror-composing {
border-bottom: 2px solid;
}
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {
color: #0b0;
}
div.CodeMirror span.CodeMirror-nonmatchingbracket {
color: #a22;
}
.CodeMirror-matchingtag {
background: rgba(255, 150, 0, 0.3);
}
.CodeMirror-activeline-background {
background: #e8f2ff;
}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px;
margin-right: -30px;
padding-bottom: 30px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar,
.CodeMirror-hscrollbar,
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0;
top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0;
left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0;
bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0;
bottom: 0;
}
.CodeMirror-gutters {
position: absolute;
left: 0;
top: 0;
min-height: 100%;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
vertical-align: top;
margin-bottom: -30px;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0;
bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper ::selection {
background-color: transparent;
}
.CodeMirror-gutter-wrapper ::-moz-selection {
background-color: transparent;
}
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0;
-webkit-border-radius: 0;
border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: contextual;
font-variant-ligatures: contextual;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
padding: 0.1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-rtl pre {
direction: rtl;
}
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-cursor {
position: absolute;
pointer-events: none;
}
.CodeMirror-measure pre {
position: static;
}
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected {
background: #d9d9d9;
}
.CodeMirror-focused .CodeMirror-selected {
background: #d7d4f0;
}
.CodeMirror-crosshair {
cursor: crosshair;
}
.CodeMirror-line::selection,
.CodeMirror-line > span::selection,
.CodeMirror-line > span > span::selection {
background: #d7d4f0;
}
.CodeMirror-line::-moz-selection,
.CodeMirror-line > span::-moz-selection,
.CodeMirror-line > span > span::-moz-selection {
background: #d7d4f0;
}
.cm-searching {
background-color: #ffa;
background-color: rgba(255, 255, 0, 0.4);
}
/* Used to force a border model for a node */
.cm-force-border {
padding-right: 0.1px;
}
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after {
content: '';
}
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext {
background: none;
}

View File

@ -1,19 +0,0 @@
import CodeMirror from 'codemirror'
import './codemirror.css'
// modes
import 'codemirror/mode/javascript/javascript.js'
import 'codemirror/mode/css/css.js'
import 'codemirror/mode/htmlmixed/htmlmixed.js'
// addons
import 'codemirror/addon/edit/closebrackets.js'
import 'codemirror/addon/edit/closetag.js'
import 'codemirror/addon/comment/comment.js'
import 'codemirror/addon/fold/foldcode.js'
import 'codemirror/addon/fold/foldgutter.js'
import 'codemirror/addon/fold/brace-fold.js'
import 'codemirror/addon/fold/indent-fold.js'
import 'codemirror/addon/fold/comment-fold.js'
export default CodeMirror

View File

@ -1,4 +1,3 @@
import { exportFiles } from '../store'
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import index from './template/index.html?raw' import index from './template/index.html?raw'
@ -7,7 +6,7 @@ import pkg from './template/package.json?raw'
import config from './template/vite.config.js?raw' import config from './template/vite.config.js?raw'
import readme from './template/README.md?raw' import readme from './template/README.md?raw'
export async function downloadProject() { export async function downloadProject(store: any) {
const { default: JSZip } = await import('jszip') const { default: JSZip } = await import('jszip')
const zip = new JSZip() const zip = new JSZip()
@ -21,7 +20,7 @@ export async function downloadProject() {
const src = zip.folder('src')! const src = zip.folder('src')!
src.file('main.js', main) src.file('main.js', main)
const files = exportFiles() const files = store.getFiles()
for (const file in files) { for (const file in files) {
src.file(file, files[file]) src.file(file, files[file])
} }

View File

@ -1,40 +0,0 @@
<script setup lang="ts">
import FileSelector from './FileSelector.vue'
import CodeMirror from '../codemirror/CodeMirror.vue'
import Message from '../Message.vue'
import { store } from '../store'
import { debounce } from '../utils'
import { ref, watch, computed } from 'vue'
const onChange = debounce((code: string) => {
store.activeFile.code = code
}, 250)
const activeCode = ref(store.activeFile.code)
const activeMode = computed(() =>
store.activeFilename.endsWith('.vue') ? 'htmlmixed' : 'javascript'
)
watch(
() => store.activeFilename,
() => {
activeCode.value = store.activeFile.code
}
)
</script>
<template>
<FileSelector />
<div class="editor-container">
<CodeMirror @change="onChange" :value="activeCode" :mode="activeMode" />
<Message :err="store.errors[0]" />
</div>
</template>
<style scoped>
.editor-container {
height: calc(100% - 35px);
overflow: hidden;
position: relative;
}
</style>

View File

@ -1,119 +0,0 @@
<script setup lang="ts">
import { store, addFile, deleteFile, setActive } from '../store'
import { ref, VNode } from 'vue'
const pending = ref(false)
const pendingFilename = ref('Comp.vue')
function startAddFile() {
pending.value = true
}
function cancelAddFile() {
pending.value = false
}
function focus({ el }: VNode) {
;(el as HTMLInputElement).focus()
}
function doneAddFile() {
const filename = pendingFilename.value
if (!/\.(vue|js|ts)$/.test(filename) && filename !== 'import-map.json') {
store.errors = [
`Playground only supports *.vue, *.js, *.ts files or import-map.json.`
]
return
}
if (filename in store.files) {
store.errors = [`File "${filename}" already exists.`]
return
}
store.errors = []
pending.value = false
addFile(filename)
pendingFilename.value = 'Comp.vue'
}
</script>
<template>
<div class="file-selector">
<div
v-for="(file, i) in Object.keys(store.files)"
class="file"
:class="{ active: store.activeFilename === file }"
@click="setActive(file)"
>
<span class="label">{{ file }}</span>
<span v-if="i > 0" class="remove" @click.stop="deleteFile(file)">
<svg width="12" height="12" viewBox="0 0 24 24" class="svelte-cghqrp">
<line stroke="#999" x1="18" y1="6" x2="6" y2="18"></line>
<line stroke="#999" x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</span>
</div>
<div v-if="pending" class="file">
<input
v-model="pendingFilename"
spellcheck="false"
@keyup.enter="doneAddFile"
@keyup.esc="cancelAddFile"
@vnodeMounted="focus"
/>
</div>
<button class="add" @click="startAddFile">+</button>
</div>
</template>
<style scoped>
.file-selector {
box-sizing: border-box;
border-bottom: 1px solid #ddd;
background-color: white;
}
.file {
display: inline-block;
font-size: 13px;
font-family: var(--font-code);
cursor: pointer;
color: #999;
box-sizing: border-box;
}
.file.active {
color: var(--color-branding);
border-bottom: 3px solid var(--color-branding);
cursor: text;
}
.file span {
display: inline-block;
padding: 8px 10px 6px;
}
.file input {
width: 80px;
outline: none;
border: 1px solid #ccc;
border-radius: 3px;
padding: 4px 6px;
margin-left: 6px;
}
.file .remove {
display: inline-block;
vertical-align: middle;
line-height: 12px;
cursor: pointer;
padding-left: 0;
}
.add {
font-size: 20px;
font-family: var(--font-code);
color: #999;
vertical-align: middle;
margin-left: 6px;
}
.add:hover {
color: var(--color-branding);
}
</style>

View File

@ -1,4 +1,6 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import '@vue/repl/style.css'
;(window as any).process = { env: {} }
createApp(App).mount('#app') createApp(App).mount('#app')

View File

@ -1,59 +0,0 @@
<script setup lang="ts">
import Preview from './Preview.vue'
import CodeMirror from '../codemirror/CodeMirror.vue'
import { store } from '../store'
import { ref } from 'vue'
const modes = ['preview', 'js', 'css', 'ssr'] as const
type Modes = typeof modes[number]
const mode = ref<Modes>('preview')
</script>
<template>
<div class="tab-buttons">
<button
v-for="m of modes"
:class="{ active: mode === m }"
@click="mode = m"
>
{{ m }}
</button>
</div>
<div class="output-container">
<Preview v-if="mode === 'preview'" />
<CodeMirror
v-else
readonly
:mode="mode === 'css' ? 'css' : 'javascript'"
:value="store.activeFile.compiled[mode]"
/>
</div>
</template>
<style scoped>
.output-container {
height: calc(100% - 35px);
overflow: hidden;
position: relative;
}
.tab-buttons {
box-sizing: border-box;
border-bottom: 1px solid #ddd;
background-color: white;
}
.tab-buttons button {
font-size: 13px;
font-family: var(--font-code);
padding: 8px 16px 6px;
text-transform: uppercase;
color: #999;
box-sizing: border-box;
}
button.active {
color: var(--color-branding-dark);
border-bottom: 3px solid var(--color-branding-dark);
}
</style>

View File

@ -1,216 +0,0 @@
<script setup lang="ts">
import Message from '../Message.vue'
import {
ref,
onMounted,
onUnmounted,
watchEffect,
watch,
WatchStopHandle
} from 'vue'
import srcdoc from './srcdoc.html?raw'
import { PreviewProxy } from './PreviewProxy'
import { MAIN_FILE, vueRuntimeUrl } from '../transform'
import { compileModulesForPreview } from './moduleCompiler'
import { store } from '../store'
const container = ref()
const runtimeError = ref()
const runtimeWarning = ref()
let sandbox: HTMLIFrameElement
let proxy: PreviewProxy
let stopUpdateWatcher: WatchStopHandle
// create sandbox on mount
onMounted(createSandbox)
// reset sandbox when import map changes
watch(
() => store.importMap,
(importMap, prev) => {
if (!importMap) {
if (prev) {
// import-map.json deleted
createSandbox()
}
return
}
try {
const map = JSON.parse(importMap)
if (!map.imports) {
store.errors = [`import-map.json is missing "imports" field.`]
return
}
if (map.imports.vue) {
store.errors = [
'Select Vue versions using the top-right dropdown.\n' +
'Specifying it in the import map has no effect.'
]
}
createSandbox()
} catch (e: any) {
store.errors = [e as Error]
return
}
}
)
// reset sandbox when version changes
watch(vueRuntimeUrl, createSandbox)
onUnmounted(() => {
proxy.destroy()
stopUpdateWatcher && stopUpdateWatcher()
})
function createSandbox() {
if (sandbox) {
// clear prev sandbox
proxy.destroy()
stopUpdateWatcher()
container.value.removeChild(sandbox)
}
sandbox = document.createElement('iframe')
sandbox.setAttribute(
'sandbox',
[
'allow-forms',
'allow-modals',
'allow-pointer-lock',
'allow-popups',
'allow-same-origin',
'allow-scripts',
'allow-top-navigation-by-user-activation'
].join(' ')
)
let importMap: Record<string, any>
try {
importMap = JSON.parse(store.importMap || `{}`)
} catch (e: any) {
store.errors = [`Syntax error in import-map.json: ${(e as Error).message}`]
return
}
if (!importMap.imports) {
importMap.imports = {}
}
importMap.imports.vue = vueRuntimeUrl.value
const sandboxSrc = srcdoc.replace(
/<!--IMPORT_MAP-->/,
JSON.stringify(importMap)
)
sandbox.srcdoc = sandboxSrc
container.value.appendChild(sandbox)
proxy = new PreviewProxy(sandbox, {
on_fetch_progress: (progress: any) => {
// pending_imports = progress;
},
on_error: (event: any) => {
const msg =
event.value instanceof Error ? event.value.message : event.value
if (
msg.includes('Failed to resolve module specifier') ||
msg.includes('Error resolving module specifier')
) {
runtimeError.value =
msg.replace(/\. Relative references must.*$/, '') +
`.\nTip: add an "import-map.json" file to specify import paths for dependencies.`
} else {
runtimeError.value = event.value
}
},
on_unhandled_rejection: (event: any) => {
let error = event.value
if (typeof error === 'string') {
error = { message: error }
}
runtimeError.value = 'Uncaught (in promise): ' + error.message
},
on_console: (log: any) => {
if (log.duplicate) {
return
}
if (log.level === 'error') {
if (log.args[0] instanceof Error) {
runtimeError.value = log.args[0].message
} else {
runtimeError.value = log.args[0]
}
} else if (log.level === 'warn') {
if (log.args[0].toString().includes('[Vue warn]')) {
runtimeWarning.value = log.args
.join('')
.replace(/\[Vue warn\]:/, '')
.trim()
}
}
},
on_console_group: (action: any) => {
// group_logs(action.label, false);
},
on_console_group_end: () => {
// ungroup_logs();
},
on_console_group_collapsed: (action: any) => {
// group_logs(action.label, true);
}
})
sandbox.addEventListener('load', () => {
proxy.handle_links()
stopUpdateWatcher = watchEffect(updatePreview)
})
}
async function updatePreview() {
// @ts-ignore
if (import.meta.env.PROD) {
console.clear()
}
runtimeError.value = null
runtimeWarning.value = null
try {
const modules = compileModulesForPreview()
console.log(`successfully compiled ${modules.length} modules.`)
// reset modules
await proxy.eval([
`window.__modules__ = {};window.__css__ = ''`,
...modules,
`
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__["${MAIN_FILE}"].default)
app.config.errorHandler = e => console.error(e)
app.mount('#app')`.trim()
])
} catch (e: any) {
runtimeError.value = (e as Error).message
}
}
</script>
<template>
<div class="preview-container" ref="container"></div>
<Message :err="runtimeError" />
<Message v-if="!runtimeError" :warn="runtimeWarning" />
</template>
<style>
.preview-container,
iframe {
width: 100%;
height: 100%;
border: none;
background-color: #fff;
}
</style>

View File

@ -1,96 +0,0 @@
// ReplProxy and srcdoc implementation from Svelte REPL
// MIT License https://github.com/sveltejs/svelte-repl/blob/master/LICENSE
let uid = 1
export class PreviewProxy {
iframe: HTMLIFrameElement
handlers: Record<string, Function>
pending_cmds: Map<
number,
{ resolve: (value: unknown) => void; reject: (reason?: any) => void }
>
handle_event: (e: any) => void
constructor(iframe: HTMLIFrameElement, handlers: Record<string, Function>) {
this.iframe = iframe
this.handlers = handlers
this.pending_cmds = new Map()
this.handle_event = e => this.handle_repl_message(e)
window.addEventListener('message', this.handle_event, false)
}
destroy() {
window.removeEventListener('message', this.handle_event)
}
iframe_command(action: string, args: any) {
return new Promise((resolve, reject) => {
const cmd_id = uid++
this.pending_cmds.set(cmd_id, { resolve, reject })
this.iframe.contentWindow!.postMessage({ action, cmd_id, args }, '*')
})
}
handle_command_message(cmd_data: any) {
let action = cmd_data.action
let id = cmd_data.cmd_id
let handler = this.pending_cmds.get(id)
if (handler) {
this.pending_cmds.delete(id)
if (action === 'cmd_error') {
let { message, stack } = cmd_data
let e = new Error(message)
e.stack = stack
handler.reject(e)
}
if (action === 'cmd_ok') {
handler.resolve(cmd_data.args)
}
} else {
console.error('command not found', id, cmd_data, [
...this.pending_cmds.keys()
])
}
}
handle_repl_message(event: any) {
if (event.source !== this.iframe.contentWindow) return
const { action, args } = event.data
switch (action) {
case 'cmd_error':
case 'cmd_ok':
return this.handle_command_message(event.data)
case 'fetch_progress':
return this.handlers.on_fetch_progress(args.remaining)
case 'error':
return this.handlers.on_error(event.data)
case 'unhandledrejection':
return this.handlers.on_unhandled_rejection(event.data)
case 'console':
return this.handlers.on_console(event.data)
case 'console_group':
return this.handlers.on_console_group(event.data)
case 'console_group_collapsed':
return this.handlers.on_console_group_collapsed(event.data)
case 'console_group_end':
return this.handlers.on_console_group_end(event.data)
}
}
eval(script: string | string[]) {
return this.iframe_command('eval', { script })
}
handle_links() {
return this.iframe_command('catch_clicks', {})
}
}

View File

@ -1,235 +0,0 @@
import { store, File } from '../store'
import { MAIN_FILE } from '../transform'
import {
babelParse,
MagicString,
walk,
walkIdentifiers,
extractIdentifiers,
isInDestructureAssignment,
isStaticProperty
} from '@vue/compiler-sfc'
import { babelParserDefaultPlugins } from '@vue/shared'
import { ExportSpecifier, Identifier, Node } from '@babel/types'
export function compileModulesForPreview() {
return processFile(store.files[MAIN_FILE]).reverse()
}
const modulesKey = `__modules__`
const exportKey = `__export__`
const dynamicImportKey = `__dynamic_import__`
const moduleKey = `__module__`
// similar logic with Vite's SSR transform, except this is targeting the browser
function processFile(file: File, seen = new Set<File>()) {
if (seen.has(file)) {
return []
}
seen.add(file)
const { js, css } = file.compiled
const s = new MagicString(js)
const ast = babelParse(js, {
sourceFilename: file.filename,
sourceType: 'module',
plugins: [...babelParserDefaultPlugins]
}).program.body
const idToImportMap = new Map<string, string>()
const declaredConst = new Set<string>()
const importedFiles = new Set<string>()
const importToIdMap = new Map<string, string>()
function defineImport(node: Node, source: string) {
const filename = source.replace(/^\.\/+/, '')
if (!(filename in store.files)) {
throw new Error(`File "${filename}" does not exist.`)
}
if (importedFiles.has(filename)) {
return importToIdMap.get(filename)!
}
importedFiles.add(filename)
const id = `__import_${importedFiles.size}__`
importToIdMap.set(filename, id)
s.appendLeft(
node.start!,
`const ${id} = ${modulesKey}[${JSON.stringify(filename)}]\n`
)
return id
}
function defineExport(name: string, local = name) {
s.append(`\n${exportKey}(${moduleKey}, "${name}", () => ${local})`)
}
// 0. instantiate module
s.prepend(
`const ${moduleKey} = __modules__[${JSON.stringify(
file.filename
)}] = { [Symbol.toStringTag]: "Module" }\n\n`
)
// 1. check all import statements and record id -> importName map
for (const node of ast) {
// import foo from 'foo' --> foo -> __import_foo__.default
// import { baz } from 'foo' --> baz -> __import_foo__.baz
// import * as ok from 'foo' --> ok -> __import_foo__
if (node.type === 'ImportDeclaration') {
const source = node.source.value
if (source.startsWith('./')) {
const importId = defineImport(node, node.source.value)
for (const spec of node.specifiers) {
if (spec.type === 'ImportSpecifier') {
idToImportMap.set(
spec.local.name,
`${importId}.${(spec.imported as Identifier).name}`
)
} else if (spec.type === 'ImportDefaultSpecifier') {
idToImportMap.set(spec.local.name, `${importId}.default`)
} else {
// namespace specifier
idToImportMap.set(spec.local.name, importId)
}
}
s.remove(node.start!, node.end!)
}
}
}
// 2. check all export statements and define exports
for (const node of ast) {
// named exports
if (node.type === 'ExportNamedDeclaration') {
if (node.declaration) {
if (
node.declaration.type === 'FunctionDeclaration' ||
node.declaration.type === 'ClassDeclaration'
) {
// export function foo() {}
defineExport(node.declaration.id!.name)
} else if (node.declaration.type === 'VariableDeclaration') {
// export const foo = 1, bar = 2
for (const decl of node.declaration.declarations) {
for (const id of extractIdentifiers(decl.id)) {
defineExport(id.name)
}
}
}
s.remove(node.start!, node.declaration.start!)
} else if (node.source) {
// export { foo, bar } from './foo'
const importId = defineImport(node, node.source.value)
for (const spec of node.specifiers) {
defineExport(
(spec.exported as Identifier).name,
`${importId}.${(spec as ExportSpecifier).local.name}`
)
}
s.remove(node.start!, node.end!)
} else {
// export { foo, bar }
for (const spec of node.specifiers) {
const local = (spec as ExportSpecifier).local.name
const binding = idToImportMap.get(local)
defineExport((spec.exported as Identifier).name, binding || local)
}
s.remove(node.start!, node.end!)
}
}
// default export
if (node.type === 'ExportDefaultDeclaration') {
if ('id' in node.declaration && node.declaration.id) {
// named hoistable/class exports
// export default function foo() {}
// export default class A {}
const { name } = node.declaration.id
s.remove(node.start!, node.start! + 15)
s.append(`\n${exportKey}(${moduleKey}, "default", () => ${name})`)
} else {
// anonymous default exports
s.overwrite(node.start!, node.start! + 14, `${moduleKey}.default =`)
}
}
// export * from './foo'
if (node.type === 'ExportAllDeclaration') {
const importId = defineImport(node, node.source.value)
s.remove(node.start!, node.end!)
s.append(`\nfor (const key in ${importId}) {
if (key !== 'default') {
${exportKey}(${moduleKey}, key, () => ${importId}[key])
}
}`)
}
}
// 3. convert references to import bindings
for (const node of ast) {
if (node.type === 'ImportDeclaration') continue
walkIdentifiers(node, (id, parent, parentStack) => {
const binding = idToImportMap.get(id.name)
if (!binding) {
return
}
if (isStaticProperty(parent) && parent.shorthand) {
// let binding used in a property shorthand
// { foo } -> { foo: __import_x__.foo }
// skip for destructure patterns
if (
!(parent as any).inPattern ||
isInDestructureAssignment(parent, parentStack)
) {
s.appendLeft(id.end!, `: ${binding}`)
}
} else if (
parent.type === 'ClassDeclaration' &&
id === parent.superClass
) {
if (!declaredConst.has(id.name)) {
declaredConst.add(id.name)
// locate the top-most node containing the class declaration
const topNode = parentStack[1]
s.prependRight(topNode.start!, `const ${id.name} = ${binding};\n`)
}
} else {
s.overwrite(id.start!, id.end!, binding)
}
})
}
// 4. convert dynamic imports
;(walk as any)(ast, {
enter(node: Node, parent: Node) {
if (node.type === 'Import' && parent.type === 'CallExpression') {
const arg = parent.arguments[0]
if (arg.type === 'StringLiteral' && arg.value.startsWith('./')) {
s.overwrite(node.start!, node.start! + 6, dynamicImportKey)
s.overwrite(
arg.start!,
arg.end!,
JSON.stringify(arg.value.replace(/^\.\/+/, ''))
)
}
}
}
})
// append CSS injection code
if (css) {
s.append(`\nwindow.__css__ += ${JSON.stringify(css)}`)
}
const processed = [s.toString()]
if (importedFiles.size) {
for (const imported of importedFiles) {
processed.push(...processFile(store.files[imported], seen))
}
}
// return a list of files to further process
return processed
}

View File

@ -1,259 +0,0 @@
<!doctype html>
<html>
<head>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}
</style>
<style id="__sfc-styles"></style>
<script>
(() => {
let scriptEls = []
window.__modules__ = {}
window.__export__ = (mod, key, get) => {
Object.defineProperty(mod, key, {
enumerable: true,
configurable: true,
get
})
}
window.__dynamic_import__ = key => {
return Promise.resolve(window.__modules__[key])
}
async function handle_message(ev) {
let { action, cmd_id } = ev.data;
const send_message = (payload) => parent.postMessage( { ...payload }, ev.origin);
const send_reply = (payload) => send_message({ ...payload, cmd_id });
const send_ok = () => send_reply({ action: 'cmd_ok' });
const send_error = (message, stack) => send_reply({ action: 'cmd_error', message, stack });
if (action === 'eval') {
try {
if (scriptEls.length) {
scriptEls.forEach(el => {
document.head.removeChild(el)
})
scriptEls.length = 0
}
let { script: scripts } = ev.data.args
if (typeof scripts === 'string') scripts = [scripts]
for (const script of scripts) {
const scriptEl = document.createElement('script')
scriptEl.setAttribute('type', 'module')
// send ok in the module script to ensure sequential evaluation
// of multiple proxy.eval() calls
const done = new Promise((resolve) => {
window.__next__ = resolve
})
scriptEl.innerHTML = script + `\nwindow.__next__()`
document.head.appendChild(scriptEl)
scriptEl.onrror = err => send_error(err.message, err.stack)
scriptEls.push(scriptEl)
await done
}
window.__next__ = undefined
send_ok()
} catch (e) {
send_error(e.message, e.stack);
}
}
if (action === 'catch_clicks') {
try {
const top_origin = ev.origin;
document.body.addEventListener('click', event => {
if (event.which !== 1) return;
if (event.metaKey || event.ctrlKey || event.shiftKey) return;
if (event.defaultPrevented) return;
// ensure target is a link
let el = event.target;
while (el && el.nodeName !== 'A') el = el.parentNode;
if (!el || el.nodeName !== 'A') return;
if (el.hasAttribute('download') || el.getAttribute('rel') === 'external' || el.target) return;
event.preventDefault();
if (el.href.startsWith(top_origin)) {
const url = new URL(el.href);
if (url.hash[0] === '#') {
window.location.hash = url.hash;
return;
}
}
window.open(el.href, '_blank');
});
send_ok();
} catch(e) {
send_error(e.message, e.stack);
}
}
}
window.addEventListener('message', handle_message, false);
window.onerror = function (msg, url, lineNo, columnNo, error) {
if (msg.includes('module specifier “vue”')) {
// firefox only error, ignore
return false
}
try {
parent.postMessage({ action: 'error', value: error }, '*');
} catch (e) {
parent.postMessage({ action: 'error', value: msg }, '*');
}
}
window.addEventListener("unhandledrejection", event => {
if (event.reason.message.includes('Cross-origin')) {
event.preventDefault()
return
}
try {
parent.postMessage({ action: 'unhandledrejection', value: event.reason }, '*');
} catch (e) {
parent.postMessage({ action: 'unhandledrejection', value: event.reason.message }, '*');
}
});
let previous = { level: null, args: null };
['clear', 'log', 'info', 'dir', 'warn', 'error', 'table'].forEach((level) => {
const original = console[level];
console[level] = (...args) => {
const msg = String(args[0])
if (
msg.includes('You are running a development build of Vue') ||
msg.includes('You are running the esm-bundler build of Vue')
) {
return
}
const stringifiedArgs = stringify(args);
if (
previous.level === level &&
previous.args &&
previous.args === stringifiedArgs
) {
parent.postMessage({ action: 'console', level, duplicate: true }, '*');
} else {
previous = { level, args: stringifiedArgs };
try {
parent.postMessage({ action: 'console', level, args }, '*');
} catch (err) {
parent.postMessage({ action: 'console', level, args: args.map(a => {
return a instanceof Error ? a.message : String(a)
}) }, '*');
}
}
original(...args);
}
});
[
{ method: 'group', action: 'console_group' },
{ method: 'groupEnd', action: 'console_group_end' },
{ method: 'groupCollapsed', action: 'console_group_collapsed' },
].forEach((group_action) => {
const original = console[group_action.method];
console[group_action.method] = (label) => {
parent.postMessage({ action: group_action.action, label }, '*');
original(label);
};
});
const timers = new Map();
const original_time = console.time;
const original_timelog = console.timeLog;
const original_timeend = console.timeEnd;
console.time = (label = 'default') => {
original_time(label);
timers.set(label, performance.now());
}
console.timeLog = (label = 'default') => {
original_timelog(label);
const now = performance.now();
if (timers.has(label)) {
parent.postMessage({ action: 'console', level: 'system-log', args: [`${label}: ${now - timers.get(label)}ms`] }, '*');
} else {
parent.postMessage({ action: 'console', level: 'system-warn', args: [`Timer '${label}' does not exist`] }, '*');
}
}
console.timeEnd = (label = 'default') => {
original_timeend(label);
const now = performance.now();
if (timers.has(label)) {
parent.postMessage({ action: 'console', level: 'system-log', args: [`${label}: ${now - timers.get(label)}ms`] }, '*');
} else {
parent.postMessage({ action: 'console', level: 'system-warn', args: [`Timer '${label}' does not exist`] }, '*');
}
timers.delete(label);
};
const original_assert = console.assert;
console.assert = (condition, ...args) => {
if (condition) {
const stack = new Error().stack;
parent.postMessage({ action: 'console', level: 'assert', args, stack }, '*');
}
original_assert(condition, ...args);
};
const counter = new Map();
const original_count = console.count;
const original_countreset = console.countReset;
console.count = (label = 'default') => {
counter.set(label, (counter.get(label) || 0) + 1);
parent.postMessage({ action: 'console', level: 'system-log', args: `${label}: ${counter.get(label)}` }, '*');
original_count(label);
};
console.countReset = (label = 'default') => {
if (counter.has(label)) {
counter.set(label, 0);
} else {
parent.postMessage({ action: 'console', level: 'system-warn', args: `Count for '${label}' does not exist` }, '*');
}
original_countreset(label);
};
const original_trace = console.trace;
console.trace = (...args) => {
const stack = new Error().stack;
parent.postMessage({ action: 'console', level: 'trace', args, stack }, '*');
original_trace(...args);
};
function stringify(args) {
try {
return JSON.stringify(args);
} catch (error) {
return null;
}
}
})()
</script>
<!-- ES Module Shims: Import maps polyfill for modules browsers without import maps support (all except Chrome 89+) -->
<script async src="https://unpkg.com/es-module-shims@0.10.1/dist/es-module-shims.js"></script>
<script type="importmap"><!--IMPORT_MAP--></script>
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@ -1,114 +0,0 @@
import { reactive, watchEffect } from 'vue'
import { compileFile, MAIN_FILE } from './transform'
import { utoa, atou } from './utils'
const welcomeCode = `
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1>{{ msg }}</h1>
<input v-model="msg">
</template>
`.trim()
export class File {
filename: string
code: string
compiled = {
js: '',
css: '',
ssr: ''
}
constructor(filename: string, code = '') {
this.filename = filename
this.code = code
}
}
interface Store {
files: Record<string, File>
activeFilename: string
readonly activeFile: File
readonly importMap: string | undefined
errors: (string | Error)[]
}
let files: Store['files'] = {}
const savedFiles = location.hash.slice(1)
if (savedFiles) {
const saved = JSON.parse(atou(savedFiles))
for (const filename in saved) {
files[filename] = new File(filename, saved[filename])
}
} else {
files = {
'App.vue': new File(MAIN_FILE, welcomeCode)
}
}
export const store: Store = reactive({
files,
activeFilename: MAIN_FILE,
get activeFile() {
return store.files[store.activeFilename]
},
get importMap() {
const file = store.files['import-map.json']
return file && file.code
},
errors: []
})
watchEffect(() => compileFile(store.activeFile))
for (const file in store.files) {
if (file !== MAIN_FILE) {
compileFile(store.files[file])
}
}
watchEffect(() => {
history.replaceState({}, '', '#' + utoa(JSON.stringify(exportFiles())))
})
export function exportFiles() {
const exported: Record<string, string> = {}
for (const filename in store.files) {
exported[filename] = store.files[filename].code
}
return exported
}
export function setActive(filename: string) {
store.activeFilename = filename
}
export function addFile(filename: string) {
const file = (store.files[filename] = new File(filename))
if (filename === 'import-map.json') {
file.code = `
{
"imports": {
}
}`.trim()
}
setActive(filename)
}
export function deleteFile(filename: string) {
if (confirm(`Are you sure you want to delete ${filename}?`)) {
if (store.activeFilename === filename) {
store.activeFilename = MAIN_FILE
}
delete store.files[filename]
}
}

View File

@ -1,285 +0,0 @@
import { store, File } from './store'
import {
SFCDescriptor,
BindingMetadata,
shouldTransformRef,
transformRef
} from '@vue/compiler-sfc'
import * as defaultCompiler from '@vue/compiler-sfc'
import { ref } from 'vue'
export const MAIN_FILE = 'App.vue'
export const COMP_IDENTIFIER = `__sfc__`
/**
* The default SFC compiler we are using is built from each commit
* but we may swap it out with a version fetched from CDNs
*/
let SFCCompiler: typeof defaultCompiler = defaultCompiler
// @ts-ignore
const defaultVueUrl = import.meta.env.PROD
? `${location.origin}/vue.runtime.esm-browser.js` // to be copied on build
: `${location.origin}/src/vue-dev-proxy`
export const vueRuntimeUrl = ref(defaultVueUrl)
export async function setVersion(version: string) {
const compilerUrl = `https://unpkg.com/@vue/compiler-sfc@${version}/dist/compiler-sfc.esm-browser.js`
const runtimeUrl = `https://unpkg.com/@vue/runtime-dom@${version}/dist/runtime-dom.esm-browser.js`
const [compiler] = await Promise.all([
import(/* @vite-ignore */ compilerUrl),
import(/* @vite-ignore */ runtimeUrl)
])
SFCCompiler = compiler
vueRuntimeUrl.value = runtimeUrl
console.info(`Now using Vue version: ${version}`)
}
export function resetVersion() {
SFCCompiler = defaultCompiler
vueRuntimeUrl.value = defaultVueUrl
}
async function transformTS(src: string) {
return (await import('sucrase')).transform(src, {
transforms: ['typescript']
}).code
}
export async function compileFile({ filename, code, compiled }: File) {
if (!code.trim()) {
store.errors = []
return
}
if (!filename.endsWith('.vue')) {
if (shouldTransformRef(code)) {
code = transformRef(code, { filename }).code
}
if (filename.endsWith('.ts')) {
code = await transformTS(code)
}
compiled.js = compiled.ssr = code
store.errors = []
return
}
const id = await hashId(filename)
const { errors, descriptor } = SFCCompiler.parse(code, {
filename,
sourceMap: true
})
if (errors.length) {
store.errors = errors
return
}
if (
descriptor.styles.some(s => s.lang) ||
(descriptor.template && descriptor.template.lang)
) {
store.errors = [
`lang="x" pre-processors for <template> or <style> are currently not ` +
`supported.`
]
return
}
const scriptLang =
(descriptor.script && descriptor.script.lang) ||
(descriptor.scriptSetup && descriptor.scriptSetup.lang)
if (scriptLang && scriptLang !== 'ts') {
store.errors = [`Only lang="ts" is supported for <script> blocks.`]
return
}
const hasScoped = descriptor.styles.some(s => s.scoped)
let clientCode = ''
let ssrCode = ''
const appendSharedCode = (code: string) => {
clientCode += code
ssrCode += code
}
const clientScriptResult = await doCompileScript(descriptor, id, false)
if (!clientScriptResult) {
return
}
const [clientScript, bindings] = clientScriptResult
clientCode += clientScript
// script ssr only needs to be performed if using <script setup> where
// the render fn is inlined.
if (descriptor.scriptSetup) {
const ssrScriptResult = await doCompileScript(descriptor, id, true)
if (ssrScriptResult) {
ssrCode += ssrScriptResult[0]
} else {
ssrCode = `/* SSR compile error: ${store.errors[0]} */`
}
} else {
// when no <script setup> is used, the script result will be identical.
ssrCode += clientScript
}
// template
// only need dedicated compilation if not using <script setup>
if (descriptor.template && !descriptor.scriptSetup) {
const clientTemplateResult = doCompileTemplate(
descriptor,
id,
bindings,
false
)
if (!clientTemplateResult) {
return
}
clientCode += clientTemplateResult
const ssrTemplateResult = doCompileTemplate(descriptor, id, bindings, true)
if (ssrTemplateResult) {
// ssr compile failure is fine
ssrCode += ssrTemplateResult
} else {
ssrCode = `/* SSR compile error: ${store.errors[0]} */`
}
}
if (hasScoped) {
appendSharedCode(
`\n${COMP_IDENTIFIER}.__scopeId = ${JSON.stringify(`data-v-${id}`)}`
)
}
if (clientCode || ssrCode) {
appendSharedCode(
`\n${COMP_IDENTIFIER}.__file = ${JSON.stringify(filename)}` +
`\nexport default ${COMP_IDENTIFIER}`
)
compiled.js = clientCode.trimStart()
compiled.ssr = ssrCode.trimStart()
}
// styles
let css = ''
for (const style of descriptor.styles) {
if (style.module) {
store.errors = [`<style module> is not supported in the playground.`]
return
}
const styleResult = await SFCCompiler.compileStyleAsync({
source: style.content,
filename,
id,
scoped: style.scoped,
modules: !!style.module
})
if (styleResult.errors.length) {
// postcss uses pathToFileURL which isn't polyfilled in the browser
// ignore these errors for now
if (!styleResult.errors[0].message.includes('pathToFileURL')) {
store.errors = styleResult.errors
}
// proceed even if css compile errors
} else {
css += styleResult.code + '\n'
}
}
if (css) {
compiled.css = css.trim()
} else {
compiled.css = '/* No <style> tags present */'
}
// clear errors
store.errors = []
}
async function doCompileScript(
descriptor: SFCDescriptor,
id: string,
ssr: boolean
): Promise<[string, BindingMetadata | undefined] | undefined> {
if (descriptor.script || descriptor.scriptSetup) {
try {
const compiledScript = SFCCompiler.compileScript(descriptor, {
id,
refTransform: true,
inlineTemplate: true,
templateOptions: {
ssr,
ssrCssVars: descriptor.cssVars
}
})
let code = ''
if (compiledScript.bindings) {
code += `\n/* Analyzed bindings: ${JSON.stringify(
compiledScript.bindings,
null,
2
)} */`
}
code +=
`\n` +
SFCCompiler.rewriteDefault(compiledScript.content, COMP_IDENTIFIER)
if ((descriptor.script || descriptor.scriptSetup)!.lang === 'ts') {
code = await transformTS(code)
}
return [code, compiledScript.bindings]
} catch (e: any) {
store.errors = [e.stack.split('\n').slice(0, 12).join('\n')]
return
}
} else {
return [`\nconst ${COMP_IDENTIFIER} = {}`, undefined]
}
}
function doCompileTemplate(
descriptor: SFCDescriptor,
id: string,
bindingMetadata: BindingMetadata | undefined,
ssr: boolean
) {
const templateResult = SFCCompiler.compileTemplate({
source: descriptor.template!.content,
filename: descriptor.filename,
id,
scoped: descriptor.styles.some(s => s.scoped),
slotted: descriptor.slotted,
ssr,
ssrCssVars: descriptor.cssVars,
isProd: false,
compilerOptions: {
bindingMetadata
}
})
if (templateResult.errors.length) {
store.errors = templateResult.errors
return
}
const fnName = ssr ? `ssrRender` : `render`
return (
`\n${templateResult.code.replace(
/\nexport (function|const) (render|ssrRender)/,
`$1 ${fnName}`
)}` + `\n${COMP_IDENTIFIER}.${fnName} = ${fnName}`
)
}
async function hashId(filename: string) {
const msgUint8 = new TextEncoder().encode(filename) // encode as (utf-8) Uint8Array
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8) // hash the message
const hashArray = Array.from(new Uint8Array(hashBuffer)) // convert buffer to byte array
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('') // convert bytes to hex string
return hashHex.slice(0, 8)
}

View File

@ -1,19 +0,0 @@
export function debounce(fn: Function, n = 100) {
let handle: any
return (...args: any[]) => {
if (handle) clearTimeout(handle)
handle = setTimeout(() => {
fn(...args)
}, n)
}
}
// prefer old unicode hacks for backward compatibility
// https://base64.guru/developers/javascript/examples/unicode-strings
export function utoa(data: string): string {
return btoa(unescape(encodeURIComponent(data)))
}
export function atou(base64: string): string {
return decodeURIComponent(escape(atob(base64)))
}

View File

@ -11,13 +11,8 @@ export default defineConfig({
define: { define: {
__COMMIT__: JSON.stringify(commit) __COMMIT__: JSON.stringify(commit)
}, },
resolve: {
alias: {
'@vue/compiler-sfc': '@vue/compiler-sfc/dist/compiler-sfc.esm-browser.js'
}
},
optimizeDeps: { optimizeDeps: {
exclude: ['consolidate'] exclude: ['@vue/repl']
} }
}) })

View File

@ -744,13 +744,6 @@
resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.36.tgz#00d9301d4dc35c2f6465a8aec634bb533674c652" resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.36.tgz#00d9301d4dc35c2f6465a8aec634bb533674c652"
integrity sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q== integrity sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==
"@types/codemirror@^0.0.108":
version "0.0.108"
resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.108.tgz#e640422b666bf49251b384c390cdeb2362585bde"
integrity sha512-3FGFcus0P7C2UOGCNUVENqObEb4SFk+S8Dnxq7K6aIsLVs/vDtlangl3PEO0ykaKXyK56swVF6Nho7VsA44uhw==
dependencies:
"@types/tern" "*"
"@types/consolidate@^0.14.0": "@types/consolidate@^0.14.0":
version "0.14.1" version "0.14.1"
resolved "https://registry.yarnpkg.com/@types/consolidate/-/consolidate-0.14.1.tgz#78f01b1ed747d945dea9969581fcc1d0cb59bad8" resolved "https://registry.yarnpkg.com/@types/consolidate/-/consolidate-0.14.1.tgz#78f01b1ed747d945dea9969581fcc1d0cb59bad8"
@ -867,13 +860,6 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
"@types/tern@*":
version "0.23.4"
resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.4.tgz#03926eb13dbeaf3ae0d390caf706b2643a0127fb"
integrity sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==
dependencies:
"@types/estree" "*"
"@types/yargs-parser@*": "@types/yargs-parser@*":
version "20.2.1" version "20.2.1"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129"
@ -942,6 +928,11 @@
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-1.6.0.tgz#e5558e20c20e9098cd5bd65b9901fdcd2c354983" resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-1.6.0.tgz#e5558e20c20e9098cd5bd65b9901fdcd2c354983"
integrity sha512-n3i8htn8pTg9M+kM3cnEfsPZx/6ngInlTroth6fA1LQTJq5aTVQ8ggaE5pPoAy9vCgHPtcaXMzwpldhqRAkebQ== integrity sha512-n3i8htn8pTg9M+kM3cnEfsPZx/6ngInlTroth6fA1LQTJq5aTVQ8ggaE5pPoAy9vCgHPtcaXMzwpldhqRAkebQ==
"@vue/repl@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@vue/repl/-/repl-0.1.0.tgz#9d37f0e8458b18737286785ea86456b8a60e3ee8"
integrity sha512-C1o7ZRJFDKjF7Hz94DVCg9XQL1gRG7+Iigu9HduNmS80LvBb6SJ6JiwOpPSV0UU/gDCt1r+Am9bUGEVj2D/fzw==
"@zeit/schemas@2.6.0": "@zeit/schemas@2.6.0":
version "2.6.0" version "2.6.0"
resolved "https://registry.yarnpkg.com/@zeit/schemas/-/schemas-2.6.0.tgz#004e8e553b4cd53d538bd38eac7bcbf58a867fe3" resolved "https://registry.yarnpkg.com/@zeit/schemas/-/schemas-2.6.0.tgz#004e8e553b4cd53d538bd38eac7bcbf58a867fe3"
@ -1096,11 +1087,6 @@ ansi-styles@^5.0.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
any-promise@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
anymatch@^3.0.3, anymatch@~3.1.2: anymatch@^3.0.3, anymatch@~3.1.2:
version "3.1.2" version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
@ -1642,11 +1628,6 @@ co@^4.6.0:
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
codemirror@^5.60.0:
version "5.62.3"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.62.3.tgz#5cfdee6931c8b2d1b39ae773aaaaec2cc6b5558e"
integrity sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg==
collect-v8-coverage@^1.0.0: collect-v8-coverage@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59"
@ -1698,11 +1679,6 @@ commander@^2.20.0, commander@^2.7.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commander@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
commander@^6.2.0: commander@^6.2.0:
version "6.2.1" version "6.2.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
@ -2890,18 +2866,6 @@ glob-parent@^5.1.2, glob-parent@~5.1.2:
dependencies: dependencies:
is-glob "^4.0.1" is-glob "^4.0.1"
glob@7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.1.7" version "7.1.7"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
@ -4533,15 +4497,6 @@ ms@2.1.2:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
mz@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
dependencies:
any-promise "^1.0.0"
object-assign "^4.0.1"
thenify-all "^1.0.0"
nanoid@^3.1.23: nanoid@^3.1.23:
version "3.1.25" version "3.1.25"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152"
@ -4651,7 +4606,7 @@ nwsapi@^2.2.0:
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
object-assign@^4.0.1, object-assign@^4.1.1: object-assign@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@ -5984,18 +5939,6 @@ strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
sucrase@^3.19.0:
version "3.20.1"
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.20.1.tgz#1c055e97d0fab2f9857f02461364075b3a4ab226"
integrity sha512-BIG59HaJOxNct9Va6KvT5yzBA/rcMGetzvZyTx0ZdCcspIbpJTPS64zuAfYlJuOj+3WaI5JOdA+F0bJQQi8ZiQ==
dependencies:
commander "^4.0.0"
glob "7.1.6"
lines-and-columns "^1.1.6"
mz "^2.7.0"
pirates "^4.0.1"
ts-interface-checker "^0.1.9"
supports-color@^5.3.0: supports-color@^5.3.0:
version "5.5.0" version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@ -6119,20 +6062,6 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
thenify-all@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=
dependencies:
thenify ">= 3.1.0 < 4"
"thenify@>= 3.1.0 < 4":
version "3.3.1"
resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f"
integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
dependencies:
any-promise "^1.0.0"
throat@^6.0.1: throat@^6.0.1:
version "6.0.1" version "6.0.1"
resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375"
@ -6216,11 +6145,6 @@ trim-off-newlines@^1.0.0:
resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3"
integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM=
ts-interface-checker@^0.1.9:
version "0.1.13"
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
ts-jest@^27.0.5: ts-jest@^27.0.5:
version "27.0.5" version "27.0.5"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.0.5.tgz#0b0604e2271167ec43c12a69770f0bb65ad1b750" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.0.5.tgz#0b0604e2271167ec43c12a69770f0bb65ad1b750"