Skip to content

Commit

Permalink
refactor!: useStore & useVueImportMap composable (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
sxzz committed Jan 21, 2024
1 parent dbe1b40 commit d01bf55
Show file tree
Hide file tree
Showing 15 changed files with 554 additions and 544 deletions.
28 changes: 5 additions & 23 deletions src/Repl.vue
@@ -1,9 +1,9 @@
<script setup lang="ts">
import SplitPane from './SplitPane.vue'
import Output from './output/Output.vue'
import { ReplStore, type SFCOptions, type Store } from './store'
import { computed, provide, ref, toRef, watchEffect } from 'vue'
import type { EditorComponentType } from './types'
import { type Store, useStore } from './store'
import { computed, provide, ref, toRef } from 'vue'
import { type EditorComponentType, injectKeyStore } from './types'
import EditorContainer from './editor/EditorContainer.vue'
export interface Props {
Expand All @@ -15,7 +15,6 @@ export interface Props {
showImportMap?: boolean
showTsConfig?: boolean
clearConsole?: boolean
sfcOptions?: SFCOptions
layout?: 'horizontal' | 'vertical'
layoutReverse?: boolean
ssr?: boolean
Expand All @@ -32,7 +31,7 @@ export interface Props {
const props = withDefaults(defineProps<Props>(), {
theme: 'light',
store: () => new ReplStore(),
store: () => useStore(),
autoResize: true,
showCompileOutput: true,
showImportMap: true,
Expand All @@ -49,7 +48,6 @@ const props = withDefaults(defineProps<Props>(), {
useCode: '',
},
}),
sfcOptions: () => ({}),
layout: 'horizontal',
})
Expand All @@ -59,28 +57,12 @@ if (!props.editor) {
const outputRef = ref<InstanceType<typeof Output>>()
watchEffect(() => {
const { store } = props
const sfcOptions = (store.options = props.sfcOptions || {})
sfcOptions.script ||= {}
sfcOptions.script.fs = {
fileExists(file: string) {
if (file.startsWith('/')) file = file.slice(1)
return !!store.state.files[file]
},
readFile(file: string) {
if (file.startsWith('/')) file = file.slice(1)
return store.state.files[file].code
},
}
})
props.store.init()
const editorSlotName = computed(() => (props.layoutReverse ? 'right' : 'left'))
const outputSlotName = computed(() => (props.layoutReverse ? 'left' : 'right'))
provide('store', props.store)
provide(injectKeyStore, props.store)
provide('autoresize', props.autoResize)
provide('import-map', toRef(props, 'showImportMap'))
provide('tsconfig', toRef(props, 'showTsConfig'))
Expand Down
8 changes: 4 additions & 4 deletions src/SplitPane.vue
@@ -1,15 +1,15 @@
<script setup lang="ts">
import { computed, inject, reactive, ref } from 'vue'
import type { Store } from './store'
import { computed, inject, reactive, ref, toRef } from 'vue'
import { injectKeyStore } from './types'
const props = defineProps<{ layout?: 'horizontal' | 'vertical' }>()
const isVertical = computed(() => props.layout === 'vertical')
const container = ref()
// mobile only
const store = inject('store') as Store
const showOutput = ref(store.initialShowOutput)
const store = inject(injectKeyStore)!
const showOutput = toRef(store, 'showOutput')
const state = reactive({
dragging: false,
Expand Down
13 changes: 6 additions & 7 deletions src/editor/EditorContainer.vue
Expand Up @@ -3,21 +3,20 @@ import FileSelector from './FileSelector.vue'
import Message from '../Message.vue'
import { debounce } from '../utils'
import { inject, ref, watch } from 'vue'
import type { Store } from '../store'
import MessageToggle from './MessageToggle.vue'
import type { EditorComponentType } from '../types'
import { type EditorComponentType, injectKeyStore } from '../types'
const SHOW_ERROR_KEY = 'repl_show_error'
const props = defineProps<{
editorComponent: EditorComponentType
}>()
const store = inject('store') as Store
const store = inject(injectKeyStore)!
const showMessage = ref(getItem())
const onChange = debounce((code: string) => {
store.state.activeFile.code = code
store.activeFile.code = code
}, 250)
function setItem() {
Expand All @@ -38,11 +37,11 @@ watch(showMessage, () => {
<FileSelector />
<div class="editor-container">
<props.editorComponent
:value="store.state.activeFile.code"
:filename="store.state.activeFile.filename"
:value="store.activeFile.code"
:filename="store.activeFile.filename"
@change="onChange"
/>
<Message v-show="showMessage" :err="store.state.errors[0]" />
<Message v-show="showMessage" :err="store.errors[0]" />
<MessageToggle v-model="showMessage" />
</div>
</template>
Expand Down
30 changes: 13 additions & 17 deletions src/editor/FileSelector.vue
@@ -1,13 +1,9 @@
<script setup lang="ts">
import {
type Store,
importMapFile,
stripSrcPrefix,
tsconfigFile,
} from '../store'
import { injectKeyStore } from '../../src/types'
import { importMapFile, stripSrcPrefix, tsconfigFile } from '../store'
import { type Ref, type VNode, computed, inject, ref } from 'vue'
const store = inject('store') as Store
const store = inject(injectKeyStore)!
/**
* When `true`: indicates adding a new file
Expand All @@ -23,7 +19,7 @@ const pendingFilename = ref('Comp.vue')
const showTsConfig = inject<Ref<boolean>>('tsconfig')
const showImportMap = inject<Ref<boolean>>('import-map')
const files = computed(() =>
Object.entries(store.state.files)
Object.entries(store.files)
.filter(
([name, file]) =>
name !== importMapFile && name !== tsconfigFile && !file.hidden,
Expand All @@ -37,7 +33,7 @@ function startAddFile() {
while (true) {
let hasConflict = false
for (const filename in store.state.files) {
for (const filename in store.files) {
if (stripSrcPrefix(filename) === name) {
hasConflict = true
name = `Comp${++i}.vue`
Expand Down Expand Up @@ -68,18 +64,18 @@ function doneNameFile() {
const oldFilename = pending.value === true ? '' : pending.value
if (!/\.(vue|js|ts|css|json)$/.test(filename)) {
store.state.errors = [
store.errors = [
`Playground only supports *.vue, *.js, *.ts, *.css, *.json files.`,
]
return
}
if (filename !== oldFilename && filename in store.state.files) {
store.state.errors = [`File "${filename}" already exists.`]
if (filename !== oldFilename && filename in store.files) {
store.errors = [`File "${filename}" already exists.`]
return
}
store.state.errors = []
store.errors = []
cancelNameFile()
if (filename === oldFilename) {
Expand Down Expand Up @@ -122,7 +118,7 @@ function horizontalScroll(e: WheelEvent) {
<div
v-if="pending !== file"
class="file"
:class="{ active: store.state.activeFile.filename === file }"
:class="{ active: store.activeFile.filename === file }"
@click="store.setActive(file)"
@dblclick="i > 0 && editFileName(file)"
>
Expand Down Expand Up @@ -154,17 +150,17 @@ function horizontalScroll(e: WheelEvent) {

<div class="import-map-wrapper">
<div
v-if="showTsConfig && store.state.files[tsconfigFile]"
v-if="showTsConfig && store.files[tsconfigFile]"
class="file"
:class="{ active: store.state.activeFile.filename === tsconfigFile }"
:class="{ active: store.activeFile.filename === tsconfigFile }"
@click="store.setActive(tsconfigFile)"
>
<span class="label">tsconfig.json</span>
</div>
<div
v-if="showImportMap"
class="file"
:class="{ active: store.state.activeFile.filename === importMapFile }"
:class="{ active: store.activeFile.filename === importMapFile }"
@click="store.setActive(importMapFile)"
>
<span class="label">Import Map</span>
Expand Down
58 changes: 58 additions & 0 deletions src/import-map.ts
@@ -0,0 +1,58 @@
import { computed, version as currentVersion, ref } from 'vue'

export function useVueImportMap(
defaults: {
runtimeDev?: string | (() => string)
runtimeProd?: string | (() => string)
serverRenderer?: string | (() => string)
} = {},
) {
function normalizeDefaults(defaults?: string | (() => string)) {
if (!defaults) return
return typeof defaults === 'string' ? defaults : defaults()
}

const productionMode = ref(false)
const vueVersion = ref<string | undefined>()
const importMap = computed<ImportMap>(() => {
const vue =
(!vueVersion.value &&
normalizeDefaults(
productionMode.value ? defaults.runtimeProd : defaults.runtimeDev,
)) ||
`https://cdn.jsdelivr.net/npm/@vue/runtime-dom@${
vueVersion.value || currentVersion
}/dist/runtime-dom.esm-browser${productionMode.value ? `.prod` : ``}.js`

const serverRenderer =
(!vueVersion.value && normalizeDefaults(defaults.serverRenderer)) ||
`https://cdn.jsdelivr.net/npm/@vue/server-renderer@${
vueVersion.value || currentVersion
}/dist/server-renderer.esm-browser.js`
return {
imports: {
vue,
'vue/server-renderer': serverRenderer,
},
}
})

return {
productionMode,
importMap,
vueVersion,
defaultVersion: currentVersion,
}
}

export interface ImportMap {
imports?: Record<string, string | undefined>
scopes?: Record<string, Record<string, string>>
}

export function mergeImportMap(a: ImportMap, b: ImportMap): ImportMap {
return {
imports: { ...a.imports, ...b.imports },
scopes: { ...a.scopes, ...b.scopes },
}
}
11 changes: 9 additions & 2 deletions src/index.ts
@@ -1,7 +1,14 @@
export { default as Repl } from './Repl.vue'
export { default as Preview } from './output/Preview.vue'
export { ReplStore, File } from './store'
export {
useStore,
File,
type SFCOptions,
type StoreState,
type Store,
type ReplStore,
} from './store'
export { useVueImportMap, mergeImportMap, type ImportMap } from './import-map'
export { compileFile } from './transform'
export type { Props as ReplProps } from './Repl.vue'
export type { Store, StoreOptions, SFCOptions, StoreState } from './store'
export type { OutputModes } from './types'
9 changes: 4 additions & 5 deletions src/monaco/Monaco.vue
Expand Up @@ -13,8 +13,7 @@ import {
import * as monaco from 'monaco-editor-core'
import { initMonaco } from './env'
import { getOrCreateModel } from './utils'
import type { Store } from '../store'
import type { EditorMode } from '../types'
import { type EditorMode, injectKeyStore } from '../types'
const props = withDefaults(
defineProps<{
Expand All @@ -37,7 +36,7 @@ const emit = defineEmits<{
const containerRef = ref<HTMLDivElement>()
const ready = ref(false)
const editor = shallowRef<monaco.editor.IStandaloneCodeEditor>()
const store = inject<Store>('store')!
const store = inject(injectKeyStore)!
initMonaco(store)
Expand Down Expand Up @@ -113,15 +112,15 @@ onMounted(async () => {
() => props.filename,
(_, oldFilename) => {
if (!editorInstance) return
const file = store.state.files[props.filename]
const file = store.files[props.filename]
if (!file) return null
const model = getOrCreateModel(
monaco.Uri.parse(`file:///${props.filename}`),
file.language,
file.code,
)
const oldFile = oldFilename ? store.state.files[oldFilename] : null
const oldFile = oldFilename ? store.files[oldFilename] : null
if (oldFile) {
oldFile.editorViewState = editorInstance.saveViewState()
}
Expand Down

0 comments on commit d01bf55

Please sign in to comment.